|
|
|
@@ -10,6 +10,7 @@ import { defaultCanvasRenderers } from "./canvas-renderers";
|
|
|
|
|
import type { Data, ElementRecord, Geometry, Id, Json, Result } from "@/lib/canvas-core/types";
|
|
|
|
|
import { equal } from "@/lib/canvas-core/values";
|
|
|
|
|
import {createGeometryProjectionCache} from "@/lib/canvas-bindings/geometry-cache";
|
|
|
|
|
import {routeCanvasKey} from "@/lib/canvas-bindings/input-router";
|
|
|
|
|
import {hitProjectedCanvas} from "@/lib/canvas-bindings/hit-query";
|
|
|
|
|
import {compatConnector,measuredConnector} from "@/lib/canvas-bindings/connectors";
|
|
|
|
|
import {createLoadingRendererCatalog} from '@/lib/canvas-bindings/renderers';
|
|
|
|
@@ -148,7 +149,7 @@ function CanvasView({ engine, board, viewId, renderers,fitLoading=true }: { engi
|
|
|
|
|
const blocked = pending.some(operation => operation.phase === 'blocked' && operation.blockers.some(reason => reason !== 'waiting_digest')) || [...root.runtime.ledger.requests.values()].some(request => request.phase !== 'resolved' && request.error && request.retryAt === undefined) || root.runtime.conflicts.size > 0;
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const cancel = () => { const state = engine.sessions.getSnapshot(); if (state.gesture?.viewId === viewId || state.connectionGesture?.viewId === viewId) engine.sessions.cancel('view_interrupted'); drag.current = null; };
|
|
|
|
|
const escape = (event: KeyboardEvent) => { if (event.key === 'Escape' && !event.isComposing) cancel(); };
|
|
|
|
|
const escape = (event: KeyboardEvent) => { const state=engine.sessions.getSnapshot(),element=event.target instanceof Element?event.target:null;if(event.key==='Escape'&&!event.defaultPrevented&&!event.isComposing&&!element?.closest('input,textarea,[contenteditable]:not([contenteditable="false"]),[role="textbox"],[role="dialog"],[role="menu"]')&&(state.gesture?.viewId===viewId||state.connectionGesture?.viewId===viewId)){cancel();} };
|
|
|
|
|
window.addEventListener('blur', cancel); window.addEventListener('keydown', escape, true);
|
|
|
|
|
return () => { window.removeEventListener('blur', cancel); window.removeEventListener('keydown', escape, true); };
|
|
|
|
|
}, [engine, viewId]);
|
|
|
|
@@ -210,13 +211,13 @@ function CanvasView({ engine, board, viewId, renderers,fitLoading=true }: { engi
|
|
|
|
|
onPointerDownCapture={event => { if (event.button === 0 && (event.target as HTMLElement).closest('.react-flow__node')) pointerStart.current = flow.screenToFlowPosition({ x: event.clientX, y: event.clientY }); }}
|
|
|
|
|
onPointerCancelCapture={() => { const state = engine.sessions.getSnapshot(); if (state.gesture?.viewId === viewId || state.connectionGesture?.viewId === viewId) engine.sessions.cancel('pointer_cancel'); drag.current = null; }}
|
|
|
|
|
onKeyDown={event => {
|
|
|
|
|
const input = (event.target as HTMLElement).closest('input,textarea,[contenteditable="true"],[role="dialog"],[role="menu"]');
|
|
|
|
|
if (input || event.nativeEvent.isComposing) return;
|
|
|
|
|
if (event.key === 'Escape') { engine.sessions.cancel('escape'); return; }
|
|
|
|
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') { event.preventDefault(); report(event.shiftKey ? engine.editor.history.redo() : engine.editor.history.undo()); }
|
|
|
|
|
else if (event.key === 'Delete' || event.key === 'Backspace') { event.preventDefault(); removeSelected(); }
|
|
|
|
|
else if (event.key.toLowerCase() === 'h') engine.sessions.tool(viewId, 'pan');
|
|
|
|
|
else if (event.key.toLowerCase() === 'v') engine.sessions.tool(viewId, 'select');
|
|
|
|
|
const element=event.target instanceof Element?event.target:null;
|
|
|
|
|
const claim=routeCanvasKey({key:event.key,composing:event.nativeEvent.isComposing,textEditing:!!element?.closest('input,textarea,[contenteditable]:not([contenteditable="false"]),[role="textbox"]'),overlay:!!element?.closest('[role="dialog"],[role="menu"]'),defaultPrevented:event.defaultPrevented,meta:event.metaKey,ctrl:event.ctrlKey,alt:event.altKey,shift:event.shiftKey},{
|
|
|
|
|
gesture:input=>{const state=engine.sessions.getSnapshot();if(input.key!=='Escape'||state.gesture?.viewId!==viewId&&state.connectionGesture?.viewId!==viewId)return false;engine.sessions.cancel('escape');drag.current=null;return true;},
|
|
|
|
|
tool:input=>{if(input.ctrl||input.meta||input.alt||input.shift)return false;const key=input.key.toLowerCase();if(key!=='h'&&key!=='v')return false;engine.sessions.tool(viewId,key==='h'?'pan':'select');return true;},
|
|
|
|
|
shortcut:input=>{if(input.alt)return false;if((input.meta||input.ctrl)&&input.key.toLowerCase()==='z'){report(input.shift?engine.editor.history.redo():engine.editor.history.undo());return true;}if(!input.meta&&!input.ctrl&&!input.shift&&(input.key==='Delete'||input.key==='Backspace')&¤t.current.view.selection.size){removeSelected();return true;}return false;},
|
|
|
|
|
});
|
|
|
|
|
if(claim==='gesture'||claim==='tool'||claim==='shortcut'){event.preventDefault();event.stopPropagation();}
|
|
|
|
|
}}>
|
|
|
|
|
<ImagePreviews.Provider value={imagePreviews}>
|
|
|
|
|
<CanvasRendererCommandsContext.Provider value={rendererCommands}>
|
|
|
|
|