47 lines
3.9 KiB
TypeScript
47 lines
3.9 KiB
TypeScript
import type {Change,Command,Document,Id,Json,Registry,Target} from './types.ts';
|
|
import {applyChanges,targetElement} from './document.ts';
|
|
import {canvasTransform,convertGeometry} from './coordinates.ts';
|
|
import {unitScale,withUnitScale} from './units.ts';
|
|
import {copySlots} from './copy-paths.ts';
|
|
import {repairConnectionOwners} from './structure.ts';
|
|
import {clone,equal,fault,freeze} from './values.ts';
|
|
|
|
/** Remove an owning canvas, retaining its entry and promoting only direct members. */
|
|
export function planDissolveCanvas(document:Document,registry:Registry,canvasId:Id,entryTarget:Target):{readonly changes:readonly Change[];readonly commands:readonly Command[]}{
|
|
const canvas=document.canvases.get(canvasId);if(!canvas)throw fault('canvas_not_loaded',[canvasId]);
|
|
if(!canvas.parent)throw fault('cannot_dissolve_root',[canvasId]);
|
|
if(canvas.parent.entryElementId!==entryTarget.id)throw fault('canvas_entry_changed',[canvasId]);
|
|
targetElement(document,entryTarget);
|
|
const parent=canvas.parent.canvasId,from=canvasTransform(document,canvasId),to=canvasTransform(document,parent),changes:Change[]=[],commands:Command[]=[];
|
|
for(const element of document.elements.values())if(element.ownerCanvasId===canvasId&&element.structure==='node'){
|
|
if(!registry.supports(element,'geometry.move'))throw fault('unsupported_capability',[element.id,'geometry.move']);
|
|
const target={id:element.id,incarnation:element.incarnation},geometry=convertGeometry(element.geometry,from,to),presentation=withUnitScale(element.data.presentation,unitScale(element.data.presentation)*from.scale/to.scale);
|
|
commands.push({type:'element.move',target,canvasId:parent});
|
|
changes.push({kind:'field',target,field:'ownerCanvasId',before:canvasId,after:parent});
|
|
if(!equal(geometry,element.geometry))changes.push({kind:'field',target,field:'geometry',before:element.geometry,after:geometry});
|
|
if(!equal(presentation,element.data.presentation))changes.push({kind:'field',target,field:'data.presentation',before:element.data.presentation??null,beforePresent:Object.hasOwn(element.data,'presentation'),after:presentation??null,afterPresent:presentation!==undefined});
|
|
}
|
|
for(const child of document.canvases.values())if(child.parent?.canvasId===canvasId)changes.push({kind:'canvas',id:child.id,before:child,after:{...child,parent:{...child.parent,canvasId:parent}}});
|
|
changes.push({kind:'canvas',id:canvasId,before:canvas,after:null});
|
|
const promoted=applyChanges(document,changes);changes.push(...repairConnectionOwners(document,promoted));
|
|
// Only declared canvas references have meaning here. Opaque payloads are retained.
|
|
for(const element of promoted.elements.values()){
|
|
if(registry.resolve(element)!=='known')continue;
|
|
const references=registry.components.get(element.componentId)?.copy?.references.filter(reference=>reference.target==='canvas')??[];
|
|
if(!references.length)continue;
|
|
const data=clone(element.data) as Record<string,Json>;
|
|
for(const reference of references)for(const slot of copySlots(data,reference.path)){
|
|
const value=slot.parent[slot.key];if(value===null)continue;
|
|
if(reference.many&&!Array.isArray(value))throw fault('invalid_copy_reference',[element.id]);
|
|
const map=(value:Json):Json=>{if(typeof value!=='string'||!value)throw fault('invalid_copy_reference',[element.id]);return value===canvasId?parent:value;};
|
|
slot.parent[slot.key]=reference.many?(value as Json[]).map(map):map(value);
|
|
}
|
|
for(const key of Object.keys(data))if(!equal(data[key],element.data[key])){
|
|
if(!registry.supports(element,'content.edit'))throw fault('unsupported_capability',[element.id,'content.edit']);
|
|
changes.push({kind:'field',target:{id:element.id,incarnation:element.incarnation},field:`data.${key}`,before:element.data[key],after:data[key]});
|
|
commands.push({type:'element.data',target:{id:element.id,incarnation:element.incarnation},patch:{[key]:data[key]}});
|
|
}
|
|
}
|
|
return freeze({changes,commands});
|
|
}
|