import { assertExists, assertType } from '@blockeditor/global/utils'; import { type BlockSnapshot, type SnapshotReturn } from '@blockeditor/store'; import type { IConnector } from '../index.js'; import { Bound } from '../utils/bound.js'; import { generateElementId } from '../utils/index.js'; import { sortIndex } from '../utils/sort.js'; import type { SlotBlockPayload, TemplateJob } from './template.js'; export const replaceIdMiddleware = (job: TemplateJob) => { const regeneratedIdMap = new Map(); job.slots.beforeInsert.on(payload => { switch (payload.type) { case 'block': regenerateBlockId(payload.data); break; } }); const regenerateBlockId = (data: SlotBlockPayload['data']) => { const { blockJson } = data; const newId = regeneratedIdMap.has(blockJson.id) ? regeneratedIdMap.get(blockJson.id)! : job.model.page.workspace.idGenerator(); if (!regeneratedIdMap.has(blockJson.id)) { regeneratedIdMap.set(blockJson.id, newId); } blockJson.id = newId; data.parent = data.parent ? regeneratedIdMap.get(data.parent) ?? data.parent : undefined; if (blockJson.flavour === 'affine:surface-ref') { assertType< SnapshotReturn<{ reference: string; }> >(blockJson); blockJson.props['reference'] = regeneratedIdMap.get(blockJson.props['reference']) ?? ''; } if (blockJson.flavour === 'affine:surface') { const elements: Record> = {}; const defered: string[] = []; Object.entries( blockJson.props.elements as Record> ).forEach(([id, val]) => { const newId = generateElementId(); regeneratedIdMap.set(id, newId); val.id = newId; elements[newId] = val; if (['group', 'connector'].includes(val['type'] as string)) { defered.push(newId); } }); blockJson.children.forEach(block => { regeneratedIdMap.set(block.id, job.model.page.workspace.idGenerator()); }); defered.forEach(id => { const element = elements[id]!; switch (element['type'] as string) { case 'group': { const children = element['children'] as { json: Record; }; const newChildrenJson: Record = {}; Object.entries(children.json).forEach(([key, val]) => { newChildrenJson[regeneratedIdMap.get(key) ?? key] = val; }); children.json = newChildrenJson; } break; case 'connector': { const target = element['target'] as { id?: string }; if (target.id) { element['target'] = { ...target, id: regeneratedIdMap.get(target.id), }; } const source = element['source'] as { id?: string }; if (source.id) { element['source'] = { ...source, id: regeneratedIdMap.get(source.id), }; } } break; } }); blockJson.props.elements = elements; } }; }; export const createInsertPlaceMiddleware = (targetPlace: Bound) => { return (job: TemplateJob) => { if (job.type !== 'template') return; let templateBound: Bound | null = null; let offset: { x: number; y: number; }; job.slots.beforeInsert.on(blockData => { if (blockData.type === 'template') { templateBound = blockData.bound; if (templateBound) { offset = { x: targetPlace.x - templateBound.x, y: targetPlace.y - templateBound.y, }; templateBound.x = targetPlace.x; templateBound.y = targetPlace.y; } } else { if (templateBound && offset) changePosition(blockData.data.blockJson); } }); const ignoreType = ['group', 'connector']; const changePosition = (blockJson: BlockSnapshot) => { assertExists(templateBound); if (blockJson.props.xywh) { const bound = Bound.deserialize(blockJson.props['xywh'] as string); blockJson.props['xywh'] = new Bound( bound.x + offset.x, bound.y + offset.y, bound.w, bound.h ).serialize(); } if (blockJson.flavour === 'affine:surface') { Object.entries( blockJson.props.elements as Record> ).forEach(([_, val]) => { const type = val['type'] as string; if (ignoreType.includes(type) && val['xywh']) { delete val['xywh']; } if (val['xywh']) { const bound = Bound.deserialize(val['xywh'] as string); val['xywh'] = new Bound( bound.x + offset.x, bound.y + offset.y, bound.w, bound.h ).serialize(); } if (type === 'connector') { (['target', 'source'] as const).forEach(prop => { const propVal = val[prop]; assertType(propVal); if (propVal['id'] || !propVal['position']) return; const pos = propVal['position']; propVal['position'] = [pos[0] + offset.x, pos[1] + offset.y]; }); } }); } }; }; }; export const createStickerMiddleware = ( center: { x: number; y: number; }, getIndex: () => string ) => { return (job: TemplateJob) => { job.slots.beforeInsert.on(blockData => { if (blockData.type === 'block') { changeInserPosition(blockData.data.blockJson); } }); const changeInserPosition = (blockJson: BlockSnapshot) => { if (blockJson.flavour === 'affine:image' && blockJson.props.xywh) { const bound = Bound.deserialize(blockJson.props['xywh'] as string); blockJson.props['xywh'] = new Bound( center.x - bound.w / 2, center.y - bound.h / 2, bound.w, bound.h ).serialize(); blockJson.props.index = getIndex(); } }; }; }; export const createRegenerateIndexMiddleware = ( generateIndex: (type: string) => string ) => { return (job: TemplateJob) => { job.slots.beforeInsert.on(blockData => { if (blockData.type === 'template') { generateIndexMap(); } if (blockData.type === 'block') { resetIndex(blockData.data.blockJson); } }); const indexMap = new Map(); const generateIndexMap = () => { const indexList: { id: string; index: string; flavour: string; element?: boolean; }[] = []; const frameList: { id: string; index: string; }[] = []; const groupIndexMap = new Map< string, { index: string; id: string; } >(); job.walk(block => { if (block.props.index) { if (block.flavour === 'affine:frame') { frameList.push({ id: block.id, index: block.props.index as string, }); } else { indexList.push({ id: block.id, index: block.props.index as string, flavour: block.flavour, }); } } if (block.flavour === 'affine:surface') { Object.entries( block.props.elements as Record> ).forEach(([_, element]) => { indexList.push({ index: element['index'] as string, flavour: element['type'] as string, id: element['id'] as string, element: true, }); if (element['type'] === 'group') { const children = element['children'] as { json: Record; }; const groupIndex = { index: element['index'] as string, id: element['id'] as string, }; Object.keys(children.json).forEach(key => { groupIndexMap.set(key, groupIndex); }); } }); } }); indexList.sort((a, b) => sortIndex(a, b, groupIndexMap)); frameList.sort((a, b) => sortIndex(a, b, groupIndexMap)); frameList.forEach(index => { indexMap.set(index.id, generateIndex('affine:frame')); }); indexList.forEach(index => { indexMap.set(index.id, generateIndex(index.flavour)); }); }; const resetIndex = (blockJson: BlockSnapshot) => { if (blockJson.props.index) { blockJson.props.index = indexMap.get(blockJson.id) ?? blockJson.props.index; } if (blockJson.flavour === 'affine:surface') { Object.entries( blockJson.props.elements as Record> ).forEach(([_, element]) => { if (element['index']) { element['index'] = indexMap.get(element['id'] as string); } }); } }; }; };