Files
Endoleap/src/lib/canvas-core/gesture-placement.ts
T

45 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 手势使用的可见坐标上下文签名。它描述目标 owner 到当前 view canvas 之间的入口链,
* 不从项目根出发;view 之外的父级变化不必被误当作当前视图内的目标坐标变化。
*/
import type {Document,Geometry,Id,Registry} from './types.ts';
import {fault} from './values.ts';
import {unitScale} from './units.ts';
/** 一层入口的身份/类型/版本和变换相关值;上传状态、普通文字等无关字段不进入签名。 */
type PlacementStep={canvasId:Id;parentCanvasId:Id;entryId:Id;incarnation:number;componentId:string;dataVersion:number;geometry:Geometry;contentScale:number;unitScale:number};
/** A short-lived reader of the visible owner chain relative to one view, not the project root.
* Cache by owner only during this admission/publication, so multi-selection shares ancestry work. */
/** 给定不可变文档/view/registry,返回短生命周期 owner→入口链读取器。
* Map 只在一次准入或发布比较中复用,多选共享祖先扫描,不能跨文档更新继续使用。
* 返回数组/步骤没有运行时 freeze,geometry 也沿用记录引用;readonly 是调用约定,不应修改缓存结果。 */
export function createGesturePlacementReader(document:Document,viewCanvasId:Id,registry:Registry){
// 链按 owner→view 方向排列,不包含 view 自身在更上级的入口;读同一 owner 会返回同一个数组。
const values=new Map<Id,readonly PlacementStep[]>();
/** 向 view 回溯目标拥有链,缺失/循环/隐藏入口明确拒绝;成功后才缓存完整链。 */
return(ownerCanvasId:Id):readonly PlacementStep[]=>{
const cached=values.get(ownerCanvasId);if(cached)return cached;
if(!document.canvases.has(viewCanvasId))throw fault('canvas_missing',[viewCanvasId]);
const chain:PlacementStep[]=[],seen=new Set<Id>();let id=ownerCanvasId;
// 当前 view 本身无需入口步骤;回溯到根仍没遇到 view,说明目标不在当前视图子树。
while(id!==viewCanvasId){
if(seen.has(id))throw fault('canvas_cycle',[id]);seen.add(id);
const parent=document.canvases.get(id)?.parent;
// owner 缺失或已回溯到无父根,统一视为不在此 view;仅 view 本身缺失在上方报 canvas_missing。
if(!parent)throw fault('target_outside_view',[ownerCanvasId]);
const entry=document.elements.get(parent.entryElementId);
// A future/unknown entry is a collapsed placeholder; never interpret its payload.
if(!entry||entry.structure!=='node'||registry.resolve(entry)!=='known')throw fault('target_hidden',[ownerCanvasId]);
// 未知/未来入口已在上方拒绝;已知入口仍可能折叠,折叠子内容不允许开始手势。
const raw=entry.data.presentation,presentation=raw&&typeof raw==='object'&&!Array.isArray(raw)?raw:{};
if(presentation.collapsed===true)throw fault('target_hidden',[ownerCanvasId]);
// 签名只读这些坐标字段,不校验整个 presentation;contentScale 这里仅检查 number 且 >0。
const contentScale=typeof presentation.contentScale==='number'&&presentation.contentScale>0?presentation.contentScale:1;
chain.push({canvasId:id,parentCanvasId:parent.canvasId,entryId:entry.id,incarnation:entry.incarnation,componentId:entry.componentId,dataVersion:entry.dataVersion,geometry:entry.geometry,contentScale,unitScale:unitScale(raw)});
id=parent.canvasId;
}
values.set(ownerCanvasId,chain);return chain;
};
}