25772 lines
No EOL
709 KiB
JavaScript
25772 lines
No EOL
709 KiB
JavaScript
var __defProp = Object.defineProperty;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
var __esm = (fn, res) => function __init() {
|
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
};
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
|
|
// src/core/types.ts
|
|
var init_types = __esm({
|
|
"src/core/types.ts"() {
|
|
"use strict";
|
|
}
|
|
});
|
|
|
|
// src/core/graph-store.ts
|
|
var graph_store_exports = {};
|
|
__export(graph_store_exports, {
|
|
currentGraphIdAtom: () => currentGraphIdAtom,
|
|
draggingNodeIdAtom: () => draggingNodeIdAtom,
|
|
edgeCreationAtom: () => edgeCreationAtom,
|
|
graphAtom: () => graphAtom,
|
|
graphOptions: () => graphOptions,
|
|
graphUpdateVersionAtom: () => graphUpdateVersionAtom,
|
|
preDragNodeAttributesAtom: () => preDragNodeAttributesAtom
|
|
});
|
|
import { atom } from "jotai";
|
|
import Graph from "graphology";
|
|
var graphOptions, currentGraphIdAtom, graphAtom, graphUpdateVersionAtom, edgeCreationAtom, draggingNodeIdAtom, preDragNodeAttributesAtom;
|
|
var init_graph_store = __esm({
|
|
"src/core/graph-store.ts"() {
|
|
"use strict";
|
|
graphOptions = {
|
|
type: "directed",
|
|
multi: true,
|
|
allowSelfLoops: true
|
|
};
|
|
currentGraphIdAtom = atom(null);
|
|
graphAtom = atom(new Graph(graphOptions));
|
|
graphUpdateVersionAtom = atom(0);
|
|
edgeCreationAtom = atom({
|
|
isCreating: false,
|
|
sourceNodeId: null,
|
|
sourceNodePosition: null,
|
|
targetPosition: null,
|
|
hoveredTargetNodeId: null,
|
|
sourceHandle: null,
|
|
targetHandle: null,
|
|
sourcePort: null,
|
|
targetPort: null,
|
|
snappedTargetPosition: null
|
|
});
|
|
draggingNodeIdAtom = atom(null);
|
|
preDragNodeAttributesAtom = atom(null);
|
|
}
|
|
});
|
|
|
|
// src/utils/debug.ts
|
|
import debugFactory from "debug";
|
|
function createDebug(module) {
|
|
const base = debugFactory(`${NAMESPACE}:${module}`);
|
|
const warn = debugFactory(`${NAMESPACE}:${module}:warn`);
|
|
const error = debugFactory(`${NAMESPACE}:${module}:error`);
|
|
warn.enabled = true;
|
|
error.enabled = true;
|
|
warn.log = console.warn.bind(console);
|
|
error.log = console.error.bind(console);
|
|
const debugFn = Object.assign(base, {
|
|
warn,
|
|
error
|
|
});
|
|
return debugFn;
|
|
}
|
|
var NAMESPACE, debug;
|
|
var init_debug = __esm({
|
|
"src/utils/debug.ts"() {
|
|
"use strict";
|
|
NAMESPACE = "canvas";
|
|
debug = {
|
|
graph: {
|
|
node: createDebug("graph:node"),
|
|
edge: createDebug("graph:edge"),
|
|
sync: createDebug("graph:sync")
|
|
},
|
|
ui: {
|
|
selection: createDebug("ui:selection"),
|
|
drag: createDebug("ui:drag"),
|
|
resize: createDebug("ui:resize")
|
|
},
|
|
sync: {
|
|
status: createDebug("sync:status"),
|
|
mutations: createDebug("sync:mutations"),
|
|
queue: createDebug("sync:queue")
|
|
},
|
|
viewport: createDebug("viewport")
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/utils/mutation-queue.ts
|
|
function getPendingState(nodeId) {
|
|
let state = pendingNodeMutations.get(nodeId);
|
|
if (!state) {
|
|
state = {
|
|
inFlight: false,
|
|
queuedPosition: null,
|
|
queuedUiProperties: null,
|
|
graphId: null
|
|
};
|
|
pendingNodeMutations.set(nodeId, state);
|
|
}
|
|
return state;
|
|
}
|
|
function clearPendingState(nodeId) {
|
|
pendingNodeMutations.delete(nodeId);
|
|
}
|
|
function clearAllPendingMutations() {
|
|
pendingNodeMutations.clear();
|
|
}
|
|
function hasPendingMutations() {
|
|
for (const state of pendingNodeMutations.values()) {
|
|
if (state.inFlight || state.queuedPosition !== null || state.queuedUiProperties !== null) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
var pendingNodeMutations;
|
|
var init_mutation_queue = __esm({
|
|
"src/utils/mutation-queue.ts"() {
|
|
"use strict";
|
|
pendingNodeMutations = /* @__PURE__ */ new Map();
|
|
}
|
|
});
|
|
|
|
// src/core/perf.ts
|
|
import { atom as atom2 } from "jotai";
|
|
function setPerfEnabled(enabled) {
|
|
_enabled = enabled;
|
|
}
|
|
function canvasMark(name) {
|
|
if (!_enabled) return _noop;
|
|
const markName = `canvas:${name}`;
|
|
try {
|
|
performance.mark(markName);
|
|
} catch {
|
|
return _noop;
|
|
}
|
|
return () => {
|
|
try {
|
|
performance.measure(`canvas:${name}`, markName);
|
|
} catch {
|
|
}
|
|
};
|
|
}
|
|
function _noop() {
|
|
}
|
|
function canvasWrap(name, fn) {
|
|
const end = canvasMark(name);
|
|
try {
|
|
return fn();
|
|
} finally {
|
|
end();
|
|
}
|
|
}
|
|
var perfEnabledAtom, _enabled;
|
|
var init_perf = __esm({
|
|
"src/core/perf.ts"() {
|
|
"use strict";
|
|
perfEnabledAtom = atom2(false);
|
|
_enabled = false;
|
|
if (typeof window !== "undefined") {
|
|
window.__canvasPerf = setPerfEnabled;
|
|
}
|
|
}
|
|
});
|
|
|
|
// src/core/graph-position.ts
|
|
import { atom as atom3 } from "jotai";
|
|
import { atomFamily } from "jotai-family";
|
|
import Graph2 from "graphology";
|
|
function getPositionCache(graph) {
|
|
let cache = _positionCacheByGraph.get(graph);
|
|
if (!cache) {
|
|
cache = /* @__PURE__ */ new Map();
|
|
_positionCacheByGraph.set(graph, cache);
|
|
}
|
|
return cache;
|
|
}
|
|
var debug2, _positionCacheByGraph, nodePositionUpdateCounterAtom, nodePositionAtomFamily, updateNodePositionAtom, cleanupNodePositionAtom, cleanupAllNodePositionsAtom, clearGraphOnSwitchAtom;
|
|
var init_graph_position = __esm({
|
|
"src/core/graph-position.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_debug();
|
|
init_mutation_queue();
|
|
init_perf();
|
|
debug2 = createDebug("graph:position");
|
|
_positionCacheByGraph = /* @__PURE__ */ new WeakMap();
|
|
nodePositionUpdateCounterAtom = atom3(0);
|
|
nodePositionAtomFamily = atomFamily((nodeId) => atom3((get) => {
|
|
get(nodePositionUpdateCounterAtom);
|
|
const graph = get(graphAtom);
|
|
if (!graph.hasNode(nodeId)) {
|
|
return {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
}
|
|
const x = graph.getNodeAttribute(nodeId, "x");
|
|
const y = graph.getNodeAttribute(nodeId, "y");
|
|
const cache = getPositionCache(graph);
|
|
const prev = cache.get(nodeId);
|
|
if (prev && prev.x === x && prev.y === y) {
|
|
return prev;
|
|
}
|
|
const pos = {
|
|
x,
|
|
y
|
|
};
|
|
cache.set(nodeId, pos);
|
|
return pos;
|
|
}));
|
|
updateNodePositionAtom = atom3(null, (get, set, {
|
|
nodeId,
|
|
position
|
|
}) => {
|
|
const end = canvasMark("drag-frame");
|
|
const graph = get(graphAtom);
|
|
if (graph.hasNode(nodeId)) {
|
|
debug2("Updating node %s position to %o", nodeId, position);
|
|
graph.setNodeAttribute(nodeId, "x", position.x);
|
|
graph.setNodeAttribute(nodeId, "y", position.y);
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
}
|
|
end();
|
|
});
|
|
cleanupNodePositionAtom = atom3(null, (get, _set, nodeId) => {
|
|
nodePositionAtomFamily.remove(nodeId);
|
|
const graph = get(graphAtom);
|
|
getPositionCache(graph).delete(nodeId);
|
|
debug2("Removed position atom for node: %s", nodeId);
|
|
});
|
|
cleanupAllNodePositionsAtom = atom3(null, (get, _set) => {
|
|
const graph = get(graphAtom);
|
|
const nodeIds = graph.nodes();
|
|
nodeIds.forEach((nodeId) => {
|
|
nodePositionAtomFamily.remove(nodeId);
|
|
});
|
|
_positionCacheByGraph.delete(graph);
|
|
debug2("Removed %d position atoms", nodeIds.length);
|
|
});
|
|
clearGraphOnSwitchAtom = atom3(null, (get, set) => {
|
|
debug2("Clearing graph for switch");
|
|
set(cleanupAllNodePositionsAtom);
|
|
clearAllPendingMutations();
|
|
const emptyGraph = new Graph2(graphOptions);
|
|
set(graphAtom, emptyGraph);
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/selection-store.ts
|
|
var selection_store_exports = {};
|
|
__export(selection_store_exports, {
|
|
addNodesToSelectionAtom: () => addNodesToSelectionAtom,
|
|
clearEdgeSelectionAtom: () => clearEdgeSelectionAtom,
|
|
clearSelectionAtom: () => clearSelectionAtom,
|
|
focusedNodeIdAtom: () => focusedNodeIdAtom,
|
|
handleNodePointerDownSelectionAtom: () => handleNodePointerDownSelectionAtom,
|
|
hasFocusedNodeAtom: () => hasFocusedNodeAtom,
|
|
hasSelectionAtom: () => hasSelectionAtom,
|
|
removeNodesFromSelectionAtom: () => removeNodesFromSelectionAtom,
|
|
selectEdgeAtom: () => selectEdgeAtom,
|
|
selectSingleNodeAtom: () => selectSingleNodeAtom,
|
|
selectedEdgeIdAtom: () => selectedEdgeIdAtom,
|
|
selectedNodeIdsAtom: () => selectedNodeIdsAtom,
|
|
selectedNodesCountAtom: () => selectedNodesCountAtom,
|
|
setFocusedNodeAtom: () => setFocusedNodeAtom,
|
|
toggleNodeInSelectionAtom: () => toggleNodeInSelectionAtom
|
|
});
|
|
import { atom as atom4 } from "jotai";
|
|
var debug3, selectedNodeIdsAtom, selectedEdgeIdAtom, handleNodePointerDownSelectionAtom, selectSingleNodeAtom, toggleNodeInSelectionAtom, clearSelectionAtom, addNodesToSelectionAtom, removeNodesFromSelectionAtom, selectEdgeAtom, clearEdgeSelectionAtom, focusedNodeIdAtom, setFocusedNodeAtom, hasFocusedNodeAtom, selectedNodesCountAtom, hasSelectionAtom;
|
|
var init_selection_store = __esm({
|
|
"src/core/selection-store.ts"() {
|
|
"use strict";
|
|
init_debug();
|
|
debug3 = createDebug("selection");
|
|
selectedNodeIdsAtom = atom4(/* @__PURE__ */ new Set());
|
|
selectedEdgeIdAtom = atom4(null);
|
|
handleNodePointerDownSelectionAtom = atom4(null, (get, set, {
|
|
nodeId,
|
|
isShiftPressed
|
|
}) => {
|
|
const currentSelection = get(selectedNodeIdsAtom);
|
|
debug3("handleNodePointerDownSelection: nodeId=%s, shift=%s, current=%o", nodeId, isShiftPressed, Array.from(currentSelection));
|
|
set(selectedEdgeIdAtom, null);
|
|
if (isShiftPressed) {
|
|
const newSelection = new Set(currentSelection);
|
|
if (newSelection.has(nodeId)) {
|
|
newSelection.delete(nodeId);
|
|
} else {
|
|
newSelection.add(nodeId);
|
|
}
|
|
debug3("Shift-click, setting selection to: %o", Array.from(newSelection));
|
|
set(selectedNodeIdsAtom, newSelection);
|
|
} else {
|
|
if (!currentSelection.has(nodeId)) {
|
|
debug3("Node not in selection, selecting: %s", nodeId);
|
|
set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId]));
|
|
} else {
|
|
debug3("Node already selected, preserving multi-select");
|
|
}
|
|
}
|
|
});
|
|
selectSingleNodeAtom = atom4(null, (get, set, nodeId) => {
|
|
debug3("selectSingleNode: %s", nodeId);
|
|
set(selectedEdgeIdAtom, null);
|
|
if (nodeId === null || nodeId === void 0) {
|
|
debug3("Clearing selection");
|
|
set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
|
|
} else {
|
|
const currentSelection = get(selectedNodeIdsAtom);
|
|
if (currentSelection.has(nodeId) && currentSelection.size === 1) {
|
|
return;
|
|
}
|
|
set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId]));
|
|
}
|
|
});
|
|
toggleNodeInSelectionAtom = atom4(null, (get, set, nodeId) => {
|
|
const currentSelection = get(selectedNodeIdsAtom);
|
|
const newSelection = new Set(currentSelection);
|
|
if (newSelection.has(nodeId)) {
|
|
newSelection.delete(nodeId);
|
|
} else {
|
|
newSelection.add(nodeId);
|
|
}
|
|
set(selectedNodeIdsAtom, newSelection);
|
|
});
|
|
clearSelectionAtom = atom4(null, (_get, set) => {
|
|
debug3("clearSelection");
|
|
set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
|
|
});
|
|
addNodesToSelectionAtom = atom4(null, (get, set, nodeIds) => {
|
|
const currentSelection = get(selectedNodeIdsAtom);
|
|
const newSelection = new Set(currentSelection);
|
|
for (const nodeId of nodeIds) {
|
|
newSelection.add(nodeId);
|
|
}
|
|
set(selectedNodeIdsAtom, newSelection);
|
|
});
|
|
removeNodesFromSelectionAtom = atom4(null, (get, set, nodeIds) => {
|
|
const currentSelection = get(selectedNodeIdsAtom);
|
|
const newSelection = new Set(currentSelection);
|
|
for (const nodeId of nodeIds) {
|
|
newSelection.delete(nodeId);
|
|
}
|
|
set(selectedNodeIdsAtom, newSelection);
|
|
});
|
|
selectEdgeAtom = atom4(null, (get, set, edgeId) => {
|
|
set(selectedEdgeIdAtom, edgeId);
|
|
if (edgeId !== null) {
|
|
set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
|
|
}
|
|
});
|
|
clearEdgeSelectionAtom = atom4(null, (_get, set) => {
|
|
set(selectedEdgeIdAtom, null);
|
|
});
|
|
focusedNodeIdAtom = atom4(null);
|
|
setFocusedNodeAtom = atom4(null, (_get, set, nodeId) => {
|
|
set(focusedNodeIdAtom, nodeId);
|
|
});
|
|
hasFocusedNodeAtom = atom4((get) => get(focusedNodeIdAtom) !== null);
|
|
selectedNodesCountAtom = atom4((get) => get(selectedNodeIdsAtom).size);
|
|
hasSelectionAtom = atom4((get) => get(selectedNodeIdsAtom).size > 0);
|
|
}
|
|
});
|
|
|
|
// src/utils/layout.ts
|
|
function getNodeCenter(node) {
|
|
return {
|
|
x: node.x + node.width / 2,
|
|
y: node.y + node.height / 2
|
|
};
|
|
}
|
|
function setNodeCenter(node, centerX, centerY) {
|
|
return {
|
|
...node,
|
|
x: centerX - node.width / 2,
|
|
y: centerY - node.height / 2
|
|
};
|
|
}
|
|
function checkNodesOverlap(node1, node2) {
|
|
const center1 = getNodeCenter(node1);
|
|
const center2 = getNodeCenter(node2);
|
|
const dx = Math.abs(center1.x - center2.x);
|
|
const dy = Math.abs(center1.y - center2.y);
|
|
const minDistanceX = (node1.width + node2.width) / 2;
|
|
const minDistanceY = (node1.height + node2.height) / 2;
|
|
return dx < minDistanceX && dy < minDistanceY;
|
|
}
|
|
function getNodeBounds(node) {
|
|
return {
|
|
x: node.x,
|
|
y: node.y,
|
|
width: node.width,
|
|
height: node.height,
|
|
left: node.x,
|
|
right: node.x + node.width,
|
|
top: node.y,
|
|
bottom: node.y + node.height
|
|
};
|
|
}
|
|
function areNodesClose(node1, node2, threshold = 200) {
|
|
const center1 = getNodeCenter(node1);
|
|
const center2 = getNodeCenter(node2);
|
|
const distance = Math.sqrt(Math.pow(center1.x - center2.x, 2) + Math.pow(center1.y - center2.y, 2));
|
|
return distance <= threshold;
|
|
}
|
|
var FitToBoundsMode, calculateBounds;
|
|
var init_layout = __esm({
|
|
"src/utils/layout.ts"() {
|
|
"use strict";
|
|
FitToBoundsMode = /* @__PURE__ */ (function(FitToBoundsMode2) {
|
|
FitToBoundsMode2["Graph"] = "graph";
|
|
FitToBoundsMode2["Selection"] = "selection";
|
|
return FitToBoundsMode2;
|
|
})({});
|
|
calculateBounds = (nodes) => {
|
|
if (nodes.length === 0) {
|
|
return {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
}
|
|
const minX = Math.min(...nodes.map((node) => node.x));
|
|
const minY = Math.min(...nodes.map((node) => node.y));
|
|
const maxX = Math.max(...nodes.map((node) => node.x + node.width));
|
|
const maxY = Math.max(...nodes.map((node) => node.y + node.height));
|
|
return {
|
|
x: minX,
|
|
y: minY,
|
|
width: maxX - minX,
|
|
height: maxY - minY
|
|
};
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/viewport-store.ts
|
|
var viewport_store_exports = {};
|
|
__export(viewport_store_exports, {
|
|
ZOOM_EXIT_THRESHOLD: () => ZOOM_EXIT_THRESHOLD,
|
|
ZOOM_TRANSITION_THRESHOLD: () => ZOOM_TRANSITION_THRESHOLD,
|
|
animateFitToBoundsAtom: () => animateFitToBoundsAtom,
|
|
animateZoomToNodeAtom: () => animateZoomToNodeAtom,
|
|
centerOnNodeAtom: () => centerOnNodeAtom,
|
|
fitToBoundsAtom: () => fitToBoundsAtom,
|
|
isZoomTransitioningAtom: () => isZoomTransitioningAtom,
|
|
panAtom: () => panAtom,
|
|
resetViewportAtom: () => resetViewportAtom,
|
|
screenToWorldAtom: () => screenToWorldAtom,
|
|
setZoomAtom: () => setZoomAtom,
|
|
viewportRectAtom: () => viewportRectAtom,
|
|
worldToScreenAtom: () => worldToScreenAtom,
|
|
zoomAnimationTargetAtom: () => zoomAnimationTargetAtom,
|
|
zoomAtom: () => zoomAtom,
|
|
zoomFocusNodeIdAtom: () => zoomFocusNodeIdAtom,
|
|
zoomTransitionProgressAtom: () => zoomTransitionProgressAtom
|
|
});
|
|
import { atom as atom5 } from "jotai";
|
|
var zoomAtom, panAtom, viewportRectAtom, screenToWorldAtom, worldToScreenAtom, setZoomAtom, resetViewportAtom, fitToBoundsAtom, centerOnNodeAtom, ZOOM_TRANSITION_THRESHOLD, ZOOM_EXIT_THRESHOLD, zoomFocusNodeIdAtom, zoomTransitionProgressAtom, isZoomTransitioningAtom, zoomAnimationTargetAtom, animateZoomToNodeAtom, animateFitToBoundsAtom;
|
|
var init_viewport_store = __esm({
|
|
"src/core/viewport-store.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_selection_store();
|
|
init_layout();
|
|
zoomAtom = atom5(1);
|
|
panAtom = atom5({
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
viewportRectAtom = atom5(null);
|
|
screenToWorldAtom = atom5((get) => {
|
|
return (screenX, screenY) => {
|
|
const pan = get(panAtom);
|
|
const zoom = get(zoomAtom);
|
|
const rect = get(viewportRectAtom);
|
|
if (!rect) {
|
|
return {
|
|
x: screenX,
|
|
y: screenY
|
|
};
|
|
}
|
|
const relativeX = screenX - rect.left;
|
|
const relativeY = screenY - rect.top;
|
|
return {
|
|
x: (relativeX - pan.x) / zoom,
|
|
y: (relativeY - pan.y) / zoom
|
|
};
|
|
};
|
|
});
|
|
worldToScreenAtom = atom5((get) => {
|
|
return (worldX, worldY) => {
|
|
const pan = get(panAtom);
|
|
const zoom = get(zoomAtom);
|
|
const rect = get(viewportRectAtom);
|
|
if (!rect) {
|
|
return {
|
|
x: worldX,
|
|
y: worldY
|
|
};
|
|
}
|
|
return {
|
|
x: worldX * zoom + pan.x + rect.left,
|
|
y: worldY * zoom + pan.y + rect.top
|
|
};
|
|
};
|
|
});
|
|
setZoomAtom = atom5(null, (get, set, {
|
|
zoom,
|
|
centerX,
|
|
centerY
|
|
}) => {
|
|
const currentZoom = get(zoomAtom);
|
|
const pan = get(panAtom);
|
|
const rect = get(viewportRectAtom);
|
|
const newZoom = Math.max(0.1, Math.min(5, zoom));
|
|
if (centerX !== void 0 && centerY !== void 0 && rect) {
|
|
const relativeX = centerX - rect.left;
|
|
const relativeY = centerY - rect.top;
|
|
const worldX = (relativeX - pan.x) / currentZoom;
|
|
const worldY = (relativeY - pan.y) / currentZoom;
|
|
const newPanX = relativeX - worldX * newZoom;
|
|
const newPanY = relativeY - worldY * newZoom;
|
|
set(panAtom, {
|
|
x: newPanX,
|
|
y: newPanY
|
|
});
|
|
}
|
|
set(zoomAtom, newZoom);
|
|
});
|
|
resetViewportAtom = atom5(null, (_get, set) => {
|
|
set(zoomAtom, 1);
|
|
set(panAtom, {
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
});
|
|
fitToBoundsAtom = atom5(null, (get, set, {
|
|
mode,
|
|
padding = 20
|
|
}) => {
|
|
const normalizedMode = typeof mode === "string" ? mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection : mode;
|
|
const viewportSize = get(viewportRectAtom);
|
|
if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return;
|
|
get(nodePositionUpdateCounterAtom);
|
|
let bounds;
|
|
if (normalizedMode === FitToBoundsMode.Graph) {
|
|
const graph = get(graphAtom);
|
|
const nodes = graph.nodes().map((node) => {
|
|
const attrs = graph.getNodeAttributes(node);
|
|
return {
|
|
x: attrs.x,
|
|
y: attrs.y,
|
|
width: attrs.width || 500,
|
|
height: attrs.height || 500
|
|
};
|
|
});
|
|
bounds = calculateBounds(nodes);
|
|
} else {
|
|
const selectedIds = get(selectedNodeIdsAtom);
|
|
const allNodes = get(uiNodesAtom);
|
|
const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({
|
|
x: n.position.x,
|
|
y: n.position.y,
|
|
width: n.width ?? 500,
|
|
height: n.height ?? 500
|
|
}));
|
|
bounds = calculateBounds(selectedNodes);
|
|
}
|
|
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
const maxHPad = Math.max(0, viewportSize.width / 2 - 1);
|
|
const maxVPad = Math.max(0, viewportSize.height / 2 - 1);
|
|
const safePadding = Math.max(0, Math.min(padding, maxHPad, maxVPad));
|
|
const effW = Math.max(1, viewportSize.width - 2 * safePadding);
|
|
const effH = Math.max(1, viewportSize.height - 2 * safePadding);
|
|
const scale = Math.min(effW / bounds.width, effH / bounds.height);
|
|
if (scale <= 0 || !isFinite(scale)) return;
|
|
set(zoomAtom, scale);
|
|
const scaledW = bounds.width * scale;
|
|
const scaledH = bounds.height * scale;
|
|
const startX = safePadding + (effW - scaledW) / 2;
|
|
const startY = safePadding + (effH - scaledH) / 2;
|
|
set(panAtom, {
|
|
x: startX - bounds.x * scale,
|
|
y: startY - bounds.y * scale
|
|
});
|
|
});
|
|
centerOnNodeAtom = atom5(null, (get, set, nodeId) => {
|
|
const nodes = get(uiNodesAtom);
|
|
const node = nodes.find((n) => n.id === nodeId);
|
|
if (!node) return;
|
|
const {
|
|
x,
|
|
y,
|
|
width = 200,
|
|
height = 100
|
|
} = node;
|
|
const zoom = get(zoomAtom);
|
|
const centerX = x + width / 2;
|
|
const centerY = y + height / 2;
|
|
const rect = get(viewportRectAtom);
|
|
const halfWidth = rect ? rect.width / 2 : 400;
|
|
const halfHeight = rect ? rect.height / 2 : 300;
|
|
set(panAtom, {
|
|
x: halfWidth - centerX * zoom,
|
|
y: halfHeight - centerY * zoom
|
|
});
|
|
});
|
|
ZOOM_TRANSITION_THRESHOLD = 3.5;
|
|
ZOOM_EXIT_THRESHOLD = 2;
|
|
zoomFocusNodeIdAtom = atom5(null);
|
|
zoomTransitionProgressAtom = atom5(0);
|
|
isZoomTransitioningAtom = atom5((get) => {
|
|
const progress = get(zoomTransitionProgressAtom);
|
|
return progress > 0 && progress < 1;
|
|
});
|
|
zoomAnimationTargetAtom = atom5(null);
|
|
animateZoomToNodeAtom = atom5(null, (get, set, {
|
|
nodeId,
|
|
targetZoom,
|
|
duration = 300
|
|
}) => {
|
|
const nodes = get(uiNodesAtom);
|
|
const node = nodes.find((n) => n.id === nodeId);
|
|
if (!node) return;
|
|
const {
|
|
x,
|
|
y,
|
|
width = 200,
|
|
height = 100
|
|
} = node;
|
|
const centerX = x + width / 2;
|
|
const centerY = y + height / 2;
|
|
const rect = get(viewportRectAtom);
|
|
const halfWidth = rect ? rect.width / 2 : 400;
|
|
const halfHeight = rect ? rect.height / 2 : 300;
|
|
const finalZoom = targetZoom ?? get(zoomAtom);
|
|
const targetPan = {
|
|
x: halfWidth - centerX * finalZoom,
|
|
y: halfHeight - centerY * finalZoom
|
|
};
|
|
set(zoomFocusNodeIdAtom, nodeId);
|
|
set(zoomAnimationTargetAtom, {
|
|
targetZoom: finalZoom,
|
|
targetPan,
|
|
startZoom: get(zoomAtom),
|
|
startPan: {
|
|
...get(panAtom)
|
|
},
|
|
duration,
|
|
startTime: performance.now()
|
|
});
|
|
});
|
|
animateFitToBoundsAtom = atom5(null, (get, set, {
|
|
mode,
|
|
padding = 20,
|
|
duration = 300
|
|
}) => {
|
|
const viewportSize = get(viewportRectAtom);
|
|
if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return;
|
|
get(nodePositionUpdateCounterAtom);
|
|
let bounds;
|
|
if (mode === "graph") {
|
|
const graph = get(graphAtom);
|
|
const nodes = graph.nodes().map((node) => {
|
|
const attrs = graph.getNodeAttributes(node);
|
|
return {
|
|
x: attrs.x,
|
|
y: attrs.y,
|
|
width: attrs.width || 500,
|
|
height: attrs.height || 500
|
|
};
|
|
});
|
|
bounds = calculateBounds(nodes);
|
|
} else {
|
|
const selectedIds = get(selectedNodeIdsAtom);
|
|
const allNodes = get(uiNodesAtom);
|
|
const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({
|
|
x: n.position.x,
|
|
y: n.position.y,
|
|
width: n.width ?? 500,
|
|
height: n.height ?? 500
|
|
}));
|
|
bounds = calculateBounds(selectedNodes);
|
|
}
|
|
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
const safePadding = Math.max(0, Math.min(padding, viewportSize.width / 2 - 1, viewportSize.height / 2 - 1));
|
|
const effW = Math.max(1, viewportSize.width - 2 * safePadding);
|
|
const effH = Math.max(1, viewportSize.height - 2 * safePadding);
|
|
const scale = Math.min(effW / bounds.width, effH / bounds.height);
|
|
if (scale <= 0 || !isFinite(scale)) return;
|
|
const scaledW = bounds.width * scale;
|
|
const scaledH = bounds.height * scale;
|
|
const startX = safePadding + (effW - scaledW) / 2;
|
|
const startY = safePadding + (effH - scaledH) / 2;
|
|
const targetPan = {
|
|
x: startX - bounds.x * scale,
|
|
y: startY - bounds.y * scale
|
|
};
|
|
set(zoomAnimationTargetAtom, {
|
|
targetZoom: scale,
|
|
targetPan,
|
|
startZoom: get(zoomAtom),
|
|
startPan: {
|
|
...get(panAtom)
|
|
},
|
|
duration,
|
|
startTime: performance.now()
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/history-actions.ts
|
|
function applyDelta(graph, delta) {
|
|
switch (delta.type) {
|
|
case "move-node": {
|
|
if (!graph.hasNode(delta.nodeId)) return false;
|
|
graph.setNodeAttribute(delta.nodeId, "x", delta.to.x);
|
|
graph.setNodeAttribute(delta.nodeId, "y", delta.to.y);
|
|
return false;
|
|
}
|
|
case "resize-node": {
|
|
if (!graph.hasNode(delta.nodeId)) return false;
|
|
graph.setNodeAttribute(delta.nodeId, "width", delta.to.width);
|
|
graph.setNodeAttribute(delta.nodeId, "height", delta.to.height);
|
|
return false;
|
|
}
|
|
case "add-node": {
|
|
if (graph.hasNode(delta.nodeId)) return false;
|
|
graph.addNode(delta.nodeId, delta.attributes);
|
|
return true;
|
|
}
|
|
case "remove-node": {
|
|
if (!graph.hasNode(delta.nodeId)) return false;
|
|
graph.dropNode(delta.nodeId);
|
|
return true;
|
|
}
|
|
case "add-edge": {
|
|
if (graph.hasEdge(delta.edgeId)) return false;
|
|
if (!graph.hasNode(delta.source) || !graph.hasNode(delta.target)) return false;
|
|
graph.addEdgeWithKey(delta.edgeId, delta.source, delta.target, delta.attributes);
|
|
return true;
|
|
}
|
|
case "remove-edge": {
|
|
if (!graph.hasEdge(delta.edgeId)) return false;
|
|
graph.dropEdge(delta.edgeId);
|
|
return true;
|
|
}
|
|
case "update-node-attr": {
|
|
if (!graph.hasNode(delta.nodeId)) return false;
|
|
graph.setNodeAttribute(delta.nodeId, delta.key, delta.to);
|
|
return false;
|
|
}
|
|
case "batch": {
|
|
let structuralChange = false;
|
|
for (const d of delta.deltas) {
|
|
if (applyDelta(graph, d)) structuralChange = true;
|
|
}
|
|
return structuralChange;
|
|
}
|
|
case "full-snapshot": {
|
|
graph.clear();
|
|
for (const node of delta.nodes) {
|
|
graph.addNode(node.id, node.attributes);
|
|
}
|
|
for (const edge of delta.edges) {
|
|
if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) {
|
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge.attributes);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
function invertDelta(delta) {
|
|
switch (delta.type) {
|
|
case "move-node":
|
|
return {
|
|
...delta,
|
|
from: delta.to,
|
|
to: delta.from
|
|
};
|
|
case "resize-node":
|
|
return {
|
|
...delta,
|
|
from: delta.to,
|
|
to: delta.from
|
|
};
|
|
case "add-node":
|
|
return {
|
|
type: "remove-node",
|
|
nodeId: delta.nodeId,
|
|
attributes: delta.attributes,
|
|
connectedEdges: []
|
|
};
|
|
case "remove-node": {
|
|
const batch = [{
|
|
type: "add-node",
|
|
nodeId: delta.nodeId,
|
|
attributes: delta.attributes
|
|
}, ...delta.connectedEdges.map((e) => ({
|
|
type: "add-edge",
|
|
edgeId: e.id,
|
|
source: e.source,
|
|
target: e.target,
|
|
attributes: e.attributes
|
|
}))];
|
|
return batch.length === 1 ? batch[0] : {
|
|
type: "batch",
|
|
deltas: batch
|
|
};
|
|
}
|
|
case "add-edge":
|
|
return {
|
|
type: "remove-edge",
|
|
edgeId: delta.edgeId,
|
|
source: delta.source,
|
|
target: delta.target,
|
|
attributes: delta.attributes
|
|
};
|
|
case "remove-edge":
|
|
return {
|
|
type: "add-edge",
|
|
edgeId: delta.edgeId,
|
|
source: delta.source,
|
|
target: delta.target,
|
|
attributes: delta.attributes
|
|
};
|
|
case "update-node-attr":
|
|
return {
|
|
...delta,
|
|
from: delta.to,
|
|
to: delta.from
|
|
};
|
|
case "batch":
|
|
return {
|
|
type: "batch",
|
|
deltas: delta.deltas.map(invertDelta).reverse()
|
|
};
|
|
case "full-snapshot":
|
|
return delta;
|
|
}
|
|
}
|
|
function createSnapshot(graph, label) {
|
|
const nodes = [];
|
|
const edges = [];
|
|
graph.forEachNode((nodeId, attributes) => {
|
|
nodes.push({
|
|
id: nodeId,
|
|
attributes: {
|
|
...attributes
|
|
}
|
|
});
|
|
});
|
|
graph.forEachEdge((edgeId, attributes, source, target) => {
|
|
edges.push({
|
|
id: edgeId,
|
|
source,
|
|
target,
|
|
attributes: {
|
|
...attributes
|
|
}
|
|
});
|
|
});
|
|
return {
|
|
timestamp: Date.now(),
|
|
label,
|
|
nodes,
|
|
edges
|
|
};
|
|
}
|
|
var init_history_actions = __esm({
|
|
"src/core/history-actions.ts"() {
|
|
"use strict";
|
|
}
|
|
});
|
|
|
|
// src/core/history-store.ts
|
|
var history_store_exports = {};
|
|
__export(history_store_exports, {
|
|
applyDelta: () => applyDelta,
|
|
canRedoAtom: () => canRedoAtom,
|
|
canUndoAtom: () => canUndoAtom,
|
|
clearHistoryAtom: () => clearHistoryAtom,
|
|
createSnapshot: () => createSnapshot,
|
|
historyLabelsAtom: () => historyLabelsAtom,
|
|
historyStateAtom: () => historyStateAtom,
|
|
invertDelta: () => invertDelta,
|
|
pushDeltaAtom: () => pushDeltaAtom,
|
|
pushHistoryAtom: () => pushHistoryAtom,
|
|
redoAtom: () => redoAtom,
|
|
redoCountAtom: () => redoCountAtom,
|
|
undoAtom: () => undoAtom,
|
|
undoCountAtom: () => undoCountAtom
|
|
});
|
|
import { atom as atom6 } from "jotai";
|
|
var debug4, MAX_HISTORY_SIZE, historyStateAtom, canUndoAtom, canRedoAtom, undoCountAtom, redoCountAtom, pushDeltaAtom, pushHistoryAtom, undoAtom, redoAtom, clearHistoryAtom, historyLabelsAtom;
|
|
var init_history_store = __esm({
|
|
"src/core/history-store.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_debug();
|
|
init_history_actions();
|
|
init_history_actions();
|
|
debug4 = createDebug("history");
|
|
MAX_HISTORY_SIZE = 50;
|
|
historyStateAtom = atom6({
|
|
past: [],
|
|
future: [],
|
|
isApplying: false
|
|
});
|
|
canUndoAtom = atom6((get) => {
|
|
const history = get(historyStateAtom);
|
|
return history.past.length > 0 && !history.isApplying;
|
|
});
|
|
canRedoAtom = atom6((get) => {
|
|
const history = get(historyStateAtom);
|
|
return history.future.length > 0 && !history.isApplying;
|
|
});
|
|
undoCountAtom = atom6((get) => get(historyStateAtom).past.length);
|
|
redoCountAtom = atom6((get) => get(historyStateAtom).future.length);
|
|
pushDeltaAtom = atom6(null, (get, set, delta) => {
|
|
const history = get(historyStateAtom);
|
|
if (history.isApplying) return;
|
|
const {
|
|
label,
|
|
...cleanDelta
|
|
} = delta;
|
|
const entry = {
|
|
forward: cleanDelta,
|
|
reverse: invertDelta(cleanDelta),
|
|
timestamp: Date.now(),
|
|
label
|
|
};
|
|
const newPast = [...history.past, entry];
|
|
if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();
|
|
set(historyStateAtom, {
|
|
past: newPast,
|
|
future: [],
|
|
// Clear redo stack
|
|
isApplying: false
|
|
});
|
|
debug4("Pushed delta: %s (past: %d)", label || delta.type, newPast.length);
|
|
});
|
|
pushHistoryAtom = atom6(null, (get, set, label) => {
|
|
const history = get(historyStateAtom);
|
|
if (history.isApplying) return;
|
|
const graph = get(graphAtom);
|
|
const snapshot = createSnapshot(graph, label);
|
|
const forward = {
|
|
type: "full-snapshot",
|
|
nodes: snapshot.nodes,
|
|
edges: snapshot.edges
|
|
};
|
|
const entry = {
|
|
forward,
|
|
reverse: forward,
|
|
// For full snapshots, reverse IS the current state
|
|
timestamp: Date.now(),
|
|
label
|
|
};
|
|
const newPast = [...history.past, entry];
|
|
if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();
|
|
set(historyStateAtom, {
|
|
past: newPast,
|
|
future: [],
|
|
isApplying: false
|
|
});
|
|
debug4("Pushed snapshot: %s (past: %d)", label || "unnamed", newPast.length);
|
|
});
|
|
undoAtom = atom6(null, (get, set) => {
|
|
const history = get(historyStateAtom);
|
|
if (history.past.length === 0 || history.isApplying) return false;
|
|
set(historyStateAtom, {
|
|
...history,
|
|
isApplying: true
|
|
});
|
|
try {
|
|
const graph = get(graphAtom);
|
|
const newPast = [...history.past];
|
|
const entry = newPast.pop();
|
|
let forwardForRedo = entry.forward;
|
|
if (entry.reverse.type === "full-snapshot") {
|
|
const currentSnapshot = createSnapshot(graph, "current");
|
|
forwardForRedo = {
|
|
type: "full-snapshot",
|
|
nodes: currentSnapshot.nodes,
|
|
edges: currentSnapshot.edges
|
|
};
|
|
}
|
|
const structuralChange = applyDelta(graph, entry.reverse);
|
|
if (structuralChange) {
|
|
set(graphAtom, graph);
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
}
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
const redoEntry = {
|
|
forward: forwardForRedo,
|
|
reverse: entry.reverse,
|
|
timestamp: entry.timestamp,
|
|
label: entry.label
|
|
};
|
|
set(historyStateAtom, {
|
|
past: newPast,
|
|
future: [redoEntry, ...history.future],
|
|
isApplying: false
|
|
});
|
|
debug4("Undo: %s (past: %d, future: %d)", entry.label, newPast.length, history.future.length + 1);
|
|
return true;
|
|
} catch (error) {
|
|
debug4.error("Undo failed: %O", error);
|
|
set(historyStateAtom, {
|
|
...history,
|
|
isApplying: false
|
|
});
|
|
return false;
|
|
}
|
|
});
|
|
redoAtom = atom6(null, (get, set) => {
|
|
const history = get(historyStateAtom);
|
|
if (history.future.length === 0 || history.isApplying) return false;
|
|
set(historyStateAtom, {
|
|
...history,
|
|
isApplying: true
|
|
});
|
|
try {
|
|
const graph = get(graphAtom);
|
|
const newFuture = [...history.future];
|
|
const entry = newFuture.shift();
|
|
let reverseForUndo = entry.reverse;
|
|
if (entry.forward.type === "full-snapshot") {
|
|
const currentSnapshot = createSnapshot(graph, "current");
|
|
reverseForUndo = {
|
|
type: "full-snapshot",
|
|
nodes: currentSnapshot.nodes,
|
|
edges: currentSnapshot.edges
|
|
};
|
|
}
|
|
const structuralChange = applyDelta(graph, entry.forward);
|
|
if (structuralChange) {
|
|
set(graphAtom, graph);
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
}
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
const undoEntry = {
|
|
forward: entry.forward,
|
|
reverse: reverseForUndo,
|
|
timestamp: entry.timestamp,
|
|
label: entry.label
|
|
};
|
|
set(historyStateAtom, {
|
|
past: [...history.past, undoEntry],
|
|
future: newFuture,
|
|
isApplying: false
|
|
});
|
|
debug4("Redo: %s (past: %d, future: %d)", entry.label, history.past.length + 1, newFuture.length);
|
|
return true;
|
|
} catch (error) {
|
|
debug4.error("Redo failed: %O", error);
|
|
set(historyStateAtom, {
|
|
...history,
|
|
isApplying: false
|
|
});
|
|
return false;
|
|
}
|
|
});
|
|
clearHistoryAtom = atom6(null, (_get, set) => {
|
|
set(historyStateAtom, {
|
|
past: [],
|
|
future: [],
|
|
isApplying: false
|
|
});
|
|
debug4("History cleared");
|
|
});
|
|
historyLabelsAtom = atom6((get) => {
|
|
const history = get(historyStateAtom);
|
|
return {
|
|
past: history.past.map((e) => e.label || "Unnamed"),
|
|
future: history.future.map((e) => e.label || "Unnamed")
|
|
};
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/group-store.ts
|
|
var group_store_exports = {};
|
|
__export(group_store_exports, {
|
|
autoResizeGroupAtom: () => autoResizeGroupAtom,
|
|
collapseGroupAtom: () => collapseGroupAtom,
|
|
collapsedEdgeRemapAtom: () => collapsedEdgeRemapAtom,
|
|
collapsedGroupsAtom: () => collapsedGroupsAtom,
|
|
expandGroupAtom: () => expandGroupAtom,
|
|
getNodeDescendants: () => getNodeDescendants,
|
|
groupChildCountAtom: () => groupChildCountAtom,
|
|
groupSelectedNodesAtom: () => groupSelectedNodesAtom,
|
|
isGroupNodeAtom: () => isGroupNodeAtom,
|
|
isNodeCollapsed: () => isNodeCollapsed,
|
|
moveNodesToGroupAtom: () => moveNodesToGroupAtom,
|
|
nestNodesOnDropAtom: () => nestNodesOnDropAtom,
|
|
nodeChildrenAtom: () => nodeChildrenAtom,
|
|
nodeParentAtom: () => nodeParentAtom,
|
|
removeFromGroupAtom: () => removeFromGroupAtom,
|
|
setNodeParentAtom: () => setNodeParentAtom,
|
|
toggleGroupCollapseAtom: () => toggleGroupCollapseAtom,
|
|
ungroupNodesAtom: () => ungroupNodesAtom
|
|
});
|
|
import { atom as atom7 } from "jotai";
|
|
function getNodeDescendants(graph, groupId) {
|
|
const descendants = [];
|
|
const stack = [groupId];
|
|
while (stack.length > 0) {
|
|
const current = stack.pop();
|
|
graph.forEachNode((nodeId, attrs) => {
|
|
if (attrs.parentId === current) {
|
|
descendants.push(nodeId);
|
|
stack.push(nodeId);
|
|
}
|
|
});
|
|
}
|
|
return descendants;
|
|
}
|
|
function isNodeCollapsed(nodeId, getParentId, collapsed) {
|
|
let current = nodeId;
|
|
while (true) {
|
|
const parentId = getParentId(current);
|
|
if (!parentId) return false;
|
|
if (collapsed.has(parentId)) return true;
|
|
current = parentId;
|
|
}
|
|
}
|
|
var collapsedGroupsAtom, toggleGroupCollapseAtom, collapseGroupAtom, expandGroupAtom, nodeChildrenAtom, nodeParentAtom, isGroupNodeAtom, groupChildCountAtom, setNodeParentAtom, moveNodesToGroupAtom, removeFromGroupAtom, groupSelectedNodesAtom, ungroupNodesAtom, nestNodesOnDropAtom, collapsedEdgeRemapAtom, autoResizeGroupAtom;
|
|
var init_group_store = __esm({
|
|
"src/core/group-store.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_history_store();
|
|
collapsedGroupsAtom = atom7(/* @__PURE__ */ new Set());
|
|
toggleGroupCollapseAtom = atom7(null, (get, set, groupId) => {
|
|
const current = get(collapsedGroupsAtom);
|
|
const next = new Set(current);
|
|
if (next.has(groupId)) {
|
|
next.delete(groupId);
|
|
} else {
|
|
next.add(groupId);
|
|
}
|
|
set(collapsedGroupsAtom, next);
|
|
});
|
|
collapseGroupAtom = atom7(null, (get, set, groupId) => {
|
|
const current = get(collapsedGroupsAtom);
|
|
if (!current.has(groupId)) {
|
|
const next = new Set(current);
|
|
next.add(groupId);
|
|
set(collapsedGroupsAtom, next);
|
|
}
|
|
});
|
|
expandGroupAtom = atom7(null, (get, set, groupId) => {
|
|
const current = get(collapsedGroupsAtom);
|
|
if (current.has(groupId)) {
|
|
const next = new Set(current);
|
|
next.delete(groupId);
|
|
set(collapsedGroupsAtom, next);
|
|
}
|
|
});
|
|
nodeChildrenAtom = atom7((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
return (parentId) => {
|
|
const children = [];
|
|
graph.forEachNode((nodeId, attrs) => {
|
|
if (attrs.parentId === parentId) {
|
|
children.push(nodeId);
|
|
}
|
|
});
|
|
return children;
|
|
};
|
|
});
|
|
nodeParentAtom = atom7((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
return (nodeId) => {
|
|
if (!graph.hasNode(nodeId)) return void 0;
|
|
return graph.getNodeAttribute(nodeId, "parentId");
|
|
};
|
|
});
|
|
isGroupNodeAtom = atom7((get) => {
|
|
const getChildren = get(nodeChildrenAtom);
|
|
return (nodeId) => getChildren(nodeId).length > 0;
|
|
});
|
|
groupChildCountAtom = atom7((get) => {
|
|
const getChildren = get(nodeChildrenAtom);
|
|
return (groupId) => getChildren(groupId).length;
|
|
});
|
|
setNodeParentAtom = atom7(null, (get, set, {
|
|
nodeId,
|
|
parentId
|
|
}) => {
|
|
const graph = get(graphAtom);
|
|
if (!graph.hasNode(nodeId)) return;
|
|
if (parentId) {
|
|
if (parentId === nodeId) return;
|
|
let current = parentId;
|
|
while (current) {
|
|
if (current === nodeId) return;
|
|
if (!graph.hasNode(current)) break;
|
|
current = graph.getNodeAttribute(current, "parentId");
|
|
}
|
|
}
|
|
graph.setNodeAttribute(nodeId, "parentId", parentId);
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
});
|
|
moveNodesToGroupAtom = atom7(null, (get, set, {
|
|
nodeIds,
|
|
groupId
|
|
}) => {
|
|
for (const nodeId of nodeIds) {
|
|
set(setNodeParentAtom, {
|
|
nodeId,
|
|
parentId: groupId
|
|
});
|
|
}
|
|
});
|
|
removeFromGroupAtom = atom7(null, (get, set, nodeId) => {
|
|
set(setNodeParentAtom, {
|
|
nodeId,
|
|
parentId: void 0
|
|
});
|
|
});
|
|
groupSelectedNodesAtom = atom7(null, (get, set, {
|
|
nodeIds,
|
|
groupNodeId
|
|
}) => {
|
|
set(pushHistoryAtom, `Group ${nodeIds.length} nodes`);
|
|
const graph = get(graphAtom);
|
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
for (const nodeId of nodeIds) {
|
|
if (!graph.hasNode(nodeId)) continue;
|
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
minX = Math.min(minX, attrs.x);
|
|
minY = Math.min(minY, attrs.y);
|
|
maxX = Math.max(maxX, attrs.x + (attrs.width || 200));
|
|
maxY = Math.max(maxY, attrs.y + (attrs.height || 100));
|
|
}
|
|
const padding = 20;
|
|
if (graph.hasNode(groupNodeId)) {
|
|
graph.setNodeAttribute(groupNodeId, "x", minX - padding);
|
|
graph.setNodeAttribute(groupNodeId, "y", minY - padding - 30);
|
|
graph.setNodeAttribute(groupNodeId, "width", maxX - minX + 2 * padding);
|
|
graph.setNodeAttribute(groupNodeId, "height", maxY - minY + 2 * padding + 30);
|
|
}
|
|
for (const nodeId of nodeIds) {
|
|
if (nodeId !== groupNodeId && graph.hasNode(nodeId)) {
|
|
graph.setNodeAttribute(nodeId, "parentId", groupNodeId);
|
|
}
|
|
}
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
});
|
|
ungroupNodesAtom = atom7(null, (get, set, groupId) => {
|
|
set(pushHistoryAtom, "Ungroup nodes");
|
|
const graph = get(graphAtom);
|
|
graph.forEachNode((nodeId, attrs) => {
|
|
if (attrs.parentId === groupId) {
|
|
graph.setNodeAttribute(nodeId, "parentId", void 0);
|
|
}
|
|
});
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
});
|
|
nestNodesOnDropAtom = atom7(null, (get, set, {
|
|
nodeIds,
|
|
targetId
|
|
}) => {
|
|
set(pushHistoryAtom, "Nest nodes");
|
|
for (const nodeId of nodeIds) {
|
|
if (nodeId === targetId) continue;
|
|
set(setNodeParentAtom, {
|
|
nodeId,
|
|
parentId: targetId
|
|
});
|
|
}
|
|
set(autoResizeGroupAtom, targetId);
|
|
});
|
|
collapsedEdgeRemapAtom = atom7((get) => {
|
|
const collapsed = get(collapsedGroupsAtom);
|
|
if (collapsed.size === 0) return /* @__PURE__ */ new Map();
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
const remap = /* @__PURE__ */ new Map();
|
|
for (const nodeId of graph.nodes()) {
|
|
let current = nodeId;
|
|
let outermost = null;
|
|
while (true) {
|
|
if (!graph.hasNode(current)) break;
|
|
const parent = graph.getNodeAttribute(current, "parentId");
|
|
if (!parent) break;
|
|
if (collapsed.has(parent)) outermost = parent;
|
|
current = parent;
|
|
}
|
|
if (outermost) remap.set(nodeId, outermost);
|
|
}
|
|
return remap;
|
|
});
|
|
autoResizeGroupAtom = atom7(null, (get, set, groupId) => {
|
|
const graph = get(graphAtom);
|
|
if (!graph.hasNode(groupId)) return;
|
|
const children = [];
|
|
graph.forEachNode((nodeId, attrs) => {
|
|
if (attrs.parentId === groupId) {
|
|
children.push(nodeId);
|
|
}
|
|
});
|
|
if (children.length === 0) return;
|
|
const padding = 20;
|
|
const headerHeight = 30;
|
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
for (const childId of children) {
|
|
const attrs = graph.getNodeAttributes(childId);
|
|
minX = Math.min(minX, attrs.x);
|
|
minY = Math.min(minY, attrs.y);
|
|
maxX = Math.max(maxX, attrs.x + (attrs.width || 200));
|
|
maxY = Math.max(maxY, attrs.y + (attrs.height || 100));
|
|
}
|
|
graph.setNodeAttribute(groupId, "x", minX - padding);
|
|
graph.setNodeAttribute(groupId, "y", minY - padding - headerHeight);
|
|
graph.setNodeAttribute(groupId, "width", maxX - minX + 2 * padding);
|
|
graph.setNodeAttribute(groupId, "height", maxY - minY + 2 * padding + headerHeight);
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/graph-derived.ts
|
|
import { atom as atom8 } from "jotai";
|
|
import { atomFamily as atomFamily2 } from "jotai-family";
|
|
function getEdgeCache(graph) {
|
|
let cache = _edgeCacheByGraph.get(graph);
|
|
if (!cache) {
|
|
cache = /* @__PURE__ */ new Map();
|
|
_edgeCacheByGraph.set(graph, cache);
|
|
}
|
|
return cache;
|
|
}
|
|
var highestZIndexAtom, _prevUiNodesByGraph, uiNodesAtom, nodeKeysAtom, nodeFamilyAtom, edgeKeysAtom, edgeKeysWithTempEdgeAtom, _edgeCacheByGraph, edgeFamilyAtom;
|
|
var init_graph_derived = __esm({
|
|
"src/core/graph-derived.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_viewport_store();
|
|
init_group_store();
|
|
highestZIndexAtom = atom8((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
let maxZ = 0;
|
|
graph.forEachNode((_node, attributes) => {
|
|
if (attributes.zIndex > maxZ) {
|
|
maxZ = attributes.zIndex;
|
|
}
|
|
});
|
|
return maxZ;
|
|
});
|
|
_prevUiNodesByGraph = /* @__PURE__ */ new WeakMap();
|
|
uiNodesAtom = atom8((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
const currentDraggingId = get(draggingNodeIdAtom);
|
|
const collapsed = get(collapsedGroupsAtom);
|
|
const nodes = [];
|
|
graph.forEachNode((nodeId, attributes) => {
|
|
if (collapsed.size > 0) {
|
|
let current = nodeId;
|
|
let hidden = false;
|
|
while (true) {
|
|
if (!graph.hasNode(current)) break;
|
|
const pid = graph.getNodeAttributes(current).parentId;
|
|
if (!pid) break;
|
|
if (collapsed.has(pid)) {
|
|
hidden = true;
|
|
break;
|
|
}
|
|
current = pid;
|
|
}
|
|
if (hidden) return;
|
|
}
|
|
const position = get(nodePositionAtomFamily(nodeId));
|
|
nodes.push({
|
|
...attributes,
|
|
id: nodeId,
|
|
position,
|
|
isDragging: nodeId === currentDraggingId
|
|
});
|
|
});
|
|
const prev = _prevUiNodesByGraph.get(graph) ?? [];
|
|
if (nodes.length === prev.length && nodes.every((n, i) => n.id === prev[i].id && n.position === prev[i].position && n.isDragging === prev[i].isDragging)) {
|
|
return prev;
|
|
}
|
|
_prevUiNodesByGraph.set(graph, nodes);
|
|
return nodes;
|
|
});
|
|
nodeKeysAtom = atom8((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
return graph.nodes();
|
|
});
|
|
nodeFamilyAtom = atomFamily2((nodeId) => atom8((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
if (!graph.hasNode(nodeId)) {
|
|
return null;
|
|
}
|
|
const attributes = graph.getNodeAttributes(nodeId);
|
|
const position = get(nodePositionAtomFamily(nodeId));
|
|
const currentDraggingId = get(draggingNodeIdAtom);
|
|
return {
|
|
...attributes,
|
|
id: nodeId,
|
|
position,
|
|
isDragging: nodeId === currentDraggingId
|
|
};
|
|
}), (a, b) => a === b);
|
|
edgeKeysAtom = atom8((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
return graph.edges();
|
|
});
|
|
edgeKeysWithTempEdgeAtom = atom8((get) => {
|
|
const keys = get(edgeKeysAtom);
|
|
const edgeCreation = get(edgeCreationAtom);
|
|
if (edgeCreation.isCreating) {
|
|
return [...keys, "temp-creating-edge"];
|
|
}
|
|
return keys;
|
|
});
|
|
_edgeCacheByGraph = /* @__PURE__ */ new WeakMap();
|
|
edgeFamilyAtom = atomFamily2((key) => atom8((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
if (key === "temp-creating-edge") {
|
|
const edgeCreationState = get(edgeCreationAtom);
|
|
const graph2 = get(graphAtom);
|
|
if (edgeCreationState.isCreating && edgeCreationState.sourceNodeId && edgeCreationState.targetPosition) {
|
|
const sourceNodeAttrs = graph2.getNodeAttributes(edgeCreationState.sourceNodeId);
|
|
const sourceNodePosition = get(nodePositionAtomFamily(edgeCreationState.sourceNodeId));
|
|
const pan = get(panAtom);
|
|
const zoom = get(zoomAtom);
|
|
const viewportRect = get(viewportRectAtom);
|
|
if (sourceNodeAttrs && viewportRect) {
|
|
const mouseX = edgeCreationState.targetPosition.x - viewportRect.left;
|
|
const mouseY = edgeCreationState.targetPosition.y - viewportRect.top;
|
|
const worldTargetX = (mouseX - pan.x) / zoom;
|
|
const worldTargetY = (mouseY - pan.y) / zoom;
|
|
const tempEdge = {
|
|
key: "temp-creating-edge",
|
|
sourceId: edgeCreationState.sourceNodeId,
|
|
targetId: "temp-cursor",
|
|
sourcePosition: sourceNodePosition,
|
|
targetPosition: {
|
|
x: worldTargetX,
|
|
y: worldTargetY
|
|
},
|
|
sourceNodeSize: sourceNodeAttrs.size,
|
|
sourceNodeWidth: sourceNodeAttrs.width,
|
|
sourceNodeHeight: sourceNodeAttrs.height,
|
|
targetNodeSize: 0,
|
|
targetNodeWidth: 0,
|
|
targetNodeHeight: 0,
|
|
type: "dashed",
|
|
color: "#FF9800",
|
|
weight: 2,
|
|
label: void 0,
|
|
dbData: {
|
|
id: "temp-creating-edge",
|
|
graph_id: get(currentGraphIdAtom) || "",
|
|
source_node_id: edgeCreationState.sourceNodeId,
|
|
target_node_id: "temp-cursor",
|
|
edge_type: "temp",
|
|
filter_condition: null,
|
|
ui_properties: null,
|
|
data: null,
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
}
|
|
};
|
|
return tempEdge;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
const graph = get(graphAtom);
|
|
if (!graph.hasEdge(key)) {
|
|
getEdgeCache(graph).delete(key);
|
|
return null;
|
|
}
|
|
const sourceId = graph.source(key);
|
|
const targetId = graph.target(key);
|
|
const attributes = graph.getEdgeAttributes(key);
|
|
const remap = get(collapsedEdgeRemapAtom);
|
|
const effectiveSourceId = remap.get(sourceId) ?? sourceId;
|
|
const effectiveTargetId = remap.get(targetId) ?? targetId;
|
|
if (!graph.hasNode(effectiveSourceId) || !graph.hasNode(effectiveTargetId)) {
|
|
getEdgeCache(graph).delete(key);
|
|
return null;
|
|
}
|
|
const sourceAttributes = graph.getNodeAttributes(effectiveSourceId);
|
|
const targetAttributes = graph.getNodeAttributes(effectiveTargetId);
|
|
const sourcePosition = get(nodePositionAtomFamily(effectiveSourceId));
|
|
const targetPosition = get(nodePositionAtomFamily(effectiveTargetId));
|
|
if (sourceAttributes && targetAttributes) {
|
|
const next = {
|
|
...attributes,
|
|
key,
|
|
sourceId: effectiveSourceId,
|
|
targetId: effectiveTargetId,
|
|
sourcePosition,
|
|
targetPosition,
|
|
sourceNodeSize: sourceAttributes.size,
|
|
targetNodeSize: targetAttributes.size,
|
|
sourceNodeWidth: sourceAttributes.width ?? sourceAttributes.size,
|
|
sourceNodeHeight: sourceAttributes.height ?? sourceAttributes.size,
|
|
targetNodeWidth: targetAttributes.width ?? targetAttributes.size,
|
|
targetNodeHeight: targetAttributes.height ?? targetAttributes.size
|
|
};
|
|
const edgeCache = getEdgeCache(graph);
|
|
const prev = edgeCache.get(key);
|
|
if (prev && prev.sourcePosition === next.sourcePosition && prev.targetPosition === next.targetPosition && prev.sourceId === next.sourceId && prev.targetId === next.targetId && prev.type === next.type && prev.color === next.color && prev.weight === next.weight && prev.label === next.label && prev.sourceNodeSize === next.sourceNodeSize && prev.targetNodeSize === next.targetNodeSize && prev.sourceNodeWidth === next.sourceNodeWidth && prev.sourceNodeHeight === next.sourceNodeHeight && prev.targetNodeWidth === next.targetNodeWidth && prev.targetNodeHeight === next.targetNodeHeight) {
|
|
return prev;
|
|
}
|
|
edgeCache.set(key, next);
|
|
return next;
|
|
}
|
|
getEdgeCache(graph).delete(key);
|
|
return null;
|
|
}), (a, b) => a === b);
|
|
}
|
|
});
|
|
|
|
// src/core/reduced-motion-store.ts
|
|
import { atom as atom9 } from "jotai";
|
|
var prefersReducedMotionAtom, watchReducedMotionAtom;
|
|
var init_reduced_motion_store = __esm({
|
|
"src/core/reduced-motion-store.ts"() {
|
|
"use strict";
|
|
prefersReducedMotionAtom = atom9(typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false);
|
|
watchReducedMotionAtom = atom9(null, (_get, set) => {
|
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
const handler = (e) => {
|
|
set(prefersReducedMotionAtom, e.matches);
|
|
};
|
|
set(prefersReducedMotionAtom, mql.matches);
|
|
mql.addEventListener("change", handler);
|
|
return () => mql.removeEventListener("change", handler);
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/graph-mutations-edges.ts
|
|
import { atom as atom10 } from "jotai";
|
|
var debug5, addEdgeToLocalGraphAtom, removeEdgeFromLocalGraphAtom, swapEdgeAtomicAtom, departingEdgesAtom, EDGE_ANIMATION_DURATION, removeEdgeWithAnimationAtom, editingEdgeLabelAtom, updateEdgeLabelAtom;
|
|
var init_graph_mutations_edges = __esm({
|
|
"src/core/graph-mutations-edges.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_debug();
|
|
init_reduced_motion_store();
|
|
debug5 = createDebug("graph:mutations:edges");
|
|
addEdgeToLocalGraphAtom = atom10(null, (get, set, newEdge) => {
|
|
const graph = get(graphAtom);
|
|
if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
|
|
const uiProps = newEdge.ui_properties || {};
|
|
const attributes = {
|
|
type: typeof uiProps.style === "string" ? uiProps.style : "solid",
|
|
color: typeof uiProps.color === "string" ? uiProps.color : "#999",
|
|
label: newEdge.edge_type ?? void 0,
|
|
weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
|
|
dbData: newEdge
|
|
};
|
|
if (!graph.hasEdge(newEdge.id)) {
|
|
try {
|
|
debug5("Adding edge %s to local graph", newEdge.id);
|
|
graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
|
|
set(graphAtom, graph.copy());
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
} catch (e) {
|
|
debug5("Failed to add edge %s: %o", newEdge.id, e);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
removeEdgeFromLocalGraphAtom = atom10(null, (get, set, edgeId) => {
|
|
const graph = get(graphAtom);
|
|
if (graph.hasEdge(edgeId)) {
|
|
graph.dropEdge(edgeId);
|
|
set(graphAtom, graph.copy());
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
}
|
|
});
|
|
swapEdgeAtomicAtom = atom10(null, (get, set, {
|
|
tempEdgeId,
|
|
newEdge
|
|
}) => {
|
|
const graph = get(graphAtom);
|
|
if (graph.hasEdge(tempEdgeId)) {
|
|
graph.dropEdge(tempEdgeId);
|
|
}
|
|
if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
|
|
const uiProps = newEdge.ui_properties || {};
|
|
const attributes = {
|
|
type: typeof uiProps.style === "string" ? uiProps.style : "solid",
|
|
color: typeof uiProps.color === "string" ? uiProps.color : "#999",
|
|
label: newEdge.edge_type ?? void 0,
|
|
weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
|
|
dbData: newEdge
|
|
};
|
|
if (!graph.hasEdge(newEdge.id)) {
|
|
try {
|
|
debug5("Atomically swapping temp edge %s with real edge %s", tempEdgeId, newEdge.id);
|
|
graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
|
|
} catch (e) {
|
|
debug5("Failed to add edge %s: %o", newEdge.id, e);
|
|
}
|
|
}
|
|
}
|
|
set(graphAtom, graph.copy());
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
});
|
|
departingEdgesAtom = atom10(/* @__PURE__ */ new Map());
|
|
EDGE_ANIMATION_DURATION = 300;
|
|
removeEdgeWithAnimationAtom = atom10(null, (get, set, edgeKey) => {
|
|
const edgeState = get(edgeFamilyAtom(edgeKey));
|
|
if (edgeState) {
|
|
const departing = new Map(get(departingEdgesAtom));
|
|
departing.set(edgeKey, edgeState);
|
|
set(departingEdgesAtom, departing);
|
|
set(removeEdgeFromLocalGraphAtom, edgeKey);
|
|
const duration = get(prefersReducedMotionAtom) ? 0 : EDGE_ANIMATION_DURATION;
|
|
setTimeout(() => {
|
|
const current = new Map(get(departingEdgesAtom));
|
|
current.delete(edgeKey);
|
|
set(departingEdgesAtom, current);
|
|
}, duration);
|
|
}
|
|
});
|
|
editingEdgeLabelAtom = atom10(null);
|
|
updateEdgeLabelAtom = atom10(null, (get, set, {
|
|
edgeKey,
|
|
label
|
|
}) => {
|
|
const graph = get(graphAtom);
|
|
if (graph.hasEdge(edgeKey)) {
|
|
graph.setEdgeAttribute(edgeKey, "label", label || void 0);
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/graph-mutations-advanced.ts
|
|
import { atom as atom11 } from "jotai";
|
|
var debug6, dropTargetNodeIdAtom, splitNodeAtom, mergeNodesAtom;
|
|
var init_graph_mutations_advanced = __esm({
|
|
"src/core/graph-mutations-advanced.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_history_store();
|
|
init_debug();
|
|
init_graph_mutations_edges();
|
|
init_graph_mutations();
|
|
debug6 = createDebug("graph:mutations:advanced");
|
|
dropTargetNodeIdAtom = atom11(null);
|
|
splitNodeAtom = atom11(null, (get, set, {
|
|
nodeId,
|
|
position1,
|
|
position2
|
|
}) => {
|
|
const graph = get(graphAtom);
|
|
if (!graph.hasNode(nodeId)) return;
|
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
const graphId = get(currentGraphIdAtom) || attrs.dbData.graph_id;
|
|
set(pushHistoryAtom, "Split node");
|
|
graph.setNodeAttribute(nodeId, "x", position1.x);
|
|
graph.setNodeAttribute(nodeId, "y", position1.y);
|
|
const edges = [];
|
|
graph.forEachEdge(nodeId, (_key, eAttrs, source, target) => {
|
|
edges.push({
|
|
source,
|
|
target,
|
|
attrs: eAttrs
|
|
});
|
|
});
|
|
const cloneId = `split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
const cloneDbNode = {
|
|
...attrs.dbData,
|
|
id: cloneId,
|
|
graph_id: graphId,
|
|
ui_properties: {
|
|
...attrs.dbData.ui_properties || {},
|
|
x: position2.x,
|
|
y: position2.y
|
|
},
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
};
|
|
set(addNodeToLocalGraphAtom, cloneDbNode);
|
|
for (const edge of edges) {
|
|
const newSource = edge.source === nodeId ? cloneId : edge.source;
|
|
const newTarget = edge.target === nodeId ? cloneId : edge.target;
|
|
set(addEdgeToLocalGraphAtom, {
|
|
...edge.attrs.dbData,
|
|
id: `split-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
source_node_id: newSource,
|
|
target_node_id: newTarget
|
|
});
|
|
}
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
debug6("Split node %s \u2192 clone %s", nodeId, cloneId);
|
|
});
|
|
mergeNodesAtom = atom11(null, (get, set, {
|
|
nodeIds
|
|
}) => {
|
|
if (nodeIds.length < 2) return;
|
|
const graph = get(graphAtom);
|
|
const [survivorId, ...doomed] = nodeIds;
|
|
if (!graph.hasNode(survivorId)) return;
|
|
set(pushHistoryAtom, `Merge ${nodeIds.length} nodes`);
|
|
const doomedSet = new Set(doomed);
|
|
for (const doomedId of doomed) {
|
|
if (!graph.hasNode(doomedId)) continue;
|
|
const edges = [];
|
|
graph.forEachEdge(doomedId, (_key, eAttrs, source, target) => {
|
|
edges.push({
|
|
source,
|
|
target,
|
|
attrs: eAttrs
|
|
});
|
|
});
|
|
for (const edge of edges) {
|
|
const newSource = doomedSet.has(edge.source) ? survivorId : edge.source;
|
|
const newTarget = doomedSet.has(edge.target) ? survivorId : edge.target;
|
|
if (newSource === newTarget) continue;
|
|
set(addEdgeToLocalGraphAtom, {
|
|
...edge.attrs.dbData,
|
|
id: `merge-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
source_node_id: newSource,
|
|
target_node_id: newTarget
|
|
});
|
|
}
|
|
set(optimisticDeleteNodeAtom, {
|
|
nodeId: doomedId
|
|
});
|
|
}
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
debug6("Merged nodes %o \u2192 survivor %s", nodeIds, survivorId);
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/graph-mutations.ts
|
|
var graph_mutations_exports = {};
|
|
__export(graph_mutations_exports, {
|
|
EDGE_ANIMATION_DURATION: () => EDGE_ANIMATION_DURATION,
|
|
addNodeToLocalGraphAtom: () => addNodeToLocalGraphAtom,
|
|
departingEdgesAtom: () => departingEdgesAtom,
|
|
dropTargetNodeIdAtom: () => dropTargetNodeIdAtom,
|
|
editingEdgeLabelAtom: () => editingEdgeLabelAtom,
|
|
endNodeDragAtom: () => endNodeDragAtom,
|
|
loadGraphFromDbAtom: () => loadGraphFromDbAtom,
|
|
mergeNodesAtom: () => mergeNodesAtom,
|
|
optimisticDeleteEdgeAtom: () => optimisticDeleteEdgeAtom,
|
|
optimisticDeleteNodeAtom: () => optimisticDeleteNodeAtom,
|
|
removeEdgeWithAnimationAtom: () => removeEdgeWithAnimationAtom,
|
|
splitNodeAtom: () => splitNodeAtom,
|
|
startNodeDragAtom: () => startNodeDragAtom,
|
|
swapEdgeAtomicAtom: () => swapEdgeAtomicAtom,
|
|
updateEdgeLabelAtom: () => updateEdgeLabelAtom
|
|
});
|
|
import { atom as atom12 } from "jotai";
|
|
import Graph3 from "graphology";
|
|
var debug7, startNodeDragAtom, endNodeDragAtom, optimisticDeleteNodeAtom, optimisticDeleteEdgeAtom, addNodeToLocalGraphAtom, loadGraphFromDbAtom;
|
|
var init_graph_mutations = __esm({
|
|
"src/core/graph-mutations.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_group_store();
|
|
init_debug();
|
|
init_graph_mutations_edges();
|
|
init_graph_mutations_advanced();
|
|
debug7 = createDebug("graph:mutations");
|
|
startNodeDragAtom = atom12(null, (get, set, {
|
|
nodeId
|
|
}) => {
|
|
const graph = get(graphAtom);
|
|
if (!graph.hasNode(nodeId)) return;
|
|
const currentAttributes = graph.getNodeAttributes(nodeId);
|
|
set(preDragNodeAttributesAtom, JSON.parse(JSON.stringify(currentAttributes)));
|
|
const currentHighestZIndex = get(highestZIndexAtom);
|
|
const newZIndex = currentHighestZIndex + 1;
|
|
graph.setNodeAttribute(nodeId, "zIndex", newZIndex);
|
|
set(draggingNodeIdAtom, nodeId);
|
|
});
|
|
endNodeDragAtom = atom12(null, (get, set, _payload) => {
|
|
const currentDraggingId = get(draggingNodeIdAtom);
|
|
if (currentDraggingId) {
|
|
debug7("Node %s drag ended", currentDraggingId);
|
|
const graph = get(graphAtom);
|
|
if (graph.hasNode(currentDraggingId)) {
|
|
const parentId = graph.getNodeAttribute(currentDraggingId, "parentId");
|
|
if (parentId) {
|
|
set(autoResizeGroupAtom, parentId);
|
|
}
|
|
}
|
|
}
|
|
set(draggingNodeIdAtom, null);
|
|
set(preDragNodeAttributesAtom, null);
|
|
});
|
|
optimisticDeleteNodeAtom = atom12(null, (get, set, {
|
|
nodeId
|
|
}) => {
|
|
const graph = get(graphAtom);
|
|
if (graph.hasNode(nodeId)) {
|
|
graph.dropNode(nodeId);
|
|
set(cleanupNodePositionAtom, nodeId);
|
|
set(graphAtom, graph.copy());
|
|
debug7("Optimistically deleted node %s", nodeId);
|
|
}
|
|
});
|
|
optimisticDeleteEdgeAtom = atom12(null, (get, set, {
|
|
edgeKey
|
|
}) => {
|
|
const graph = get(graphAtom);
|
|
if (graph.hasEdge(edgeKey)) {
|
|
graph.dropEdge(edgeKey);
|
|
set(graphAtom, graph.copy());
|
|
debug7("Optimistically deleted edge %s", edgeKey);
|
|
}
|
|
});
|
|
addNodeToLocalGraphAtom = atom12(null, (get, set, newNode) => {
|
|
const graph = get(graphAtom);
|
|
if (graph.hasNode(newNode.id)) {
|
|
debug7("Node %s already exists, skipping", newNode.id);
|
|
return;
|
|
}
|
|
const uiProps = newNode.ui_properties || {};
|
|
const attributes = {
|
|
x: typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800,
|
|
y: typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600,
|
|
size: typeof uiProps.size === "number" ? uiProps.size : 15,
|
|
width: typeof uiProps.width === "number" ? uiProps.width : 500,
|
|
height: typeof uiProps.height === "number" ? uiProps.height : 500,
|
|
color: typeof uiProps.color === "string" ? uiProps.color : "#ccc",
|
|
label: newNode.label || newNode.node_type || newNode.id,
|
|
zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0,
|
|
dbData: newNode
|
|
};
|
|
debug7("Adding node %s to local graph at (%d, %d)", newNode.id, attributes.x, attributes.y);
|
|
graph.addNode(newNode.id, attributes);
|
|
set(graphAtom, graph.copy());
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
});
|
|
loadGraphFromDbAtom = atom12(null, (get, set, fetchedNodes, fetchedEdges) => {
|
|
debug7("========== START SYNC ==========");
|
|
debug7("Fetched nodes: %d, edges: %d", fetchedNodes.length, fetchedEdges.length);
|
|
const currentGraphId = get(currentGraphIdAtom);
|
|
if (fetchedNodes.length > 0 && fetchedNodes[0].graph_id !== currentGraphId) {
|
|
debug7("Skipping sync - data belongs to different graph");
|
|
return;
|
|
}
|
|
const existingGraph = get(graphAtom);
|
|
const isDragging = get(draggingNodeIdAtom) !== null;
|
|
if (isDragging) {
|
|
debug7("Skipping sync - drag in progress");
|
|
return;
|
|
}
|
|
const existingNodeIds = new Set(existingGraph.nodes());
|
|
const fetchedNodeIds = new Set(fetchedNodes.map((n) => n.id));
|
|
const hasAnyCommonNodes = Array.from(existingNodeIds).some((id) => fetchedNodeIds.has(id));
|
|
let graph;
|
|
if (hasAnyCommonNodes && existingNodeIds.size > 0) {
|
|
debug7("Merging DB data into existing graph");
|
|
graph = existingGraph.copy();
|
|
} else {
|
|
debug7("Creating fresh graph (graph switch detected)");
|
|
graph = new Graph3(graphOptions);
|
|
}
|
|
const fetchedEdgeIds = new Set(fetchedEdges.map((e) => e.id));
|
|
if (hasAnyCommonNodes && existingNodeIds.size > 0) {
|
|
graph.forEachNode((nodeId) => {
|
|
if (!fetchedNodeIds.has(nodeId)) {
|
|
debug7("Removing deleted node: %s", nodeId);
|
|
graph.dropNode(nodeId);
|
|
nodePositionAtomFamily.remove(nodeId);
|
|
}
|
|
});
|
|
}
|
|
fetchedNodes.forEach((node) => {
|
|
const uiProps = node.ui_properties || {};
|
|
const newX = typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800;
|
|
const newY = typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600;
|
|
if (graph.hasNode(node.id)) {
|
|
const currentAttrs = graph.getNodeAttributes(node.id);
|
|
const attributes = {
|
|
x: newX,
|
|
y: newY,
|
|
size: typeof uiProps.size === "number" ? uiProps.size : currentAttrs.size,
|
|
width: typeof uiProps.width === "number" ? uiProps.width : currentAttrs.width ?? 500,
|
|
height: typeof uiProps.height === "number" ? uiProps.height : currentAttrs.height ?? 500,
|
|
color: typeof uiProps.color === "string" ? uiProps.color : currentAttrs.color,
|
|
label: node.label || node.node_type || node.id,
|
|
zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : currentAttrs.zIndex,
|
|
dbData: node
|
|
};
|
|
graph.replaceNodeAttributes(node.id, attributes);
|
|
} else {
|
|
const attributes = {
|
|
x: newX,
|
|
y: newY,
|
|
size: typeof uiProps.size === "number" ? uiProps.size : 15,
|
|
width: typeof uiProps.width === "number" ? uiProps.width : 500,
|
|
height: typeof uiProps.height === "number" ? uiProps.height : 500,
|
|
color: typeof uiProps.color === "string" ? uiProps.color : "#ccc",
|
|
label: node.label || node.node_type || node.id,
|
|
zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0,
|
|
dbData: node
|
|
};
|
|
graph.addNode(node.id, attributes);
|
|
}
|
|
});
|
|
graph.forEachEdge((edgeId) => {
|
|
if (!fetchedEdgeIds.has(edgeId)) {
|
|
debug7("Removing deleted edge: %s", edgeId);
|
|
graph.dropEdge(edgeId);
|
|
}
|
|
});
|
|
fetchedEdges.forEach((edge) => {
|
|
if (graph.hasNode(edge.source_node_id) && graph.hasNode(edge.target_node_id)) {
|
|
const uiProps = edge.ui_properties || {};
|
|
const attributes = {
|
|
type: typeof uiProps.style === "string" ? uiProps.style : "solid",
|
|
color: typeof uiProps.color === "string" ? uiProps.color : "#999",
|
|
label: edge.edge_type ?? void 0,
|
|
weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
|
|
dbData: edge
|
|
};
|
|
if (graph.hasEdge(edge.id)) {
|
|
graph.replaceEdgeAttributes(edge.id, attributes);
|
|
} else {
|
|
try {
|
|
graph.addEdgeWithKey(edge.id, edge.source_node_id, edge.target_node_id, attributes);
|
|
} catch (e) {
|
|
debug7("Failed to add edge %s: %o", edge.id, e);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
set(graphAtom, graph);
|
|
set(graphUpdateVersionAtom, (v) => v + 1);
|
|
debug7("========== SYNC COMPLETE ==========");
|
|
debug7("Final graph: %d nodes, %d edges", graph.order, graph.size);
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/sync-store.ts
|
|
import { atom as atom13 } from "jotai";
|
|
var debug8, syncStatusAtom, pendingMutationsCountAtom, isOnlineAtom, lastSyncErrorAtom, lastSyncTimeAtom, mutationQueueAtom, syncStateAtom, startMutationAtom, completeMutationAtom, trackMutationErrorAtom, setOnlineStatusAtom, queueMutationAtom, dequeueMutationAtom, incrementRetryCountAtom, getNextQueuedMutationAtom, clearMutationQueueAtom;
|
|
var init_sync_store = __esm({
|
|
"src/core/sync-store.ts"() {
|
|
"use strict";
|
|
init_debug();
|
|
debug8 = createDebug("sync");
|
|
syncStatusAtom = atom13("synced");
|
|
pendingMutationsCountAtom = atom13(0);
|
|
isOnlineAtom = atom13(typeof navigator !== "undefined" ? navigator.onLine : true);
|
|
lastSyncErrorAtom = atom13(null);
|
|
lastSyncTimeAtom = atom13(Date.now());
|
|
mutationQueueAtom = atom13([]);
|
|
syncStateAtom = atom13((get) => ({
|
|
status: get(syncStatusAtom),
|
|
pendingMutations: get(pendingMutationsCountAtom),
|
|
lastError: get(lastSyncErrorAtom),
|
|
lastSyncTime: get(lastSyncTimeAtom),
|
|
isOnline: get(isOnlineAtom),
|
|
queuedMutations: get(mutationQueueAtom).length
|
|
}));
|
|
startMutationAtom = atom13(null, (get, set) => {
|
|
const currentCount = get(pendingMutationsCountAtom);
|
|
const newCount = currentCount + 1;
|
|
set(pendingMutationsCountAtom, newCount);
|
|
debug8("Mutation started. Pending count: %d -> %d", currentCount, newCount);
|
|
if (newCount > 0 && get(syncStatusAtom) !== "syncing") {
|
|
set(syncStatusAtom, "syncing");
|
|
debug8("Status -> syncing");
|
|
}
|
|
});
|
|
completeMutationAtom = atom13(null, (get, set, success = true) => {
|
|
const currentCount = get(pendingMutationsCountAtom);
|
|
const newCount = Math.max(0, currentCount - 1);
|
|
set(pendingMutationsCountAtom, newCount);
|
|
debug8("Mutation completed (success: %s). Pending count: %d -> %d", success, currentCount, newCount);
|
|
if (success) {
|
|
set(lastSyncTimeAtom, Date.now());
|
|
if (newCount === 0) {
|
|
set(lastSyncErrorAtom, null);
|
|
}
|
|
}
|
|
if (newCount === 0) {
|
|
const isOnline = get(isOnlineAtom);
|
|
const hasError = get(lastSyncErrorAtom) !== null;
|
|
if (hasError) {
|
|
set(syncStatusAtom, "error");
|
|
debug8("Status -> error");
|
|
} else if (!isOnline) {
|
|
set(syncStatusAtom, "offline");
|
|
debug8("Status -> offline");
|
|
} else {
|
|
set(syncStatusAtom, "synced");
|
|
debug8("Status -> synced");
|
|
}
|
|
}
|
|
});
|
|
trackMutationErrorAtom = atom13(null, (_get, set, error) => {
|
|
set(lastSyncErrorAtom, error);
|
|
debug8("Mutation failed: %s", error);
|
|
});
|
|
setOnlineStatusAtom = atom13(null, (get, set, isOnline) => {
|
|
set(isOnlineAtom, isOnline);
|
|
const pendingCount = get(pendingMutationsCountAtom);
|
|
const hasError = get(lastSyncErrorAtom) !== null;
|
|
const queueLength = get(mutationQueueAtom).length;
|
|
if (pendingCount === 0) {
|
|
if (hasError || queueLength > 0) {
|
|
set(syncStatusAtom, "error");
|
|
} else {
|
|
set(syncStatusAtom, isOnline ? "synced" : "offline");
|
|
}
|
|
}
|
|
});
|
|
queueMutationAtom = atom13(null, (get, set, mutation) => {
|
|
const queue = get(mutationQueueAtom);
|
|
const newMutation = {
|
|
...mutation,
|
|
id: crypto.randomUUID(),
|
|
timestamp: Date.now(),
|
|
retryCount: 0,
|
|
maxRetries: mutation.maxRetries ?? 3
|
|
};
|
|
const newQueue = [...queue, newMutation];
|
|
set(mutationQueueAtom, newQueue);
|
|
debug8("Queued mutation: %s. Queue size: %d", mutation.type, newQueue.length);
|
|
if (get(pendingMutationsCountAtom) === 0) {
|
|
set(syncStatusAtom, "error");
|
|
}
|
|
return newMutation.id;
|
|
});
|
|
dequeueMutationAtom = atom13(null, (get, set, mutationId) => {
|
|
const queue = get(mutationQueueAtom);
|
|
const newQueue = queue.filter((m) => m.id !== mutationId);
|
|
set(mutationQueueAtom, newQueue);
|
|
debug8("Dequeued mutation: %s. Queue size: %d", mutationId, newQueue.length);
|
|
if (newQueue.length === 0 && get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) {
|
|
set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline");
|
|
}
|
|
});
|
|
incrementRetryCountAtom = atom13(null, (get, set, mutationId) => {
|
|
const queue = get(mutationQueueAtom);
|
|
const newQueue = queue.map((m) => m.id === mutationId ? {
|
|
...m,
|
|
retryCount: m.retryCount + 1
|
|
} : m);
|
|
set(mutationQueueAtom, newQueue);
|
|
});
|
|
getNextQueuedMutationAtom = atom13((get) => {
|
|
const queue = get(mutationQueueAtom);
|
|
return queue.find((m) => m.retryCount < m.maxRetries) ?? null;
|
|
});
|
|
clearMutationQueueAtom = atom13(null, (get, set) => {
|
|
set(mutationQueueAtom, []);
|
|
debug8("Cleared mutation queue");
|
|
if (get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) {
|
|
set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/interaction-store.ts
|
|
import { atom as atom14 } from "jotai";
|
|
var inputModeAtom, keyboardInteractionModeAtom, interactionFeedbackAtom, pendingInputResolverAtom, resetInputModeAtom, resetKeyboardInteractionModeAtom, setKeyboardInteractionModeAtom, startPickNodeAtom, startPickNodesAtom, startPickPointAtom, provideInputAtom, updateInteractionFeedbackAtom, isPickingModeAtom, isPickNodeModeAtom;
|
|
var init_interaction_store = __esm({
|
|
"src/core/interaction-store.ts"() {
|
|
"use strict";
|
|
inputModeAtom = atom14({
|
|
type: "normal"
|
|
});
|
|
keyboardInteractionModeAtom = atom14("navigate");
|
|
interactionFeedbackAtom = atom14(null);
|
|
pendingInputResolverAtom = atom14(null);
|
|
resetInputModeAtom = atom14(null, (_get, set) => {
|
|
set(inputModeAtom, {
|
|
type: "normal"
|
|
});
|
|
set(interactionFeedbackAtom, null);
|
|
set(pendingInputResolverAtom, null);
|
|
});
|
|
resetKeyboardInteractionModeAtom = atom14(null, (_get, set) => {
|
|
set(keyboardInteractionModeAtom, "navigate");
|
|
});
|
|
setKeyboardInteractionModeAtom = atom14(null, (_get, set, mode) => {
|
|
set(keyboardInteractionModeAtom, mode);
|
|
});
|
|
startPickNodeAtom = atom14(null, (_get, set, options) => {
|
|
set(inputModeAtom, {
|
|
type: "pickNode",
|
|
...options
|
|
});
|
|
});
|
|
startPickNodesAtom = atom14(null, (_get, set, options) => {
|
|
set(inputModeAtom, {
|
|
type: "pickNodes",
|
|
...options
|
|
});
|
|
});
|
|
startPickPointAtom = atom14(null, (_get, set, options) => {
|
|
set(inputModeAtom, {
|
|
type: "pickPoint",
|
|
...options
|
|
});
|
|
});
|
|
provideInputAtom = atom14(null, (get, set, value) => {
|
|
set(pendingInputResolverAtom, value);
|
|
});
|
|
updateInteractionFeedbackAtom = atom14(null, (get, set, feedback) => {
|
|
const current = get(interactionFeedbackAtom);
|
|
set(interactionFeedbackAtom, {
|
|
...current,
|
|
...feedback
|
|
});
|
|
});
|
|
isPickingModeAtom = atom14((get) => {
|
|
const mode = get(inputModeAtom);
|
|
return mode.type !== "normal";
|
|
});
|
|
isPickNodeModeAtom = atom14((get) => {
|
|
const mode = get(inputModeAtom);
|
|
return mode.type === "pickNode" || mode.type === "pickNodes";
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/locked-node-store.ts
|
|
import { atom as atom15 } from "jotai";
|
|
var lockedNodeIdAtom, lockedNodeDataAtom, lockedNodePageIndexAtom, lockedNodePageCountAtom, lockNodeAtom, unlockNodeAtom, nextLockedPageAtom, prevLockedPageAtom, goToLockedPageAtom, hasLockedNodeAtom;
|
|
var init_locked_node_store = __esm({
|
|
"src/core/locked-node-store.ts"() {
|
|
"use strict";
|
|
init_graph_derived();
|
|
lockedNodeIdAtom = atom15(null);
|
|
lockedNodeDataAtom = atom15((get) => {
|
|
const id = get(lockedNodeIdAtom);
|
|
if (!id) return null;
|
|
const nodes = get(uiNodesAtom);
|
|
return nodes.find((n) => n.id === id) || null;
|
|
});
|
|
lockedNodePageIndexAtom = atom15(0);
|
|
lockedNodePageCountAtom = atom15(1);
|
|
lockNodeAtom = atom15(null, (_get, set, payload) => {
|
|
set(lockedNodeIdAtom, payload.nodeId);
|
|
set(lockedNodePageIndexAtom, 0);
|
|
});
|
|
unlockNodeAtom = atom15(null, (_get, set) => {
|
|
set(lockedNodeIdAtom, null);
|
|
});
|
|
nextLockedPageAtom = atom15(null, (get, set) => {
|
|
const current = get(lockedNodePageIndexAtom);
|
|
const pageCount = get(lockedNodePageCountAtom);
|
|
set(lockedNodePageIndexAtom, (current + 1) % pageCount);
|
|
});
|
|
prevLockedPageAtom = atom15(null, (get, set) => {
|
|
const current = get(lockedNodePageIndexAtom);
|
|
const pageCount = get(lockedNodePageCountAtom);
|
|
set(lockedNodePageIndexAtom, (current - 1 + pageCount) % pageCount);
|
|
});
|
|
goToLockedPageAtom = atom15(null, (get, set, index) => {
|
|
const pageCount = get(lockedNodePageCountAtom);
|
|
if (index >= 0 && index < pageCount) {
|
|
set(lockedNodePageIndexAtom, index);
|
|
}
|
|
});
|
|
hasLockedNodeAtom = atom15((get) => get(lockedNodeIdAtom) !== null);
|
|
}
|
|
});
|
|
|
|
// src/core/node-type-registry.tsx
|
|
import { c as _c } from "react/compiler-runtime";
|
|
import React from "react";
|
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
function registerNodeType(nodeType, component) {
|
|
nodeTypeRegistry.set(nodeType, component);
|
|
}
|
|
function registerNodeTypes(types) {
|
|
for (const [nodeType, component] of Object.entries(types)) {
|
|
nodeTypeRegistry.set(nodeType, component);
|
|
}
|
|
}
|
|
function unregisterNodeType(nodeType) {
|
|
return nodeTypeRegistry.delete(nodeType);
|
|
}
|
|
function getNodeTypeComponent(nodeType) {
|
|
if (!nodeType) return void 0;
|
|
return nodeTypeRegistry.get(nodeType);
|
|
}
|
|
function hasNodeTypeComponent(nodeType) {
|
|
if (!nodeType) return false;
|
|
return nodeTypeRegistry.has(nodeType);
|
|
}
|
|
function getRegisteredNodeTypes() {
|
|
return Array.from(nodeTypeRegistry.keys());
|
|
}
|
|
function clearNodeTypeRegistry() {
|
|
nodeTypeRegistry.clear();
|
|
}
|
|
var nodeTypeRegistry, FallbackNodeTypeComponent;
|
|
var init_node_type_registry = __esm({
|
|
"src/core/node-type-registry.tsx"() {
|
|
"use strict";
|
|
nodeTypeRegistry = /* @__PURE__ */ new Map();
|
|
FallbackNodeTypeComponent = (t0) => {
|
|
const $ = _c(11);
|
|
const {
|
|
nodeData
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
padding: "12px",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100%",
|
|
color: "#666",
|
|
fontSize: "12px"
|
|
};
|
|
$[0] = t1;
|
|
} else {
|
|
t1 = $[0];
|
|
}
|
|
const t2 = nodeData.dbData.node_type || "none";
|
|
let t3;
|
|
if ($[1] !== t2) {
|
|
t3 = /* @__PURE__ */ _jsxs("div", {
|
|
children: ["Unknown type: ", t2]
|
|
});
|
|
$[1] = t2;
|
|
$[2] = t3;
|
|
} else {
|
|
t3 = $[2];
|
|
}
|
|
let t4;
|
|
if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = {
|
|
marginTop: "4px",
|
|
opacity: 0.7
|
|
};
|
|
$[3] = t4;
|
|
} else {
|
|
t4 = $[3];
|
|
}
|
|
let t5;
|
|
if ($[4] !== nodeData.id) {
|
|
t5 = nodeData.id.substring(0, 8);
|
|
$[4] = nodeData.id;
|
|
$[5] = t5;
|
|
} else {
|
|
t5 = $[5];
|
|
}
|
|
let t6;
|
|
if ($[6] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx("div", {
|
|
style: t4,
|
|
children: t5
|
|
});
|
|
$[6] = t5;
|
|
$[7] = t6;
|
|
} else {
|
|
t6 = $[7];
|
|
}
|
|
let t7;
|
|
if ($[8] !== t3 || $[9] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsxs("div", {
|
|
style: t1,
|
|
children: [t3, t6]
|
|
});
|
|
$[8] = t3;
|
|
$[9] = t6;
|
|
$[10] = t7;
|
|
} else {
|
|
t7 = $[10];
|
|
}
|
|
return t7;
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/toast-store.ts
|
|
var toast_store_exports = {};
|
|
__export(toast_store_exports, {
|
|
canvasToastAtom: () => canvasToastAtom,
|
|
showToastAtom: () => showToastAtom
|
|
});
|
|
import { atom as atom16 } from "jotai";
|
|
var canvasToastAtom, showToastAtom;
|
|
var init_toast_store = __esm({
|
|
"src/core/toast-store.ts"() {
|
|
"use strict";
|
|
canvasToastAtom = atom16(null);
|
|
showToastAtom = atom16(null, (_get, set, message) => {
|
|
const id = `toast-${Date.now()}`;
|
|
set(canvasToastAtom, {
|
|
id,
|
|
message,
|
|
timestamp: Date.now()
|
|
});
|
|
setTimeout(() => {
|
|
set(canvasToastAtom, (current) => current?.id === id ? null : current);
|
|
}, 2e3);
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/snap-store.ts
|
|
import { atom as atom17 } from "jotai";
|
|
function snapToGrid(pos, gridSize) {
|
|
return {
|
|
x: Math.round(pos.x / gridSize) * gridSize,
|
|
y: Math.round(pos.y / gridSize) * gridSize
|
|
};
|
|
}
|
|
function conditionalSnap(pos, gridSize, isActive) {
|
|
return isActive ? snapToGrid(pos, gridSize) : pos;
|
|
}
|
|
function getSnapGuides(pos, gridSize, tolerance = 5) {
|
|
const snappedX = Math.round(pos.x / gridSize) * gridSize;
|
|
const snappedY = Math.round(pos.y / gridSize) * gridSize;
|
|
return {
|
|
x: Math.abs(pos.x - snappedX) < tolerance ? snappedX : null,
|
|
y: Math.abs(pos.y - snappedY) < tolerance ? snappedY : null
|
|
};
|
|
}
|
|
function findAlignmentGuides(dragged, others, tolerance = 5) {
|
|
const verticals = /* @__PURE__ */ new Set();
|
|
const horizontals = /* @__PURE__ */ new Set();
|
|
const dragCX = dragged.x + dragged.width / 2;
|
|
const dragCY = dragged.y + dragged.height / 2;
|
|
const dragRight = dragged.x + dragged.width;
|
|
const dragBottom = dragged.y + dragged.height;
|
|
for (const other of others) {
|
|
const otherCX = other.x + other.width / 2;
|
|
const otherCY = other.y + other.height / 2;
|
|
const otherRight = other.x + other.width;
|
|
const otherBottom = other.y + other.height;
|
|
if (Math.abs(dragCX - otherCX) < tolerance) verticals.add(otherCX);
|
|
if (Math.abs(dragged.x - other.x) < tolerance) verticals.add(other.x);
|
|
if (Math.abs(dragRight - otherRight) < tolerance) verticals.add(otherRight);
|
|
if (Math.abs(dragged.x - otherRight) < tolerance) verticals.add(otherRight);
|
|
if (Math.abs(dragRight - other.x) < tolerance) verticals.add(other.x);
|
|
if (Math.abs(dragCX - other.x) < tolerance) verticals.add(other.x);
|
|
if (Math.abs(dragCX - otherRight) < tolerance) verticals.add(otherRight);
|
|
if (Math.abs(dragCY - otherCY) < tolerance) horizontals.add(otherCY);
|
|
if (Math.abs(dragged.y - other.y) < tolerance) horizontals.add(other.y);
|
|
if (Math.abs(dragBottom - otherBottom) < tolerance) horizontals.add(otherBottom);
|
|
if (Math.abs(dragged.y - otherBottom) < tolerance) horizontals.add(otherBottom);
|
|
if (Math.abs(dragBottom - other.y) < tolerance) horizontals.add(other.y);
|
|
if (Math.abs(dragCY - other.y) < tolerance) horizontals.add(other.y);
|
|
if (Math.abs(dragCY - otherBottom) < tolerance) horizontals.add(otherBottom);
|
|
}
|
|
return {
|
|
verticalGuides: Array.from(verticals),
|
|
horizontalGuides: Array.from(horizontals)
|
|
};
|
|
}
|
|
var snapEnabledAtom, snapGridSizeAtom, snapTemporaryDisableAtom, isSnappingActiveAtom, toggleSnapAtom, setGridSizeAtom, snapAlignmentEnabledAtom, toggleAlignmentGuidesAtom, alignmentGuidesAtom, clearAlignmentGuidesAtom;
|
|
var init_snap_store = __esm({
|
|
"src/core/snap-store.ts"() {
|
|
"use strict";
|
|
snapEnabledAtom = atom17(false);
|
|
snapGridSizeAtom = atom17(20);
|
|
snapTemporaryDisableAtom = atom17(false);
|
|
isSnappingActiveAtom = atom17((get) => {
|
|
return get(snapEnabledAtom) && !get(snapTemporaryDisableAtom);
|
|
});
|
|
toggleSnapAtom = atom17(null, (get, set) => {
|
|
set(snapEnabledAtom, !get(snapEnabledAtom));
|
|
});
|
|
setGridSizeAtom = atom17(null, (_get, set, size) => {
|
|
set(snapGridSizeAtom, Math.max(5, Math.min(200, size)));
|
|
});
|
|
snapAlignmentEnabledAtom = atom17(true);
|
|
toggleAlignmentGuidesAtom = atom17(null, (get, set) => {
|
|
set(snapAlignmentEnabledAtom, !get(snapAlignmentEnabledAtom));
|
|
});
|
|
alignmentGuidesAtom = atom17({
|
|
verticalGuides: [],
|
|
horizontalGuides: []
|
|
});
|
|
clearAlignmentGuidesAtom = atom17(null, (_get, set) => {
|
|
set(alignmentGuidesAtom, {
|
|
verticalGuides: [],
|
|
horizontalGuides: []
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/event-types.ts
|
|
var CanvasEventType, EVENT_TYPE_INFO;
|
|
var init_event_types = __esm({
|
|
"src/core/event-types.ts"() {
|
|
"use strict";
|
|
CanvasEventType = /* @__PURE__ */ (function(CanvasEventType2) {
|
|
CanvasEventType2["NodeClick"] = "node:click";
|
|
CanvasEventType2["NodeDoubleClick"] = "node:double-click";
|
|
CanvasEventType2["NodeTripleClick"] = "node:triple-click";
|
|
CanvasEventType2["NodeRightClick"] = "node:right-click";
|
|
CanvasEventType2["NodeLongPress"] = "node:long-press";
|
|
CanvasEventType2["EdgeClick"] = "edge:click";
|
|
CanvasEventType2["EdgeDoubleClick"] = "edge:double-click";
|
|
CanvasEventType2["EdgeRightClick"] = "edge:right-click";
|
|
CanvasEventType2["BackgroundClick"] = "background:click";
|
|
CanvasEventType2["BackgroundDoubleClick"] = "background:double-click";
|
|
CanvasEventType2["BackgroundRightClick"] = "background:right-click";
|
|
CanvasEventType2["BackgroundLongPress"] = "background:long-press";
|
|
return CanvasEventType2;
|
|
})({});
|
|
EVENT_TYPE_INFO = {
|
|
[CanvasEventType.NodeClick]: {
|
|
type: CanvasEventType.NodeClick,
|
|
label: "Click Node",
|
|
description: "Triggered when clicking on a node",
|
|
category: "node"
|
|
},
|
|
[CanvasEventType.NodeDoubleClick]: {
|
|
type: CanvasEventType.NodeDoubleClick,
|
|
label: "Double-click Node",
|
|
description: "Triggered when double-clicking on a node",
|
|
category: "node"
|
|
},
|
|
[CanvasEventType.NodeTripleClick]: {
|
|
type: CanvasEventType.NodeTripleClick,
|
|
label: "Triple-click Node",
|
|
description: "Triggered when triple-clicking on a node",
|
|
category: "node"
|
|
},
|
|
[CanvasEventType.NodeRightClick]: {
|
|
type: CanvasEventType.NodeRightClick,
|
|
label: "Right-click Node",
|
|
description: "Triggered when right-clicking on a node",
|
|
category: "node"
|
|
},
|
|
[CanvasEventType.NodeLongPress]: {
|
|
type: CanvasEventType.NodeLongPress,
|
|
label: "Long-press Node",
|
|
description: "Triggered when long-pressing on a node (mobile/touch)",
|
|
category: "node"
|
|
},
|
|
[CanvasEventType.EdgeClick]: {
|
|
type: CanvasEventType.EdgeClick,
|
|
label: "Click Edge",
|
|
description: "Triggered when clicking on an edge",
|
|
category: "edge"
|
|
},
|
|
[CanvasEventType.EdgeDoubleClick]: {
|
|
type: CanvasEventType.EdgeDoubleClick,
|
|
label: "Double-click Edge",
|
|
description: "Triggered when double-clicking on an edge",
|
|
category: "edge"
|
|
},
|
|
[CanvasEventType.EdgeRightClick]: {
|
|
type: CanvasEventType.EdgeRightClick,
|
|
label: "Right-click Edge",
|
|
description: "Triggered when right-clicking on an edge",
|
|
category: "edge"
|
|
},
|
|
[CanvasEventType.BackgroundClick]: {
|
|
type: CanvasEventType.BackgroundClick,
|
|
label: "Click Background",
|
|
description: "Triggered when clicking on the canvas background",
|
|
category: "background"
|
|
},
|
|
[CanvasEventType.BackgroundDoubleClick]: {
|
|
type: CanvasEventType.BackgroundDoubleClick,
|
|
label: "Double-click Background",
|
|
description: "Triggered when double-clicking on the canvas background",
|
|
category: "background"
|
|
},
|
|
[CanvasEventType.BackgroundRightClick]: {
|
|
type: CanvasEventType.BackgroundRightClick,
|
|
label: "Right-click Background",
|
|
description: "Triggered when right-clicking on the canvas background",
|
|
category: "background"
|
|
},
|
|
[CanvasEventType.BackgroundLongPress]: {
|
|
type: CanvasEventType.BackgroundLongPress,
|
|
label: "Long-press Background",
|
|
description: "Triggered when long-pressing on the canvas background (mobile/touch)",
|
|
category: "background"
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/action-types.ts
|
|
var ActionCategory, BuiltInActionId;
|
|
var init_action_types = __esm({
|
|
"src/core/action-types.ts"() {
|
|
"use strict";
|
|
ActionCategory = /* @__PURE__ */ (function(ActionCategory2) {
|
|
ActionCategory2["None"] = "none";
|
|
ActionCategory2["Selection"] = "selection";
|
|
ActionCategory2["Viewport"] = "viewport";
|
|
ActionCategory2["Node"] = "node";
|
|
ActionCategory2["Layout"] = "layout";
|
|
ActionCategory2["History"] = "history";
|
|
ActionCategory2["Custom"] = "custom";
|
|
return ActionCategory2;
|
|
})({});
|
|
BuiltInActionId = {
|
|
// None
|
|
None: "none",
|
|
// Selection
|
|
SelectNode: "select-node",
|
|
SelectEdge: "select-edge",
|
|
AddToSelection: "add-to-selection",
|
|
ClearSelection: "clear-selection",
|
|
DeleteSelected: "delete-selected",
|
|
// Viewport
|
|
FitToView: "fit-to-view",
|
|
FitAllToView: "fit-all-to-view",
|
|
CenterOnNode: "center-on-node",
|
|
ResetViewport: "reset-viewport",
|
|
// Node
|
|
LockNode: "lock-node",
|
|
UnlockNode: "unlock-node",
|
|
ToggleLock: "toggle-lock",
|
|
OpenContextMenu: "open-context-menu",
|
|
SplitNode: "split-node",
|
|
GroupNodes: "group-nodes",
|
|
MergeNodes: "merge-nodes",
|
|
// Layout
|
|
ApplyForceLayout: "apply-force-layout",
|
|
// History
|
|
Undo: "undo",
|
|
Redo: "redo",
|
|
// Creation
|
|
CreateNode: "create-node"
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/settings-state-types.ts
|
|
var DEFAULT_MAPPINGS;
|
|
var init_settings_state_types = __esm({
|
|
"src/core/settings-state-types.ts"() {
|
|
"use strict";
|
|
init_event_types();
|
|
init_action_types();
|
|
DEFAULT_MAPPINGS = {
|
|
[CanvasEventType.NodeClick]: BuiltInActionId.None,
|
|
[CanvasEventType.NodeDoubleClick]: BuiltInActionId.FitToView,
|
|
[CanvasEventType.NodeTripleClick]: BuiltInActionId.ToggleLock,
|
|
[CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
|
|
[CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
|
|
[CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
|
|
[CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.FitAllToView,
|
|
[CanvasEventType.BackgroundRightClick]: BuiltInActionId.None,
|
|
[CanvasEventType.BackgroundLongPress]: BuiltInActionId.CreateNode
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/settings-types.ts
|
|
var init_settings_types = __esm({
|
|
"src/core/settings-types.ts"() {
|
|
"use strict";
|
|
init_event_types();
|
|
init_action_types();
|
|
init_settings_state_types();
|
|
}
|
|
});
|
|
|
|
// src/core/actions-node.ts
|
|
function registerSelectionActions() {
|
|
registerAction({
|
|
id: BuiltInActionId.SelectNode,
|
|
label: "Select Node",
|
|
description: "Select this node (replacing current selection)",
|
|
category: ActionCategory.Selection,
|
|
icon: "pointer",
|
|
requiresNode: true,
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.nodeId) {
|
|
helpers.selectNode(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.SelectEdge,
|
|
label: "Select Edge",
|
|
description: "Select this edge",
|
|
category: ActionCategory.Selection,
|
|
icon: "git-commit",
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.edgeId) {
|
|
helpers.selectEdge(context.edgeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.AddToSelection,
|
|
label: "Add to Selection",
|
|
description: "Add this node to the current selection",
|
|
category: ActionCategory.Selection,
|
|
icon: "plus-square",
|
|
requiresNode: true,
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.nodeId) {
|
|
helpers.addToSelection(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.ClearSelection,
|
|
label: "Clear Selection",
|
|
description: "Deselect all nodes",
|
|
category: ActionCategory.Selection,
|
|
icon: "x-square",
|
|
isBuiltIn: true,
|
|
handler: (_context, helpers) => {
|
|
helpers.clearSelection();
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.DeleteSelected,
|
|
label: "Delete Selected",
|
|
description: "Delete all selected nodes",
|
|
category: ActionCategory.Selection,
|
|
icon: "trash-2",
|
|
isBuiltIn: true,
|
|
handler: async (_context, helpers) => {
|
|
const selectedIds = helpers.getSelectedNodeIds();
|
|
for (const nodeId of selectedIds) {
|
|
await helpers.deleteNode(nodeId);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function registerNodeActions() {
|
|
registerAction({
|
|
id: BuiltInActionId.LockNode,
|
|
label: "Lock Node",
|
|
description: "Prevent this node from being moved",
|
|
category: ActionCategory.Node,
|
|
icon: "lock",
|
|
requiresNode: true,
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.nodeId) {
|
|
helpers.lockNode(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.UnlockNode,
|
|
label: "Unlock Node",
|
|
description: "Allow this node to be moved",
|
|
category: ActionCategory.Node,
|
|
icon: "unlock",
|
|
requiresNode: true,
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.nodeId) {
|
|
helpers.unlockNode(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.ToggleLock,
|
|
label: "Toggle Lock",
|
|
description: "Toggle whether this node can be moved",
|
|
category: ActionCategory.Node,
|
|
icon: "lock",
|
|
requiresNode: true,
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.nodeId) {
|
|
helpers.toggleLock(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.OpenContextMenu,
|
|
label: "Open Context Menu",
|
|
description: "Show the context menu for this node",
|
|
category: ActionCategory.Node,
|
|
icon: "more-vertical",
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (helpers.openContextMenu) {
|
|
helpers.openContextMenu(context.screenPosition, context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.CreateNode,
|
|
label: "Create Node",
|
|
description: "Create a new node at this position",
|
|
category: ActionCategory.Node,
|
|
icon: "plus",
|
|
isBuiltIn: true,
|
|
handler: async (context, helpers) => {
|
|
if (helpers.createNode) {
|
|
await helpers.createNode(context.worldPosition);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.SplitNode,
|
|
label: "Split Node",
|
|
description: "Split a node into two separate nodes",
|
|
category: ActionCategory.Node,
|
|
icon: "split",
|
|
isBuiltIn: true,
|
|
handler: async (context, helpers) => {
|
|
if (helpers.splitNode && context.nodeId) {
|
|
await helpers.splitNode(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.GroupNodes,
|
|
label: "Group Nodes",
|
|
description: "Group selected nodes into a parent container",
|
|
category: ActionCategory.Node,
|
|
icon: "group",
|
|
isBuiltIn: true,
|
|
handler: async (context, helpers) => {
|
|
if (helpers.groupNodes) {
|
|
await helpers.groupNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds());
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.MergeNodes,
|
|
label: "Merge Nodes",
|
|
description: "Merge selected nodes into one",
|
|
category: ActionCategory.Node,
|
|
icon: "merge",
|
|
isBuiltIn: true,
|
|
handler: async (context, helpers) => {
|
|
if (helpers.mergeNodes) {
|
|
await helpers.mergeNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
var init_actions_node = __esm({
|
|
"src/core/actions-node.ts"() {
|
|
"use strict";
|
|
init_settings_types();
|
|
init_action_registry();
|
|
}
|
|
});
|
|
|
|
// src/core/actions-viewport.ts
|
|
function registerViewportActions() {
|
|
registerAction({
|
|
id: BuiltInActionId.FitToView,
|
|
label: "Fit to View",
|
|
description: "Zoom and pan to fit this node in view",
|
|
category: ActionCategory.Viewport,
|
|
icon: "maximize-2",
|
|
requiresNode: true,
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.nodeId) {
|
|
helpers.centerOnNode(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.FitAllToView,
|
|
label: "Fit All to View",
|
|
description: "Zoom and pan to fit all nodes in view",
|
|
category: ActionCategory.Viewport,
|
|
icon: "maximize",
|
|
isBuiltIn: true,
|
|
handler: (_context, helpers) => {
|
|
helpers.fitToBounds("graph");
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.CenterOnNode,
|
|
label: "Center on Node",
|
|
description: "Center the viewport on this node",
|
|
category: ActionCategory.Viewport,
|
|
icon: "crosshair",
|
|
requiresNode: true,
|
|
isBuiltIn: true,
|
|
handler: (context, helpers) => {
|
|
if (context.nodeId) {
|
|
helpers.centerOnNode(context.nodeId);
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.ResetViewport,
|
|
label: "Reset Viewport",
|
|
description: "Reset zoom to 100% and center on origin",
|
|
category: ActionCategory.Viewport,
|
|
icon: "home",
|
|
isBuiltIn: true,
|
|
handler: (_context, helpers) => {
|
|
helpers.resetViewport();
|
|
}
|
|
});
|
|
}
|
|
function registerHistoryActions() {
|
|
registerAction({
|
|
id: BuiltInActionId.Undo,
|
|
label: "Undo",
|
|
description: "Undo the last action",
|
|
category: ActionCategory.History,
|
|
icon: "undo-2",
|
|
isBuiltIn: true,
|
|
handler: (_context, helpers) => {
|
|
if (helpers.canUndo()) {
|
|
helpers.undo();
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.Redo,
|
|
label: "Redo",
|
|
description: "Redo the last undone action",
|
|
category: ActionCategory.History,
|
|
icon: "redo-2",
|
|
isBuiltIn: true,
|
|
handler: (_context, helpers) => {
|
|
if (helpers.canRedo()) {
|
|
helpers.redo();
|
|
}
|
|
}
|
|
});
|
|
registerAction({
|
|
id: BuiltInActionId.ApplyForceLayout,
|
|
label: "Apply Force Layout",
|
|
description: "Automatically arrange nodes using force-directed layout",
|
|
category: ActionCategory.Layout,
|
|
icon: "layout-grid",
|
|
isBuiltIn: true,
|
|
handler: async (_context, helpers) => {
|
|
await helpers.applyForceLayout();
|
|
}
|
|
});
|
|
}
|
|
var init_actions_viewport = __esm({
|
|
"src/core/actions-viewport.ts"() {
|
|
"use strict";
|
|
init_settings_types();
|
|
init_action_registry();
|
|
}
|
|
});
|
|
|
|
// src/core/built-in-actions.ts
|
|
function registerBuiltInActions() {
|
|
registerAction({
|
|
id: BuiltInActionId.None,
|
|
label: "None",
|
|
description: "Do nothing",
|
|
category: ActionCategory.None,
|
|
icon: "ban",
|
|
isBuiltIn: true,
|
|
handler: () => {
|
|
}
|
|
});
|
|
registerSelectionActions();
|
|
registerNodeActions();
|
|
registerViewportActions();
|
|
registerHistoryActions();
|
|
}
|
|
var init_built_in_actions = __esm({
|
|
"src/core/built-in-actions.ts"() {
|
|
"use strict";
|
|
init_settings_types();
|
|
init_action_registry();
|
|
init_actions_node();
|
|
init_actions_viewport();
|
|
}
|
|
});
|
|
|
|
// src/core/action-registry.ts
|
|
function registerAction(action) {
|
|
actionRegistry.set(action.id, action);
|
|
}
|
|
function getAction(id) {
|
|
return actionRegistry.get(id);
|
|
}
|
|
function hasAction(id) {
|
|
return actionRegistry.has(id);
|
|
}
|
|
function getAllActions() {
|
|
return Array.from(actionRegistry.values());
|
|
}
|
|
function getActionsByCategory(category) {
|
|
return getAllActions().filter((action) => action.category === category);
|
|
}
|
|
function unregisterAction(id) {
|
|
return actionRegistry.delete(id);
|
|
}
|
|
function clearActions() {
|
|
actionRegistry.clear();
|
|
}
|
|
function getActionsByCategories() {
|
|
const categoryLabels = {
|
|
[ActionCategory.None]: "None",
|
|
[ActionCategory.Selection]: "Selection",
|
|
[ActionCategory.Viewport]: "Viewport",
|
|
[ActionCategory.Node]: "Node",
|
|
[ActionCategory.Layout]: "Layout",
|
|
[ActionCategory.History]: "History",
|
|
[ActionCategory.Custom]: "Custom"
|
|
};
|
|
const categoryOrder = [ActionCategory.None, ActionCategory.Selection, ActionCategory.Viewport, ActionCategory.Node, ActionCategory.Layout, ActionCategory.History, ActionCategory.Custom];
|
|
return categoryOrder.map((category) => ({
|
|
category,
|
|
label: categoryLabels[category],
|
|
actions: getActionsByCategory(category)
|
|
})).filter((group) => group.actions.length > 0);
|
|
}
|
|
var actionRegistry;
|
|
var init_action_registry = __esm({
|
|
"src/core/action-registry.ts"() {
|
|
"use strict";
|
|
init_settings_types();
|
|
init_built_in_actions();
|
|
actionRegistry = /* @__PURE__ */ new Map();
|
|
registerBuiltInActions();
|
|
}
|
|
});
|
|
|
|
// src/core/action-executor.ts
|
|
async function executeAction(actionId, context, helpers) {
|
|
if (actionId === BuiltInActionId.None) {
|
|
return {
|
|
success: true,
|
|
actionId
|
|
};
|
|
}
|
|
const action = getAction(actionId);
|
|
if (!action) {
|
|
debug9.warn("Action not found: %s", actionId);
|
|
return {
|
|
success: false,
|
|
actionId,
|
|
error: new Error(`Action not found: ${actionId}`)
|
|
};
|
|
}
|
|
if (action.requiresNode && !context.nodeId) {
|
|
debug9.warn("Action %s requires a node context", actionId);
|
|
return {
|
|
success: false,
|
|
actionId,
|
|
error: new Error(`Action ${actionId} requires a node context`)
|
|
};
|
|
}
|
|
try {
|
|
const result = action.handler(context, helpers);
|
|
if (result instanceof Promise) {
|
|
await result;
|
|
}
|
|
return {
|
|
success: true,
|
|
actionId
|
|
};
|
|
} catch (error) {
|
|
debug9.error("Error executing action %s: %O", actionId, error);
|
|
return {
|
|
success: false,
|
|
actionId,
|
|
error: error instanceof Error ? error : new Error(String(error))
|
|
};
|
|
}
|
|
}
|
|
function createActionContext(eventType, screenEvent, worldPosition, options) {
|
|
return {
|
|
eventType,
|
|
nodeId: options?.nodeId,
|
|
nodeData: options?.nodeData,
|
|
edgeId: options?.edgeId,
|
|
edgeData: options?.edgeData,
|
|
worldPosition,
|
|
screenPosition: {
|
|
x: screenEvent.clientX,
|
|
y: screenEvent.clientY
|
|
},
|
|
modifiers: {
|
|
shift: false,
|
|
ctrl: false,
|
|
alt: false,
|
|
meta: false
|
|
}
|
|
};
|
|
}
|
|
function createActionContextFromReactEvent(eventType, event, worldPosition, options) {
|
|
return {
|
|
eventType,
|
|
nodeId: options?.nodeId,
|
|
nodeData: options?.nodeData,
|
|
edgeId: options?.edgeId,
|
|
edgeData: options?.edgeData,
|
|
worldPosition,
|
|
screenPosition: {
|
|
x: event.clientX,
|
|
y: event.clientY
|
|
},
|
|
modifiers: {
|
|
shift: event.shiftKey,
|
|
ctrl: event.ctrlKey,
|
|
alt: event.altKey,
|
|
meta: event.metaKey
|
|
}
|
|
};
|
|
}
|
|
function createActionContextFromTouchEvent(eventType, touch, worldPosition, options) {
|
|
return {
|
|
eventType,
|
|
nodeId: options?.nodeId,
|
|
nodeData: options?.nodeData,
|
|
edgeId: options?.edgeId,
|
|
edgeData: options?.edgeData,
|
|
worldPosition,
|
|
screenPosition: {
|
|
x: touch.clientX,
|
|
y: touch.clientY
|
|
},
|
|
modifiers: {
|
|
shift: false,
|
|
ctrl: false,
|
|
alt: false,
|
|
meta: false
|
|
}
|
|
};
|
|
}
|
|
function buildActionHelpers(store, options = {}) {
|
|
return {
|
|
selectNode: (nodeId) => store.set(selectSingleNodeAtom, nodeId),
|
|
addToSelection: (nodeId) => store.set(addNodesToSelectionAtom, [nodeId]),
|
|
clearSelection: () => store.set(clearSelectionAtom),
|
|
getSelectedNodeIds: () => Array.from(store.get(selectedNodeIdsAtom)),
|
|
fitToBounds: (mode, padding) => {
|
|
const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection;
|
|
store.set(fitToBoundsAtom, {
|
|
mode: fitMode,
|
|
padding
|
|
});
|
|
},
|
|
centerOnNode: (nodeId) => store.set(centerOnNodeAtom, nodeId),
|
|
resetViewport: () => store.set(resetViewportAtom),
|
|
lockNode: (nodeId) => store.set(lockNodeAtom, {
|
|
nodeId
|
|
}),
|
|
unlockNode: (_nodeId) => store.set(unlockNodeAtom),
|
|
toggleLock: (nodeId) => {
|
|
const currentLockedId = store.get(lockedNodeIdAtom);
|
|
if (currentLockedId === nodeId) {
|
|
store.set(unlockNodeAtom);
|
|
} else {
|
|
store.set(lockNodeAtom, {
|
|
nodeId
|
|
});
|
|
}
|
|
},
|
|
deleteNode: async (nodeId) => {
|
|
if (options.onDeleteNode) {
|
|
await options.onDeleteNode(nodeId);
|
|
} else {
|
|
debug9.warn("deleteNode called but onDeleteNode callback not provided");
|
|
}
|
|
},
|
|
isNodeLocked: (nodeId) => store.get(lockedNodeIdAtom) === nodeId,
|
|
applyForceLayout: async () => {
|
|
if (options.onApplyForceLayout) {
|
|
await options.onApplyForceLayout();
|
|
} else {
|
|
debug9.warn("applyForceLayout called but onApplyForceLayout callback not provided");
|
|
}
|
|
},
|
|
undo: () => store.set(undoAtom),
|
|
redo: () => store.set(redoAtom),
|
|
canUndo: () => store.get(canUndoAtom),
|
|
canRedo: () => store.get(canRedoAtom),
|
|
selectEdge: (edgeId) => store.set(selectEdgeAtom, edgeId),
|
|
clearEdgeSelection: () => store.set(clearEdgeSelectionAtom),
|
|
openContextMenu: options.onOpenContextMenu,
|
|
createNode: options.onCreateNode
|
|
};
|
|
}
|
|
var debug9;
|
|
var init_action_executor = __esm({
|
|
"src/core/action-executor.ts"() {
|
|
"use strict";
|
|
init_action_registry();
|
|
init_settings_types();
|
|
init_selection_store();
|
|
init_viewport_store();
|
|
init_locked_node_store();
|
|
init_history_store();
|
|
init_layout();
|
|
init_debug();
|
|
debug9 = createDebug("actions");
|
|
}
|
|
});
|
|
|
|
// src/core/settings-presets.ts
|
|
function getActionForEvent(mappings, event) {
|
|
return mappings[event] || BuiltInActionId.None;
|
|
}
|
|
var BUILT_IN_PRESETS;
|
|
var init_settings_presets = __esm({
|
|
"src/core/settings-presets.ts"() {
|
|
"use strict";
|
|
init_settings_types();
|
|
BUILT_IN_PRESETS = [{
|
|
id: "default",
|
|
name: "Default",
|
|
description: "Standard canvas interactions",
|
|
isBuiltIn: true,
|
|
mappings: DEFAULT_MAPPINGS
|
|
}, {
|
|
id: "minimal",
|
|
name: "Minimal",
|
|
description: "Only essential selection and context menu actions",
|
|
isBuiltIn: true,
|
|
mappings: {
|
|
[CanvasEventType.NodeClick]: BuiltInActionId.None,
|
|
[CanvasEventType.NodeDoubleClick]: BuiltInActionId.None,
|
|
[CanvasEventType.NodeTripleClick]: BuiltInActionId.None,
|
|
[CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
|
|
[CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
|
|
[CanvasEventType.EdgeRightClick]: BuiltInActionId.None,
|
|
[CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
|
|
[CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.None,
|
|
[CanvasEventType.BackgroundRightClick]: BuiltInActionId.None,
|
|
[CanvasEventType.BackgroundLongPress]: BuiltInActionId.None
|
|
}
|
|
}, {
|
|
id: "power-user",
|
|
name: "Power User",
|
|
description: "Quick actions for experienced users",
|
|
isBuiltIn: true,
|
|
mappings: {
|
|
[CanvasEventType.NodeClick]: BuiltInActionId.None,
|
|
[CanvasEventType.NodeDoubleClick]: BuiltInActionId.ToggleLock,
|
|
[CanvasEventType.NodeTripleClick]: BuiltInActionId.DeleteSelected,
|
|
[CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.NodeLongPress]: BuiltInActionId.AddToSelection,
|
|
[CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
|
|
[CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
|
|
[CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
|
|
[CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.CreateNode,
|
|
[CanvasEventType.BackgroundRightClick]: BuiltInActionId.OpenContextMenu,
|
|
[CanvasEventType.BackgroundLongPress]: BuiltInActionId.ApplyForceLayout
|
|
}
|
|
}];
|
|
}
|
|
});
|
|
|
|
// src/core/settings-store.ts
|
|
import { atom as atom18 } from "jotai";
|
|
import { atomWithStorage } from "jotai/utils";
|
|
var debug10, DEFAULT_STATE, canvasSettingsAtom, eventMappingsAtom, activePresetIdAtom, allPresetsAtom, activePresetAtom, isPanelOpenAtom, virtualizationEnabledAtom, hasUnsavedChangesAtom, setEventMappingAtom, applyPresetAtom, saveAsPresetAtom, updatePresetAtom, deletePresetAtom, resetSettingsAtom, togglePanelAtom, setPanelOpenAtom, setVirtualizationEnabledAtom, toggleVirtualizationAtom;
|
|
var init_settings_store = __esm({
|
|
"src/core/settings-store.ts"() {
|
|
"use strict";
|
|
init_settings_types();
|
|
init_debug();
|
|
init_settings_presets();
|
|
init_settings_presets();
|
|
debug10 = createDebug("settings");
|
|
DEFAULT_STATE = {
|
|
mappings: DEFAULT_MAPPINGS,
|
|
activePresetId: "default",
|
|
customPresets: [],
|
|
isPanelOpen: false,
|
|
virtualizationEnabled: true
|
|
};
|
|
canvasSettingsAtom = atomWithStorage("@blinksgg/canvas/settings", DEFAULT_STATE);
|
|
eventMappingsAtom = atom18((get) => get(canvasSettingsAtom).mappings);
|
|
activePresetIdAtom = atom18((get) => get(canvasSettingsAtom).activePresetId);
|
|
allPresetsAtom = atom18((get) => {
|
|
const state = get(canvasSettingsAtom);
|
|
return [...BUILT_IN_PRESETS, ...state.customPresets];
|
|
});
|
|
activePresetAtom = atom18((get) => {
|
|
const presetId = get(activePresetIdAtom);
|
|
if (!presetId) return null;
|
|
const allPresets = get(allPresetsAtom);
|
|
return allPresets.find((p) => p.id === presetId) || null;
|
|
});
|
|
isPanelOpenAtom = atom18((get) => get(canvasSettingsAtom).isPanelOpen);
|
|
virtualizationEnabledAtom = atom18((get) => get(canvasSettingsAtom).virtualizationEnabled ?? true);
|
|
hasUnsavedChangesAtom = atom18((get) => {
|
|
const state = get(canvasSettingsAtom);
|
|
const activePreset = get(activePresetAtom);
|
|
if (!activePreset) return true;
|
|
const events = Object.values(CanvasEventType);
|
|
return events.some((event) => state.mappings[event] !== activePreset.mappings[event]);
|
|
});
|
|
setEventMappingAtom = atom18(null, (get, set, {
|
|
event,
|
|
actionId
|
|
}) => {
|
|
const current = get(canvasSettingsAtom);
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
mappings: {
|
|
...current.mappings,
|
|
[event]: actionId
|
|
},
|
|
// Clear active preset since mappings have changed
|
|
activePresetId: null
|
|
});
|
|
});
|
|
applyPresetAtom = atom18(null, (get, set, presetId) => {
|
|
const allPresets = get(allPresetsAtom);
|
|
const preset = allPresets.find((p) => p.id === presetId);
|
|
if (!preset) {
|
|
debug10.warn("Preset not found: %s", presetId);
|
|
return;
|
|
}
|
|
const current = get(canvasSettingsAtom);
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
mappings: {
|
|
...preset.mappings
|
|
},
|
|
activePresetId: presetId
|
|
});
|
|
});
|
|
saveAsPresetAtom = atom18(null, (get, set, {
|
|
name,
|
|
description
|
|
}) => {
|
|
const current = get(canvasSettingsAtom);
|
|
const id = `custom-${Date.now()}`;
|
|
const newPreset = {
|
|
id,
|
|
name,
|
|
description,
|
|
mappings: {
|
|
...current.mappings
|
|
},
|
|
isBuiltIn: false
|
|
};
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
customPresets: [...current.customPresets, newPreset],
|
|
activePresetId: id
|
|
});
|
|
return id;
|
|
});
|
|
updatePresetAtom = atom18(null, (get, set, presetId) => {
|
|
const current = get(canvasSettingsAtom);
|
|
const presetIndex = current.customPresets.findIndex((p) => p.id === presetId);
|
|
if (presetIndex === -1) {
|
|
debug10.warn("Cannot update preset: %s (not found or built-in)", presetId);
|
|
return;
|
|
}
|
|
const updatedPresets = [...current.customPresets];
|
|
updatedPresets[presetIndex] = {
|
|
...updatedPresets[presetIndex],
|
|
mappings: {
|
|
...current.mappings
|
|
}
|
|
};
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
customPresets: updatedPresets,
|
|
activePresetId: presetId
|
|
});
|
|
});
|
|
deletePresetAtom = atom18(null, (get, set, presetId) => {
|
|
const current = get(canvasSettingsAtom);
|
|
const newCustomPresets = current.customPresets.filter((p) => p.id !== presetId);
|
|
if (newCustomPresets.length === current.customPresets.length) {
|
|
debug10.warn("Cannot delete preset: %s (not found or built-in)", presetId);
|
|
return;
|
|
}
|
|
const newActiveId = current.activePresetId === presetId ? "default" : current.activePresetId;
|
|
const newMappings = newActiveId === "default" ? DEFAULT_MAPPINGS : current.mappings;
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
customPresets: newCustomPresets,
|
|
activePresetId: newActiveId,
|
|
mappings: newMappings
|
|
});
|
|
});
|
|
resetSettingsAtom = atom18(null, (get, set) => {
|
|
const current = get(canvasSettingsAtom);
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
mappings: DEFAULT_MAPPINGS,
|
|
activePresetId: "default"
|
|
});
|
|
});
|
|
togglePanelAtom = atom18(null, (get, set) => {
|
|
const current = get(canvasSettingsAtom);
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
isPanelOpen: !current.isPanelOpen
|
|
});
|
|
});
|
|
setPanelOpenAtom = atom18(null, (get, set, isOpen) => {
|
|
const current = get(canvasSettingsAtom);
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
isPanelOpen: isOpen
|
|
});
|
|
});
|
|
setVirtualizationEnabledAtom = atom18(null, (get, set, enabled) => {
|
|
const current = get(canvasSettingsAtom);
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
virtualizationEnabled: enabled
|
|
});
|
|
});
|
|
toggleVirtualizationAtom = atom18(null, (get, set) => {
|
|
const current = get(canvasSettingsAtom);
|
|
set(canvasSettingsAtom, {
|
|
...current,
|
|
virtualizationEnabled: !(current.virtualizationEnabled ?? true)
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/canvas-serializer.ts
|
|
var canvas_serializer_exports = {};
|
|
__export(canvas_serializer_exports, {
|
|
SNAPSHOT_VERSION: () => SNAPSHOT_VERSION,
|
|
exportGraph: () => exportGraph,
|
|
importGraph: () => importGraph,
|
|
validateSnapshot: () => validateSnapshot
|
|
});
|
|
import Graph4 from "graphology";
|
|
function exportGraph(store, metadata) {
|
|
const graph = store.get(graphAtom);
|
|
const zoom = store.get(zoomAtom);
|
|
const pan = store.get(panAtom);
|
|
const collapsed = store.get(collapsedGroupsAtom);
|
|
const nodes = [];
|
|
const groups = [];
|
|
const seenGroupParents = /* @__PURE__ */ new Set();
|
|
graph.forEachNode((nodeId, attrs) => {
|
|
const a = attrs;
|
|
nodes.push({
|
|
id: nodeId,
|
|
position: {
|
|
x: a.x,
|
|
y: a.y
|
|
},
|
|
dimensions: {
|
|
width: a.width,
|
|
height: a.height
|
|
},
|
|
size: a.size,
|
|
color: a.color,
|
|
zIndex: a.zIndex,
|
|
label: a.label,
|
|
parentId: a.parentId,
|
|
dbData: a.dbData
|
|
});
|
|
if (a.parentId) {
|
|
const key = `${nodeId}:${a.parentId}`;
|
|
if (!seenGroupParents.has(key)) {
|
|
seenGroupParents.add(key);
|
|
groups.push({
|
|
nodeId,
|
|
parentId: a.parentId,
|
|
isCollapsed: collapsed.has(a.parentId)
|
|
});
|
|
}
|
|
}
|
|
});
|
|
const edges = [];
|
|
graph.forEachEdge((key, attrs, source, target) => {
|
|
const a = attrs;
|
|
edges.push({
|
|
key,
|
|
sourceId: source,
|
|
targetId: target,
|
|
attributes: {
|
|
weight: a.weight,
|
|
type: a.type,
|
|
color: a.color,
|
|
label: a.label
|
|
},
|
|
dbData: a.dbData
|
|
});
|
|
});
|
|
return {
|
|
version: SNAPSHOT_VERSION,
|
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
nodes,
|
|
edges,
|
|
groups,
|
|
viewport: {
|
|
zoom,
|
|
pan: {
|
|
...pan
|
|
}
|
|
},
|
|
metadata
|
|
};
|
|
}
|
|
function importGraph(store, snapshot, options = {}) {
|
|
const {
|
|
clearExisting = true,
|
|
offsetPosition,
|
|
remapIds = false
|
|
} = options;
|
|
const idMap = /* @__PURE__ */ new Map();
|
|
if (remapIds) {
|
|
for (const node of snapshot.nodes) {
|
|
idMap.set(node.id, crypto.randomUUID());
|
|
}
|
|
for (const edge of snapshot.edges) {
|
|
idMap.set(edge.key, crypto.randomUUID());
|
|
}
|
|
}
|
|
const remap = (id) => idMap.get(id) ?? id;
|
|
let graph;
|
|
if (clearExisting) {
|
|
graph = new Graph4(graphOptions);
|
|
} else {
|
|
graph = store.get(graphAtom);
|
|
}
|
|
const ox = offsetPosition?.x ?? 0;
|
|
const oy = offsetPosition?.y ?? 0;
|
|
for (const node of snapshot.nodes) {
|
|
const nodeId = remap(node.id);
|
|
const parentId = node.parentId ? remap(node.parentId) : void 0;
|
|
const dbData = remapIds ? {
|
|
...node.dbData,
|
|
id: nodeId
|
|
} : node.dbData;
|
|
const attrs = {
|
|
x: node.position.x + ox,
|
|
y: node.position.y + oy,
|
|
width: node.dimensions.width,
|
|
height: node.dimensions.height,
|
|
size: node.size,
|
|
color: node.color,
|
|
zIndex: node.zIndex,
|
|
label: node.label,
|
|
parentId,
|
|
dbData
|
|
};
|
|
graph.addNode(nodeId, attrs);
|
|
}
|
|
for (const edge of snapshot.edges) {
|
|
const edgeKey = remap(edge.key);
|
|
const sourceId = remap(edge.sourceId);
|
|
const targetId = remap(edge.targetId);
|
|
if (!graph.hasNode(sourceId) || !graph.hasNode(targetId)) continue;
|
|
const dbData = remapIds ? {
|
|
...edge.dbData,
|
|
id: edgeKey,
|
|
source_node_id: sourceId,
|
|
target_node_id: targetId
|
|
} : edge.dbData;
|
|
const attrs = {
|
|
weight: edge.attributes.weight,
|
|
type: edge.attributes.type,
|
|
color: edge.attributes.color,
|
|
label: edge.attributes.label,
|
|
dbData
|
|
};
|
|
graph.addEdgeWithKey(edgeKey, sourceId, targetId, attrs);
|
|
}
|
|
store.set(graphAtom, graph);
|
|
store.set(graphUpdateVersionAtom, (v) => v + 1);
|
|
store.set(nodePositionUpdateCounterAtom, (c) => c + 1);
|
|
const collapsedSet = /* @__PURE__ */ new Set();
|
|
for (const group of snapshot.groups) {
|
|
if (group.isCollapsed) {
|
|
collapsedSet.add(remap(group.parentId));
|
|
}
|
|
}
|
|
store.set(collapsedGroupsAtom, collapsedSet);
|
|
store.set(zoomAtom, snapshot.viewport.zoom);
|
|
store.set(panAtom, {
|
|
...snapshot.viewport.pan
|
|
});
|
|
}
|
|
function validateSnapshot(data) {
|
|
const errors = [];
|
|
if (!data || typeof data !== "object") {
|
|
return {
|
|
valid: false,
|
|
errors: ["Snapshot must be a non-null object"]
|
|
};
|
|
}
|
|
const obj = data;
|
|
if (obj.version !== SNAPSHOT_VERSION) {
|
|
errors.push(`Expected version ${SNAPSHOT_VERSION}, got ${String(obj.version)}`);
|
|
}
|
|
if (typeof obj.exportedAt !== "string") {
|
|
errors.push('Missing or invalid "exportedAt" (expected ISO string)');
|
|
}
|
|
if (!Array.isArray(obj.nodes)) {
|
|
errors.push('Missing or invalid "nodes" (expected array)');
|
|
} else {
|
|
for (let i = 0; i < obj.nodes.length; i++) {
|
|
const node = obj.nodes[i];
|
|
if (!node || typeof node !== "object") {
|
|
errors.push(`nodes[${i}]: expected object`);
|
|
continue;
|
|
}
|
|
if (typeof node.id !== "string") errors.push(`nodes[${i}]: missing "id"`);
|
|
if (!node.position || typeof node.position !== "object") errors.push(`nodes[${i}]: missing "position"`);
|
|
if (!node.dimensions || typeof node.dimensions !== "object") errors.push(`nodes[${i}]: missing "dimensions"`);
|
|
if (!node.dbData || typeof node.dbData !== "object") errors.push(`nodes[${i}]: missing "dbData"`);
|
|
}
|
|
}
|
|
if (!Array.isArray(obj.edges)) {
|
|
errors.push('Missing or invalid "edges" (expected array)');
|
|
} else {
|
|
for (let i = 0; i < obj.edges.length; i++) {
|
|
const edge = obj.edges[i];
|
|
if (!edge || typeof edge !== "object") {
|
|
errors.push(`edges[${i}]: expected object`);
|
|
continue;
|
|
}
|
|
if (typeof edge.key !== "string") errors.push(`edges[${i}]: missing "key"`);
|
|
if (typeof edge.sourceId !== "string") errors.push(`edges[${i}]: missing "sourceId"`);
|
|
if (typeof edge.targetId !== "string") errors.push(`edges[${i}]: missing "targetId"`);
|
|
if (!edge.dbData || typeof edge.dbData !== "object") errors.push(`edges[${i}]: missing "dbData"`);
|
|
}
|
|
}
|
|
if (!Array.isArray(obj.groups)) {
|
|
errors.push('Missing or invalid "groups" (expected array)');
|
|
}
|
|
if (!obj.viewport || typeof obj.viewport !== "object") {
|
|
errors.push('Missing or invalid "viewport" (expected object)');
|
|
} else {
|
|
const vp = obj.viewport;
|
|
if (typeof vp.zoom !== "number") errors.push('viewport: missing "zoom"');
|
|
if (!vp.pan || typeof vp.pan !== "object") errors.push('viewport: missing "pan"');
|
|
}
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors
|
|
};
|
|
}
|
|
var SNAPSHOT_VERSION;
|
|
var init_canvas_serializer = __esm({
|
|
"src/core/canvas-serializer.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_viewport_store();
|
|
init_group_store();
|
|
SNAPSHOT_VERSION = 1;
|
|
}
|
|
});
|
|
|
|
// src/core/clipboard-store.ts
|
|
var clipboard_store_exports = {};
|
|
__export(clipboard_store_exports, {
|
|
PASTE_OFFSET: () => PASTE_OFFSET,
|
|
clearClipboardAtom: () => clearClipboardAtom,
|
|
clipboardAtom: () => clipboardAtom,
|
|
clipboardNodeCountAtom: () => clipboardNodeCountAtom,
|
|
copyToClipboardAtom: () => copyToClipboardAtom,
|
|
cutToClipboardAtom: () => cutToClipboardAtom,
|
|
duplicateSelectionAtom: () => duplicateSelectionAtom,
|
|
hasClipboardContentAtom: () => hasClipboardContentAtom,
|
|
pasteFromClipboardAtom: () => pasteFromClipboardAtom
|
|
});
|
|
import { atom as atom19 } from "jotai";
|
|
function calculateBounds2(nodes) {
|
|
if (nodes.length === 0) {
|
|
return {
|
|
minX: 0,
|
|
minY: 0,
|
|
maxX: 0,
|
|
maxY: 0
|
|
};
|
|
}
|
|
let minX = Infinity;
|
|
let minY = Infinity;
|
|
let maxX = -Infinity;
|
|
let maxY = -Infinity;
|
|
for (const node of nodes) {
|
|
minX = Math.min(minX, node.attrs.x);
|
|
minY = Math.min(minY, node.attrs.y);
|
|
maxX = Math.max(maxX, node.attrs.x + node.attrs.width);
|
|
maxY = Math.max(maxY, node.attrs.y + node.attrs.height);
|
|
}
|
|
return {
|
|
minX,
|
|
minY,
|
|
maxX,
|
|
maxY
|
|
};
|
|
}
|
|
function generatePasteId(index) {
|
|
return `paste-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 8)}`;
|
|
}
|
|
var debug11, PASTE_OFFSET, clipboardAtom, hasClipboardContentAtom, clipboardNodeCountAtom, copyToClipboardAtom, cutToClipboardAtom, pasteFromClipboardAtom, duplicateSelectionAtom, clearClipboardAtom;
|
|
var init_clipboard_store = __esm({
|
|
"src/core/clipboard-store.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_mutations();
|
|
init_graph_mutations_edges();
|
|
init_selection_store();
|
|
init_history_store();
|
|
init_debug();
|
|
debug11 = createDebug("clipboard");
|
|
PASTE_OFFSET = {
|
|
x: 50,
|
|
y: 50
|
|
};
|
|
clipboardAtom = atom19(null);
|
|
hasClipboardContentAtom = atom19((get) => get(clipboardAtom) !== null);
|
|
clipboardNodeCountAtom = atom19((get) => {
|
|
const clipboard = get(clipboardAtom);
|
|
return clipboard?.nodes.length ?? 0;
|
|
});
|
|
copyToClipboardAtom = atom19(null, (get, set, nodeIds) => {
|
|
const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));
|
|
if (selectedIds.length === 0) {
|
|
debug11("Nothing to copy - no nodes selected");
|
|
return;
|
|
}
|
|
const graph = get(graphAtom);
|
|
const selectedSet = new Set(selectedIds);
|
|
const nodes = [];
|
|
const edges = [];
|
|
for (const nodeId of selectedIds) {
|
|
if (!graph.hasNode(nodeId)) {
|
|
debug11("Node %s not found in graph, skipping", nodeId);
|
|
continue;
|
|
}
|
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
nodes.push({
|
|
attrs: {
|
|
...attrs
|
|
},
|
|
dbData: {
|
|
...attrs.dbData
|
|
}
|
|
});
|
|
}
|
|
graph.forEachEdge((edgeKey, attrs, source, target) => {
|
|
if (selectedSet.has(source) && selectedSet.has(target)) {
|
|
edges.push({
|
|
source,
|
|
target,
|
|
attrs: {
|
|
...attrs
|
|
},
|
|
dbData: {
|
|
...attrs.dbData
|
|
}
|
|
});
|
|
}
|
|
});
|
|
const bounds = calculateBounds2(nodes);
|
|
const clipboardData = {
|
|
nodes,
|
|
edges,
|
|
bounds,
|
|
timestamp: Date.now()
|
|
};
|
|
set(clipboardAtom, clipboardData);
|
|
debug11("Copied %d nodes and %d edges to clipboard", nodes.length, edges.length);
|
|
});
|
|
cutToClipboardAtom = atom19(null, (get, set, nodeIds) => {
|
|
const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));
|
|
if (selectedIds.length === 0) return;
|
|
set(copyToClipboardAtom, selectedIds);
|
|
set(pushHistoryAtom, "Cut nodes");
|
|
for (const nodeId of selectedIds) {
|
|
set(optimisticDeleteNodeAtom, {
|
|
nodeId
|
|
});
|
|
}
|
|
set(clearSelectionAtom);
|
|
debug11("Cut %d nodes \u2014 copied to clipboard and deleted from graph", selectedIds.length);
|
|
});
|
|
pasteFromClipboardAtom = atom19(null, (get, set, offset) => {
|
|
const clipboard = get(clipboardAtom);
|
|
if (!clipboard || clipboard.nodes.length === 0) {
|
|
debug11("Nothing to paste - clipboard empty");
|
|
return [];
|
|
}
|
|
const pasteOffset = offset ?? PASTE_OFFSET;
|
|
const graph = get(graphAtom);
|
|
set(pushHistoryAtom, "Paste nodes");
|
|
const idMap = /* @__PURE__ */ new Map();
|
|
const newNodeIds = [];
|
|
for (let i = 0; i < clipboard.nodes.length; i++) {
|
|
const nodeData = clipboard.nodes[i];
|
|
const newId = generatePasteId(i);
|
|
idMap.set(nodeData.dbData.id, newId);
|
|
newNodeIds.push(newId);
|
|
const newDbNode = {
|
|
...nodeData.dbData,
|
|
id: newId,
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
ui_properties: {
|
|
...nodeData.dbData.ui_properties || {},
|
|
x: nodeData.attrs.x + pasteOffset.x,
|
|
y: nodeData.attrs.y + pasteOffset.y
|
|
}
|
|
};
|
|
debug11("Pasting node %s -> %s at (%d, %d)", nodeData.dbData.id, newId, nodeData.attrs.x + pasteOffset.x, nodeData.attrs.y + pasteOffset.y);
|
|
set(addNodeToLocalGraphAtom, newDbNode);
|
|
}
|
|
for (const edgeData of clipboard.edges) {
|
|
const newSourceId = idMap.get(edgeData.source);
|
|
const newTargetId = idMap.get(edgeData.target);
|
|
if (!newSourceId || !newTargetId) {
|
|
debug11("Edge %s: source or target not found in id map, skipping", edgeData.dbData.id);
|
|
continue;
|
|
}
|
|
const newEdgeId = generatePasteId(clipboard.edges.indexOf(edgeData) + clipboard.nodes.length);
|
|
const newDbEdge = {
|
|
...edgeData.dbData,
|
|
id: newEdgeId,
|
|
source_node_id: newSourceId,
|
|
target_node_id: newTargetId,
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
};
|
|
debug11("Pasting edge %s -> %s (from %s to %s)", edgeData.dbData.id, newEdgeId, newSourceId, newTargetId);
|
|
set(addEdgeToLocalGraphAtom, newDbEdge);
|
|
}
|
|
set(clearSelectionAtom);
|
|
set(addNodesToSelectionAtom, newNodeIds);
|
|
debug11("Pasted %d nodes and %d edges", newNodeIds.length, clipboard.edges.length);
|
|
return newNodeIds;
|
|
});
|
|
duplicateSelectionAtom = atom19(null, (get, set) => {
|
|
set(copyToClipboardAtom);
|
|
return set(pasteFromClipboardAtom);
|
|
});
|
|
clearClipboardAtom = atom19(null, (_get, set) => {
|
|
set(clipboardAtom, null);
|
|
debug11("Clipboard cleared");
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/spatial-index.ts
|
|
var SpatialGrid;
|
|
var init_spatial_index = __esm({
|
|
"src/core/spatial-index.ts"() {
|
|
"use strict";
|
|
SpatialGrid = class {
|
|
constructor(cellSize = 500) {
|
|
/** cell key → set of node IDs in that cell */
|
|
__publicField(this, "cells", /* @__PURE__ */ new Map());
|
|
/** node ID → entry data (for update/remove) */
|
|
__publicField(this, "entries", /* @__PURE__ */ new Map());
|
|
this.cellSize = cellSize;
|
|
}
|
|
/** Number of tracked entries */
|
|
get size() {
|
|
return this.entries.size;
|
|
}
|
|
cellKey(cx, cy) {
|
|
return `${cx},${cy}`;
|
|
}
|
|
getCellRange(x, y, w, h) {
|
|
const cs = this.cellSize;
|
|
return {
|
|
minCX: Math.floor(x / cs),
|
|
minCY: Math.floor(y / cs),
|
|
maxCX: Math.floor((x + w) / cs),
|
|
maxCY: Math.floor((y + h) / cs)
|
|
};
|
|
}
|
|
/**
|
|
* Insert a node into the index.
|
|
* If the node already exists, it is updated.
|
|
*/
|
|
insert(id, x, y, width, height) {
|
|
if (this.entries.has(id)) {
|
|
this.update(id, x, y, width, height);
|
|
return;
|
|
}
|
|
const entry = {
|
|
id,
|
|
x,
|
|
y,
|
|
width,
|
|
height
|
|
};
|
|
this.entries.set(id, entry);
|
|
const {
|
|
minCX,
|
|
minCY,
|
|
maxCX,
|
|
maxCY
|
|
} = this.getCellRange(x, y, width, height);
|
|
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
const key = this.cellKey(cx, cy);
|
|
let cell = this.cells.get(key);
|
|
if (!cell) {
|
|
cell = /* @__PURE__ */ new Set();
|
|
this.cells.set(key, cell);
|
|
}
|
|
cell.add(id);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Update a node's position/dimensions.
|
|
*/
|
|
update(id, x, y, width, height) {
|
|
const prev = this.entries.get(id);
|
|
if (!prev) {
|
|
this.insert(id, x, y, width, height);
|
|
return;
|
|
}
|
|
const prevRange = this.getCellRange(prev.x, prev.y, prev.width, prev.height);
|
|
const newRange = this.getCellRange(x, y, width, height);
|
|
prev.x = x;
|
|
prev.y = y;
|
|
prev.width = width;
|
|
prev.height = height;
|
|
if (prevRange.minCX === newRange.minCX && prevRange.minCY === newRange.minCY && prevRange.maxCX === newRange.maxCX && prevRange.maxCY === newRange.maxCY) {
|
|
return;
|
|
}
|
|
for (let cx = prevRange.minCX; cx <= prevRange.maxCX; cx++) {
|
|
for (let cy = prevRange.minCY; cy <= prevRange.maxCY; cy++) {
|
|
const key = this.cellKey(cx, cy);
|
|
const cell = this.cells.get(key);
|
|
if (cell) {
|
|
cell.delete(id);
|
|
if (cell.size === 0) this.cells.delete(key);
|
|
}
|
|
}
|
|
}
|
|
for (let cx = newRange.minCX; cx <= newRange.maxCX; cx++) {
|
|
for (let cy = newRange.minCY; cy <= newRange.maxCY; cy++) {
|
|
const key = this.cellKey(cx, cy);
|
|
let cell = this.cells.get(key);
|
|
if (!cell) {
|
|
cell = /* @__PURE__ */ new Set();
|
|
this.cells.set(key, cell);
|
|
}
|
|
cell.add(id);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Remove a node from the index.
|
|
*/
|
|
remove(id) {
|
|
const entry = this.entries.get(id);
|
|
if (!entry) return;
|
|
const {
|
|
minCX,
|
|
minCY,
|
|
maxCX,
|
|
maxCY
|
|
} = this.getCellRange(entry.x, entry.y, entry.width, entry.height);
|
|
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
const key = this.cellKey(cx, cy);
|
|
const cell = this.cells.get(key);
|
|
if (cell) {
|
|
cell.delete(id);
|
|
if (cell.size === 0) this.cells.delete(key);
|
|
}
|
|
}
|
|
}
|
|
this.entries.delete(id);
|
|
}
|
|
/**
|
|
* Query all node IDs whose bounding box overlaps the given bounds.
|
|
* Returns a Set for O(1) membership checks.
|
|
*/
|
|
query(bounds) {
|
|
const result = /* @__PURE__ */ new Set();
|
|
const {
|
|
minCX,
|
|
minCY,
|
|
maxCX,
|
|
maxCY
|
|
} = this.getCellRange(bounds.minX, bounds.minY, bounds.maxX - bounds.minX, bounds.maxY - bounds.minY);
|
|
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
const cell = this.cells.get(this.cellKey(cx, cy));
|
|
if (!cell) continue;
|
|
for (const id of cell) {
|
|
if (result.has(id)) continue;
|
|
const entry = this.entries.get(id);
|
|
const entryRight = entry.x + entry.width;
|
|
const entryBottom = entry.y + entry.height;
|
|
if (entry.x <= bounds.maxX && entryRight >= bounds.minX && entry.y <= bounds.maxY && entryBottom >= bounds.minY) {
|
|
result.add(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Clear all entries.
|
|
*/
|
|
clear() {
|
|
this.cells.clear();
|
|
this.entries.clear();
|
|
}
|
|
/**
|
|
* Check if a node is tracked.
|
|
*/
|
|
has(id) {
|
|
return this.entries.has(id);
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/virtualization-store.ts
|
|
import { atom as atom20 } from "jotai";
|
|
var VIRTUALIZATION_BUFFER, spatialIndexAtom, visibleBoundsAtom, visibleNodeKeysAtom, visibleEdgeKeysAtom, virtualizationMetricsAtom;
|
|
var init_virtualization_store = __esm({
|
|
"src/core/virtualization-store.ts"() {
|
|
"use strict";
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_viewport_store();
|
|
init_settings_store();
|
|
init_group_store();
|
|
init_spatial_index();
|
|
init_perf();
|
|
init_settings_store();
|
|
VIRTUALIZATION_BUFFER = 200;
|
|
spatialIndexAtom = atom20((get) => {
|
|
get(graphUpdateVersionAtom);
|
|
get(nodePositionUpdateCounterAtom);
|
|
const graph = get(graphAtom);
|
|
const grid = new SpatialGrid(500);
|
|
graph.forEachNode((nodeId, attrs) => {
|
|
const a = attrs;
|
|
grid.insert(nodeId, a.x, a.y, a.width || 200, a.height || 100);
|
|
});
|
|
return grid;
|
|
});
|
|
visibleBoundsAtom = atom20((get) => {
|
|
const viewport = get(viewportRectAtom);
|
|
const pan = get(panAtom);
|
|
const zoom = get(zoomAtom);
|
|
if (!viewport || zoom === 0) {
|
|
return null;
|
|
}
|
|
const buffer = VIRTUALIZATION_BUFFER;
|
|
return {
|
|
minX: (-buffer - pan.x) / zoom,
|
|
minY: (-buffer - pan.y) / zoom,
|
|
maxX: (viewport.width + buffer - pan.x) / zoom,
|
|
maxY: (viewport.height + buffer - pan.y) / zoom
|
|
};
|
|
});
|
|
visibleNodeKeysAtom = atom20((get) => {
|
|
const end = canvasMark("virtualization-cull");
|
|
const enabled = get(virtualizationEnabledAtom);
|
|
const allKeys = get(nodeKeysAtom);
|
|
if (!enabled) {
|
|
end();
|
|
return allKeys;
|
|
}
|
|
const bounds = get(visibleBoundsAtom);
|
|
if (!bounds) {
|
|
end();
|
|
return allKeys;
|
|
}
|
|
const grid = get(spatialIndexAtom);
|
|
const visibleSet = grid.query(bounds);
|
|
const result = allKeys.filter((k) => visibleSet.has(k));
|
|
end();
|
|
return result;
|
|
});
|
|
visibleEdgeKeysAtom = atom20((get) => {
|
|
const enabled = get(virtualizationEnabledAtom);
|
|
const allEdgeKeys = get(edgeKeysAtom);
|
|
const edgeCreation = get(edgeCreationAtom);
|
|
const remap = get(collapsedEdgeRemapAtom);
|
|
const tempEdgeKey = edgeCreation.isCreating ? "temp-creating-edge" : null;
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
const filteredEdges = allEdgeKeys.filter((edgeKey) => {
|
|
const source = graph.source(edgeKey);
|
|
const target = graph.target(edgeKey);
|
|
const effectiveSource = remap.get(source) ?? source;
|
|
const effectiveTarget = remap.get(target) ?? target;
|
|
if (effectiveSource === effectiveTarget) return false;
|
|
return true;
|
|
});
|
|
if (!enabled) {
|
|
return tempEdgeKey ? [...filteredEdges, tempEdgeKey] : filteredEdges;
|
|
}
|
|
const visibleNodeKeys = get(visibleNodeKeysAtom);
|
|
const visibleNodeSet = new Set(visibleNodeKeys);
|
|
const visibleEdges = filteredEdges.filter((edgeKey) => {
|
|
const source = graph.source(edgeKey);
|
|
const target = graph.target(edgeKey);
|
|
const effectiveSource = remap.get(source) ?? source;
|
|
const effectiveTarget = remap.get(target) ?? target;
|
|
return visibleNodeSet.has(effectiveSource) && visibleNodeSet.has(effectiveTarget);
|
|
});
|
|
return tempEdgeKey ? [...visibleEdges, tempEdgeKey] : visibleEdges;
|
|
});
|
|
virtualizationMetricsAtom = atom20((get) => {
|
|
const enabled = get(virtualizationEnabledAtom);
|
|
const totalNodes = get(nodeKeysAtom).length;
|
|
const totalEdges = get(edgeKeysAtom).length;
|
|
const visibleNodes = get(visibleNodeKeysAtom).length;
|
|
const visibleEdges = get(visibleEdgeKeysAtom).length;
|
|
const bounds = get(visibleBoundsAtom);
|
|
return {
|
|
enabled,
|
|
totalNodes,
|
|
totalEdges,
|
|
visibleNodes,
|
|
visibleEdges,
|
|
culledNodes: totalNodes - visibleNodes,
|
|
culledEdges: totalEdges - visibleEdges,
|
|
bounds
|
|
};
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/canvas-api.ts
|
|
function createCanvasAPI(store, options = {}) {
|
|
const helpers = buildActionHelpers(store, options);
|
|
const api = {
|
|
// Selection
|
|
selectNode: (id) => store.set(selectSingleNodeAtom, id),
|
|
addToSelection: (ids) => store.set(addNodesToSelectionAtom, ids),
|
|
clearSelection: () => store.set(clearSelectionAtom),
|
|
getSelectedNodeIds: () => Array.from(store.get(selectedNodeIdsAtom)),
|
|
selectEdge: (edgeId) => store.set(selectEdgeAtom, edgeId),
|
|
clearEdgeSelection: () => store.set(clearEdgeSelectionAtom),
|
|
getSelectedEdgeId: () => store.get(selectedEdgeIdAtom),
|
|
// Viewport
|
|
getZoom: () => store.get(zoomAtom),
|
|
setZoom: (zoom) => store.set(zoomAtom, zoom),
|
|
getPan: () => store.get(panAtom),
|
|
setPan: (pan) => store.set(panAtom, pan),
|
|
resetViewport: () => store.set(resetViewportAtom),
|
|
fitToBounds: (mode, padding) => {
|
|
const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection;
|
|
store.set(fitToBoundsAtom, {
|
|
mode: fitMode,
|
|
padding
|
|
});
|
|
},
|
|
centerOnNode: (nodeId) => store.set(centerOnNodeAtom, nodeId),
|
|
// Graph
|
|
addNode: (node) => store.set(addNodeToLocalGraphAtom, node),
|
|
removeNode: (nodeId) => store.set(optimisticDeleteNodeAtom, {
|
|
nodeId
|
|
}),
|
|
addEdge: (edge) => store.set(addEdgeToLocalGraphAtom, edge),
|
|
removeEdge: (edgeKey) => store.set(optimisticDeleteEdgeAtom, {
|
|
edgeKey
|
|
}),
|
|
getNodeKeys: () => store.get(nodeKeysAtom),
|
|
getEdgeKeys: () => store.get(edgeKeysAtom),
|
|
getNodeAttributes: (id) => {
|
|
const graph = store.get(graphAtom);
|
|
return graph.hasNode(id) ? graph.getNodeAttributes(id) : void 0;
|
|
},
|
|
// History
|
|
undo: () => store.set(undoAtom),
|
|
redo: () => store.set(redoAtom),
|
|
canUndo: () => store.get(canUndoAtom),
|
|
canRedo: () => store.get(canRedoAtom),
|
|
recordSnapshot: (label) => store.set(pushHistoryAtom, label),
|
|
clearHistory: () => store.set(clearHistoryAtom),
|
|
// Clipboard
|
|
copy: () => store.set(copyToClipboardAtom),
|
|
cut: () => store.set(cutToClipboardAtom),
|
|
paste: () => store.set(pasteFromClipboardAtom),
|
|
duplicate: () => store.set(duplicateSelectionAtom),
|
|
hasClipboardContent: () => store.get(clipboardAtom) !== null,
|
|
// Snap
|
|
isSnapEnabled: () => store.get(snapEnabledAtom),
|
|
toggleSnap: () => store.set(toggleSnapAtom),
|
|
getSnapGridSize: () => store.get(snapGridSizeAtom),
|
|
// Virtualization
|
|
isVirtualizationEnabled: () => store.get(virtualizationEnabledAtom),
|
|
getVisibleNodeKeys: () => store.get(visibleNodeKeysAtom),
|
|
getVisibleEdgeKeys: () => store.get(visibleEdgeKeysAtom),
|
|
// Actions
|
|
executeAction: (actionId, context) => executeAction(actionId, context, helpers),
|
|
executeEventAction: (event, context) => {
|
|
const mappings = store.get(eventMappingsAtom);
|
|
const actionId = getActionForEvent(mappings, event);
|
|
return executeAction(actionId, context, helpers);
|
|
},
|
|
// Serialization
|
|
exportSnapshot: (metadata) => exportGraph(store, metadata),
|
|
importSnapshot: (snapshot, options2) => importGraph(store, snapshot, options2),
|
|
validateSnapshot: (data) => validateSnapshot(data)
|
|
};
|
|
return api;
|
|
}
|
|
var init_canvas_api = __esm({
|
|
"src/core/canvas-api.ts"() {
|
|
"use strict";
|
|
init_action_executor();
|
|
init_canvas_serializer();
|
|
init_settings_store();
|
|
init_selection_store();
|
|
init_viewport_store();
|
|
init_graph_store();
|
|
init_graph_derived();
|
|
init_graph_mutations();
|
|
init_graph_mutations_edges();
|
|
init_history_store();
|
|
init_clipboard_store();
|
|
init_snap_store();
|
|
init_virtualization_store();
|
|
init_layout();
|
|
}
|
|
});
|
|
|
|
// src/core/port-types.ts
|
|
function calculatePortPosition(nodeX, nodeY, nodeWidth, nodeHeight, port) {
|
|
switch (port.side) {
|
|
case "left":
|
|
return {
|
|
x: nodeX,
|
|
y: nodeY + nodeHeight * port.position
|
|
};
|
|
case "right":
|
|
return {
|
|
x: nodeX + nodeWidth,
|
|
y: nodeY + nodeHeight * port.position
|
|
};
|
|
case "top":
|
|
return {
|
|
x: nodeX + nodeWidth * port.position,
|
|
y: nodeY
|
|
};
|
|
case "bottom":
|
|
return {
|
|
x: nodeX + nodeWidth * port.position,
|
|
y: nodeY + nodeHeight
|
|
};
|
|
}
|
|
}
|
|
function getNodePorts(ports) {
|
|
if (ports && ports.length > 0) {
|
|
return ports;
|
|
}
|
|
return [DEFAULT_PORT];
|
|
}
|
|
function canPortAcceptConnection(port, currentConnections, isSource) {
|
|
if (isSource && port.type === "input") {
|
|
return false;
|
|
}
|
|
if (!isSource && port.type === "output") {
|
|
return false;
|
|
}
|
|
if (port.maxConnections !== void 0 && currentConnections >= port.maxConnections) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function arePortsCompatible(sourcePort, targetPort) {
|
|
if (sourcePort.type === "input") {
|
|
return false;
|
|
}
|
|
if (targetPort.type === "output") {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
var DEFAULT_PORT;
|
|
var init_port_types = __esm({
|
|
"src/core/port-types.ts"() {
|
|
"use strict";
|
|
DEFAULT_PORT = {
|
|
id: "default",
|
|
type: "bidirectional",
|
|
side: "right",
|
|
position: 0.5
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/input-classifier.ts
|
|
function classifyPointer(e) {
|
|
const source = pointerTypeToSource(e.pointerType);
|
|
return {
|
|
source,
|
|
pointerId: e.pointerId,
|
|
pressure: e.pressure,
|
|
tiltX: e.tiltX,
|
|
tiltY: e.tiltY,
|
|
isPrimary: e.isPrimary,
|
|
rawPointerType: e.pointerType
|
|
};
|
|
}
|
|
function pointerTypeToSource(pointerType) {
|
|
switch (pointerType) {
|
|
case "pen":
|
|
return "pencil";
|
|
case "touch":
|
|
return "finger";
|
|
case "mouse":
|
|
return "mouse";
|
|
default:
|
|
return "mouse";
|
|
}
|
|
}
|
|
function detectInputCapabilities() {
|
|
if (typeof window === "undefined") {
|
|
return {
|
|
hasTouch: false,
|
|
hasStylus: false,
|
|
hasMouse: true,
|
|
hasCoarsePointer: false
|
|
};
|
|
}
|
|
const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
const supportsMatchMedia = typeof window.matchMedia === "function";
|
|
const hasCoarsePointer = supportsMatchMedia ? window.matchMedia("(pointer: coarse)").matches : false;
|
|
const hasFinePointer = supportsMatchMedia ? window.matchMedia("(pointer: fine)").matches : true;
|
|
const hasMouse = hasFinePointer || !hasTouch;
|
|
return {
|
|
hasTouch,
|
|
hasStylus: false,
|
|
// Set to true on first pen event
|
|
hasMouse,
|
|
hasCoarsePointer
|
|
};
|
|
}
|
|
function getGestureThresholds(source) {
|
|
switch (source) {
|
|
case "finger":
|
|
return {
|
|
dragThreshold: 10,
|
|
tapThreshold: 10,
|
|
longPressDuration: 600,
|
|
longPressMoveLimit: 10
|
|
};
|
|
case "pencil":
|
|
return {
|
|
dragThreshold: 2,
|
|
tapThreshold: 3,
|
|
longPressDuration: 500,
|
|
longPressMoveLimit: 5
|
|
};
|
|
case "mouse":
|
|
return {
|
|
dragThreshold: 3,
|
|
tapThreshold: 5,
|
|
longPressDuration: 0,
|
|
// Mouse uses right-click instead
|
|
longPressMoveLimit: 0
|
|
};
|
|
}
|
|
}
|
|
function getHitTargetSize(source) {
|
|
return HIT_TARGET_SIZES[source];
|
|
}
|
|
var HIT_TARGET_SIZES;
|
|
var init_input_classifier = __esm({
|
|
"src/core/input-classifier.ts"() {
|
|
"use strict";
|
|
HIT_TARGET_SIZES = {
|
|
/** Minimum touch target (Apple HIG: 44pt) */
|
|
finger: 44,
|
|
/** Stylus target (precise, can use smaller targets) */
|
|
pencil: 24,
|
|
/** Mouse target (hover-discoverable, smallest) */
|
|
mouse: 16
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/input-store.ts
|
|
import { atom as atom21 } from "jotai";
|
|
var activePointersAtom, primaryInputSourceAtom, inputCapabilitiesAtom, isStylusActiveAtom, isMultiTouchAtom, fingerCountAtom, isTouchDeviceAtom, pointerDownAtom, pointerUpAtom, clearPointersAtom;
|
|
var init_input_store = __esm({
|
|
"src/core/input-store.ts"() {
|
|
"use strict";
|
|
init_input_classifier();
|
|
activePointersAtom = atom21(/* @__PURE__ */ new Map());
|
|
primaryInputSourceAtom = atom21("mouse");
|
|
inputCapabilitiesAtom = atom21(detectInputCapabilities());
|
|
isStylusActiveAtom = atom21((get) => {
|
|
const pointers = get(activePointersAtom);
|
|
for (const [, pointer] of pointers) {
|
|
if (pointer.source === "pencil") return true;
|
|
}
|
|
return false;
|
|
});
|
|
isMultiTouchAtom = atom21((get) => {
|
|
const pointers = get(activePointersAtom);
|
|
let fingerCount = 0;
|
|
for (const [, pointer] of pointers) {
|
|
if (pointer.source === "finger") fingerCount++;
|
|
}
|
|
return fingerCount > 1;
|
|
});
|
|
fingerCountAtom = atom21((get) => {
|
|
const pointers = get(activePointersAtom);
|
|
let count = 0;
|
|
for (const [, pointer] of pointers) {
|
|
if (pointer.source === "finger") count++;
|
|
}
|
|
return count;
|
|
});
|
|
isTouchDeviceAtom = atom21((get) => {
|
|
const caps = get(inputCapabilitiesAtom);
|
|
return caps.hasTouch;
|
|
});
|
|
pointerDownAtom = atom21(null, (get, set, pointer) => {
|
|
const pointers = new Map(get(activePointersAtom));
|
|
pointers.set(pointer.pointerId, pointer);
|
|
set(activePointersAtom, pointers);
|
|
set(primaryInputSourceAtom, pointer.source);
|
|
if (pointer.source === "pencil") {
|
|
const caps = get(inputCapabilitiesAtom);
|
|
if (!caps.hasStylus) {
|
|
set(inputCapabilitiesAtom, {
|
|
...caps,
|
|
hasStylus: true
|
|
});
|
|
}
|
|
}
|
|
});
|
|
pointerUpAtom = atom21(null, (get, set, pointerId) => {
|
|
const pointers = new Map(get(activePointersAtom));
|
|
pointers.delete(pointerId);
|
|
set(activePointersAtom, pointers);
|
|
});
|
|
clearPointersAtom = atom21(null, (_get, set) => {
|
|
set(activePointersAtom, /* @__PURE__ */ new Map());
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/selection-path-store.ts
|
|
import { atom as atom22 } from "jotai";
|
|
function pointInPolygon(px, py, polygon) {
|
|
let inside = false;
|
|
const n = polygon.length;
|
|
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
const xi = polygon[i].x;
|
|
const yi = polygon[i].y;
|
|
const xj = polygon[j].x;
|
|
const yj = polygon[j].y;
|
|
if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
|
|
inside = !inside;
|
|
}
|
|
}
|
|
return inside;
|
|
}
|
|
var selectionPathAtom, isSelectingAtom, startSelectionAtom, updateSelectionAtom, cancelSelectionAtom, endSelectionAtom, selectionRectAtom;
|
|
var init_selection_path_store = __esm({
|
|
"src/core/selection-path-store.ts"() {
|
|
"use strict";
|
|
init_graph_derived();
|
|
init_selection_store();
|
|
selectionPathAtom = atom22(null);
|
|
isSelectingAtom = atom22((get) => get(selectionPathAtom) !== null);
|
|
startSelectionAtom = atom22(null, (_get, set, {
|
|
type,
|
|
point
|
|
}) => {
|
|
set(selectionPathAtom, {
|
|
type,
|
|
points: [point]
|
|
});
|
|
});
|
|
updateSelectionAtom = atom22(null, (get, set, point) => {
|
|
const current = get(selectionPathAtom);
|
|
if (!current) return;
|
|
if (current.type === "rect") {
|
|
set(selectionPathAtom, {
|
|
...current,
|
|
points: [current.points[0], point]
|
|
});
|
|
} else {
|
|
set(selectionPathAtom, {
|
|
...current,
|
|
points: [...current.points, point]
|
|
});
|
|
}
|
|
});
|
|
cancelSelectionAtom = atom22(null, (_get, set) => {
|
|
set(selectionPathAtom, null);
|
|
});
|
|
endSelectionAtom = atom22(null, (get, set) => {
|
|
const path = get(selectionPathAtom);
|
|
if (!path || path.points.length < 2) {
|
|
set(selectionPathAtom, null);
|
|
return;
|
|
}
|
|
const nodes = get(uiNodesAtom);
|
|
const selectedIds = [];
|
|
if (path.type === "rect") {
|
|
const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
|
|
const minX = Math.min(p1.x, p2.x);
|
|
const maxX = Math.max(p1.x, p2.x);
|
|
const minY = Math.min(p1.y, p2.y);
|
|
const maxY = Math.max(p1.y, p2.y);
|
|
for (const node of nodes) {
|
|
const nodeRight = node.position.x + (node.width ?? 200);
|
|
const nodeBottom = node.position.y + (node.height ?? 100);
|
|
if (node.position.x < maxX && nodeRight > minX && node.position.y < maxY && nodeBottom > minY) {
|
|
selectedIds.push(node.id);
|
|
}
|
|
}
|
|
} else {
|
|
const polygon = path.points;
|
|
for (const node of nodes) {
|
|
const cx = node.position.x + (node.width ?? 200) / 2;
|
|
const cy = node.position.y + (node.height ?? 100) / 2;
|
|
if (pointInPolygon(cx, cy, polygon)) {
|
|
selectedIds.push(node.id);
|
|
}
|
|
}
|
|
}
|
|
set(selectedNodeIdsAtom, new Set(selectedIds));
|
|
set(selectionPathAtom, null);
|
|
});
|
|
selectionRectAtom = atom22((get) => {
|
|
const path = get(selectionPathAtom);
|
|
if (!path || path.type !== "rect" || path.points.length < 2) return null;
|
|
const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
|
|
return {
|
|
x: Math.min(p1.x, p2.x),
|
|
y: Math.min(p1.y, p2.y),
|
|
width: Math.abs(p2.x - p1.x),
|
|
height: Math.abs(p2.y - p1.y)
|
|
};
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/search-store.ts
|
|
var search_store_exports = {};
|
|
__export(search_store_exports, {
|
|
clearSearchAtom: () => clearSearchAtom,
|
|
fuzzyMatch: () => fuzzyMatch,
|
|
highlightedSearchIndexAtom: () => highlightedSearchIndexAtom,
|
|
highlightedSearchNodeIdAtom: () => highlightedSearchNodeIdAtom,
|
|
isFilterActiveAtom: () => isFilterActiveAtom,
|
|
nextSearchResultAtom: () => nextSearchResultAtom,
|
|
prevSearchResultAtom: () => prevSearchResultAtom,
|
|
searchEdgeResultCountAtom: () => searchEdgeResultCountAtom,
|
|
searchEdgeResultsAtom: () => searchEdgeResultsAtom,
|
|
searchQueryAtom: () => searchQueryAtom,
|
|
searchResultCountAtom: () => searchResultCountAtom,
|
|
searchResultsArrayAtom: () => searchResultsArrayAtom,
|
|
searchResultsAtom: () => searchResultsAtom,
|
|
searchTotalResultCountAtom: () => searchTotalResultCountAtom,
|
|
setSearchQueryAtom: () => setSearchQueryAtom
|
|
});
|
|
import { atom as atom23 } from "jotai";
|
|
function fuzzyMatch(query, ...haystacks) {
|
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
if (tokens.length === 0) return false;
|
|
const combined = haystacks.join(" ").toLowerCase();
|
|
return tokens.every((token) => combined.includes(token));
|
|
}
|
|
var searchQueryAtom, setSearchQueryAtom, clearSearchAtom, searchResultsAtom, searchResultsArrayAtom, searchResultCountAtom, searchEdgeResultsAtom, searchEdgeResultCountAtom, isFilterActiveAtom, searchTotalResultCountAtom, highlightedSearchIndexAtom, nextSearchResultAtom, prevSearchResultAtom, highlightedSearchNodeIdAtom;
|
|
var init_search_store = __esm({
|
|
"src/core/search-store.ts"() {
|
|
"use strict";
|
|
init_graph_derived();
|
|
init_graph_store();
|
|
init_viewport_store();
|
|
init_selection_store();
|
|
searchQueryAtom = atom23("");
|
|
setSearchQueryAtom = atom23(null, (_get, set, query) => {
|
|
set(searchQueryAtom, query);
|
|
set(highlightedSearchIndexAtom, 0);
|
|
});
|
|
clearSearchAtom = atom23(null, (_get, set) => {
|
|
set(searchQueryAtom, "");
|
|
set(highlightedSearchIndexAtom, 0);
|
|
});
|
|
searchResultsAtom = atom23((get) => {
|
|
const query = get(searchQueryAtom).trim();
|
|
if (!query) return /* @__PURE__ */ new Set();
|
|
const nodes = get(uiNodesAtom);
|
|
const matches = /* @__PURE__ */ new Set();
|
|
for (const node of nodes) {
|
|
if (fuzzyMatch(query, node.label || "", node.dbData.node_type || "", node.id)) {
|
|
matches.add(node.id);
|
|
}
|
|
}
|
|
return matches;
|
|
});
|
|
searchResultsArrayAtom = atom23((get) => {
|
|
return Array.from(get(searchResultsAtom));
|
|
});
|
|
searchResultCountAtom = atom23((get) => {
|
|
return get(searchResultsAtom).size;
|
|
});
|
|
searchEdgeResultsAtom = atom23((get) => {
|
|
const query = get(searchQueryAtom).trim();
|
|
if (!query) return /* @__PURE__ */ new Set();
|
|
get(graphUpdateVersionAtom);
|
|
const graph = get(graphAtom);
|
|
const matches = /* @__PURE__ */ new Set();
|
|
graph.forEachEdge((edgeKey, attrs) => {
|
|
const label = attrs.label || "";
|
|
const edgeType = attrs.dbData?.edge_type || "";
|
|
if (fuzzyMatch(query, label, edgeType, edgeKey)) {
|
|
matches.add(edgeKey);
|
|
}
|
|
});
|
|
return matches;
|
|
});
|
|
searchEdgeResultCountAtom = atom23((get) => {
|
|
return get(searchEdgeResultsAtom).size;
|
|
});
|
|
isFilterActiveAtom = atom23((get) => {
|
|
return get(searchQueryAtom).trim().length > 0;
|
|
});
|
|
searchTotalResultCountAtom = atom23((get) => {
|
|
return get(searchResultCountAtom) + get(searchEdgeResultCountAtom);
|
|
});
|
|
highlightedSearchIndexAtom = atom23(0);
|
|
nextSearchResultAtom = atom23(null, (get, set) => {
|
|
const results = get(searchResultsArrayAtom);
|
|
if (results.length === 0) return;
|
|
const currentIndex = get(highlightedSearchIndexAtom);
|
|
const nextIndex = (currentIndex + 1) % results.length;
|
|
set(highlightedSearchIndexAtom, nextIndex);
|
|
const nodeId = results[nextIndex];
|
|
set(centerOnNodeAtom, nodeId);
|
|
set(selectSingleNodeAtom, nodeId);
|
|
});
|
|
prevSearchResultAtom = atom23(null, (get, set) => {
|
|
const results = get(searchResultsArrayAtom);
|
|
if (results.length === 0) return;
|
|
const currentIndex = get(highlightedSearchIndexAtom);
|
|
const prevIndex = (currentIndex - 1 + results.length) % results.length;
|
|
set(highlightedSearchIndexAtom, prevIndex);
|
|
const nodeId = results[prevIndex];
|
|
set(centerOnNodeAtom, nodeId);
|
|
set(selectSingleNodeAtom, nodeId);
|
|
});
|
|
highlightedSearchNodeIdAtom = atom23((get) => {
|
|
const results = get(searchResultsArrayAtom);
|
|
if (results.length === 0) return null;
|
|
const index = get(highlightedSearchIndexAtom);
|
|
return results[index] ?? null;
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/gesture-resolver.ts
|
|
var init_gesture_resolver = __esm({
|
|
"src/core/gesture-resolver.ts"() {
|
|
"use strict";
|
|
}
|
|
});
|
|
|
|
// src/core/gesture-rules-defaults.ts
|
|
function formatRuleLabel(pattern) {
|
|
const parts = [];
|
|
if (pattern.modifiers) {
|
|
const mods = MODIFIER_KEYS.filter((k) => pattern.modifiers[k]).map((k) => k.charAt(0).toUpperCase() + k.slice(1));
|
|
if (mods.length) parts.push(mods.join("+"));
|
|
}
|
|
if (pattern.button !== void 0 && pattern.button !== 0) {
|
|
parts.push(BUTTON_LABELS[pattern.button]);
|
|
}
|
|
if (pattern.source) {
|
|
parts.push(SOURCE_LABELS[pattern.source]);
|
|
}
|
|
if (pattern.gesture) {
|
|
parts.push(GESTURE_LABELS[pattern.gesture] ?? pattern.gesture);
|
|
}
|
|
if (pattern.target) {
|
|
parts.push("on " + (TARGET_LABELS[pattern.target] ?? pattern.target));
|
|
}
|
|
if (parts.length === 0) return "Any gesture";
|
|
if (pattern.modifiers) {
|
|
const modCount = MODIFIER_KEYS.filter((k) => pattern.modifiers[k]).length;
|
|
if (modCount > 0 && parts.length > modCount) {
|
|
const modPart = parts.slice(0, 1).join("");
|
|
const rest = parts.slice(1).join(" ").toLowerCase();
|
|
return `${modPart} + ${rest}`;
|
|
}
|
|
}
|
|
return parts.join(" ");
|
|
}
|
|
function mergeRules(defaults, overrides) {
|
|
const overrideMap = new Map(overrides.map((r) => [r.id, r]));
|
|
const result = [];
|
|
for (const rule of defaults) {
|
|
const override = overrideMap.get(rule.id);
|
|
if (override) {
|
|
result.push(override);
|
|
overrideMap.delete(rule.id);
|
|
} else {
|
|
result.push(rule);
|
|
}
|
|
}
|
|
for (const rule of overrideMap.values()) {
|
|
result.push(rule);
|
|
}
|
|
return result;
|
|
}
|
|
var MODIFIER_KEYS, SOURCE_LABELS, GESTURE_LABELS, TARGET_LABELS, BUTTON_LABELS, DEFAULT_GESTURE_RULES;
|
|
var init_gesture_rules_defaults = __esm({
|
|
"src/core/gesture-rules-defaults.ts"() {
|
|
"use strict";
|
|
MODIFIER_KEYS = ["shift", "ctrl", "alt", "meta"];
|
|
SOURCE_LABELS = {
|
|
mouse: "Mouse",
|
|
pencil: "Pencil",
|
|
finger: "Touch"
|
|
};
|
|
GESTURE_LABELS = {
|
|
tap: "Tap",
|
|
"double-tap": "Double-tap",
|
|
"triple-tap": "Triple-tap",
|
|
drag: "Drag",
|
|
"long-press": "Long-press",
|
|
"right-click": "Right-click",
|
|
pinch: "Pinch",
|
|
scroll: "Scroll"
|
|
};
|
|
TARGET_LABELS = {
|
|
node: "node",
|
|
edge: "edge",
|
|
port: "port",
|
|
"resize-handle": "resize handle",
|
|
background: "background"
|
|
};
|
|
BUTTON_LABELS = {
|
|
0: "Left",
|
|
1: "Middle",
|
|
2: "Right"
|
|
};
|
|
DEFAULT_GESTURE_RULES = [
|
|
// ── Tap gestures ──────────────────────────────────────────────
|
|
{
|
|
id: "tap-node",
|
|
pattern: {
|
|
gesture: "tap",
|
|
target: "node"
|
|
},
|
|
actionId: "select-node"
|
|
},
|
|
{
|
|
id: "tap-edge",
|
|
pattern: {
|
|
gesture: "tap",
|
|
target: "edge"
|
|
},
|
|
actionId: "select-edge"
|
|
},
|
|
{
|
|
id: "tap-port",
|
|
pattern: {
|
|
gesture: "tap",
|
|
target: "port"
|
|
},
|
|
actionId: "select-node"
|
|
},
|
|
{
|
|
id: "tap-bg",
|
|
pattern: {
|
|
gesture: "tap",
|
|
target: "background"
|
|
},
|
|
actionId: "clear-selection"
|
|
},
|
|
// ── Double-tap ────────────────────────────────────────────────
|
|
{
|
|
id: "dtap-node",
|
|
pattern: {
|
|
gesture: "double-tap",
|
|
target: "node"
|
|
},
|
|
actionId: "fit-to-view"
|
|
},
|
|
{
|
|
id: "dtap-bg",
|
|
pattern: {
|
|
gesture: "double-tap",
|
|
target: "background"
|
|
},
|
|
actionId: "fit-all-to-view"
|
|
},
|
|
// ── Triple-tap ────────────────────────────────────────────────
|
|
{
|
|
id: "ttap-node",
|
|
pattern: {
|
|
gesture: "triple-tap",
|
|
target: "node"
|
|
},
|
|
actionId: "toggle-lock"
|
|
},
|
|
// ── Left-button drag ──────────────────────────────────────────
|
|
{
|
|
id: "drag-node",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "node"
|
|
},
|
|
actionId: "move-node"
|
|
},
|
|
{
|
|
id: "drag-port",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "port"
|
|
},
|
|
actionId: "create-edge"
|
|
},
|
|
{
|
|
id: "drag-bg-finger",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "background",
|
|
source: "finger"
|
|
},
|
|
actionId: "pan"
|
|
},
|
|
{
|
|
id: "drag-bg-mouse",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "background",
|
|
source: "mouse"
|
|
},
|
|
actionId: "pan"
|
|
},
|
|
{
|
|
id: "drag-bg-pencil",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "background",
|
|
source: "pencil"
|
|
},
|
|
actionId: "lasso-select"
|
|
},
|
|
// ── Shift+drag overrides ──────────────────────────────────────
|
|
{
|
|
id: "shift-drag-bg",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "background",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "rect-select"
|
|
},
|
|
// ── Right-click tap (context menu) ────────────────────────────
|
|
{
|
|
id: "rc-node",
|
|
pattern: {
|
|
gesture: "tap",
|
|
target: "node",
|
|
button: 2
|
|
},
|
|
actionId: "open-context-menu"
|
|
},
|
|
{
|
|
id: "rc-edge",
|
|
pattern: {
|
|
gesture: "tap",
|
|
target: "edge",
|
|
button: 2
|
|
},
|
|
actionId: "open-context-menu"
|
|
},
|
|
{
|
|
id: "rc-bg",
|
|
pattern: {
|
|
gesture: "tap",
|
|
target: "background",
|
|
button: 2
|
|
},
|
|
actionId: "open-context-menu"
|
|
},
|
|
// ── Long-press ────────────────────────────────────────────────
|
|
{
|
|
id: "lp-node",
|
|
pattern: {
|
|
gesture: "long-press",
|
|
target: "node"
|
|
},
|
|
actionId: "open-context-menu"
|
|
},
|
|
{
|
|
id: "lp-bg-finger",
|
|
pattern: {
|
|
gesture: "long-press",
|
|
target: "background",
|
|
source: "finger"
|
|
},
|
|
actionId: "create-node"
|
|
},
|
|
// ── Right-button drag (defaults to none — consumers override) ─
|
|
{
|
|
id: "rdrag-node",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "node",
|
|
button: 2
|
|
},
|
|
actionId: "none"
|
|
},
|
|
{
|
|
id: "rdrag-bg",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "background",
|
|
button: 2
|
|
},
|
|
actionId: "none"
|
|
},
|
|
// ── Middle-button drag (defaults to none) ─────────────────────
|
|
{
|
|
id: "mdrag-node",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "node",
|
|
button: 1
|
|
},
|
|
actionId: "none"
|
|
},
|
|
{
|
|
id: "mdrag-bg",
|
|
pattern: {
|
|
gesture: "drag",
|
|
target: "background",
|
|
button: 1
|
|
},
|
|
actionId: "none"
|
|
},
|
|
// ── Zoom ──────────────────────────────────────────────────────
|
|
{
|
|
id: "pinch-bg",
|
|
pattern: {
|
|
gesture: "pinch",
|
|
target: "background"
|
|
},
|
|
actionId: "zoom"
|
|
},
|
|
{
|
|
id: "scroll-any",
|
|
pattern: {
|
|
gesture: "scroll"
|
|
},
|
|
actionId: "zoom"
|
|
},
|
|
// ── Split ─────────────────────────────────────────────────────
|
|
{
|
|
id: "pinch-node",
|
|
pattern: {
|
|
gesture: "pinch",
|
|
target: "node"
|
|
},
|
|
actionId: "split-node"
|
|
}
|
|
];
|
|
}
|
|
});
|
|
|
|
// src/core/gesture-rules.ts
|
|
function matchSpecificity(pattern, desc) {
|
|
let score = 0;
|
|
if (pattern.gesture !== void 0) {
|
|
if (pattern.gesture !== desc.gesture) return -1;
|
|
score += 32;
|
|
}
|
|
if (pattern.target !== void 0) {
|
|
if (pattern.target !== desc.target) return -1;
|
|
score += 16;
|
|
}
|
|
if (pattern.source !== void 0) {
|
|
if (pattern.source !== desc.source) return -1;
|
|
score += 4;
|
|
}
|
|
if (pattern.button !== void 0) {
|
|
if (pattern.button !== (desc.button ?? 0)) return -1;
|
|
score += 2;
|
|
}
|
|
if (pattern.modifiers !== void 0) {
|
|
const dm = desc.modifiers ?? {};
|
|
for (const key of MODIFIER_KEYS2) {
|
|
const required = pattern.modifiers[key];
|
|
if (required === void 0) continue;
|
|
const actual = dm[key] ?? false;
|
|
if (required !== actual) return -1;
|
|
score += 8;
|
|
}
|
|
}
|
|
return score;
|
|
}
|
|
function resolveGesture(desc, rules, options) {
|
|
const palmRejection = options?.palmRejection !== false;
|
|
if (palmRejection && desc.isStylusActive && desc.source === "finger") {
|
|
if (desc.gesture === "tap" || desc.gesture === "long-press" || desc.gesture === "double-tap" || desc.gesture === "triple-tap") {
|
|
return {
|
|
actionId: "none",
|
|
rule: PALM_REJECTION_RULE,
|
|
score: Infinity
|
|
};
|
|
}
|
|
if (desc.gesture === "drag" && desc.target !== "background") {
|
|
return resolveGesture({
|
|
...desc,
|
|
target: "background",
|
|
isStylusActive: false
|
|
}, rules, {
|
|
palmRejection: false
|
|
});
|
|
}
|
|
}
|
|
let best = null;
|
|
for (const rule of rules) {
|
|
const specificity2 = matchSpecificity(rule.pattern, desc);
|
|
if (specificity2 < 0) continue;
|
|
const effectiveScore = specificity2 * 1e3 + (rule.priority ?? 0);
|
|
if (!best || effectiveScore > best.score) {
|
|
best = {
|
|
actionId: rule.actionId,
|
|
rule,
|
|
score: effectiveScore
|
|
};
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
function buildRuleIndex(rules) {
|
|
const buckets = /* @__PURE__ */ new Map();
|
|
const wildcardRules = [];
|
|
for (const rule of rules) {
|
|
const key = rule.pattern.gesture;
|
|
if (key === void 0) {
|
|
wildcardRules.push(rule);
|
|
} else {
|
|
const bucket = buckets.get(key);
|
|
if (bucket) {
|
|
bucket.push(rule);
|
|
} else {
|
|
buckets.set(key, [rule]);
|
|
}
|
|
}
|
|
}
|
|
const index = /* @__PURE__ */ new Map();
|
|
if (wildcardRules.length > 0) {
|
|
for (const [key, bucket] of buckets) {
|
|
index.set(key, bucket.concat(wildcardRules));
|
|
}
|
|
index.set("__wildcard__", wildcardRules);
|
|
} else {
|
|
for (const [key, bucket] of buckets) {
|
|
index.set(key, bucket);
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
function resolveGestureIndexed(desc, index, options) {
|
|
const rules = index.get(desc.gesture) ?? index.get("__wildcard__") ?? [];
|
|
return resolveGesture(desc, rules, options);
|
|
}
|
|
var MODIFIER_KEYS2, PALM_REJECTION_RULE;
|
|
var init_gesture_rules = __esm({
|
|
"src/core/gesture-rules.ts"() {
|
|
"use strict";
|
|
init_gesture_rules_defaults();
|
|
MODIFIER_KEYS2 = ["shift", "ctrl", "alt", "meta"];
|
|
PALM_REJECTION_RULE = {
|
|
id: "__palm-rejection__",
|
|
pattern: {},
|
|
actionId: "none",
|
|
label: "Palm rejection"
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/core/gesture-rule-store.ts
|
|
import { atom as atom24 } from "jotai";
|
|
import { atomWithStorage as atomWithStorage2 } from "jotai/utils";
|
|
var DEFAULT_RULE_STATE, gestureRuleSettingsAtom, consumerGestureRulesAtom, gestureRulesAtom, gestureRuleIndexAtom, palmRejectionEnabledAtom, addGestureRuleAtom, removeGestureRuleAtom, updateGestureRuleAtom, resetGestureRulesAtom;
|
|
var init_gesture_rule_store = __esm({
|
|
"src/core/gesture-rule-store.ts"() {
|
|
"use strict";
|
|
init_gesture_rules();
|
|
DEFAULT_RULE_STATE = {
|
|
customRules: [],
|
|
palmRejection: true
|
|
};
|
|
gestureRuleSettingsAtom = atomWithStorage2("canvas-gesture-rules", DEFAULT_RULE_STATE);
|
|
consumerGestureRulesAtom = atom24([]);
|
|
gestureRulesAtom = atom24((get) => {
|
|
const settings = get(gestureRuleSettingsAtom);
|
|
const consumerRules = get(consumerGestureRulesAtom);
|
|
let rules = mergeRules(DEFAULT_GESTURE_RULES, settings.customRules);
|
|
if (consumerRules.length > 0) {
|
|
rules = mergeRules(rules, consumerRules);
|
|
}
|
|
return rules;
|
|
});
|
|
gestureRuleIndexAtom = atom24((get) => {
|
|
return buildRuleIndex(get(gestureRulesAtom));
|
|
});
|
|
palmRejectionEnabledAtom = atom24((get) => get(gestureRuleSettingsAtom).palmRejection, (get, set, enabled) => {
|
|
const current = get(gestureRuleSettingsAtom);
|
|
set(gestureRuleSettingsAtom, {
|
|
...current,
|
|
palmRejection: enabled
|
|
});
|
|
});
|
|
addGestureRuleAtom = atom24(null, (get, set, rule) => {
|
|
const current = get(gestureRuleSettingsAtom);
|
|
const existing = current.customRules.findIndex((r) => r.id === rule.id);
|
|
const newRules = [...current.customRules];
|
|
if (existing >= 0) {
|
|
newRules[existing] = rule;
|
|
} else {
|
|
newRules.push(rule);
|
|
}
|
|
set(gestureRuleSettingsAtom, {
|
|
...current,
|
|
customRules: newRules
|
|
});
|
|
});
|
|
removeGestureRuleAtom = atom24(null, (get, set, ruleId) => {
|
|
const current = get(gestureRuleSettingsAtom);
|
|
set(gestureRuleSettingsAtom, {
|
|
...current,
|
|
customRules: current.customRules.filter((r) => r.id !== ruleId)
|
|
});
|
|
});
|
|
updateGestureRuleAtom = atom24(null, (get, set, {
|
|
id,
|
|
updates
|
|
}) => {
|
|
const current = get(gestureRuleSettingsAtom);
|
|
const index = current.customRules.findIndex((r) => r.id === id);
|
|
if (index < 0) return;
|
|
const newRules = [...current.customRules];
|
|
newRules[index] = {
|
|
...newRules[index],
|
|
...updates
|
|
};
|
|
set(gestureRuleSettingsAtom, {
|
|
...current,
|
|
customRules: newRules
|
|
});
|
|
});
|
|
resetGestureRulesAtom = atom24(null, (get, set) => {
|
|
const current = get(gestureRuleSettingsAtom);
|
|
set(gestureRuleSettingsAtom, {
|
|
...current,
|
|
customRules: []
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/external-keyboard-store.ts
|
|
import { atom as atom25 } from "jotai";
|
|
var hasExternalKeyboardAtom, watchExternalKeyboardAtom;
|
|
var init_external_keyboard_store = __esm({
|
|
"src/core/external-keyboard-store.ts"() {
|
|
"use strict";
|
|
hasExternalKeyboardAtom = atom25(false);
|
|
watchExternalKeyboardAtom = atom25(null, (get, set) => {
|
|
if (typeof window === "undefined") return;
|
|
const handler = (e) => {
|
|
if (e.key && e.key.length === 1 || ["Tab", "Escape", "Enter", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
|
|
set(hasExternalKeyboardAtom, true);
|
|
window.removeEventListener("keydown", handler);
|
|
}
|
|
};
|
|
window.addEventListener("keydown", handler);
|
|
return () => window.removeEventListener("keydown", handler);
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/core/plugin-types.ts
|
|
var PluginError;
|
|
var init_plugin_types = __esm({
|
|
"src/core/plugin-types.ts"() {
|
|
"use strict";
|
|
PluginError = class extends Error {
|
|
constructor(message, pluginId, code) {
|
|
super(`[Plugin "${pluginId}"] ${message}`);
|
|
this.pluginId = pluginId;
|
|
this.code = code;
|
|
this.name = "PluginError";
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
// src/gestures/types.ts
|
|
function isKeyInputEvent(event) {
|
|
return event.kind === "key";
|
|
}
|
|
function isPointerGestureEvent(event) {
|
|
return event.kind !== "key";
|
|
}
|
|
var NO_MODIFIERS, NO_HELD_KEYS;
|
|
var init_types2 = __esm({
|
|
"src/gestures/types.ts"() {
|
|
"use strict";
|
|
NO_MODIFIERS = Object.freeze({
|
|
shift: false,
|
|
ctrl: false,
|
|
alt: false,
|
|
meta: false
|
|
});
|
|
NO_HELD_KEYS = Object.freeze({
|
|
byKey: Object.freeze({}),
|
|
byCode: Object.freeze({})
|
|
});
|
|
}
|
|
});
|
|
|
|
// src/gestures/dispatcher.ts
|
|
function registerAction2(actionId, handler) {
|
|
handlers.set(actionId, handler);
|
|
}
|
|
function unregisterAction2(actionId) {
|
|
handlers.delete(actionId);
|
|
}
|
|
function getHandler(actionId) {
|
|
return handlers.get(actionId);
|
|
}
|
|
function clearHandlers() {
|
|
handlers.clear();
|
|
}
|
|
function dispatch(event, resolution) {
|
|
if (resolution.actionId === "none") return true;
|
|
const handler = handlers.get(resolution.actionId);
|
|
if (!handler) return false;
|
|
if (typeof handler === "function") {
|
|
if (isKeyInputEvent(event) && event.phase === "down" || !isKeyInputEvent(event) && (event.phase === "start" || event.phase === "instant")) {
|
|
handler(event);
|
|
}
|
|
return true;
|
|
}
|
|
routePhase(handler, event.phase, event);
|
|
return true;
|
|
}
|
|
function routePhase(handler, phase, event) {
|
|
if (isKeyInputEvent(event)) {
|
|
routeKeyPhase(handler, phase, event);
|
|
return;
|
|
}
|
|
switch (phase) {
|
|
case "start":
|
|
handler.onStart?.(event);
|
|
break;
|
|
case "move":
|
|
handler.onMove?.(event);
|
|
break;
|
|
case "end":
|
|
handler.onEnd?.(event);
|
|
break;
|
|
case "instant":
|
|
handler.onInstant?.(event);
|
|
break;
|
|
case "cancel":
|
|
handler.onCancel?.(event);
|
|
break;
|
|
}
|
|
}
|
|
function routeKeyPhase(handler, phase, event) {
|
|
switch (phase) {
|
|
case "down":
|
|
handler.onDown?.(event);
|
|
break;
|
|
case "up":
|
|
handler.onUp?.(event);
|
|
break;
|
|
}
|
|
}
|
|
var handlers;
|
|
var init_dispatcher = __esm({
|
|
"src/gestures/dispatcher.ts"() {
|
|
"use strict";
|
|
init_types2();
|
|
handlers = /* @__PURE__ */ new Map();
|
|
}
|
|
});
|
|
|
|
// src/commands/registry.ts
|
|
function registerCommand(command) {
|
|
commandRegistry.register(command);
|
|
}
|
|
var CommandRegistry, commandRegistry;
|
|
var init_registry = __esm({
|
|
"src/commands/registry.ts"() {
|
|
"use strict";
|
|
CommandRegistry = class {
|
|
constructor() {
|
|
__publicField(this, "commands", /* @__PURE__ */ new Map());
|
|
__publicField(this, "aliases", /* @__PURE__ */ new Map());
|
|
}
|
|
// alias -> command name
|
|
/**
|
|
* Register a command with the registry.
|
|
* @param command The command definition to register
|
|
* @throws Error if command name or alias already exists
|
|
*/
|
|
register(command) {
|
|
if (this.commands.has(command.name)) {
|
|
throw new Error(`Command "${command.name}" is already registered`);
|
|
}
|
|
this.commands.set(command.name, command);
|
|
if (command.aliases) {
|
|
for (const alias of command.aliases) {
|
|
if (this.aliases.has(alias)) {
|
|
throw new Error(`Alias "${alias}" is already registered for command "${this.aliases.get(alias)}"`);
|
|
}
|
|
if (this.commands.has(alias)) {
|
|
throw new Error(`Alias "${alias}" conflicts with existing command name`);
|
|
}
|
|
this.aliases.set(alias, command.name);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Unregister a command by name.
|
|
* @param name The command name to remove
|
|
*/
|
|
unregister(name) {
|
|
const command = this.commands.get(name);
|
|
if (command) {
|
|
if (command.aliases) {
|
|
for (const alias of command.aliases) {
|
|
this.aliases.delete(alias);
|
|
}
|
|
}
|
|
this.commands.delete(name);
|
|
}
|
|
}
|
|
/**
|
|
* Get a command by name or alias.
|
|
* @param nameOrAlias Command name or alias
|
|
* @returns The command definition or undefined if not found
|
|
*/
|
|
get(nameOrAlias) {
|
|
const direct = this.commands.get(nameOrAlias);
|
|
if (direct) return direct;
|
|
const commandName = this.aliases.get(nameOrAlias);
|
|
if (commandName) {
|
|
return this.commands.get(commandName);
|
|
}
|
|
return void 0;
|
|
}
|
|
/**
|
|
* Check if a command exists by name or alias.
|
|
* @param nameOrAlias Command name or alias
|
|
*/
|
|
has(nameOrAlias) {
|
|
return this.commands.has(nameOrAlias) || this.aliases.has(nameOrAlias);
|
|
}
|
|
/**
|
|
* Search for commands matching a query.
|
|
* Searches command names, aliases, and descriptions.
|
|
* @param query Search query (case-insensitive)
|
|
* @returns Array of matching commands, sorted by relevance
|
|
*/
|
|
search(query) {
|
|
if (!query.trim()) {
|
|
return this.all();
|
|
}
|
|
const lowerQuery = query.toLowerCase().trim();
|
|
const results = [];
|
|
const commands = Array.from(this.commands.values());
|
|
for (const command of commands) {
|
|
let score = 0;
|
|
if (command.name.toLowerCase() === lowerQuery) {
|
|
score = 100;
|
|
} else if (command.name.toLowerCase().startsWith(lowerQuery)) {
|
|
score = 80;
|
|
} else if (command.name.toLowerCase().includes(lowerQuery)) {
|
|
score = 60;
|
|
} else if (command.aliases?.some((a) => a.toLowerCase() === lowerQuery)) {
|
|
score = 90;
|
|
} else if (command.aliases?.some((a) => a.toLowerCase().startsWith(lowerQuery))) {
|
|
score = 70;
|
|
} else if (command.aliases?.some((a) => a.toLowerCase().includes(lowerQuery))) {
|
|
score = 50;
|
|
} else if (command.description.toLowerCase().includes(lowerQuery)) {
|
|
score = 30;
|
|
}
|
|
if (score > 0) {
|
|
results.push({
|
|
command,
|
|
score
|
|
});
|
|
}
|
|
}
|
|
return results.sort((a, b) => b.score - a.score || a.command.name.localeCompare(b.command.name)).map((r) => r.command);
|
|
}
|
|
/**
|
|
* Get all registered commands.
|
|
* @returns Array of all commands, sorted alphabetically by name
|
|
*/
|
|
all() {
|
|
return Array.from(this.commands.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
/**
|
|
* Get commands by category.
|
|
* @param category The category to filter by
|
|
* @returns Array of commands in the category
|
|
*/
|
|
byCategory(category) {
|
|
return this.all().filter((cmd) => cmd.category === category);
|
|
}
|
|
/**
|
|
* Get all available categories.
|
|
* @returns Array of unique categories
|
|
*/
|
|
categories() {
|
|
const categories = /* @__PURE__ */ new Set();
|
|
const commands = Array.from(this.commands.values());
|
|
for (const command of commands) {
|
|
categories.add(command.category);
|
|
}
|
|
return Array.from(categories).sort();
|
|
}
|
|
/**
|
|
* Get the count of registered commands.
|
|
*/
|
|
get size() {
|
|
return this.commands.size;
|
|
}
|
|
/**
|
|
* Clear all registered commands.
|
|
* Useful for testing.
|
|
*/
|
|
clear() {
|
|
this.commands.clear();
|
|
this.aliases.clear();
|
|
}
|
|
/**
|
|
* Get a serializable list of commands for API responses.
|
|
*/
|
|
toJSON() {
|
|
return this.all().map((cmd) => ({
|
|
name: cmd.name,
|
|
aliases: cmd.aliases || [],
|
|
description: cmd.description,
|
|
category: cmd.category,
|
|
inputs: cmd.inputs.map((input) => ({
|
|
name: input.name,
|
|
type: input.type,
|
|
prompt: input.prompt,
|
|
required: input.required !== false
|
|
}))
|
|
}));
|
|
}
|
|
};
|
|
commandRegistry = new CommandRegistry();
|
|
}
|
|
});
|
|
|
|
// src/utils/edge-path-calculators.ts
|
|
function getEdgePathCalculator(type) {
|
|
switch (type) {
|
|
case "bezier":
|
|
return bezierHorizontal;
|
|
case "bezier-vertical":
|
|
return bezierVertical;
|
|
case "bezier-smart":
|
|
return bezierSmart;
|
|
case "straight":
|
|
return straight;
|
|
case "step":
|
|
return stepHorizontal;
|
|
case "step-vertical":
|
|
return stepVertical;
|
|
case "step-smart":
|
|
return stepSmart;
|
|
case "smooth-step":
|
|
return smoothStep;
|
|
default:
|
|
return bezierHorizontal;
|
|
}
|
|
}
|
|
var bezierHorizontal, bezierVertical, bezierSmart, straight, stepHorizontal, stepVertical, stepSmart, smoothStep, defaultEdgePathCalculator;
|
|
var init_edge_path_calculators = __esm({
|
|
"src/utils/edge-path-calculators.ts"() {
|
|
"use strict";
|
|
bezierHorizontal = ({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
}) => {
|
|
const dist = Math.abs(x2 - x1);
|
|
const offset = Math.max(dist * 0.5, 50);
|
|
const cp1x = x1 + offset;
|
|
const cp1y = y1;
|
|
const cp2x = x2 - offset;
|
|
const cp2y = y2;
|
|
const path = `M ${x1} ${y1} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`;
|
|
const labelX = (x1 + x2) / 2;
|
|
const labelY = (y1 + y2) / 2;
|
|
return {
|
|
path,
|
|
labelX,
|
|
labelY
|
|
};
|
|
};
|
|
bezierVertical = ({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
}) => {
|
|
const dist = Math.abs(y2 - y1);
|
|
const offset = Math.max(dist * 0.5, 50);
|
|
const cp1x = x1;
|
|
const cp1y = y1 + offset;
|
|
const cp2x = x2;
|
|
const cp2y = y2 - offset;
|
|
const path = `M ${x1} ${y1} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`;
|
|
const labelX = (x1 + x2) / 2;
|
|
const labelY = (y1 + y2) / 2;
|
|
return {
|
|
path,
|
|
labelX,
|
|
labelY
|
|
};
|
|
};
|
|
bezierSmart = (input) => {
|
|
const {
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
} = input;
|
|
const dx = Math.abs(x2 - x1);
|
|
const dy = Math.abs(y2 - y1);
|
|
return dx > dy ? bezierHorizontal(input) : bezierVertical(input);
|
|
};
|
|
straight = ({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
}) => {
|
|
const path = `M ${x1} ${y1} L ${x2} ${y2}`;
|
|
const labelX = (x1 + x2) / 2;
|
|
const labelY = (y1 + y2) / 2;
|
|
return {
|
|
path,
|
|
labelX,
|
|
labelY
|
|
};
|
|
};
|
|
stepHorizontal = ({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
}) => {
|
|
const midX = (x1 + x2) / 2;
|
|
const path = `M ${x1} ${y1} L ${midX} ${y1} L ${midX} ${y2} L ${x2} ${y2}`;
|
|
const labelX = midX;
|
|
const labelY = (y1 + y2) / 2;
|
|
return {
|
|
path,
|
|
labelX,
|
|
labelY
|
|
};
|
|
};
|
|
stepVertical = ({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
}) => {
|
|
const midY = (y1 + y2) / 2;
|
|
const path = `M ${x1} ${y1} L ${x1} ${midY} L ${x2} ${midY} L ${x2} ${y2}`;
|
|
const labelX = (x1 + x2) / 2;
|
|
const labelY = midY;
|
|
return {
|
|
path,
|
|
labelX,
|
|
labelY
|
|
};
|
|
};
|
|
stepSmart = (input) => {
|
|
const {
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
} = input;
|
|
const dx = Math.abs(x2 - x1);
|
|
const dy = Math.abs(y2 - y1);
|
|
return dx > dy ? stepHorizontal(input) : stepVertical(input);
|
|
};
|
|
smoothStep = ({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2
|
|
}) => {
|
|
const midX = (x1 + x2) / 2;
|
|
const radius = Math.min(20, Math.abs(x2 - x1) / 4, Math.abs(y2 - y1) / 2);
|
|
if (radius < 5 || Math.abs(y2 - y1) < radius * 2) {
|
|
return stepHorizontal({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
sourceWidth: 0,
|
|
sourceHeight: 0,
|
|
targetWidth: 0,
|
|
targetHeight: 0
|
|
});
|
|
}
|
|
const yDir = y2 > y1 ? 1 : -1;
|
|
const path = `
|
|
M ${x1} ${y1}
|
|
L ${midX - radius} ${y1}
|
|
Q ${midX} ${y1}, ${midX} ${y1 + radius * yDir}
|
|
L ${midX} ${y2 - radius * yDir}
|
|
Q ${midX} ${y2}, ${midX + radius} ${y2}
|
|
L ${x2} ${y2}
|
|
`.replace(/\s+/g, " ").trim();
|
|
const labelX = midX;
|
|
const labelY = (y1 + y2) / 2;
|
|
return {
|
|
path,
|
|
labelX,
|
|
labelY
|
|
};
|
|
};
|
|
defaultEdgePathCalculator = bezierHorizontal;
|
|
}
|
|
});
|
|
|
|
// src/utils/edge-path-registry.ts
|
|
function registerEdgePathCalculator(name, calculator) {
|
|
customCalculators.set(name, calculator);
|
|
}
|
|
function unregisterEdgePathCalculator(name) {
|
|
return customCalculators.delete(name);
|
|
}
|
|
function resolveEdgePathCalculator(name) {
|
|
return customCalculators.get(name) ?? getEdgePathCalculator(name);
|
|
}
|
|
function hasCustomEdgePathCalculator(name) {
|
|
return customCalculators.has(name);
|
|
}
|
|
function getCustomEdgePathCalculatorNames() {
|
|
return Array.from(customCalculators.keys());
|
|
}
|
|
function clearCustomEdgePathCalculators() {
|
|
customCalculators.clear();
|
|
}
|
|
var customCalculators;
|
|
var init_edge_path_registry = __esm({
|
|
"src/utils/edge-path-registry.ts"() {
|
|
"use strict";
|
|
init_edge_path_calculators();
|
|
customCalculators = /* @__PURE__ */ new Map();
|
|
}
|
|
});
|
|
|
|
// src/core/plugin-registry.ts
|
|
function registerPlugin(plugin) {
|
|
debug12("Registering plugin: %s", plugin.id);
|
|
if (plugins.has(plugin.id)) {
|
|
throw new PluginError("Plugin is already registered", plugin.id, "ALREADY_REGISTERED");
|
|
}
|
|
if (plugin.dependencies) {
|
|
for (const depId of plugin.dependencies) {
|
|
if (!plugins.has(depId)) {
|
|
throw new PluginError(`Missing dependency: "${depId}"`, plugin.id, "MISSING_DEPENDENCY");
|
|
}
|
|
}
|
|
}
|
|
detectConflicts(plugin);
|
|
const cleanups = [];
|
|
try {
|
|
if (plugin.nodeTypes) {
|
|
const nodeTypeNames = Object.keys(plugin.nodeTypes);
|
|
registerNodeTypes(plugin.nodeTypes);
|
|
cleanups.push(() => {
|
|
for (const name of nodeTypeNames) {
|
|
unregisterNodeType(name);
|
|
}
|
|
});
|
|
}
|
|
if (plugin.edgePathCalculators) {
|
|
for (const [name, calc] of Object.entries(plugin.edgePathCalculators)) {
|
|
registerEdgePathCalculator(name, calc);
|
|
cleanups.push(() => unregisterEdgePathCalculator(name));
|
|
}
|
|
}
|
|
if (plugin.actionHandlers) {
|
|
for (const [actionId, handler] of Object.entries(plugin.actionHandlers)) {
|
|
registerAction2(actionId, handler);
|
|
cleanups.push(() => unregisterAction2(actionId));
|
|
}
|
|
}
|
|
if (plugin.commands) {
|
|
for (const cmd of plugin.commands) {
|
|
commandRegistry.register(cmd);
|
|
cleanups.push(() => commandRegistry.unregister(cmd.name));
|
|
}
|
|
}
|
|
if (plugin.actions) {
|
|
for (const action of plugin.actions) {
|
|
registerAction(action);
|
|
cleanups.push(() => unregisterAction(action.id));
|
|
}
|
|
}
|
|
let lifecycleCleanup = null;
|
|
if (plugin.onRegister) {
|
|
const ctx = makePluginContext(plugin.id);
|
|
try {
|
|
const result = plugin.onRegister(ctx);
|
|
if (typeof result === "function") {
|
|
lifecycleCleanup = result;
|
|
}
|
|
} catch (err) {
|
|
for (const cleanup of cleanups.reverse()) {
|
|
try {
|
|
cleanup();
|
|
} catch {
|
|
}
|
|
}
|
|
throw new PluginError(`onRegister failed: ${err instanceof Error ? err.message : String(err)}`, plugin.id, "LIFECYCLE_ERROR");
|
|
}
|
|
}
|
|
plugins.set(plugin.id, {
|
|
plugin,
|
|
cleanup: () => {
|
|
for (const cleanup of cleanups.reverse()) {
|
|
try {
|
|
cleanup();
|
|
} catch {
|
|
}
|
|
}
|
|
if (lifecycleCleanup) {
|
|
try {
|
|
lifecycleCleanup();
|
|
} catch {
|
|
}
|
|
}
|
|
},
|
|
registeredAt: Date.now()
|
|
});
|
|
debug12("Plugin registered: %s (%d node types, %d commands, %d actions)", plugin.id, Object.keys(plugin.nodeTypes ?? {}).length, plugin.commands?.length ?? 0, plugin.actions?.length ?? 0);
|
|
} catch (err) {
|
|
if (err instanceof PluginError) throw err;
|
|
for (const cleanup of cleanups.reverse()) {
|
|
try {
|
|
cleanup();
|
|
} catch {
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
function unregisterPlugin(pluginId) {
|
|
const registration = plugins.get(pluginId);
|
|
if (!registration) {
|
|
throw new PluginError("Plugin is not registered", pluginId, "NOT_FOUND");
|
|
}
|
|
for (const [otherId, other] of plugins) {
|
|
if (other.plugin.dependencies?.includes(pluginId)) {
|
|
throw new PluginError(`Cannot unregister: plugin "${otherId}" depends on it`, pluginId, "CONFLICT");
|
|
}
|
|
}
|
|
if (registration.cleanup) {
|
|
registration.cleanup();
|
|
}
|
|
plugins.delete(pluginId);
|
|
debug12("Plugin unregistered: %s", pluginId);
|
|
}
|
|
function getPlugin(id) {
|
|
return plugins.get(id)?.plugin;
|
|
}
|
|
function hasPlugin(id) {
|
|
return plugins.has(id);
|
|
}
|
|
function getAllPlugins() {
|
|
return Array.from(plugins.values()).map((r) => r.plugin);
|
|
}
|
|
function getPluginIds() {
|
|
return Array.from(plugins.keys());
|
|
}
|
|
function getPluginGestureContexts() {
|
|
const contexts = [];
|
|
for (const registration of plugins.values()) {
|
|
if (registration.plugin.gestureContexts) {
|
|
contexts.push(...registration.plugin.gestureContexts);
|
|
}
|
|
}
|
|
return contexts;
|
|
}
|
|
function clearPlugins() {
|
|
const ids = Array.from(plugins.keys()).reverse();
|
|
for (const id of ids) {
|
|
const reg = plugins.get(id);
|
|
if (reg?.cleanup) {
|
|
try {
|
|
reg.cleanup();
|
|
} catch {
|
|
}
|
|
}
|
|
plugins.delete(id);
|
|
}
|
|
debug12("All plugins cleared");
|
|
}
|
|
function detectConflicts(plugin) {
|
|
if (plugin.commands) {
|
|
for (const cmd of plugin.commands) {
|
|
if (commandRegistry.has(cmd.name)) {
|
|
throw new PluginError(`Command "${cmd.name}" is already registered`, plugin.id, "CONFLICT");
|
|
}
|
|
}
|
|
}
|
|
if (plugin.edgePathCalculators) {
|
|
for (const name of Object.keys(plugin.edgePathCalculators)) {
|
|
for (const [otherId, other] of plugins) {
|
|
if (other.plugin.edgePathCalculators?.[name]) {
|
|
throw new PluginError(`Edge path calculator "${name}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (plugin.nodeTypes) {
|
|
for (const nodeType of Object.keys(plugin.nodeTypes)) {
|
|
for (const [otherId, other] of plugins) {
|
|
if (other.plugin.nodeTypes?.[nodeType]) {
|
|
throw new PluginError(`Node type "${nodeType}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (plugin.actionHandlers) {
|
|
for (const actionId of Object.keys(plugin.actionHandlers)) {
|
|
for (const [otherId, other] of plugins) {
|
|
if (other.plugin.actionHandlers?.[actionId]) {
|
|
throw new PluginError(`Action handler "${actionId}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function makePluginContext(pluginId) {
|
|
return {
|
|
pluginId,
|
|
getPlugin,
|
|
hasPlugin
|
|
};
|
|
}
|
|
var debug12, plugins;
|
|
var init_plugin_registry = __esm({
|
|
"src/core/plugin-registry.ts"() {
|
|
"use strict";
|
|
init_plugin_types();
|
|
init_node_type_registry();
|
|
init_action_registry();
|
|
init_dispatcher();
|
|
init_registry();
|
|
init_edge_path_registry();
|
|
init_debug();
|
|
debug12 = createDebug("plugins");
|
|
plugins = /* @__PURE__ */ new Map();
|
|
}
|
|
});
|
|
|
|
// src/core/index.ts
|
|
var core_exports = {};
|
|
__export(core_exports, {
|
|
ActionCategory: () => ActionCategory,
|
|
BUILT_IN_PRESETS: () => BUILT_IN_PRESETS,
|
|
BuiltInActionId: () => BuiltInActionId,
|
|
CanvasEventType: () => CanvasEventType,
|
|
DEFAULT_GESTURE_RULES: () => DEFAULT_GESTURE_RULES,
|
|
DEFAULT_MAPPINGS: () => DEFAULT_MAPPINGS,
|
|
DEFAULT_PORT: () => DEFAULT_PORT,
|
|
EDGE_ANIMATION_DURATION: () => EDGE_ANIMATION_DURATION,
|
|
EVENT_TYPE_INFO: () => EVENT_TYPE_INFO,
|
|
FallbackNodeTypeComponent: () => FallbackNodeTypeComponent,
|
|
HIT_TARGET_SIZES: () => HIT_TARGET_SIZES,
|
|
PASTE_OFFSET: () => PASTE_OFFSET,
|
|
PluginError: () => PluginError,
|
|
SNAPSHOT_VERSION: () => SNAPSHOT_VERSION,
|
|
SpatialGrid: () => SpatialGrid,
|
|
VIRTUALIZATION_BUFFER: () => VIRTUALIZATION_BUFFER,
|
|
ZOOM_EXIT_THRESHOLD: () => ZOOM_EXIT_THRESHOLD,
|
|
ZOOM_TRANSITION_THRESHOLD: () => ZOOM_TRANSITION_THRESHOLD,
|
|
activePointersAtom: () => activePointersAtom,
|
|
activePresetAtom: () => activePresetAtom,
|
|
activePresetIdAtom: () => activePresetIdAtom,
|
|
addGestureRuleAtom: () => addGestureRuleAtom,
|
|
addNodeToLocalGraphAtom: () => addNodeToLocalGraphAtom,
|
|
addNodesToSelectionAtom: () => addNodesToSelectionAtom,
|
|
alignmentGuidesAtom: () => alignmentGuidesAtom,
|
|
allPresetsAtom: () => allPresetsAtom,
|
|
animateFitToBoundsAtom: () => animateFitToBoundsAtom,
|
|
animateZoomToNodeAtom: () => animateZoomToNodeAtom,
|
|
applyDelta: () => applyDelta,
|
|
applyPresetAtom: () => applyPresetAtom,
|
|
arePortsCompatible: () => arePortsCompatible,
|
|
autoResizeGroupAtom: () => autoResizeGroupAtom,
|
|
buildActionHelpers: () => buildActionHelpers,
|
|
buildRuleIndex: () => buildRuleIndex,
|
|
calculatePortPosition: () => calculatePortPosition,
|
|
canPortAcceptConnection: () => canPortAcceptConnection,
|
|
canRedoAtom: () => canRedoAtom,
|
|
canUndoAtom: () => canUndoAtom,
|
|
cancelSelectionAtom: () => cancelSelectionAtom,
|
|
canvasMark: () => canvasMark,
|
|
canvasSettingsAtom: () => canvasSettingsAtom,
|
|
canvasToastAtom: () => canvasToastAtom,
|
|
canvasWrap: () => canvasWrap,
|
|
centerOnNodeAtom: () => centerOnNodeAtom,
|
|
classifyPointer: () => classifyPointer,
|
|
cleanupAllNodePositionsAtom: () => cleanupAllNodePositionsAtom,
|
|
cleanupNodePositionAtom: () => cleanupNodePositionAtom,
|
|
clearActions: () => clearActions,
|
|
clearAlignmentGuidesAtom: () => clearAlignmentGuidesAtom,
|
|
clearClipboardAtom: () => clearClipboardAtom,
|
|
clearEdgeSelectionAtom: () => clearEdgeSelectionAtom,
|
|
clearGraphOnSwitchAtom: () => clearGraphOnSwitchAtom,
|
|
clearHistoryAtom: () => clearHistoryAtom,
|
|
clearMutationQueueAtom: () => clearMutationQueueAtom,
|
|
clearNodeTypeRegistry: () => clearNodeTypeRegistry,
|
|
clearPlugins: () => clearPlugins,
|
|
clearPointersAtom: () => clearPointersAtom,
|
|
clearSearchAtom: () => clearSearchAtom,
|
|
clearSelectionAtom: () => clearSelectionAtom,
|
|
clipboardAtom: () => clipboardAtom,
|
|
clipboardNodeCountAtom: () => clipboardNodeCountAtom,
|
|
collapseGroupAtom: () => collapseGroupAtom,
|
|
collapsedEdgeRemapAtom: () => collapsedEdgeRemapAtom,
|
|
collapsedGroupsAtom: () => collapsedGroupsAtom,
|
|
completeMutationAtom: () => completeMutationAtom,
|
|
conditionalSnap: () => conditionalSnap,
|
|
consumerGestureRulesAtom: () => consumerGestureRulesAtom,
|
|
copyToClipboardAtom: () => copyToClipboardAtom,
|
|
createActionContext: () => createActionContext,
|
|
createActionContextFromReactEvent: () => createActionContextFromReactEvent,
|
|
createActionContextFromTouchEvent: () => createActionContextFromTouchEvent,
|
|
createCanvasAPI: () => createCanvasAPI,
|
|
createSnapshot: () => createSnapshot,
|
|
currentGraphIdAtom: () => currentGraphIdAtom,
|
|
cutToClipboardAtom: () => cutToClipboardAtom,
|
|
deletePresetAtom: () => deletePresetAtom,
|
|
departingEdgesAtom: () => departingEdgesAtom,
|
|
dequeueMutationAtom: () => dequeueMutationAtom,
|
|
detectInputCapabilities: () => detectInputCapabilities,
|
|
draggingNodeIdAtom: () => draggingNodeIdAtom,
|
|
dropTargetNodeIdAtom: () => dropTargetNodeIdAtom,
|
|
duplicateSelectionAtom: () => duplicateSelectionAtom,
|
|
edgeCreationAtom: () => edgeCreationAtom,
|
|
edgeFamilyAtom: () => edgeFamilyAtom,
|
|
edgeKeysAtom: () => edgeKeysAtom,
|
|
edgeKeysWithTempEdgeAtom: () => edgeKeysWithTempEdgeAtom,
|
|
editingEdgeLabelAtom: () => editingEdgeLabelAtom,
|
|
endNodeDragAtom: () => endNodeDragAtom,
|
|
endSelectionAtom: () => endSelectionAtom,
|
|
eventMappingsAtom: () => eventMappingsAtom,
|
|
executeAction: () => executeAction,
|
|
expandGroupAtom: () => expandGroupAtom,
|
|
exportGraph: () => exportGraph,
|
|
findAlignmentGuides: () => findAlignmentGuides,
|
|
fingerCountAtom: () => fingerCountAtom,
|
|
fitToBoundsAtom: () => fitToBoundsAtom,
|
|
focusedNodeIdAtom: () => focusedNodeIdAtom,
|
|
formatRuleLabel: () => formatRuleLabel,
|
|
fuzzyMatch: () => fuzzyMatch,
|
|
gestureRuleIndexAtom: () => gestureRuleIndexAtom,
|
|
gestureRuleSettingsAtom: () => gestureRuleSettingsAtom,
|
|
gestureRulesAtom: () => gestureRulesAtom,
|
|
getAction: () => getAction,
|
|
getActionForEvent: () => getActionForEvent,
|
|
getActionsByCategories: () => getActionsByCategories,
|
|
getActionsByCategory: () => getActionsByCategory,
|
|
getAllActions: () => getAllActions,
|
|
getAllPlugins: () => getAllPlugins,
|
|
getGestureThresholds: () => getGestureThresholds,
|
|
getHitTargetSize: () => getHitTargetSize,
|
|
getNextQueuedMutationAtom: () => getNextQueuedMutationAtom,
|
|
getNodeDescendants: () => getNodeDescendants,
|
|
getNodePorts: () => getNodePorts,
|
|
getNodeTypeComponent: () => getNodeTypeComponent,
|
|
getPlugin: () => getPlugin,
|
|
getPluginGestureContexts: () => getPluginGestureContexts,
|
|
getPluginIds: () => getPluginIds,
|
|
getRegisteredNodeTypes: () => getRegisteredNodeTypes,
|
|
getSnapGuides: () => getSnapGuides,
|
|
goToLockedPageAtom: () => goToLockedPageAtom,
|
|
graphAtom: () => graphAtom,
|
|
graphOptions: () => graphOptions,
|
|
graphUpdateVersionAtom: () => graphUpdateVersionAtom,
|
|
groupChildCountAtom: () => groupChildCountAtom,
|
|
groupSelectedNodesAtom: () => groupSelectedNodesAtom,
|
|
handleNodePointerDownSelectionAtom: () => handleNodePointerDownSelectionAtom,
|
|
hasAction: () => hasAction,
|
|
hasClipboardContentAtom: () => hasClipboardContentAtom,
|
|
hasExternalKeyboardAtom: () => hasExternalKeyboardAtom,
|
|
hasFocusedNodeAtom: () => hasFocusedNodeAtom,
|
|
hasLockedNodeAtom: () => hasLockedNodeAtom,
|
|
hasNodeTypeComponent: () => hasNodeTypeComponent,
|
|
hasPlugin: () => hasPlugin,
|
|
hasSelectionAtom: () => hasSelectionAtom,
|
|
hasUnsavedChangesAtom: () => hasUnsavedChangesAtom,
|
|
highestZIndexAtom: () => highestZIndexAtom,
|
|
highlightedSearchIndexAtom: () => highlightedSearchIndexAtom,
|
|
highlightedSearchNodeIdAtom: () => highlightedSearchNodeIdAtom,
|
|
historyLabelsAtom: () => historyLabelsAtom,
|
|
historyStateAtom: () => historyStateAtom,
|
|
importGraph: () => importGraph,
|
|
incrementRetryCountAtom: () => incrementRetryCountAtom,
|
|
inputCapabilitiesAtom: () => inputCapabilitiesAtom,
|
|
inputModeAtom: () => inputModeAtom,
|
|
interactionFeedbackAtom: () => interactionFeedbackAtom,
|
|
invertDelta: () => invertDelta,
|
|
isFilterActiveAtom: () => isFilterActiveAtom,
|
|
isGroupNodeAtom: () => isGroupNodeAtom,
|
|
isMultiTouchAtom: () => isMultiTouchAtom,
|
|
isNodeCollapsed: () => isNodeCollapsed,
|
|
isOnlineAtom: () => isOnlineAtom,
|
|
isPanelOpenAtom: () => isPanelOpenAtom,
|
|
isPickNodeModeAtom: () => isPickNodeModeAtom,
|
|
isPickingModeAtom: () => isPickingModeAtom,
|
|
isSelectingAtom: () => isSelectingAtom,
|
|
isSnappingActiveAtom: () => isSnappingActiveAtom,
|
|
isStylusActiveAtom: () => isStylusActiveAtom,
|
|
isTouchDeviceAtom: () => isTouchDeviceAtom,
|
|
isZoomTransitioningAtom: () => isZoomTransitioningAtom,
|
|
keyboardInteractionModeAtom: () => keyboardInteractionModeAtom,
|
|
lastSyncErrorAtom: () => lastSyncErrorAtom,
|
|
lastSyncTimeAtom: () => lastSyncTimeAtom,
|
|
loadGraphFromDbAtom: () => loadGraphFromDbAtom,
|
|
lockNodeAtom: () => lockNodeAtom,
|
|
lockedNodeDataAtom: () => lockedNodeDataAtom,
|
|
lockedNodeIdAtom: () => lockedNodeIdAtom,
|
|
lockedNodePageCountAtom: () => lockedNodePageCountAtom,
|
|
lockedNodePageIndexAtom: () => lockedNodePageIndexAtom,
|
|
matchSpecificity: () => matchSpecificity,
|
|
mergeNodesAtom: () => mergeNodesAtom,
|
|
mergeRules: () => mergeRules,
|
|
moveNodesToGroupAtom: () => moveNodesToGroupAtom,
|
|
mutationQueueAtom: () => mutationQueueAtom,
|
|
nestNodesOnDropAtom: () => nestNodesOnDropAtom,
|
|
nextLockedPageAtom: () => nextLockedPageAtom,
|
|
nextSearchResultAtom: () => nextSearchResultAtom,
|
|
nodeChildrenAtom: () => nodeChildrenAtom,
|
|
nodeFamilyAtom: () => nodeFamilyAtom,
|
|
nodeKeysAtom: () => nodeKeysAtom,
|
|
nodeParentAtom: () => nodeParentAtom,
|
|
nodePositionAtomFamily: () => nodePositionAtomFamily,
|
|
nodePositionUpdateCounterAtom: () => nodePositionUpdateCounterAtom,
|
|
optimisticDeleteEdgeAtom: () => optimisticDeleteEdgeAtom,
|
|
optimisticDeleteNodeAtom: () => optimisticDeleteNodeAtom,
|
|
palmRejectionEnabledAtom: () => palmRejectionEnabledAtom,
|
|
panAtom: () => panAtom,
|
|
pasteFromClipboardAtom: () => pasteFromClipboardAtom,
|
|
pendingInputResolverAtom: () => pendingInputResolverAtom,
|
|
pendingMutationsCountAtom: () => pendingMutationsCountAtom,
|
|
perfEnabledAtom: () => perfEnabledAtom,
|
|
pointInPolygon: () => pointInPolygon,
|
|
pointerDownAtom: () => pointerDownAtom,
|
|
pointerUpAtom: () => pointerUpAtom,
|
|
preDragNodeAttributesAtom: () => preDragNodeAttributesAtom,
|
|
prefersReducedMotionAtom: () => prefersReducedMotionAtom,
|
|
prevLockedPageAtom: () => prevLockedPageAtom,
|
|
prevSearchResultAtom: () => prevSearchResultAtom,
|
|
primaryInputSourceAtom: () => primaryInputSourceAtom,
|
|
provideInputAtom: () => provideInputAtom,
|
|
pushDeltaAtom: () => pushDeltaAtom,
|
|
pushHistoryAtom: () => pushHistoryAtom,
|
|
queueMutationAtom: () => queueMutationAtom,
|
|
redoAtom: () => redoAtom,
|
|
redoCountAtom: () => redoCountAtom,
|
|
registerAction: () => registerAction,
|
|
registerNodeType: () => registerNodeType,
|
|
registerNodeTypes: () => registerNodeTypes,
|
|
registerPlugin: () => registerPlugin,
|
|
removeEdgeWithAnimationAtom: () => removeEdgeWithAnimationAtom,
|
|
removeFromGroupAtom: () => removeFromGroupAtom,
|
|
removeGestureRuleAtom: () => removeGestureRuleAtom,
|
|
removeNodesFromSelectionAtom: () => removeNodesFromSelectionAtom,
|
|
resetGestureRulesAtom: () => resetGestureRulesAtom,
|
|
resetInputModeAtom: () => resetInputModeAtom,
|
|
resetKeyboardInteractionModeAtom: () => resetKeyboardInteractionModeAtom,
|
|
resetSettingsAtom: () => resetSettingsAtom,
|
|
resetViewportAtom: () => resetViewportAtom,
|
|
resolveGesture: () => resolveGesture,
|
|
resolveGestureIndexed: () => resolveGestureIndexed,
|
|
saveAsPresetAtom: () => saveAsPresetAtom,
|
|
screenToWorldAtom: () => screenToWorldAtom,
|
|
searchEdgeResultCountAtom: () => searchEdgeResultCountAtom,
|
|
searchEdgeResultsAtom: () => searchEdgeResultsAtom,
|
|
searchQueryAtom: () => searchQueryAtom,
|
|
searchResultCountAtom: () => searchResultCountAtom,
|
|
searchResultsArrayAtom: () => searchResultsArrayAtom,
|
|
searchResultsAtom: () => searchResultsAtom,
|
|
searchTotalResultCountAtom: () => searchTotalResultCountAtom,
|
|
selectEdgeAtom: () => selectEdgeAtom,
|
|
selectSingleNodeAtom: () => selectSingleNodeAtom,
|
|
selectedEdgeIdAtom: () => selectedEdgeIdAtom,
|
|
selectedNodeIdsAtom: () => selectedNodeIdsAtom,
|
|
selectedNodesCountAtom: () => selectedNodesCountAtom,
|
|
selectionPathAtom: () => selectionPathAtom,
|
|
selectionRectAtom: () => selectionRectAtom,
|
|
setEventMappingAtom: () => setEventMappingAtom,
|
|
setFocusedNodeAtom: () => setFocusedNodeAtom,
|
|
setGridSizeAtom: () => setGridSizeAtom,
|
|
setKeyboardInteractionModeAtom: () => setKeyboardInteractionModeAtom,
|
|
setNodeParentAtom: () => setNodeParentAtom,
|
|
setOnlineStatusAtom: () => setOnlineStatusAtom,
|
|
setPanelOpenAtom: () => setPanelOpenAtom,
|
|
setPerfEnabled: () => setPerfEnabled,
|
|
setSearchQueryAtom: () => setSearchQueryAtom,
|
|
setVirtualizationEnabledAtom: () => setVirtualizationEnabledAtom,
|
|
setZoomAtom: () => setZoomAtom,
|
|
showToastAtom: () => showToastAtom,
|
|
snapAlignmentEnabledAtom: () => snapAlignmentEnabledAtom,
|
|
snapEnabledAtom: () => snapEnabledAtom,
|
|
snapGridSizeAtom: () => snapGridSizeAtom,
|
|
snapTemporaryDisableAtom: () => snapTemporaryDisableAtom,
|
|
snapToGrid: () => snapToGrid,
|
|
spatialIndexAtom: () => spatialIndexAtom,
|
|
splitNodeAtom: () => splitNodeAtom,
|
|
startMutationAtom: () => startMutationAtom,
|
|
startNodeDragAtom: () => startNodeDragAtom,
|
|
startPickNodeAtom: () => startPickNodeAtom,
|
|
startPickNodesAtom: () => startPickNodesAtom,
|
|
startPickPointAtom: () => startPickPointAtom,
|
|
startSelectionAtom: () => startSelectionAtom,
|
|
swapEdgeAtomicAtom: () => swapEdgeAtomicAtom,
|
|
syncStateAtom: () => syncStateAtom,
|
|
syncStatusAtom: () => syncStatusAtom,
|
|
toggleAlignmentGuidesAtom: () => toggleAlignmentGuidesAtom,
|
|
toggleGroupCollapseAtom: () => toggleGroupCollapseAtom,
|
|
toggleNodeInSelectionAtom: () => toggleNodeInSelectionAtom,
|
|
togglePanelAtom: () => togglePanelAtom,
|
|
toggleSnapAtom: () => toggleSnapAtom,
|
|
toggleVirtualizationAtom: () => toggleVirtualizationAtom,
|
|
trackMutationErrorAtom: () => trackMutationErrorAtom,
|
|
uiNodesAtom: () => uiNodesAtom,
|
|
undoAtom: () => undoAtom,
|
|
undoCountAtom: () => undoCountAtom,
|
|
ungroupNodesAtom: () => ungroupNodesAtom,
|
|
unlockNodeAtom: () => unlockNodeAtom,
|
|
unregisterAction: () => unregisterAction,
|
|
unregisterNodeType: () => unregisterNodeType,
|
|
unregisterPlugin: () => unregisterPlugin,
|
|
updateEdgeLabelAtom: () => updateEdgeLabelAtom,
|
|
updateGestureRuleAtom: () => updateGestureRuleAtom,
|
|
updateInteractionFeedbackAtom: () => updateInteractionFeedbackAtom,
|
|
updateNodePositionAtom: () => updateNodePositionAtom,
|
|
updatePresetAtom: () => updatePresetAtom,
|
|
updateSelectionAtom: () => updateSelectionAtom,
|
|
validateSnapshot: () => validateSnapshot,
|
|
viewportRectAtom: () => viewportRectAtom,
|
|
virtualizationEnabledAtom: () => virtualizationEnabledAtom,
|
|
virtualizationMetricsAtom: () => virtualizationMetricsAtom,
|
|
visibleBoundsAtom: () => visibleBoundsAtom,
|
|
visibleEdgeKeysAtom: () => visibleEdgeKeysAtom,
|
|
visibleNodeKeysAtom: () => visibleNodeKeysAtom,
|
|
watchExternalKeyboardAtom: () => watchExternalKeyboardAtom,
|
|
watchReducedMotionAtom: () => watchReducedMotionAtom,
|
|
worldToScreenAtom: () => worldToScreenAtom,
|
|
zoomAnimationTargetAtom: () => zoomAnimationTargetAtom,
|
|
zoomAtom: () => zoomAtom,
|
|
zoomFocusNodeIdAtom: () => zoomFocusNodeIdAtom,
|
|
zoomTransitionProgressAtom: () => zoomTransitionProgressAtom
|
|
});
|
|
var init_core = __esm({
|
|
"src/core/index.ts"() {
|
|
"use strict";
|
|
init_types();
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_graph_mutations();
|
|
init_viewport_store();
|
|
init_selection_store();
|
|
init_sync_store();
|
|
init_interaction_store();
|
|
init_locked_node_store();
|
|
init_node_type_registry();
|
|
init_history_store();
|
|
init_toast_store();
|
|
init_snap_store();
|
|
init_settings_types();
|
|
init_action_registry();
|
|
init_action_executor();
|
|
init_settings_store();
|
|
init_canvas_api();
|
|
init_virtualization_store();
|
|
init_port_types();
|
|
init_clipboard_store();
|
|
init_input_classifier();
|
|
init_input_store();
|
|
init_selection_path_store();
|
|
init_group_store();
|
|
init_search_store();
|
|
init_gesture_resolver();
|
|
init_gesture_rules();
|
|
init_gesture_rule_store();
|
|
init_reduced_motion_store();
|
|
init_external_keyboard_store();
|
|
init_perf();
|
|
init_spatial_index();
|
|
init_plugin_types();
|
|
init_plugin_registry();
|
|
init_canvas_serializer();
|
|
}
|
|
});
|
|
|
|
// src/index.ts
|
|
init_core();
|
|
|
|
// src/hooks/useNodeSelection.ts
|
|
init_selection_store();
|
|
import { c as _c2 } from "react/compiler-runtime";
|
|
import { useAtom, useSetAtom } from "jotai";
|
|
function useNodeSelection(nodeId) {
|
|
const $ = _c2(13);
|
|
const [selectedIds] = useAtom(selectedNodeIdsAtom);
|
|
const selectSingle = useSetAtom(selectSingleNodeAtom);
|
|
const toggleNode = useSetAtom(toggleNodeInSelectionAtom);
|
|
let t0;
|
|
if ($[0] !== nodeId || $[1] !== selectedIds) {
|
|
t0 = selectedIds.has(nodeId);
|
|
$[0] = nodeId;
|
|
$[1] = selectedIds;
|
|
$[2] = t0;
|
|
} else {
|
|
t0 = $[2];
|
|
}
|
|
let t1;
|
|
if ($[3] !== nodeId || $[4] !== selectSingle) {
|
|
t1 = () => selectSingle(nodeId);
|
|
$[3] = nodeId;
|
|
$[4] = selectSingle;
|
|
$[5] = t1;
|
|
} else {
|
|
t1 = $[5];
|
|
}
|
|
let t2;
|
|
if ($[6] !== nodeId || $[7] !== toggleNode) {
|
|
t2 = () => toggleNode(nodeId);
|
|
$[6] = nodeId;
|
|
$[7] = toggleNode;
|
|
$[8] = t2;
|
|
} else {
|
|
t2 = $[8];
|
|
}
|
|
let t3;
|
|
if ($[9] !== t0 || $[10] !== t1 || $[11] !== t2) {
|
|
t3 = {
|
|
isSelected: t0,
|
|
selectNode: t1,
|
|
toggleNode: t2
|
|
};
|
|
$[9] = t0;
|
|
$[10] = t1;
|
|
$[11] = t2;
|
|
$[12] = t3;
|
|
} else {
|
|
t3 = $[12];
|
|
}
|
|
return t3;
|
|
}
|
|
|
|
// src/hooks/useNodeDrag.ts
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_mutations();
|
|
init_viewport_store();
|
|
init_selection_store();
|
|
init_sync_store();
|
|
init_history_store();
|
|
init_group_store();
|
|
import { c as _c3 } from "react/compiler-runtime";
|
|
import { useAtom as useAtom2, useAtomValue, useSetAtom as useSetAtom2, atom as atom26 } from "jotai";
|
|
import { useGesture } from "@use-gesture/react";
|
|
import { useRef } from "react";
|
|
|
|
// src/utils/gesture-configs.ts
|
|
var fingerGestureConfig = {
|
|
eventOptions: {
|
|
passive: false,
|
|
capture: false
|
|
},
|
|
drag: {
|
|
pointer: {
|
|
touch: true,
|
|
keys: false,
|
|
capture: false,
|
|
buttons: -1
|
|
},
|
|
filterTaps: true,
|
|
tapsThreshold: 10,
|
|
// Was 3 — too strict for fingers
|
|
threshold: 10
|
|
// Was 3 — needs larger dead zone
|
|
}
|
|
};
|
|
var pencilGestureConfig = {
|
|
eventOptions: {
|
|
passive: false,
|
|
capture: false
|
|
},
|
|
drag: {
|
|
pointer: {
|
|
touch: true,
|
|
keys: false,
|
|
capture: false,
|
|
buttons: -1
|
|
},
|
|
filterTaps: true,
|
|
tapsThreshold: 3,
|
|
threshold: 2
|
|
// Very precise — small dead zone
|
|
}
|
|
};
|
|
var mouseGestureConfig = {
|
|
eventOptions: {
|
|
passive: false,
|
|
capture: false
|
|
},
|
|
drag: {
|
|
pointer: {
|
|
touch: true,
|
|
keys: false,
|
|
capture: false,
|
|
buttons: -1
|
|
},
|
|
filterTaps: true,
|
|
tapsThreshold: 5,
|
|
// Was 3
|
|
threshold: 3
|
|
}
|
|
};
|
|
function getNodeGestureConfig(source) {
|
|
switch (source) {
|
|
case "finger":
|
|
return fingerGestureConfig;
|
|
case "pencil":
|
|
return pencilGestureConfig;
|
|
case "mouse":
|
|
return mouseGestureConfig;
|
|
}
|
|
}
|
|
function getViewportGestureConfig(source) {
|
|
const base = getNodeGestureConfig(source);
|
|
return {
|
|
...base,
|
|
eventOptions: {
|
|
passive: false
|
|
},
|
|
pinch: {
|
|
pointer: {
|
|
touch: true
|
|
}
|
|
},
|
|
wheel: {
|
|
eventOptions: {
|
|
passive: false
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// src/hooks/useNodeDrag.ts
|
|
init_input_store();
|
|
init_mutation_queue();
|
|
init_debug();
|
|
|
|
// src/utils/hit-test.ts
|
|
var defaultProvider = {
|
|
elementFromPoint: (x, y) => document.elementFromPoint(x, y),
|
|
elementsFromPoint: (x, y) => document.elementsFromPoint(x, y)
|
|
};
|
|
var _provider = defaultProvider;
|
|
function setHitTestProvider(provider) {
|
|
_provider = provider ?? defaultProvider;
|
|
}
|
|
function hitTestNode(screenX, screenY) {
|
|
const element = _provider.elementFromPoint(screenX, screenY);
|
|
const nodeElement = element?.closest("[data-node-id]") ?? null;
|
|
const nodeId = nodeElement?.getAttribute("data-node-id") ?? null;
|
|
return {
|
|
nodeId,
|
|
element: nodeElement
|
|
};
|
|
}
|
|
function hitTestPort(screenX, screenY) {
|
|
const elements = _provider.elementsFromPoint(screenX, screenY);
|
|
for (const el of elements) {
|
|
const portElement = el.closest("[data-drag-port-id]");
|
|
if (portElement) {
|
|
const portId = portElement.dataset.dragPortId;
|
|
const portBar = portElement.closest("[data-port-bar]");
|
|
const nodeId = portBar?.dataset.nodeId;
|
|
if (portId && nodeId) {
|
|
return {
|
|
nodeId,
|
|
portId
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// src/hooks/useDragStateMachine.ts
|
|
init_group_store();
|
|
function buildDragPositions(graph, selectedNodeIds) {
|
|
const positions = /* @__PURE__ */ new Map();
|
|
for (const nodeId of selectedNodeIds) {
|
|
if (graph.hasNode(nodeId)) {
|
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
positions.set(nodeId, {
|
|
x: attrs.x,
|
|
y: attrs.y
|
|
});
|
|
}
|
|
}
|
|
const currentKeys = Array.from(positions.keys());
|
|
for (const nodeId of currentKeys) {
|
|
const descendants = getNodeDescendants(graph, nodeId);
|
|
for (const descId of descendants) {
|
|
if (!positions.has(descId) && graph.hasNode(descId)) {
|
|
const attrs = graph.getNodeAttributes(descId);
|
|
positions.set(descId, {
|
|
x: attrs.x,
|
|
y: attrs.y
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return positions;
|
|
}
|
|
function computeDragUpdates(initialPositions, movementX, movementY, zoom, graph) {
|
|
const deltaX = movementX / zoom;
|
|
const deltaY = movementY / zoom;
|
|
const updates = [];
|
|
initialPositions.forEach((initialPos, nodeId) => {
|
|
if (graph.hasNode(nodeId)) {
|
|
updates.push({
|
|
id: nodeId,
|
|
pos: {
|
|
x: initialPos.x + deltaX,
|
|
y: initialPos.y + deltaY
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return updates;
|
|
}
|
|
function isDragPrevented(target) {
|
|
return !!target.closest('[data-no-drag="true"]') || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
}
|
|
|
|
// src/hooks/useNodeDrag.ts
|
|
var debug13 = createDebug("drag");
|
|
function useNodeDrag(id, t0) {
|
|
const $ = _c3(49);
|
|
let t1;
|
|
if ($[0] !== t0) {
|
|
t1 = t0 === void 0 ? {} : t0;
|
|
$[0] = t0;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const options = t1;
|
|
const {
|
|
onPersist,
|
|
onPersistError,
|
|
heldKeys
|
|
} = options;
|
|
const graph = useAtomValue(graphAtom);
|
|
const [pan, setPan] = useAtom2(panAtom);
|
|
const startMutation = useSetAtom2(startMutationAtom);
|
|
const completeMutation = useSetAtom2(completeMutationAtom);
|
|
const setStartDrag = useSetAtom2(startNodeDragAtom);
|
|
const setEndDrag = useSetAtom2(endNodeDragAtom);
|
|
const getPreDragAttributes = useAtomValue(preDragNodeAttributesAtom);
|
|
const currentZoom = useAtomValue(zoomAtom);
|
|
const getSelectedNodeIds2 = useAtomValue(selectedNodeIdsAtom);
|
|
const currentGraphId = useAtomValue(currentGraphIdAtom);
|
|
const edgeCreation = useAtomValue(edgeCreationAtom);
|
|
const setGraph = useSetAtom2(graphAtom);
|
|
useSetAtom2(nodePositionUpdateCounterAtom);
|
|
const pushHistory = useSetAtom2(pushHistoryAtom);
|
|
const setDropTarget = useSetAtom2(dropTargetNodeIdAtom);
|
|
const nestNodesOnDrop = useSetAtom2(nestNodesOnDropAtom);
|
|
const inputSource = useAtomValue(primaryInputSourceAtom);
|
|
let t2;
|
|
if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = atom26(null, _temp2);
|
|
$[2] = t2;
|
|
} else {
|
|
t2 = $[2];
|
|
}
|
|
const batchUpdatePositions = useSetAtom2(t2);
|
|
let t3;
|
|
if ($[3] !== batchUpdatePositions) {
|
|
t3 = (updates_0) => {
|
|
batchUpdatePositions(updates_0);
|
|
};
|
|
$[3] = batchUpdatePositions;
|
|
$[4] = t3;
|
|
} else {
|
|
t3 = $[4];
|
|
}
|
|
const updateNodePositions = t3;
|
|
const gestureInstanceRef = useRef(0);
|
|
let t4;
|
|
if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
$[5] = t4;
|
|
} else {
|
|
t4 = $[5];
|
|
}
|
|
const panStartRef = useRef(t4);
|
|
const isSpaceHeld = Boolean(heldKeys?.byCode.Space || heldKeys?.byKey[" "] || heldKeys?.byKey.Spacebar);
|
|
let t5;
|
|
if ($[6] !== isSpaceHeld || $[7] !== pan) {
|
|
t5 = (state) => {
|
|
if (isDragPrevented(state.event.target)) {
|
|
return;
|
|
}
|
|
gestureInstanceRef.current = gestureInstanceRef.current + 1;
|
|
if (isSpaceHeld) {
|
|
panStartRef.current = pan;
|
|
}
|
|
};
|
|
$[6] = isSpaceHeld;
|
|
$[7] = pan;
|
|
$[8] = t5;
|
|
} else {
|
|
t5 = $[8];
|
|
}
|
|
let t6;
|
|
if ($[9] !== currentZoom || $[10] !== edgeCreation || $[11] !== getSelectedNodeIds2 || $[12] !== graph || $[13] !== id || $[14] !== isSpaceHeld || $[15] !== pushHistory || $[16] !== setDropTarget || $[17] !== setPan || $[18] !== setStartDrag || $[19] !== startMutation || $[20] !== updateNodePositions) {
|
|
t6 = (state_0) => {
|
|
if (isDragPrevented(state_0.event.target)) {
|
|
return;
|
|
}
|
|
if (edgeCreation.isCreating) {
|
|
return;
|
|
}
|
|
state_0.event.stopPropagation();
|
|
if (isSpaceHeld) {
|
|
if (!state_0.tap && state_0.active) {
|
|
const [mx, my] = state_0.movement;
|
|
setPan({
|
|
x: panStartRef.current.x + mx,
|
|
y: panStartRef.current.y + my
|
|
});
|
|
}
|
|
return state_0.memo;
|
|
}
|
|
const {
|
|
movement: t72,
|
|
first,
|
|
active,
|
|
down,
|
|
pinching,
|
|
cancel,
|
|
tap
|
|
} = state_0;
|
|
const [mx_0, my_0] = t72;
|
|
let currentMemo = state_0.memo;
|
|
if (tap || !active) {
|
|
return currentMemo;
|
|
}
|
|
const currentGestureInstance = gestureInstanceRef.current;
|
|
if (first) {
|
|
const selectionSize = getSelectedNodeIds2.size;
|
|
const label = selectionSize > 1 ? `Move ${selectionSize} nodes` : "Move node";
|
|
pushHistory(label);
|
|
setStartDrag({
|
|
nodeId: id
|
|
});
|
|
const initialPositions = buildDragPositions(graph, getSelectedNodeIds2);
|
|
initialPositions.forEach(() => startMutation());
|
|
currentMemo = {
|
|
initialPositions,
|
|
gestureInstance: currentGestureInstance
|
|
};
|
|
}
|
|
if (!currentMemo || currentMemo.gestureInstance !== currentGestureInstance || !currentMemo.initialPositions) {
|
|
if (cancel && !pinching && !down && !tap && !active) {
|
|
cancel();
|
|
}
|
|
return currentMemo;
|
|
}
|
|
const updates_1 = computeDragUpdates(currentMemo.initialPositions, mx_0, my_0, currentZoom, graph);
|
|
if (updates_1.length > 0) {
|
|
updateNodePositions(updates_1);
|
|
}
|
|
if (state_0.event && "clientX" in state_0.event) {
|
|
const {
|
|
nodeId: hoveredId
|
|
} = hitTestNode(state_0.event.clientX, state_0.event.clientY);
|
|
const validTarget = hoveredId && !currentMemo.initialPositions.has(hoveredId) ? hoveredId : null;
|
|
setDropTarget(validTarget);
|
|
}
|
|
return currentMemo;
|
|
};
|
|
$[9] = currentZoom;
|
|
$[10] = edgeCreation;
|
|
$[11] = getSelectedNodeIds2;
|
|
$[12] = graph;
|
|
$[13] = id;
|
|
$[14] = isSpaceHeld;
|
|
$[15] = pushHistory;
|
|
$[16] = setDropTarget;
|
|
$[17] = setPan;
|
|
$[18] = setStartDrag;
|
|
$[19] = startMutation;
|
|
$[20] = updateNodePositions;
|
|
$[21] = t6;
|
|
} else {
|
|
t6 = $[21];
|
|
}
|
|
let t7;
|
|
if ($[22] !== completeMutation || $[23] !== currentGraphId || $[24] !== currentZoom || $[25] !== edgeCreation || $[26] !== getPreDragAttributes || $[27] !== getSelectedNodeIds2 || $[28] !== graph || $[29] !== id || $[30] !== isSpaceHeld || $[31] !== nestNodesOnDrop || $[32] !== onPersist || $[33] !== onPersistError || $[34] !== setDropTarget || $[35] !== setEndDrag || $[36] !== setGraph || $[37] !== startMutation || $[38] !== updateNodePositions) {
|
|
t7 = (state_1) => {
|
|
if (isDragPrevented(state_1.event.target)) {
|
|
return;
|
|
}
|
|
if (edgeCreation.isCreating) {
|
|
setEndDrag({
|
|
nodeId: id
|
|
});
|
|
return;
|
|
}
|
|
if (isSpaceHeld) {
|
|
return;
|
|
}
|
|
state_1.event.stopPropagation();
|
|
const memo = state_1.memo;
|
|
setDropTarget(null);
|
|
if (state_1.event && "clientX" in state_1.event && memo?.initialPositions) {
|
|
const {
|
|
nodeId: hoveredId_0
|
|
} = hitTestNode(state_1.event.clientX, state_1.event.clientY);
|
|
if (hoveredId_0 && !memo.initialPositions.has(hoveredId_0)) {
|
|
const draggedNodeIds = Array.from(memo.initialPositions.keys()).filter((nid) => getSelectedNodeIds2.has(nid));
|
|
if (draggedNodeIds.length > 0) {
|
|
nestNodesOnDrop({
|
|
nodeIds: draggedNodeIds,
|
|
targetId: hoveredId_0
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (!currentGraphId) {
|
|
debug13("Cannot update node position: currentGraphId is not set");
|
|
setEndDrag({
|
|
nodeId: id
|
|
});
|
|
return;
|
|
}
|
|
const nodesToUpdate = memo?.initialPositions ? Array.from(memo.initialPositions.keys()) : [id];
|
|
nodesToUpdate.forEach((draggedNodeId) => {
|
|
if (!graph.hasNode(draggedNodeId)) {
|
|
completeMutation(false);
|
|
return;
|
|
}
|
|
const finalAttrs = graph.getNodeAttributes(draggedNodeId);
|
|
const initialPos = memo?.initialPositions.get(draggedNodeId);
|
|
if (!initialPos) {
|
|
completeMutation(false);
|
|
return;
|
|
}
|
|
const [mx_1, my_1] = state_1.movement;
|
|
const deltaX = mx_1 / currentZoom;
|
|
const deltaY = my_1 / currentZoom;
|
|
const finalPosition = {
|
|
x: initialPos.x + deltaX,
|
|
y: initialPos.y + deltaY
|
|
};
|
|
updateNodePositions([{
|
|
id: draggedNodeId,
|
|
pos: finalPosition
|
|
}]);
|
|
if (!onPersist) {
|
|
completeMutation(true);
|
|
setEndDrag({
|
|
nodeId: draggedNodeId
|
|
});
|
|
return;
|
|
}
|
|
const existingDbUiProps = typeof finalAttrs.dbData.ui_properties === "object" && finalAttrs.dbData.ui_properties !== null && !Array.isArray(finalAttrs.dbData.ui_properties) ? finalAttrs.dbData.ui_properties : {};
|
|
const newUiProperties = {
|
|
...existingDbUiProps,
|
|
x: finalPosition.x,
|
|
y: finalPosition.y,
|
|
zIndex: finalAttrs.zIndex
|
|
};
|
|
const pendingState = getPendingState(draggedNodeId);
|
|
if (pendingState.inFlight) {
|
|
pendingState.queuedPosition = finalPosition;
|
|
pendingState.queuedUiProperties = newUiProperties;
|
|
pendingState.graphId = currentGraphId;
|
|
return;
|
|
}
|
|
pendingState.inFlight = true;
|
|
pendingState.graphId = currentGraphId;
|
|
const processQueuedUpdate = async (nodeId) => {
|
|
const state_2 = getPendingState(nodeId);
|
|
if (state_2 && state_2.queuedPosition && state_2.queuedUiProperties && state_2.graphId) {
|
|
const queuedProps = state_2.queuedUiProperties;
|
|
const queuedGraphId = state_2.graphId;
|
|
state_2.queuedPosition = null;
|
|
state_2.queuedUiProperties = null;
|
|
startMutation();
|
|
;
|
|
try {
|
|
await onPersist(nodeId, queuedGraphId, queuedProps);
|
|
completeMutation(true);
|
|
} catch (t82) {
|
|
const error = t82;
|
|
completeMutation(false);
|
|
onPersistError?.(nodeId, error);
|
|
}
|
|
state_2.inFlight = false;
|
|
processQueuedUpdate(nodeId);
|
|
} else {
|
|
if (state_2) {
|
|
state_2.inFlight = false;
|
|
}
|
|
}
|
|
};
|
|
onPersist(draggedNodeId, currentGraphId, newUiProperties).then(() => {
|
|
completeMutation(true);
|
|
processQueuedUpdate(draggedNodeId);
|
|
}).catch((error_0) => {
|
|
completeMutation(false);
|
|
const state_3 = getPendingState(draggedNodeId);
|
|
if (state_3) {
|
|
state_3.inFlight = false;
|
|
}
|
|
const preDragAttrsForNode = getPreDragAttributes;
|
|
if (preDragAttrsForNode && preDragAttrsForNode.dbData.id === draggedNodeId && graph.hasNode(draggedNodeId)) {
|
|
graph.replaceNodeAttributes(draggedNodeId, preDragAttrsForNode);
|
|
setGraph(graph);
|
|
}
|
|
onPersistError?.(draggedNodeId, error_0);
|
|
processQueuedUpdate(draggedNodeId);
|
|
}).finally(() => {
|
|
setEndDrag({
|
|
nodeId: draggedNodeId
|
|
});
|
|
});
|
|
});
|
|
};
|
|
$[22] = completeMutation;
|
|
$[23] = currentGraphId;
|
|
$[24] = currentZoom;
|
|
$[25] = edgeCreation;
|
|
$[26] = getPreDragAttributes;
|
|
$[27] = getSelectedNodeIds2;
|
|
$[28] = graph;
|
|
$[29] = id;
|
|
$[30] = isSpaceHeld;
|
|
$[31] = nestNodesOnDrop;
|
|
$[32] = onPersist;
|
|
$[33] = onPersistError;
|
|
$[34] = setDropTarget;
|
|
$[35] = setEndDrag;
|
|
$[36] = setGraph;
|
|
$[37] = startMutation;
|
|
$[38] = updateNodePositions;
|
|
$[39] = t7;
|
|
} else {
|
|
t7 = $[39];
|
|
}
|
|
let t8;
|
|
if ($[40] !== t5 || $[41] !== t6 || $[42] !== t7) {
|
|
t8 = {
|
|
onPointerDown: t5,
|
|
onDrag: t6,
|
|
onDragEnd: t7
|
|
};
|
|
$[40] = t5;
|
|
$[41] = t6;
|
|
$[42] = t7;
|
|
$[43] = t8;
|
|
} else {
|
|
t8 = $[43];
|
|
}
|
|
let t9;
|
|
if ($[44] !== inputSource) {
|
|
t9 = getNodeGestureConfig(inputSource);
|
|
$[44] = inputSource;
|
|
$[45] = t9;
|
|
} else {
|
|
t9 = $[45];
|
|
}
|
|
const bind = useGesture(t8, t9);
|
|
let t10;
|
|
if ($[46] !== bind || $[47] !== updateNodePositions) {
|
|
t10 = {
|
|
bind,
|
|
updateNodePositions
|
|
};
|
|
$[46] = bind;
|
|
$[47] = updateNodePositions;
|
|
$[48] = t10;
|
|
} else {
|
|
t10 = $[48];
|
|
}
|
|
return t10;
|
|
}
|
|
function _temp2(get, set, updates) {
|
|
const graph_0 = get(graphAtom);
|
|
updates.forEach((u) => {
|
|
if (graph_0.hasNode(u.id)) {
|
|
graph_0.setNodeAttribute(u.id, "x", u.pos.x);
|
|
graph_0.setNodeAttribute(u.id, "y", u.pos.y);
|
|
}
|
|
});
|
|
set(nodePositionUpdateCounterAtom, _temp);
|
|
}
|
|
function _temp(c) {
|
|
return c + 1;
|
|
}
|
|
|
|
// src/hooks/useNodeResize.ts
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_viewport_store();
|
|
init_sync_store();
|
|
init_debug();
|
|
import { c as _c4 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue2, useSetAtom as useSetAtom3 } from "jotai";
|
|
import { useRef as useRef2, useState, useEffect } from "react";
|
|
import { flushSync } from "react-dom";
|
|
var debug14 = createDebug("resize");
|
|
function useNodeResize(t0) {
|
|
const $ = _c4(38);
|
|
const {
|
|
id,
|
|
nodeData,
|
|
updateNodePositions,
|
|
options: t1
|
|
} = t0;
|
|
let t2;
|
|
if ($[0] !== t1) {
|
|
t2 = t1 === void 0 ? {} : t1;
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
} else {
|
|
t2 = $[1];
|
|
}
|
|
const options = t2;
|
|
const {
|
|
onPersist,
|
|
onPersistError,
|
|
minWidth: t3,
|
|
minHeight: t4
|
|
} = options;
|
|
const minWidth = t3 === void 0 ? 200 : t3;
|
|
const minHeight = t4 === void 0 ? 150 : t4;
|
|
const [localWidth, setLocalWidth] = useState(nodeData.width || 500);
|
|
const [localHeight, setLocalHeight] = useState(nodeData.height || 500);
|
|
const [isResizing, setIsResizing] = useState(false);
|
|
const resizeStartRef = useRef2(null);
|
|
const graph = useAtomValue2(graphAtom);
|
|
const currentZoom = useAtomValue2(zoomAtom);
|
|
const currentGraphId = useAtomValue2(currentGraphIdAtom);
|
|
const startMutation = useSetAtom3(startMutationAtom);
|
|
const completeMutation = useSetAtom3(completeMutationAtom);
|
|
const setGraphUpdateVersion = useSetAtom3(graphUpdateVersionAtom);
|
|
const setNodePositionUpdateCounter = useSetAtom3(nodePositionUpdateCounterAtom);
|
|
let t5;
|
|
let t6;
|
|
if ($[2] !== isResizing || $[3] !== nodeData.height || $[4] !== nodeData.width) {
|
|
t5 = () => {
|
|
if (!isResizing) {
|
|
setLocalWidth(nodeData.width || 500);
|
|
setLocalHeight(nodeData.height || 500);
|
|
}
|
|
};
|
|
t6 = [nodeData.width, nodeData.height, isResizing];
|
|
$[2] = isResizing;
|
|
$[3] = nodeData.height;
|
|
$[4] = nodeData.width;
|
|
$[5] = t5;
|
|
$[6] = t6;
|
|
} else {
|
|
t5 = $[5];
|
|
t6 = $[6];
|
|
}
|
|
useEffect(t5, t6);
|
|
let t7;
|
|
if ($[7] !== graph || $[8] !== id || $[9] !== localHeight || $[10] !== localWidth) {
|
|
t7 = (direction) => (e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
setIsResizing(true);
|
|
const nodeAttrs = graph.hasNode(id) ? graph.getNodeAttributes(id) : {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
resizeStartRef.current = {
|
|
width: localWidth,
|
|
height: localHeight,
|
|
startX: e.clientX,
|
|
startY: e.clientY,
|
|
startNodeX: nodeAttrs.x,
|
|
startNodeY: nodeAttrs.y,
|
|
direction
|
|
};
|
|
e.target.setPointerCapture(e.pointerId);
|
|
};
|
|
$[7] = graph;
|
|
$[8] = id;
|
|
$[9] = localHeight;
|
|
$[10] = localWidth;
|
|
$[11] = t7;
|
|
} else {
|
|
t7 = $[11];
|
|
}
|
|
const createResizeStart = t7;
|
|
let t8;
|
|
if ($[12] !== currentZoom || $[13] !== graph || $[14] !== id || $[15] !== minHeight || $[16] !== minWidth || $[17] !== setGraphUpdateVersion || $[18] !== setNodePositionUpdateCounter || $[19] !== updateNodePositions) {
|
|
t8 = (e_0) => {
|
|
if (!resizeStartRef.current) {
|
|
return;
|
|
}
|
|
e_0.stopPropagation();
|
|
e_0.preventDefault();
|
|
const deltaX = (e_0.clientX - resizeStartRef.current.startX) / currentZoom;
|
|
const deltaY = (e_0.clientY - resizeStartRef.current.startY) / currentZoom;
|
|
const {
|
|
direction: direction_0,
|
|
width: startWidth,
|
|
height: startHeight,
|
|
startNodeX,
|
|
startNodeY
|
|
} = resizeStartRef.current;
|
|
let newWidth = startWidth;
|
|
let newHeight = startHeight;
|
|
let newX = startNodeX;
|
|
let newY = startNodeY;
|
|
if (direction_0.includes("e")) {
|
|
newWidth = Math.max(minWidth, startWidth + deltaX);
|
|
}
|
|
if (direction_0.includes("w")) {
|
|
newWidth = Math.max(minWidth, startWidth - deltaX);
|
|
newX = startNodeX + (startWidth - newWidth);
|
|
}
|
|
if (direction_0.includes("s")) {
|
|
newHeight = Math.max(minHeight, startHeight + deltaY);
|
|
}
|
|
if (direction_0.includes("n")) {
|
|
newHeight = Math.max(minHeight, startHeight - deltaY);
|
|
newY = startNodeY + (startHeight - newHeight);
|
|
}
|
|
if (graph.hasNode(id)) {
|
|
graph.setNodeAttribute(id, "width", newWidth);
|
|
graph.setNodeAttribute(id, "height", newHeight);
|
|
graph.setNodeAttribute(id, "x", newX);
|
|
graph.setNodeAttribute(id, "y", newY);
|
|
}
|
|
flushSync(() => {
|
|
setLocalWidth(newWidth);
|
|
setLocalHeight(newHeight);
|
|
setGraphUpdateVersion(_temp3);
|
|
});
|
|
if (direction_0.includes("n") || direction_0.includes("w")) {
|
|
updateNodePositions([{
|
|
id,
|
|
pos: {
|
|
x: newX,
|
|
y: newY
|
|
}
|
|
}]);
|
|
} else {
|
|
setNodePositionUpdateCounter(_temp22);
|
|
}
|
|
};
|
|
$[12] = currentZoom;
|
|
$[13] = graph;
|
|
$[14] = id;
|
|
$[15] = minHeight;
|
|
$[16] = minWidth;
|
|
$[17] = setGraphUpdateVersion;
|
|
$[18] = setNodePositionUpdateCounter;
|
|
$[19] = updateNodePositions;
|
|
$[20] = t8;
|
|
} else {
|
|
t8 = $[20];
|
|
}
|
|
const handleResizeMove = t8;
|
|
let t9;
|
|
if ($[21] !== completeMutation || $[22] !== currentGraphId || $[23] !== graph || $[24] !== id || $[25] !== localHeight || $[26] !== localWidth || $[27] !== onPersist || $[28] !== onPersistError || $[29] !== startMutation) {
|
|
t9 = (e_1) => {
|
|
if (!resizeStartRef.current) {
|
|
return;
|
|
}
|
|
e_1.stopPropagation();
|
|
e_1.target.releasePointerCapture(e_1.pointerId);
|
|
setIsResizing(false);
|
|
if (!currentGraphId || !resizeStartRef.current) {
|
|
resizeStartRef.current = null;
|
|
return;
|
|
}
|
|
const finalAttrs = graph.hasNode(id) ? graph.getNodeAttributes(id) : null;
|
|
if (!finalAttrs) {
|
|
resizeStartRef.current = null;
|
|
return;
|
|
}
|
|
const finalWidth = finalAttrs.width || localWidth;
|
|
const finalHeight = finalAttrs.height || localHeight;
|
|
const finalX = finalAttrs.x;
|
|
const finalY = finalAttrs.y;
|
|
setLocalWidth(finalWidth);
|
|
setLocalHeight(finalHeight);
|
|
if (!onPersist) {
|
|
resizeStartRef.current = null;
|
|
return;
|
|
}
|
|
const existingDbUiProps = typeof finalAttrs.dbData.ui_properties === "object" && finalAttrs.dbData.ui_properties !== null && !Array.isArray(finalAttrs.dbData.ui_properties) ? finalAttrs.dbData.ui_properties : {};
|
|
const newUiProperties = {
|
|
...existingDbUiProps,
|
|
width: finalWidth,
|
|
height: finalHeight,
|
|
x: finalX,
|
|
y: finalY
|
|
};
|
|
startMutation();
|
|
onPersist(id, currentGraphId, newUiProperties).then(() => {
|
|
completeMutation(true);
|
|
}).catch((error) => {
|
|
completeMutation(false);
|
|
if (resizeStartRef.current) {
|
|
setLocalWidth(resizeStartRef.current.width);
|
|
setLocalHeight(resizeStartRef.current.height);
|
|
}
|
|
onPersistError?.(id, error);
|
|
}).finally(() => {
|
|
resizeStartRef.current = null;
|
|
});
|
|
};
|
|
$[21] = completeMutation;
|
|
$[22] = currentGraphId;
|
|
$[23] = graph;
|
|
$[24] = id;
|
|
$[25] = localHeight;
|
|
$[26] = localWidth;
|
|
$[27] = onPersist;
|
|
$[28] = onPersistError;
|
|
$[29] = startMutation;
|
|
$[30] = t9;
|
|
} else {
|
|
t9 = $[30];
|
|
}
|
|
const handleResizeEnd = t9;
|
|
let t10;
|
|
if ($[31] !== createResizeStart || $[32] !== handleResizeEnd || $[33] !== handleResizeMove || $[34] !== isResizing || $[35] !== localHeight || $[36] !== localWidth) {
|
|
t10 = {
|
|
localWidth,
|
|
localHeight,
|
|
isResizing,
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd
|
|
};
|
|
$[31] = createResizeStart;
|
|
$[32] = handleResizeEnd;
|
|
$[33] = handleResizeMove;
|
|
$[34] = isResizing;
|
|
$[35] = localHeight;
|
|
$[36] = localWidth;
|
|
$[37] = t10;
|
|
} else {
|
|
t10 = $[37];
|
|
}
|
|
return t10;
|
|
}
|
|
function _temp22(c) {
|
|
return c + 1;
|
|
}
|
|
function _temp3(v) {
|
|
return v + 1;
|
|
}
|
|
|
|
// src/hooks/useCanvasHistory.ts
|
|
init_history_store();
|
|
init_toast_store();
|
|
import { c as _c5 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue3, useSetAtom as useSetAtom4, useStore } from "jotai";
|
|
function useCanvasHistory(t0) {
|
|
const $ = _c5(22);
|
|
const options = t0 === void 0 ? {} : t0;
|
|
const {
|
|
enableKeyboardShortcuts: t1
|
|
} = options;
|
|
t1 === void 0 ? false : t1;
|
|
const canUndo = useAtomValue3(canUndoAtom);
|
|
const canRedo = useAtomValue3(canRedoAtom);
|
|
const undoCount = useAtomValue3(undoCountAtom);
|
|
const redoCount = useAtomValue3(redoCountAtom);
|
|
const historyLabels = useAtomValue3(historyLabelsAtom);
|
|
const undoAction = useSetAtom4(undoAtom);
|
|
const redoAction = useSetAtom4(redoAtom);
|
|
const pushHistory = useSetAtom4(pushHistoryAtom);
|
|
const clearHistory = useSetAtom4(clearHistoryAtom);
|
|
const showToast = useSetAtom4(showToastAtom);
|
|
const store = useStore();
|
|
let t2;
|
|
if ($[0] !== showToast || $[1] !== store || $[2] !== undoAction) {
|
|
t2 = () => {
|
|
const state = store.get(historyStateAtom);
|
|
const label = state.past[state.past.length - 1]?.label;
|
|
const result = undoAction();
|
|
if (result && label) {
|
|
showToast(`Undo: ${label}`);
|
|
}
|
|
return result;
|
|
};
|
|
$[0] = showToast;
|
|
$[1] = store;
|
|
$[2] = undoAction;
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
const undo = t2;
|
|
let t3;
|
|
if ($[4] !== redoAction || $[5] !== showToast || $[6] !== store) {
|
|
t3 = () => {
|
|
const state_0 = store.get(historyStateAtom);
|
|
const label_0 = state_0.future[0]?.label;
|
|
const result_0 = redoAction();
|
|
if (result_0 && label_0) {
|
|
showToast(`Redo: ${label_0}`);
|
|
}
|
|
return result_0;
|
|
};
|
|
$[4] = redoAction;
|
|
$[5] = showToast;
|
|
$[6] = store;
|
|
$[7] = t3;
|
|
} else {
|
|
t3 = $[7];
|
|
}
|
|
const redo = t3;
|
|
let t4;
|
|
if ($[8] !== pushHistory) {
|
|
t4 = (label_1) => {
|
|
pushHistory(label_1);
|
|
};
|
|
$[8] = pushHistory;
|
|
$[9] = t4;
|
|
} else {
|
|
t4 = $[9];
|
|
}
|
|
const recordSnapshot = t4;
|
|
let t5;
|
|
if ($[10] !== clearHistory) {
|
|
t5 = () => {
|
|
clearHistory();
|
|
};
|
|
$[10] = clearHistory;
|
|
$[11] = t5;
|
|
} else {
|
|
t5 = $[11];
|
|
}
|
|
const clear = t5;
|
|
let t6;
|
|
if ($[12] !== canRedo || $[13] !== canUndo || $[14] !== clear || $[15] !== historyLabels || $[16] !== recordSnapshot || $[17] !== redo || $[18] !== redoCount || $[19] !== undo || $[20] !== undoCount) {
|
|
t6 = {
|
|
undo,
|
|
redo,
|
|
canUndo,
|
|
canRedo,
|
|
undoCount,
|
|
redoCount,
|
|
historyLabels,
|
|
recordSnapshot,
|
|
clear
|
|
};
|
|
$[12] = canRedo;
|
|
$[13] = canUndo;
|
|
$[14] = clear;
|
|
$[15] = historyLabels;
|
|
$[16] = recordSnapshot;
|
|
$[17] = redo;
|
|
$[18] = redoCount;
|
|
$[19] = undo;
|
|
$[20] = undoCount;
|
|
$[21] = t6;
|
|
} else {
|
|
t6 = $[21];
|
|
}
|
|
return t6;
|
|
}
|
|
|
|
// src/hooks/useCanvasSelection.ts
|
|
init_selection_store();
|
|
import { c as _c6 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue4 } from "jotai";
|
|
function useCanvasSelection() {
|
|
const $ = _c6(6);
|
|
const selectedNodeIds = useAtomValue4(selectedNodeIdsAtom);
|
|
const selectedEdgeId = useAtomValue4(selectedEdgeIdAtom);
|
|
const count = useAtomValue4(selectedNodesCountAtom);
|
|
const hasSelection = useAtomValue4(hasSelectionAtom);
|
|
const t0 = selectedEdgeId !== null;
|
|
let t1;
|
|
if ($[0] !== count || $[1] !== hasSelection || $[2] !== selectedEdgeId || $[3] !== selectedNodeIds || $[4] !== t0) {
|
|
t1 = {
|
|
selectedNodeIds,
|
|
selectedEdgeId,
|
|
count,
|
|
hasSelection,
|
|
hasEdgeSelection: t0
|
|
};
|
|
$[0] = count;
|
|
$[1] = hasSelection;
|
|
$[2] = selectedEdgeId;
|
|
$[3] = selectedNodeIds;
|
|
$[4] = t0;
|
|
$[5] = t1;
|
|
} else {
|
|
t1 = $[5];
|
|
}
|
|
return t1;
|
|
}
|
|
|
|
// src/hooks/useCanvasViewport.ts
|
|
init_viewport_store();
|
|
import { c as _c7 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue5 } from "jotai";
|
|
function useCanvasViewport() {
|
|
const $ = _c7(9);
|
|
const zoom = useAtomValue5(zoomAtom);
|
|
const pan = useAtomValue5(panAtom);
|
|
const viewportRect = useAtomValue5(viewportRectAtom);
|
|
const screenToWorld = useAtomValue5(screenToWorldAtom);
|
|
const worldToScreen = useAtomValue5(worldToScreenAtom);
|
|
const zoomFocusNodeId = useAtomValue5(zoomFocusNodeIdAtom);
|
|
const zoomTransitionProgress = useAtomValue5(zoomTransitionProgressAtom);
|
|
const isZoomTransitioning = useAtomValue5(isZoomTransitioningAtom);
|
|
let t0;
|
|
if ($[0] !== isZoomTransitioning || $[1] !== pan || $[2] !== screenToWorld || $[3] !== viewportRect || $[4] !== worldToScreen || $[5] !== zoom || $[6] !== zoomFocusNodeId || $[7] !== zoomTransitionProgress) {
|
|
t0 = {
|
|
zoom,
|
|
pan,
|
|
viewportRect,
|
|
screenToWorld,
|
|
worldToScreen,
|
|
zoomFocusNodeId,
|
|
zoomTransitionProgress,
|
|
isZoomTransitioning,
|
|
zoomTransitionThreshold: ZOOM_TRANSITION_THRESHOLD,
|
|
zoomExitThreshold: ZOOM_EXIT_THRESHOLD
|
|
};
|
|
$[0] = isZoomTransitioning;
|
|
$[1] = pan;
|
|
$[2] = screenToWorld;
|
|
$[3] = viewportRect;
|
|
$[4] = worldToScreen;
|
|
$[5] = zoom;
|
|
$[6] = zoomFocusNodeId;
|
|
$[7] = zoomTransitionProgress;
|
|
$[8] = t0;
|
|
} else {
|
|
t0 = $[8];
|
|
}
|
|
return t0;
|
|
}
|
|
|
|
// src/hooks/useCanvasDrag.ts
|
|
init_graph_store();
|
|
import { c as _c8 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue6 } from "jotai";
|
|
function useCanvasDrag() {
|
|
const $ = _c8(3);
|
|
const draggingNodeId = useAtomValue6(draggingNodeIdAtom);
|
|
const t0 = draggingNodeId !== null;
|
|
let t1;
|
|
if ($[0] !== draggingNodeId || $[1] !== t0) {
|
|
t1 = {
|
|
draggingNodeId,
|
|
isDragging: t0
|
|
};
|
|
$[0] = draggingNodeId;
|
|
$[1] = t0;
|
|
$[2] = t1;
|
|
} else {
|
|
t1 = $[2];
|
|
}
|
|
return t1;
|
|
}
|
|
|
|
// src/hooks/useLayout.ts
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_viewport_store();
|
|
init_selection_store();
|
|
init_layout();
|
|
init_layout();
|
|
import { c as _c9 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue7, useSetAtom as useSetAtom5 } from "jotai";
|
|
var useGetGraphBounds = () => {
|
|
const $ = _c9(6);
|
|
const graph = useAtomValue7(graphAtom);
|
|
useAtomValue7(nodePositionUpdateCounterAtom);
|
|
let nodes;
|
|
let t0;
|
|
if ($[0] !== graph) {
|
|
nodes = graph.nodes().map((node) => {
|
|
const nodeAttributes = graph.getNodeAttributes(node);
|
|
return {
|
|
x: nodeAttributes.x,
|
|
y: nodeAttributes.y,
|
|
width: nodeAttributes.width || 500,
|
|
height: nodeAttributes.height || 500
|
|
};
|
|
});
|
|
t0 = calculateBounds(nodes);
|
|
$[0] = graph;
|
|
$[1] = nodes;
|
|
$[2] = t0;
|
|
} else {
|
|
nodes = $[1];
|
|
t0 = $[2];
|
|
}
|
|
const bounds = t0;
|
|
let t1;
|
|
if ($[3] !== bounds || $[4] !== nodes) {
|
|
t1 = {
|
|
bounds,
|
|
nodes
|
|
};
|
|
$[3] = bounds;
|
|
$[4] = nodes;
|
|
$[5] = t1;
|
|
} else {
|
|
t1 = $[5];
|
|
}
|
|
return t1;
|
|
};
|
|
var useSelectionBounds = () => {
|
|
const $ = _c9(5);
|
|
const selectedNodeIds = useAtomValue7(selectedNodeIdsAtom);
|
|
const nodes = useAtomValue7(uiNodesAtom);
|
|
let t0;
|
|
if ($[0] !== nodes || $[1] !== selectedNodeIds) {
|
|
let t1;
|
|
if ($[3] !== selectedNodeIds) {
|
|
t1 = (node) => selectedNodeIds.has(node.id);
|
|
$[3] = selectedNodeIds;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
const selectedNodes = nodes.filter(t1).map(_temp4);
|
|
t0 = calculateBounds(selectedNodes);
|
|
$[0] = nodes;
|
|
$[1] = selectedNodeIds;
|
|
$[2] = t0;
|
|
} else {
|
|
t0 = $[2];
|
|
}
|
|
return t0;
|
|
};
|
|
var useFitToBounds = () => {
|
|
const $ = _c9(2);
|
|
const setFitToBounds = useSetAtom5(fitToBoundsAtom);
|
|
let t0;
|
|
if ($[0] !== setFitToBounds) {
|
|
const fitToBounds = (mode, t1) => {
|
|
const padding = t1 === void 0 ? 20 : t1;
|
|
setFitToBounds({
|
|
mode,
|
|
padding
|
|
});
|
|
};
|
|
t0 = {
|
|
fitToBounds
|
|
};
|
|
$[0] = setFitToBounds;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
return t0;
|
|
};
|
|
var useLayout = () => {
|
|
const $ = _c9(5);
|
|
const {
|
|
fitToBounds
|
|
} = useFitToBounds();
|
|
const {
|
|
bounds: graphBounds,
|
|
nodes: graphNodes
|
|
} = useGetGraphBounds();
|
|
const selectionBounds = useSelectionBounds();
|
|
let t0;
|
|
if ($[0] !== fitToBounds || $[1] !== graphBounds || $[2] !== graphNodes || $[3] !== selectionBounds) {
|
|
t0 = {
|
|
fitToBounds,
|
|
graphBounds,
|
|
graphNodes,
|
|
selectionBounds
|
|
};
|
|
$[0] = fitToBounds;
|
|
$[1] = graphBounds;
|
|
$[2] = graphNodes;
|
|
$[3] = selectionBounds;
|
|
$[4] = t0;
|
|
} else {
|
|
t0 = $[4];
|
|
}
|
|
return t0;
|
|
};
|
|
function _temp4(node_0) {
|
|
return {
|
|
x: node_0.position.x,
|
|
y: node_0.position.y,
|
|
width: node_0.width ?? 500,
|
|
height: node_0.height ?? 500
|
|
};
|
|
}
|
|
|
|
// src/hooks/useForceLayout.ts
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_layout();
|
|
init_debug();
|
|
import * as d3 from "d3-force";
|
|
import { useAtomValue as useAtomValue8, useSetAtom as useSetAtom6 } from "jotai";
|
|
import { useRef as useRef3 } from "react";
|
|
var debug15 = createDebug("force-layout");
|
|
var useForceLayout = (options = {}) => {
|
|
const {
|
|
onPositionsChanged,
|
|
maxIterations = 1e3,
|
|
chargeStrength = -6e3,
|
|
linkStrength = 0.03
|
|
} = options;
|
|
const nodes = useAtomValue8(uiNodesAtom);
|
|
const graph = useAtomValue8(graphAtom);
|
|
const updateNodePosition = useSetAtom6(updateNodePositionAtom);
|
|
const isRunningRef = useRef3(false);
|
|
const createVirtualLinks = () => {
|
|
const links = [];
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
for (let j = 1; j <= 3; j++) {
|
|
const targetIndex = (i + j) % nodes.length;
|
|
links.push({
|
|
source: nodes[i].id,
|
|
target: nodes[targetIndex].id,
|
|
strength: 0.05
|
|
// Very weak connection
|
|
});
|
|
}
|
|
}
|
|
return links;
|
|
};
|
|
const applyForceLayout = async () => {
|
|
if (isRunningRef.current) {
|
|
debug15.warn("Layout already running, ignoring request.");
|
|
return;
|
|
}
|
|
if (nodes.length === 0) {
|
|
debug15.warn("No nodes to layout.");
|
|
return;
|
|
}
|
|
isRunningRef.current = true;
|
|
const simulationNodes = nodes.map((node) => {
|
|
const width = node.width || 80;
|
|
const height = node.height || 80;
|
|
return {
|
|
id: node.id,
|
|
x: node.position?.x || 0,
|
|
y: node.position?.y || 0,
|
|
width,
|
|
height,
|
|
radius: Math.max(width, height) + 80
|
|
};
|
|
});
|
|
const simulation = d3.forceSimulation(simulationNodes).force("charge", d3.forceManyBody().strength(chargeStrength).distanceMax(900)).force("collide", d3.forceCollide().radius((d) => d.radius).strength(2).iterations(8)).force("link", d3.forceLink(createVirtualLinks()).id((d_0) => d_0.id).strength(linkStrength)).force("center", d3.forceCenter(0, 0)).stop();
|
|
debug15("Starting simulation...");
|
|
return new Promise((resolve2) => {
|
|
let iterations = 0;
|
|
function runSimulationStep() {
|
|
if (iterations >= maxIterations) {
|
|
debug15("Reached max iterations (%d), finalizing.", maxIterations);
|
|
finalizeLayout();
|
|
return;
|
|
}
|
|
simulation.tick();
|
|
iterations++;
|
|
let hasOverlaps = false;
|
|
for (let i_0 = 0; i_0 < simulationNodes.length; i_0++) {
|
|
for (let j_0 = i_0 + 1; j_0 < simulationNodes.length; j_0++) {
|
|
if (checkNodesOverlap(simulationNodes[i_0], simulationNodes[j_0])) {
|
|
hasOverlaps = true;
|
|
break;
|
|
}
|
|
}
|
|
if (hasOverlaps) break;
|
|
}
|
|
if (!hasOverlaps) {
|
|
debug15("No overlaps after %d iterations.", iterations);
|
|
finalizeLayout();
|
|
return;
|
|
}
|
|
requestAnimationFrame(runSimulationStep);
|
|
}
|
|
function finalizeLayout() {
|
|
const positionUpdates = [];
|
|
simulationNodes.forEach((simNode) => {
|
|
if (graph.hasNode(simNode.id)) {
|
|
const newPosition = {
|
|
x: Math.round(simNode.x),
|
|
y: Math.round(simNode.y)
|
|
};
|
|
updateNodePosition({
|
|
nodeId: simNode.id,
|
|
position: newPosition
|
|
});
|
|
positionUpdates.push({
|
|
nodeId: simNode.id,
|
|
position: newPosition
|
|
});
|
|
}
|
|
});
|
|
if (onPositionsChanged && positionUpdates.length > 0) {
|
|
debug15("Saving %d positions via callback...", positionUpdates.length);
|
|
Promise.resolve(onPositionsChanged(positionUpdates)).then(() => debug15("Positions saved successfully.")).catch((err) => debug15.error("Error saving positions: %O", err));
|
|
}
|
|
debug15("Layout complete.");
|
|
isRunningRef.current = false;
|
|
resolve2();
|
|
}
|
|
requestAnimationFrame(runSimulationStep);
|
|
});
|
|
};
|
|
return {
|
|
applyForceLayout,
|
|
isRunning: isRunningRef.current
|
|
};
|
|
};
|
|
|
|
// src/hooks/useCanvasSettings.ts
|
|
init_settings_store();
|
|
import { c as _c10 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue9, useSetAtom as useSetAtom7 } from "jotai";
|
|
function useCanvasSettings() {
|
|
const $ = _c10(34);
|
|
const mappings = useAtomValue9(eventMappingsAtom);
|
|
const activePresetId = useAtomValue9(activePresetIdAtom);
|
|
const activePreset = useAtomValue9(activePresetAtom);
|
|
const allPresets = useAtomValue9(allPresetsAtom);
|
|
const hasUnsavedChanges = useAtomValue9(hasUnsavedChangesAtom);
|
|
const isPanelOpen = useAtomValue9(isPanelOpenAtom);
|
|
const setEventMappingAction = useSetAtom7(setEventMappingAtom);
|
|
const applyPresetAction = useSetAtom7(applyPresetAtom);
|
|
const saveAsPresetAction = useSetAtom7(saveAsPresetAtom);
|
|
const updatePresetAction = useSetAtom7(updatePresetAtom);
|
|
const deletePresetAction = useSetAtom7(deletePresetAtom);
|
|
const resetSettingsAction = useSetAtom7(resetSettingsAtom);
|
|
const togglePanelAction = useSetAtom7(togglePanelAtom);
|
|
const setPanelOpenAction = useSetAtom7(setPanelOpenAtom);
|
|
let t0;
|
|
if ($[0] !== setEventMappingAction) {
|
|
t0 = (event, actionId) => {
|
|
setEventMappingAction({
|
|
event,
|
|
actionId
|
|
});
|
|
};
|
|
$[0] = setEventMappingAction;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
const setEventMapping = t0;
|
|
let t1;
|
|
if ($[2] !== applyPresetAction) {
|
|
t1 = (presetId) => {
|
|
applyPresetAction(presetId);
|
|
};
|
|
$[2] = applyPresetAction;
|
|
$[3] = t1;
|
|
} else {
|
|
t1 = $[3];
|
|
}
|
|
const applyPreset = t1;
|
|
let t2;
|
|
if ($[4] !== saveAsPresetAction) {
|
|
t2 = (name, description) => saveAsPresetAction({
|
|
name,
|
|
description
|
|
});
|
|
$[4] = saveAsPresetAction;
|
|
$[5] = t2;
|
|
} else {
|
|
t2 = $[5];
|
|
}
|
|
const saveAsPreset = t2;
|
|
let t3;
|
|
if ($[6] !== updatePresetAction) {
|
|
t3 = (presetId_0) => {
|
|
updatePresetAction(presetId_0);
|
|
};
|
|
$[6] = updatePresetAction;
|
|
$[7] = t3;
|
|
} else {
|
|
t3 = $[7];
|
|
}
|
|
const updatePreset = t3;
|
|
let t4;
|
|
if ($[8] !== deletePresetAction) {
|
|
t4 = (presetId_1) => {
|
|
deletePresetAction(presetId_1);
|
|
};
|
|
$[8] = deletePresetAction;
|
|
$[9] = t4;
|
|
} else {
|
|
t4 = $[9];
|
|
}
|
|
const deletePreset = t4;
|
|
let t5;
|
|
if ($[10] !== resetSettingsAction) {
|
|
t5 = () => {
|
|
resetSettingsAction();
|
|
};
|
|
$[10] = resetSettingsAction;
|
|
$[11] = t5;
|
|
} else {
|
|
t5 = $[11];
|
|
}
|
|
const resetSettings = t5;
|
|
let t6;
|
|
if ($[12] !== togglePanelAction) {
|
|
t6 = () => {
|
|
togglePanelAction();
|
|
};
|
|
$[12] = togglePanelAction;
|
|
$[13] = t6;
|
|
} else {
|
|
t6 = $[13];
|
|
}
|
|
const togglePanel = t6;
|
|
let t7;
|
|
if ($[14] !== setPanelOpenAction) {
|
|
t7 = (isOpen) => {
|
|
setPanelOpenAction(isOpen);
|
|
};
|
|
$[14] = setPanelOpenAction;
|
|
$[15] = t7;
|
|
} else {
|
|
t7 = $[15];
|
|
}
|
|
const setPanelOpen = t7;
|
|
let t8;
|
|
if ($[16] !== mappings) {
|
|
t8 = (event_0) => getActionForEvent(mappings, event_0);
|
|
$[16] = mappings;
|
|
$[17] = t8;
|
|
} else {
|
|
t8 = $[17];
|
|
}
|
|
const getActionForEventFn = t8;
|
|
let t9;
|
|
if ($[18] !== activePreset || $[19] !== activePresetId || $[20] !== allPresets || $[21] !== applyPreset || $[22] !== deletePreset || $[23] !== getActionForEventFn || $[24] !== hasUnsavedChanges || $[25] !== isPanelOpen || $[26] !== mappings || $[27] !== resetSettings || $[28] !== saveAsPreset || $[29] !== setEventMapping || $[30] !== setPanelOpen || $[31] !== togglePanel || $[32] !== updatePreset) {
|
|
t9 = {
|
|
mappings,
|
|
activePresetId,
|
|
activePreset,
|
|
allPresets,
|
|
hasUnsavedChanges,
|
|
isPanelOpen,
|
|
setEventMapping,
|
|
applyPreset,
|
|
saveAsPreset,
|
|
updatePreset,
|
|
deletePreset,
|
|
resetSettings,
|
|
togglePanel,
|
|
setPanelOpen,
|
|
getActionForEvent: getActionForEventFn
|
|
};
|
|
$[18] = activePreset;
|
|
$[19] = activePresetId;
|
|
$[20] = allPresets;
|
|
$[21] = applyPreset;
|
|
$[22] = deletePreset;
|
|
$[23] = getActionForEventFn;
|
|
$[24] = hasUnsavedChanges;
|
|
$[25] = isPanelOpen;
|
|
$[26] = mappings;
|
|
$[27] = resetSettings;
|
|
$[28] = saveAsPreset;
|
|
$[29] = setEventMapping;
|
|
$[30] = setPanelOpen;
|
|
$[31] = togglePanel;
|
|
$[32] = updatePreset;
|
|
$[33] = t9;
|
|
} else {
|
|
t9 = $[33];
|
|
}
|
|
return t9;
|
|
}
|
|
|
|
// src/hooks/useActionExecutor.ts
|
|
init_action_executor();
|
|
init_settings_store();
|
|
import { c as _c11 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue10, useStore as useStore2 } from "jotai";
|
|
function useActionExecutor(t0) {
|
|
const $ = _c11(13);
|
|
const options = t0 === void 0 ? {} : t0;
|
|
const store = useStore2();
|
|
const mappings = useAtomValue10(eventMappingsAtom);
|
|
const helpers = buildActionHelpers(store, {
|
|
onDeleteNode: options.onDeleteNode,
|
|
onOpenContextMenu: options.onOpenContextMenu,
|
|
onCreateNode: options.onCreateNode,
|
|
onApplyForceLayout: options.onApplyForceLayout
|
|
});
|
|
let t1;
|
|
if ($[0] !== helpers) {
|
|
t1 = async (actionId, context) => executeAction(actionId, context, helpers);
|
|
$[0] = helpers;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const executeActionById = t1;
|
|
let t2;
|
|
if ($[2] !== helpers || $[3] !== mappings) {
|
|
t2 = async (event, context_0) => {
|
|
const actionId_0 = getActionForEvent(mappings, event);
|
|
return executeAction(actionId_0, context_0, helpers);
|
|
};
|
|
$[2] = helpers;
|
|
$[3] = mappings;
|
|
$[4] = t2;
|
|
} else {
|
|
t2 = $[4];
|
|
}
|
|
const executeEventAction = t2;
|
|
let t3;
|
|
if ($[5] !== mappings) {
|
|
t3 = (event_0) => getActionForEvent(mappings, event_0);
|
|
$[5] = mappings;
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
const getActionForEventFn = t3;
|
|
let t4;
|
|
if ($[7] !== executeActionById || $[8] !== executeEventAction || $[9] !== getActionForEventFn || $[10] !== helpers || $[11] !== mappings) {
|
|
t4 = {
|
|
executeActionById,
|
|
executeEventAction,
|
|
getActionForEvent: getActionForEventFn,
|
|
mappings,
|
|
helpers
|
|
};
|
|
$[7] = executeActionById;
|
|
$[8] = executeEventAction;
|
|
$[9] = getActionForEventFn;
|
|
$[10] = helpers;
|
|
$[11] = mappings;
|
|
$[12] = t4;
|
|
} else {
|
|
t4 = $[12];
|
|
}
|
|
return t4;
|
|
}
|
|
|
|
// src/hooks/useGestureResolver.ts
|
|
init_gesture_rules();
|
|
init_gesture_rule_store();
|
|
init_input_store();
|
|
import { c as _c12 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue11 } from "jotai";
|
|
function useGestureResolver() {
|
|
const $ = _c12(4);
|
|
const index = useAtomValue11(gestureRuleIndexAtom);
|
|
const palmRejection = useAtomValue11(palmRejectionEnabledAtom);
|
|
const isStylusActive = useAtomValue11(isStylusActiveAtom);
|
|
let t0;
|
|
if ($[0] !== index || $[1] !== isStylusActive || $[2] !== palmRejection) {
|
|
t0 = (desc) => resolveGestureIndexed({
|
|
...desc,
|
|
isStylusActive: desc.isStylusActive ?? isStylusActive
|
|
}, index, {
|
|
palmRejection
|
|
});
|
|
$[0] = index;
|
|
$[1] = isStylusActive;
|
|
$[2] = palmRejection;
|
|
$[3] = t0;
|
|
} else {
|
|
t0 = $[3];
|
|
}
|
|
return t0;
|
|
}
|
|
|
|
// src/hooks/useCommandLine.ts
|
|
import { c as _c13 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue12, useSetAtom as useSetAtom8 } from "jotai";
|
|
|
|
// src/commands/store.ts
|
|
init_registry();
|
|
import { atom as atom28 } from "jotai";
|
|
|
|
// src/commands/store-atoms.ts
|
|
import { atom as atom27 } from "jotai";
|
|
import { atomWithStorage as atomWithStorage3 } from "jotai/utils";
|
|
var inputModeAtom2 = atom27({
|
|
type: "normal"
|
|
});
|
|
var commandLineVisibleAtom = atom27(false);
|
|
var commandLineStateAtom = atom27({
|
|
phase: "idle"
|
|
});
|
|
var commandFeedbackAtom = atom27(null);
|
|
var commandHistoryAtom = atomWithStorage3("canvas-command-history", []);
|
|
var selectedSuggestionIndexAtom = atom27(0);
|
|
var pendingInputResolverAtom2 = atom27(null);
|
|
var isCommandActiveAtom = atom27((get) => {
|
|
const state = get(commandLineStateAtom);
|
|
return state.phase === "collecting" || state.phase === "executing";
|
|
});
|
|
var currentInputAtom = atom27((get) => {
|
|
const state = get(commandLineStateAtom);
|
|
if (state.phase !== "collecting") return null;
|
|
return state.command.inputs[state.inputIndex];
|
|
});
|
|
var commandProgressAtom = atom27((get) => {
|
|
const state = get(commandLineStateAtom);
|
|
if (state.phase !== "collecting") return null;
|
|
return {
|
|
current: state.inputIndex + 1,
|
|
total: state.command.inputs.length
|
|
};
|
|
});
|
|
|
|
// src/commands/store.ts
|
|
var openCommandLineAtom = atom28(null, (get, set) => {
|
|
set(commandLineVisibleAtom, true);
|
|
set(commandLineStateAtom, {
|
|
phase: "searching",
|
|
query: "",
|
|
suggestions: commandRegistry.all()
|
|
});
|
|
set(selectedSuggestionIndexAtom, 0);
|
|
});
|
|
var closeCommandLineAtom = atom28(null, (get, set) => {
|
|
set(commandLineVisibleAtom, false);
|
|
set(commandLineStateAtom, {
|
|
phase: "idle"
|
|
});
|
|
set(inputModeAtom2, {
|
|
type: "normal"
|
|
});
|
|
set(commandFeedbackAtom, null);
|
|
set(pendingInputResolverAtom2, null);
|
|
});
|
|
var updateSearchQueryAtom = atom28(null, (get, set, query) => {
|
|
const suggestions = commandRegistry.search(query);
|
|
set(commandLineStateAtom, {
|
|
phase: "searching",
|
|
query,
|
|
suggestions
|
|
});
|
|
set(selectedSuggestionIndexAtom, 0);
|
|
});
|
|
var selectCommandAtom = atom28(null, (get, set, command) => {
|
|
const history = get(commandHistoryAtom);
|
|
const newHistory = [command.name, ...history.filter((h) => h !== command.name)].slice(0, 50);
|
|
set(commandHistoryAtom, newHistory);
|
|
if (command.inputs.length === 0) {
|
|
set(commandLineStateAtom, {
|
|
phase: "executing",
|
|
command
|
|
});
|
|
return;
|
|
}
|
|
set(commandLineStateAtom, {
|
|
phase: "collecting",
|
|
command,
|
|
inputIndex: 0,
|
|
collected: {}
|
|
});
|
|
const firstInput = command.inputs[0];
|
|
set(inputModeAtom2, inputDefToMode(firstInput));
|
|
});
|
|
var provideInputAtom2 = atom28(null, (get, set, value) => {
|
|
const state = get(commandLineStateAtom);
|
|
if (state.phase !== "collecting") return;
|
|
const {
|
|
command,
|
|
inputIndex,
|
|
collected
|
|
} = state;
|
|
const currentInput = command.inputs[inputIndex];
|
|
if (currentInput.validate) {
|
|
const result = currentInput.validate(value, collected);
|
|
if (result !== true) {
|
|
set(commandLineStateAtom, {
|
|
phase: "error",
|
|
message: typeof result === "string" ? result : `Invalid value for ${currentInput.name}`
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
const newCollected = {
|
|
...collected,
|
|
[currentInput.name]: value
|
|
};
|
|
if (inputIndex < command.inputs.length - 1) {
|
|
const nextInputIndex = inputIndex + 1;
|
|
const nextInput = command.inputs[nextInputIndex];
|
|
set(commandLineStateAtom, {
|
|
phase: "collecting",
|
|
command,
|
|
inputIndex: nextInputIndex,
|
|
collected: newCollected
|
|
});
|
|
set(inputModeAtom2, inputDefToMode(nextInput, newCollected));
|
|
if (command.feedback) {
|
|
const feedback = command.feedback(newCollected, nextInput);
|
|
if (feedback) {
|
|
const feedbackState = {
|
|
hoveredNodeId: feedback.highlightNodeId,
|
|
ghostNode: feedback.ghostNode,
|
|
crosshair: feedback.crosshair,
|
|
// Handle previewEdge conversion - toCursor variant needs cursorWorldPos
|
|
previewEdge: feedback.previewEdge && "to" in feedback.previewEdge ? {
|
|
from: feedback.previewEdge.from,
|
|
to: feedback.previewEdge.to
|
|
} : void 0
|
|
};
|
|
set(commandFeedbackAtom, feedbackState);
|
|
} else {
|
|
set(commandFeedbackAtom, null);
|
|
}
|
|
}
|
|
} else {
|
|
set(commandLineStateAtom, {
|
|
phase: "collecting",
|
|
command,
|
|
inputIndex,
|
|
collected: newCollected
|
|
});
|
|
set(inputModeAtom2, {
|
|
type: "normal"
|
|
});
|
|
}
|
|
});
|
|
var skipInputAtom = atom28(null, (get, set) => {
|
|
const state = get(commandLineStateAtom);
|
|
if (state.phase !== "collecting") return;
|
|
const {
|
|
command,
|
|
inputIndex
|
|
} = state;
|
|
const currentInput = command.inputs[inputIndex];
|
|
if (currentInput.required !== false) {
|
|
return;
|
|
}
|
|
const value = currentInput.default;
|
|
set(provideInputAtom2, value);
|
|
});
|
|
var goBackInputAtom = atom28(null, (get, set) => {
|
|
const state = get(commandLineStateAtom);
|
|
if (state.phase !== "collecting") return;
|
|
const {
|
|
command,
|
|
inputIndex,
|
|
collected
|
|
} = state;
|
|
if (inputIndex === 0) {
|
|
set(commandLineStateAtom, {
|
|
phase: "searching",
|
|
query: command.name,
|
|
suggestions: [command]
|
|
});
|
|
set(inputModeAtom2, {
|
|
type: "normal"
|
|
});
|
|
return;
|
|
}
|
|
const prevInputIndex = inputIndex - 1;
|
|
const prevInput = command.inputs[prevInputIndex];
|
|
const newCollected = {
|
|
...collected
|
|
};
|
|
delete newCollected[prevInput.name];
|
|
set(commandLineStateAtom, {
|
|
phase: "collecting",
|
|
command,
|
|
inputIndex: prevInputIndex,
|
|
collected: newCollected
|
|
});
|
|
set(inputModeAtom2, inputDefToMode(prevInput, newCollected));
|
|
});
|
|
var setCommandErrorAtom = atom28(null, (get, set, message) => {
|
|
set(commandLineStateAtom, {
|
|
phase: "error",
|
|
message
|
|
});
|
|
set(inputModeAtom2, {
|
|
type: "normal"
|
|
});
|
|
});
|
|
var clearCommandErrorAtom = atom28(null, (get, set) => {
|
|
set(commandLineStateAtom, {
|
|
phase: "idle"
|
|
});
|
|
});
|
|
function inputDefToMode(input, collected) {
|
|
switch (input.type) {
|
|
case "point":
|
|
return {
|
|
type: "pickPoint",
|
|
prompt: input.prompt,
|
|
snapToGrid: input.snapToGrid
|
|
};
|
|
case "node":
|
|
return {
|
|
type: "pickNode",
|
|
prompt: input.prompt,
|
|
filter: input.filter ? (node) => input.filter(node, collected || {}) : void 0
|
|
};
|
|
case "nodes":
|
|
return {
|
|
type: "pickNodes",
|
|
prompt: input.prompt,
|
|
filter: input.filter ? (node) => input.filter(node, collected || {}) : void 0
|
|
};
|
|
case "select":
|
|
return {
|
|
type: "select",
|
|
prompt: input.prompt,
|
|
options: input.options || []
|
|
};
|
|
case "text":
|
|
case "number":
|
|
case "color":
|
|
case "boolean":
|
|
default:
|
|
return {
|
|
type: "text",
|
|
prompt: input.prompt
|
|
};
|
|
}
|
|
}
|
|
|
|
// src/hooks/useCommandLine.ts
|
|
init_registry();
|
|
function useCommandLine() {
|
|
const $ = _c13(16);
|
|
const visible = useAtomValue12(commandLineVisibleAtom);
|
|
const state = useAtomValue12(commandLineStateAtom);
|
|
const history = useAtomValue12(commandHistoryAtom);
|
|
const currentInput = useAtomValue12(currentInputAtom);
|
|
const progress = useAtomValue12(commandProgressAtom);
|
|
const open = useSetAtom8(openCommandLineAtom);
|
|
const close = useSetAtom8(closeCommandLineAtom);
|
|
const updateQuery = useSetAtom8(updateSearchQueryAtom);
|
|
const selectCommand = useSetAtom8(selectCommandAtom);
|
|
const t0 = state.phase === "searching";
|
|
const t1 = state.phase === "collecting";
|
|
const t2 = state.phase === "executing";
|
|
const t3 = state.phase === "error";
|
|
const t4 = state.phase === "error" ? state.message : null;
|
|
let t5;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t5 = commandRegistry.all();
|
|
$[0] = t5;
|
|
} else {
|
|
t5 = $[0];
|
|
}
|
|
let t6;
|
|
if ($[1] !== close || $[2] !== currentInput || $[3] !== history || $[4] !== open || $[5] !== progress || $[6] !== selectCommand || $[7] !== state || $[8] !== t0 || $[9] !== t1 || $[10] !== t2 || $[11] !== t3 || $[12] !== t4 || $[13] !== updateQuery || $[14] !== visible) {
|
|
t6 = {
|
|
visible,
|
|
state,
|
|
history,
|
|
currentInput,
|
|
progress,
|
|
open,
|
|
close,
|
|
updateQuery,
|
|
selectCommand,
|
|
isSearching: t0,
|
|
isCollecting: t1,
|
|
isExecuting: t2,
|
|
hasError: t3,
|
|
errorMessage: t4,
|
|
allCommands: t5,
|
|
searchCommands: _temp5
|
|
};
|
|
$[1] = close;
|
|
$[2] = currentInput;
|
|
$[3] = history;
|
|
$[4] = open;
|
|
$[5] = progress;
|
|
$[6] = selectCommand;
|
|
$[7] = state;
|
|
$[8] = t0;
|
|
$[9] = t1;
|
|
$[10] = t2;
|
|
$[11] = t3;
|
|
$[12] = t4;
|
|
$[13] = updateQuery;
|
|
$[14] = visible;
|
|
$[15] = t6;
|
|
} else {
|
|
t6 = $[15];
|
|
}
|
|
return t6;
|
|
}
|
|
function _temp5(query) {
|
|
return commandRegistry.search(query);
|
|
}
|
|
|
|
// src/hooks/useVirtualization.ts
|
|
init_virtualization_store();
|
|
init_settings_store();
|
|
import { c as _c14 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue13, useSetAtom as useSetAtom9 } from "jotai";
|
|
function useVirtualization() {
|
|
const $ = _c14(8);
|
|
const metrics = useAtomValue13(virtualizationMetricsAtom);
|
|
const setEnabled = useSetAtom9(setVirtualizationEnabledAtom);
|
|
const toggle = useSetAtom9(toggleVirtualizationAtom);
|
|
let t0;
|
|
let t1;
|
|
if ($[0] !== setEnabled) {
|
|
t0 = () => setEnabled(true);
|
|
t1 = () => setEnabled(false);
|
|
$[0] = setEnabled;
|
|
$[1] = t0;
|
|
$[2] = t1;
|
|
} else {
|
|
t0 = $[1];
|
|
t1 = $[2];
|
|
}
|
|
let t2;
|
|
if ($[3] !== metrics || $[4] !== t0 || $[5] !== t1 || $[6] !== toggle) {
|
|
t2 = {
|
|
...metrics,
|
|
enable: t0,
|
|
disable: t1,
|
|
toggle
|
|
};
|
|
$[3] = metrics;
|
|
$[4] = t0;
|
|
$[5] = t1;
|
|
$[6] = toggle;
|
|
$[7] = t2;
|
|
} else {
|
|
t2 = $[7];
|
|
}
|
|
return t2;
|
|
}
|
|
|
|
// src/hooks/useTapGesture.ts
|
|
import { c as _c15 } from "react/compiler-runtime";
|
|
import { useRef as useRef4 } from "react";
|
|
function useTapGesture(t0) {
|
|
const $ = _c15(12);
|
|
let t1;
|
|
if ($[0] !== t0) {
|
|
t1 = t0 === void 0 ? {} : t0;
|
|
$[0] = t0;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const options = t1;
|
|
const {
|
|
onSingleTap,
|
|
onDoubleTap,
|
|
onTripleTap,
|
|
tapDelay: t2,
|
|
tapDistance: t3
|
|
} = options;
|
|
const tapDelay = t2 === void 0 ? 300 : t2;
|
|
const tapDistance = t3 === void 0 ? 25 : t3;
|
|
let t4;
|
|
if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = {
|
|
count: 0,
|
|
timer: null,
|
|
lastX: 0,
|
|
lastY: 0,
|
|
lastEvent: null
|
|
};
|
|
$[2] = t4;
|
|
} else {
|
|
t4 = $[2];
|
|
}
|
|
const stateRef = useRef4(t4);
|
|
let t5;
|
|
if ($[3] !== onDoubleTap || $[4] !== onSingleTap || $[5] !== onTripleTap || $[6] !== tapDelay || $[7] !== tapDistance) {
|
|
t5 = (event) => {
|
|
const state = stateRef.current;
|
|
const dx = Math.abs(event.clientX - state.lastX);
|
|
const dy = Math.abs(event.clientY - state.lastY);
|
|
const isSameSpot = state.count === 0 || dx < tapDistance && dy < tapDistance;
|
|
if (!isSameSpot) {
|
|
if (state.timer) {
|
|
clearTimeout(state.timer);
|
|
}
|
|
state.count = 0;
|
|
}
|
|
state.count = state.count + 1;
|
|
state.lastX = event.clientX;
|
|
state.lastY = event.clientY;
|
|
state.lastEvent = event;
|
|
if (state.timer) {
|
|
clearTimeout(state.timer);
|
|
}
|
|
if (state.count >= 3) {
|
|
onTripleTap?.(event);
|
|
state.count = 0;
|
|
state.timer = null;
|
|
return;
|
|
}
|
|
if (state.count === 2) {
|
|
onDoubleTap?.(event);
|
|
state.timer = setTimeout(() => {
|
|
state.count = 0;
|
|
state.timer = null;
|
|
}, tapDelay);
|
|
return;
|
|
}
|
|
state.timer = setTimeout(() => {
|
|
if (state.count === 1 && state.lastEvent) {
|
|
onSingleTap?.(state.lastEvent);
|
|
}
|
|
state.count = 0;
|
|
state.timer = null;
|
|
}, tapDelay);
|
|
};
|
|
$[3] = onDoubleTap;
|
|
$[4] = onSingleTap;
|
|
$[5] = onTripleTap;
|
|
$[6] = tapDelay;
|
|
$[7] = tapDistance;
|
|
$[8] = t5;
|
|
} else {
|
|
t5 = $[8];
|
|
}
|
|
const handleTap = t5;
|
|
let t6;
|
|
if ($[9] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t6 = () => {
|
|
const state_0 = stateRef.current;
|
|
if (state_0.timer) {
|
|
clearTimeout(state_0.timer);
|
|
state_0.timer = null;
|
|
}
|
|
state_0.count = 0;
|
|
};
|
|
$[9] = t6;
|
|
} else {
|
|
t6 = $[9];
|
|
}
|
|
const cleanup = t6;
|
|
let t7;
|
|
if ($[10] !== handleTap) {
|
|
t7 = {
|
|
handleTap,
|
|
cleanup
|
|
};
|
|
$[10] = handleTap;
|
|
$[11] = t7;
|
|
} else {
|
|
t7 = $[11];
|
|
}
|
|
return t7;
|
|
}
|
|
|
|
// src/hooks/useArrowKeyNavigation.ts
|
|
init_selection_store();
|
|
import { c as _c16 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue14 } from "jotai";
|
|
function useArrowKeyNavigation() {
|
|
const $ = _c16(2);
|
|
const focusedNodeId = useAtomValue14(focusedNodeIdAtom);
|
|
let t0;
|
|
if ($[0] !== focusedNodeId) {
|
|
t0 = {
|
|
focusedNodeId
|
|
};
|
|
$[0] = focusedNodeId;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
return t0;
|
|
}
|
|
|
|
// src/hooks/useCanvasGraph.ts
|
|
init_graph_store();
|
|
init_graph_derived();
|
|
import { c as _c17 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue15 } from "jotai";
|
|
function useCanvasGraph() {
|
|
const $ = _c17(9);
|
|
const graph = useAtomValue15(graphAtom);
|
|
const nodeKeys = useAtomValue15(nodeKeysAtom);
|
|
const edgeKeys = useAtomValue15(edgeKeysAtom);
|
|
let t0;
|
|
if ($[0] !== graph) {
|
|
t0 = (id) => graph.hasNode(id) ? graph.getNodeAttributes(id) : void 0;
|
|
$[0] = graph;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
const getNode = t0;
|
|
let t1;
|
|
if ($[2] !== graph) {
|
|
t1 = (id_0) => graph.hasEdge(id_0) ? graph.getEdgeAttributes(id_0) : void 0;
|
|
$[2] = graph;
|
|
$[3] = t1;
|
|
} else {
|
|
t1 = $[3];
|
|
}
|
|
const getEdge = t1;
|
|
let t2;
|
|
if ($[4] !== edgeKeys || $[5] !== getEdge || $[6] !== getNode || $[7] !== nodeKeys) {
|
|
t2 = {
|
|
nodeCount: nodeKeys.length,
|
|
edgeCount: edgeKeys.length,
|
|
nodeKeys,
|
|
edgeKeys,
|
|
getNode,
|
|
getEdge
|
|
};
|
|
$[4] = edgeKeys;
|
|
$[5] = getEdge;
|
|
$[6] = getNode;
|
|
$[7] = nodeKeys;
|
|
$[8] = t2;
|
|
} else {
|
|
t2 = $[8];
|
|
}
|
|
return t2;
|
|
}
|
|
|
|
// src/hooks/useZoomTransition.ts
|
|
init_viewport_store();
|
|
import { c as _c18 } from "react/compiler-runtime";
|
|
import { useEffect as useEffect2, useRef as useRef5 } from "react";
|
|
import { useAtomValue as useAtomValue16, useSetAtom as useSetAtom10 } from "jotai";
|
|
function easeInOutCubic(t) {
|
|
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
}
|
|
function useZoomTransition() {
|
|
const $ = _c18(15);
|
|
const target = useAtomValue16(zoomAnimationTargetAtom);
|
|
const setZoom = useSetAtom10(zoomAtom);
|
|
const setPan = useSetAtom10(panAtom);
|
|
const setTarget = useSetAtom10(zoomAnimationTargetAtom);
|
|
const setProgress = useSetAtom10(zoomTransitionProgressAtom);
|
|
const setFocusNode = useSetAtom10(zoomFocusNodeIdAtom);
|
|
const progress = useAtomValue16(zoomTransitionProgressAtom);
|
|
const rafRef = useRef5(null);
|
|
let t0;
|
|
if ($[0] !== setFocusNode || $[1] !== setProgress || $[2] !== setTarget) {
|
|
t0 = () => {
|
|
if (rafRef.current !== null) {
|
|
cancelAnimationFrame(rafRef.current);
|
|
rafRef.current = null;
|
|
}
|
|
setTarget(null);
|
|
setProgress(0);
|
|
setFocusNode(null);
|
|
};
|
|
$[0] = setFocusNode;
|
|
$[1] = setProgress;
|
|
$[2] = setTarget;
|
|
$[3] = t0;
|
|
} else {
|
|
t0 = $[3];
|
|
}
|
|
const cancel = t0;
|
|
let t1;
|
|
let t2;
|
|
if ($[4] !== setPan || $[5] !== setProgress || $[6] !== setTarget || $[7] !== setZoom || $[8] !== target) {
|
|
t1 = () => {
|
|
if (!target) {
|
|
return;
|
|
}
|
|
const animate = () => {
|
|
const elapsed = performance.now() - target.startTime;
|
|
const rawT = Math.min(1, elapsed / target.duration);
|
|
const t = easeInOutCubic(rawT);
|
|
const currentZoom = target.startZoom + (target.targetZoom - target.startZoom) * t;
|
|
const currentPanX = target.startPan.x + (target.targetPan.x - target.startPan.x) * t;
|
|
const currentPanY = target.startPan.y + (target.targetPan.y - target.startPan.y) * t;
|
|
setZoom(currentZoom);
|
|
setPan({
|
|
x: currentPanX,
|
|
y: currentPanY
|
|
});
|
|
setProgress(t);
|
|
if (rawT < 1) {
|
|
rafRef.current = requestAnimationFrame(animate);
|
|
} else {
|
|
rafRef.current = null;
|
|
setTarget(null);
|
|
}
|
|
};
|
|
rafRef.current = requestAnimationFrame(animate);
|
|
return () => {
|
|
if (rafRef.current !== null) {
|
|
cancelAnimationFrame(rafRef.current);
|
|
rafRef.current = null;
|
|
}
|
|
};
|
|
};
|
|
t2 = [target, setZoom, setPan, setTarget, setProgress];
|
|
$[4] = setPan;
|
|
$[5] = setProgress;
|
|
$[6] = setTarget;
|
|
$[7] = setZoom;
|
|
$[8] = target;
|
|
$[9] = t1;
|
|
$[10] = t2;
|
|
} else {
|
|
t1 = $[9];
|
|
t2 = $[10];
|
|
}
|
|
useEffect2(t1, t2);
|
|
const t3 = target !== null;
|
|
let t4;
|
|
if ($[11] !== cancel || $[12] !== progress || $[13] !== t3) {
|
|
t4 = {
|
|
isAnimating: t3,
|
|
progress,
|
|
cancel
|
|
};
|
|
$[11] = cancel;
|
|
$[12] = progress;
|
|
$[13] = t3;
|
|
$[14] = t4;
|
|
} else {
|
|
t4 = $[14];
|
|
}
|
|
return t4;
|
|
}
|
|
|
|
// src/hooks/useSplitGesture.ts
|
|
init_graph_mutations();
|
|
init_viewport_store();
|
|
import { c as _c19 } from "react/compiler-runtime";
|
|
import { useRef as useRef6 } from "react";
|
|
import { useAtomValue as useAtomValue17, useSetAtom as useSetAtom11 } from "jotai";
|
|
var SPLIT_THRESHOLD = 80;
|
|
function useSplitGesture(nodeId) {
|
|
const $ = _c19(9);
|
|
const splitNode = useSetAtom11(splitNodeAtom);
|
|
const screenToWorld = useAtomValue17(screenToWorldAtom);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ new Map();
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
const pointersRef = useRef6(t0);
|
|
const initialDistanceRef = useRef6(null);
|
|
const splitFiredRef = useRef6(false);
|
|
const getDistance = _temp6;
|
|
let t1;
|
|
if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = (e) => {
|
|
if (e.pointerType !== "touch") {
|
|
return;
|
|
}
|
|
pointersRef.current.set(e.pointerId, {
|
|
pointerId: e.pointerId,
|
|
x: e.clientX,
|
|
y: e.clientY
|
|
});
|
|
if (pointersRef.current.size === 2) {
|
|
const [p1, p2] = Array.from(pointersRef.current.values());
|
|
initialDistanceRef.current = getDistance(p1, p2);
|
|
splitFiredRef.current = false;
|
|
}
|
|
};
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const onPointerDown = t1;
|
|
let t2;
|
|
if ($[2] !== nodeId || $[3] !== screenToWorld || $[4] !== splitNode) {
|
|
t2 = (e_0) => {
|
|
if (e_0.pointerType !== "touch") {
|
|
return;
|
|
}
|
|
if (!pointersRef.current.has(e_0.pointerId)) {
|
|
return;
|
|
}
|
|
pointersRef.current.set(e_0.pointerId, {
|
|
pointerId: e_0.pointerId,
|
|
x: e_0.clientX,
|
|
y: e_0.clientY
|
|
});
|
|
if (pointersRef.current.size === 2 && initialDistanceRef.current !== null && !splitFiredRef.current) {
|
|
const [p1_0, p2_0] = Array.from(pointersRef.current.values());
|
|
const currentDistance = getDistance(p1_0, p2_0);
|
|
const delta = currentDistance - initialDistanceRef.current;
|
|
if (delta > SPLIT_THRESHOLD) {
|
|
splitFiredRef.current = true;
|
|
e_0.stopPropagation();
|
|
const world1 = screenToWorld(p1_0.x, p1_0.y);
|
|
const world2 = screenToWorld(p2_0.x, p2_0.y);
|
|
splitNode({
|
|
nodeId,
|
|
position1: world1,
|
|
position2: world2
|
|
});
|
|
}
|
|
}
|
|
};
|
|
$[2] = nodeId;
|
|
$[3] = screenToWorld;
|
|
$[4] = splitNode;
|
|
$[5] = t2;
|
|
} else {
|
|
t2 = $[5];
|
|
}
|
|
const onPointerMove = t2;
|
|
let t3;
|
|
if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t3 = (e_1) => {
|
|
pointersRef.current.delete(e_1.pointerId);
|
|
if (pointersRef.current.size < 2) {
|
|
initialDistanceRef.current = null;
|
|
splitFiredRef.current = false;
|
|
}
|
|
};
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
const onPointerUp = t3;
|
|
let t4;
|
|
if ($[7] !== onPointerMove) {
|
|
t4 = {
|
|
onPointerDown,
|
|
onPointerMove,
|
|
onPointerUp
|
|
};
|
|
$[7] = onPointerMove;
|
|
$[8] = t4;
|
|
} else {
|
|
t4 = $[8];
|
|
}
|
|
return t4;
|
|
}
|
|
function _temp6(a, b) {
|
|
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
}
|
|
|
|
// src/hooks/useAnimatedLayout.ts
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_history_store();
|
|
init_debug();
|
|
init_reduced_motion_store();
|
|
import { useAtomValue as useAtomValue18, useSetAtom as useSetAtom12 } from "jotai";
|
|
import { useRef as useRef7 } from "react";
|
|
var debug16 = createDebug("animated-layout");
|
|
function easeInOutCubic2(t) {
|
|
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
}
|
|
function useAnimatedLayout(options = {}) {
|
|
const {
|
|
onPositionsChanged,
|
|
duration = 400
|
|
} = options;
|
|
const graph = useAtomValue18(graphAtom);
|
|
const updateNodePosition = useSetAtom12(updateNodePositionAtom);
|
|
const pushHistory = useSetAtom12(pushHistoryAtom);
|
|
const setPositionCounter = useSetAtom12(nodePositionUpdateCounterAtom);
|
|
const reducedMotion = useAtomValue18(prefersReducedMotionAtom);
|
|
const isAnimatingRef = useRef7(false);
|
|
const animate = async (targets, label) => {
|
|
if (isAnimatingRef.current) return;
|
|
if (targets.size === 0) return;
|
|
if (label) pushHistory(label);
|
|
isAnimatingRef.current = true;
|
|
if (reducedMotion) {
|
|
for (const [nodeId, target] of targets) {
|
|
updateNodePosition({
|
|
nodeId,
|
|
position: target
|
|
});
|
|
}
|
|
isAnimatingRef.current = false;
|
|
setPositionCounter((c) => c + 1);
|
|
if (onPositionsChanged) {
|
|
const updates = [];
|
|
for (const [nodeId_0, target_0] of targets) {
|
|
updates.push({
|
|
nodeId: nodeId_0,
|
|
position: target_0
|
|
});
|
|
}
|
|
Promise.resolve(onPositionsChanged(updates)).catch((err) => debug16.error("Position change callback failed: %O", err));
|
|
}
|
|
return;
|
|
}
|
|
const starts = /* @__PURE__ */ new Map();
|
|
for (const [nodeId_1] of targets) {
|
|
if (graph.hasNode(nodeId_1)) {
|
|
const attrs = graph.getNodeAttributes(nodeId_1);
|
|
starts.set(nodeId_1, {
|
|
x: attrs.x,
|
|
y: attrs.y
|
|
});
|
|
}
|
|
}
|
|
return new Promise((resolve2) => {
|
|
const startTime = performance.now();
|
|
function tick() {
|
|
const elapsed = performance.now() - startTime;
|
|
const t = Math.min(elapsed / duration, 1);
|
|
const eased = easeInOutCubic2(t);
|
|
for (const [nodeId_2, target_1] of targets) {
|
|
const start = starts.get(nodeId_2);
|
|
if (!start) continue;
|
|
const x = Math.round(start.x + (target_1.x - start.x) * eased);
|
|
const y = Math.round(start.y + (target_1.y - start.y) * eased);
|
|
updateNodePosition({
|
|
nodeId: nodeId_2,
|
|
position: {
|
|
x,
|
|
y
|
|
}
|
|
});
|
|
}
|
|
if (t < 1) {
|
|
requestAnimationFrame(tick);
|
|
} else {
|
|
isAnimatingRef.current = false;
|
|
setPositionCounter((c_0) => c_0 + 1);
|
|
if (onPositionsChanged) {
|
|
const updates_0 = [];
|
|
for (const [nodeId_3, target_2] of targets) {
|
|
updates_0.push({
|
|
nodeId: nodeId_3,
|
|
position: target_2
|
|
});
|
|
}
|
|
Promise.resolve(onPositionsChanged(updates_0)).catch((err_0) => debug16.error("Position change callback failed: %O", err_0));
|
|
}
|
|
resolve2();
|
|
}
|
|
}
|
|
requestAnimationFrame(tick);
|
|
});
|
|
};
|
|
return {
|
|
animate,
|
|
isAnimating: isAnimatingRef.current
|
|
};
|
|
}
|
|
|
|
// src/hooks/useTreeLayout.ts
|
|
init_graph_store();
|
|
init_graph_derived();
|
|
import { useAtomValue as useAtomValue19 } from "jotai";
|
|
import { useRef as useRef8 } from "react";
|
|
function useTreeLayout(options = {}) {
|
|
const {
|
|
direction = "top-down",
|
|
levelGap = 200,
|
|
nodeGap = 100,
|
|
...animateOptions
|
|
} = options;
|
|
const graph = useAtomValue19(graphAtom);
|
|
const nodes = useAtomValue19(uiNodesAtom);
|
|
const {
|
|
animate,
|
|
isAnimating
|
|
} = useAnimatedLayout(animateOptions);
|
|
const isRunningRef = useRef8(false);
|
|
const applyLayout = async () => {
|
|
if (isRunningRef.current || isAnimating) return;
|
|
if (nodes.length === 0) return;
|
|
isRunningRef.current = true;
|
|
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
const children = /* @__PURE__ */ new Map();
|
|
const hasIncoming = /* @__PURE__ */ new Set();
|
|
for (const nodeId of nodeIds) {
|
|
children.set(nodeId, []);
|
|
}
|
|
graph.forEachEdge((_key, _attrs, source, target) => {
|
|
if (nodeIds.has(source) && nodeIds.has(target) && source !== target) {
|
|
children.get(source)?.push(target);
|
|
hasIncoming.add(target);
|
|
}
|
|
});
|
|
const roots = [...nodeIds].filter((id) => !hasIncoming.has(id));
|
|
if (roots.length === 0) {
|
|
roots.push(nodes[0].id);
|
|
}
|
|
const levels = /* @__PURE__ */ new Map();
|
|
const queue = [...roots];
|
|
for (const r of roots) levels.set(r, 0);
|
|
while (queue.length > 0) {
|
|
const current = queue.shift();
|
|
const level = levels.get(current);
|
|
for (const child of children.get(current) || []) {
|
|
if (!levels.has(child)) {
|
|
levels.set(child, level + 1);
|
|
queue.push(child);
|
|
}
|
|
}
|
|
}
|
|
for (const nodeId_0 of nodeIds) {
|
|
if (!levels.has(nodeId_0)) levels.set(nodeId_0, 0);
|
|
}
|
|
const byLevel = /* @__PURE__ */ new Map();
|
|
for (const [nodeId_1, level_0] of levels) {
|
|
if (!byLevel.has(level_0)) byLevel.set(level_0, []);
|
|
byLevel.get(level_0).push(nodeId_1);
|
|
}
|
|
const targets = /* @__PURE__ */ new Map();
|
|
const maxLevel = Math.max(...byLevel.keys());
|
|
for (const [level_1, nodeIdsAtLevel] of byLevel) {
|
|
const count = nodeIdsAtLevel.length;
|
|
let maxNodeSize = 200;
|
|
for (const nid of nodeIdsAtLevel) {
|
|
if (graph.hasNode(nid)) {
|
|
const attrs = graph.getNodeAttributes(nid);
|
|
maxNodeSize = Math.max(maxNodeSize, attrs.width || 200);
|
|
}
|
|
}
|
|
const totalWidth = (count - 1) * (maxNodeSize + nodeGap);
|
|
const startX = -totalWidth / 2;
|
|
for (let i = 0; i < count; i++) {
|
|
const primary = level_1 * levelGap;
|
|
const secondary = startX + i * (maxNodeSize + nodeGap);
|
|
if (direction === "top-down") {
|
|
targets.set(nodeIdsAtLevel[i], {
|
|
x: secondary,
|
|
y: primary
|
|
});
|
|
} else {
|
|
targets.set(nodeIdsAtLevel[i], {
|
|
x: primary,
|
|
y: secondary
|
|
});
|
|
}
|
|
}
|
|
}
|
|
await animate(targets, direction === "top-down" ? "Tree layout" : "Horizontal layout");
|
|
isRunningRef.current = false;
|
|
};
|
|
return {
|
|
applyLayout,
|
|
isRunning: isRunningRef.current || isAnimating
|
|
};
|
|
}
|
|
|
|
// src/hooks/useGridLayout.ts
|
|
init_graph_store();
|
|
init_graph_derived();
|
|
import { useAtomValue as useAtomValue20 } from "jotai";
|
|
import { useRef as useRef9 } from "react";
|
|
function useGridLayout(options = {}) {
|
|
const {
|
|
columns,
|
|
gap = 80,
|
|
...animateOptions
|
|
} = options;
|
|
const graph = useAtomValue20(graphAtom);
|
|
const nodes = useAtomValue20(uiNodesAtom);
|
|
const {
|
|
animate,
|
|
isAnimating
|
|
} = useAnimatedLayout(animateOptions);
|
|
const isRunningRef = useRef9(false);
|
|
const applyLayout = async () => {
|
|
if (isRunningRef.current || isAnimating) return;
|
|
if (nodes.length === 0) return;
|
|
isRunningRef.current = true;
|
|
const sorted = [...nodes].sort((a, b) => {
|
|
const ay = a.position?.y ?? 0;
|
|
const by = b.position?.y ?? 0;
|
|
if (Math.abs(ay - by) > 50) return ay - by;
|
|
return (a.position?.x ?? 0) - (b.position?.x ?? 0);
|
|
});
|
|
const cols = columns ?? Math.ceil(Math.sqrt(sorted.length));
|
|
let maxW = 200;
|
|
let maxH = 100;
|
|
for (const node of sorted) {
|
|
if (graph.hasNode(node.id)) {
|
|
const attrs = graph.getNodeAttributes(node.id);
|
|
maxW = Math.max(maxW, attrs.width || 200);
|
|
maxH = Math.max(maxH, attrs.height || 100);
|
|
}
|
|
}
|
|
const cellW = maxW + gap;
|
|
const cellH = maxH + gap;
|
|
const rows = Math.ceil(sorted.length / cols);
|
|
const totalW = (cols - 1) * cellW;
|
|
const totalH = (rows - 1) * cellH;
|
|
const offsetX = -totalW / 2;
|
|
const offsetY = -totalH / 2;
|
|
const targets = /* @__PURE__ */ new Map();
|
|
for (let i = 0; i < sorted.length; i++) {
|
|
const col = i % cols;
|
|
const row = Math.floor(i / cols);
|
|
targets.set(sorted[i].id, {
|
|
x: Math.round(offsetX + col * cellW),
|
|
y: Math.round(offsetY + row * cellH)
|
|
});
|
|
}
|
|
await animate(targets, "Grid layout");
|
|
isRunningRef.current = false;
|
|
};
|
|
return {
|
|
applyLayout,
|
|
isRunning: isRunningRef.current || isAnimating
|
|
};
|
|
}
|
|
|
|
// src/hooks/usePlugin.ts
|
|
init_plugin_registry();
|
|
import { c as _c20 } from "react/compiler-runtime";
|
|
import { useEffect as useEffect3, useRef as useRef10 } from "react";
|
|
function usePlugin(plugin) {
|
|
const $ = _c20(4);
|
|
const registeredRef = useRef10(false);
|
|
let t0;
|
|
if ($[0] !== plugin) {
|
|
t0 = () => {
|
|
if (!hasPlugin(plugin.id)) {
|
|
registerPlugin(plugin);
|
|
registeredRef.current = true;
|
|
}
|
|
return () => {
|
|
if (registeredRef.current && hasPlugin(plugin.id)) {
|
|
try {
|
|
unregisterPlugin(plugin.id);
|
|
} catch {
|
|
}
|
|
registeredRef.current = false;
|
|
}
|
|
};
|
|
};
|
|
$[0] = plugin;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
let t1;
|
|
if ($[2] !== plugin.id) {
|
|
t1 = [plugin.id];
|
|
$[2] = plugin.id;
|
|
$[3] = t1;
|
|
} else {
|
|
t1 = $[3];
|
|
}
|
|
useEffect3(t0, t1);
|
|
}
|
|
function usePlugins(plugins2) {
|
|
const $ = _c20(6);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = [];
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
const registeredRef = useRef10(t0);
|
|
let t1;
|
|
let t2;
|
|
if ($[1] !== plugins2) {
|
|
t1 = () => {
|
|
const registered = [];
|
|
for (const plugin of plugins2) {
|
|
if (!hasPlugin(plugin.id)) {
|
|
registerPlugin(plugin);
|
|
registered.push(plugin.id);
|
|
}
|
|
}
|
|
registeredRef.current = registered;
|
|
return () => {
|
|
for (const id of registeredRef.current.reverse()) {
|
|
if (hasPlugin(id)) {
|
|
try {
|
|
unregisterPlugin(id);
|
|
} catch {
|
|
}
|
|
}
|
|
}
|
|
registeredRef.current = [];
|
|
};
|
|
};
|
|
t2 = plugins2.map(_temp7).join(",");
|
|
$[1] = plugins2;
|
|
$[2] = t1;
|
|
$[3] = t2;
|
|
} else {
|
|
t1 = $[2];
|
|
t2 = $[3];
|
|
}
|
|
let t3;
|
|
if ($[4] !== t2) {
|
|
t3 = [t2];
|
|
$[4] = t2;
|
|
$[5] = t3;
|
|
} else {
|
|
t3 = $[5];
|
|
}
|
|
useEffect3(t1, t3);
|
|
}
|
|
function _temp7(p) {
|
|
return p.id;
|
|
}
|
|
|
|
// src/utils/index.ts
|
|
init_debug();
|
|
init_mutation_queue();
|
|
|
|
// src/utils/component-registry.tsx
|
|
import { c as _c21 } from "react/compiler-runtime";
|
|
import React2 from "react";
|
|
import { jsx as _jsx2 } from "react/jsx-runtime";
|
|
function createComponentRegistry({
|
|
registry,
|
|
fallback,
|
|
getDisplayName = (type) => `Memoized${type}Component`
|
|
}) {
|
|
const memoizedCache = /* @__PURE__ */ new Map();
|
|
const getComponent = (type) => {
|
|
if (memoizedCache.has(type)) {
|
|
return memoizedCache.get(type);
|
|
}
|
|
const LazyComponent = registry[type];
|
|
if (!LazyComponent) {
|
|
return fallback || null;
|
|
}
|
|
function RegistryComponent(props) {
|
|
const $ = _c21(2);
|
|
let t0;
|
|
if ($[0] !== props) {
|
|
t0 = /* @__PURE__ */ _jsx2(LazyComponent, {
|
|
...props
|
|
});
|
|
$[0] = props;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
return t0;
|
|
}
|
|
RegistryComponent.displayName = getDisplayName(type);
|
|
memoizedCache.set(type, RegistryComponent);
|
|
return RegistryComponent;
|
|
};
|
|
const getTypes = () => Object.keys(registry);
|
|
const hasType = (type) => type in registry;
|
|
return {
|
|
getComponent,
|
|
getTypes,
|
|
hasType,
|
|
Fallback: fallback || null
|
|
};
|
|
}
|
|
function createFallbackComponent(getMessage) {
|
|
return function FallbackComponent(props) {
|
|
const message = getMessage ? getMessage(props) : `Unsupported type: "${props.nodeData?.dbData?.node_type || "Unknown"}"`;
|
|
return /* @__PURE__ */ _jsx2("div", {
|
|
style: {
|
|
padding: "10px",
|
|
color: "orange"
|
|
},
|
|
children: /* @__PURE__ */ _jsx2("p", {
|
|
children: message
|
|
})
|
|
});
|
|
};
|
|
}
|
|
|
|
// src/utils/index.ts
|
|
init_edge_path_calculators();
|
|
init_layout();
|
|
init_edge_path_registry();
|
|
|
|
// src/db/adapter.ts
|
|
import { atom as atom29 } from "jotai";
|
|
var InMemoryStorageAdapter = class {
|
|
constructor() {
|
|
__publicField(this, "nodes", /* @__PURE__ */ new Map());
|
|
__publicField(this, "edges", /* @__PURE__ */ new Map());
|
|
}
|
|
async fetchNodes(graphId) {
|
|
return Array.from(this.nodes.values()).filter((n) => n.graph_id === graphId);
|
|
}
|
|
async createNode(graphId, node) {
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const fullNode = {
|
|
id: node.id ?? crypto.randomUUID(),
|
|
graph_id: graphId,
|
|
label: node.label ?? null,
|
|
node_type: node.node_type ?? null,
|
|
configuration: node.configuration ?? null,
|
|
ui_properties: node.ui_properties ?? null,
|
|
data: node.data ?? null,
|
|
created_at: now,
|
|
updated_at: now
|
|
};
|
|
this.nodes.set(fullNode.id, fullNode);
|
|
return fullNode;
|
|
}
|
|
async updateNode(nodeId, updates) {
|
|
const existing = this.nodes.get(nodeId);
|
|
if (!existing) throw new Error(`Node ${nodeId} not found`);
|
|
const updated = {
|
|
...existing,
|
|
...updates,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
};
|
|
this.nodes.set(nodeId, updated);
|
|
return updated;
|
|
}
|
|
async deleteNode(nodeId) {
|
|
this.nodes.delete(nodeId);
|
|
for (const [edgeId, edge] of this.edges) {
|
|
if (edge.source_node_id === nodeId || edge.target_node_id === nodeId) {
|
|
this.edges.delete(edgeId);
|
|
}
|
|
}
|
|
}
|
|
async fetchEdges(graphId) {
|
|
return Array.from(this.edges.values()).filter((e) => e.graph_id === graphId);
|
|
}
|
|
async createEdge(graphId, edge) {
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const fullEdge = {
|
|
id: edge.id ?? crypto.randomUUID(),
|
|
graph_id: graphId,
|
|
source_node_id: edge.source_node_id ?? "",
|
|
target_node_id: edge.target_node_id ?? "",
|
|
edge_type: edge.edge_type ?? null,
|
|
filter_condition: edge.filter_condition ?? null,
|
|
ui_properties: edge.ui_properties ?? null,
|
|
data: edge.data ?? null,
|
|
created_at: now,
|
|
updated_at: now
|
|
};
|
|
this.edges.set(fullEdge.id, fullEdge);
|
|
return fullEdge;
|
|
}
|
|
async updateEdge(edgeId, updates) {
|
|
const existing = this.edges.get(edgeId);
|
|
if (!existing) throw new Error(`Edge ${edgeId} not found`);
|
|
const updated = {
|
|
...existing,
|
|
...updates,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
};
|
|
this.edges.set(edgeId, updated);
|
|
return updated;
|
|
}
|
|
async deleteEdge(edgeId) {
|
|
this.edges.delete(edgeId);
|
|
}
|
|
// --- Batch Operations ---
|
|
async createNodes(graphId, nodes) {
|
|
return Promise.all(nodes.map((n) => this.createNode(graphId, n)));
|
|
}
|
|
async deleteNodes(nodeIds) {
|
|
for (const id of nodeIds) {
|
|
await this.deleteNode(id);
|
|
}
|
|
}
|
|
async createEdges(graphId, edges) {
|
|
return Promise.all(edges.map((e) => this.createEdge(graphId, e)));
|
|
}
|
|
async deleteEdges(edgeIds) {
|
|
for (const id of edgeIds) {
|
|
await this.deleteEdge(id);
|
|
}
|
|
}
|
|
};
|
|
var storageAdapterAtom = atom29(null);
|
|
|
|
// src/db/supabase-adapter.ts
|
|
var SupabaseStorageAdapter = class {
|
|
constructor(client) {
|
|
this.client = client;
|
|
}
|
|
/** Get the underlying Supabase client (for advanced use or backward compat) */
|
|
getClient() {
|
|
return this.client;
|
|
}
|
|
// --- Node Operations ---
|
|
async fetchNodes(graphId) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await this.client.from("nodes").select("*").eq("graph_id", graphId).order("created_at", {
|
|
ascending: true
|
|
});
|
|
if (error) throw error;
|
|
return data || [];
|
|
}
|
|
async createNode(graphId, node) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await this.client.from("nodes").insert({
|
|
graph_id: graphId,
|
|
label: node.label ?? null,
|
|
node_type: node.node_type ?? null,
|
|
configuration: node.configuration ?? null,
|
|
ui_properties: node.ui_properties ?? null,
|
|
data: node.data ?? null
|
|
}).select().single();
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
async updateNode(nodeId, updates) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await this.client.from("nodes").update({
|
|
...updates,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
}).eq("id", nodeId).select().single();
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
async deleteNode(nodeId) {
|
|
const {
|
|
error
|
|
} = await this.client.from("nodes").delete().eq("id", nodeId);
|
|
if (error) throw error;
|
|
}
|
|
// --- Edge Operations ---
|
|
async fetchEdges(graphId) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await this.client.from("edges").select("*").eq("graph_id", graphId).order("created_at", {
|
|
ascending: true
|
|
});
|
|
if (error) throw error;
|
|
return data || [];
|
|
}
|
|
async createEdge(graphId, edge) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await this.client.from("edges").insert({
|
|
graph_id: graphId,
|
|
source_node_id: edge.source_node_id ?? "",
|
|
target_node_id: edge.target_node_id ?? "",
|
|
edge_type: edge.edge_type ?? null,
|
|
filter_condition: edge.filter_condition ?? null,
|
|
ui_properties: edge.ui_properties ?? null,
|
|
data: edge.data ?? null
|
|
}).select().single();
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
async updateEdge(edgeId, updates) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await this.client.from("edges").update({
|
|
...updates,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
}).eq("id", edgeId).select().single();
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
async deleteEdge(edgeId) {
|
|
const {
|
|
error
|
|
} = await this.client.from("edges").delete().eq("id", edgeId);
|
|
if (error) throw error;
|
|
}
|
|
// --- Realtime (optional) ---
|
|
subscribe(graphId, onChange) {
|
|
const channel = this.client.channel(`canvas-${graphId}`).on("postgres_changes", {
|
|
event: "*",
|
|
schema: "public",
|
|
table: "nodes",
|
|
filter: `graph_id=eq.${graphId}`
|
|
}, (payload) => {
|
|
onChange({
|
|
type: payload.eventType,
|
|
table: "nodes",
|
|
new: payload.new,
|
|
old: payload.old
|
|
});
|
|
}).on("postgres_changes", {
|
|
event: "*",
|
|
schema: "public",
|
|
table: "edges",
|
|
filter: `graph_id=eq.${graphId}`
|
|
}, (payload) => {
|
|
onChange({
|
|
type: payload.eventType,
|
|
table: "edges",
|
|
new: payload.new,
|
|
old: payload.old
|
|
});
|
|
}).subscribe();
|
|
return () => {
|
|
channel.unsubscribe();
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/db/provider.tsx
|
|
import React3, { createContext, useContext, useEffect as useEffect4, useRef as useRef11 } from "react";
|
|
import { useSetAtom as useSetAtom13, useAtomValue as useAtomValue21 } from "jotai";
|
|
import { atom as atom30, useAtomValue as useAtomVal } from "jotai";
|
|
import { jsx as _jsx3 } from "react/jsx-runtime";
|
|
var AdapterContext = /* @__PURE__ */ createContext(null);
|
|
function CanvasDbProvider({
|
|
adapter,
|
|
children
|
|
}) {
|
|
const setAdapter = useSetAtom13(storageAdapterAtom);
|
|
const prevAdapterRef = useRef11(null);
|
|
const resolved = adapter === prevAdapterRef.current ? prevAdapterRef.current : adapter;
|
|
prevAdapterRef.current = resolved;
|
|
useEffect4(() => {
|
|
setAdapter(resolved);
|
|
return () => setAdapter(null);
|
|
}, [resolved, setAdapter]);
|
|
return /* @__PURE__ */ _jsx3(AdapterContext, {
|
|
value: resolved,
|
|
children
|
|
});
|
|
}
|
|
function useStorageAdapter() {
|
|
const contextAdapter = useContext(AdapterContext);
|
|
const atomAdapter = useAtomValue21(storageAdapterAtom);
|
|
const adapter = contextAdapter || atomAdapter;
|
|
if (!adapter) {
|
|
throw new Error("useStorageAdapter must be used within CanvasDbProvider");
|
|
}
|
|
return adapter;
|
|
}
|
|
var supabaseClientAtom = atom30(null);
|
|
function useSupabaseClient() {
|
|
const client = useAtomVal(supabaseClientAtom);
|
|
if (!client) {
|
|
throw new Error("useSupabaseClient: no Supabase client available. Use CanvasDbProvider with a SupabaseStorageAdapter.");
|
|
}
|
|
return client;
|
|
}
|
|
|
|
// src/db/queries/nodes.ts
|
|
init_debug();
|
|
var debug17 = createDebug("db:nodes");
|
|
async function fetchGraphNodes(supabase, graphId) {
|
|
if (!graphId) {
|
|
throw new Error("graphId is required to fetch nodes");
|
|
}
|
|
const {
|
|
data,
|
|
error
|
|
} = await supabase.from("nodes").select("*").eq("graph_id", graphId).order("created_at", {
|
|
ascending: true
|
|
});
|
|
if (error) {
|
|
debug17.error("Error fetching nodes for graph %s: %O", graphId, error);
|
|
throw error;
|
|
}
|
|
return data || [];
|
|
}
|
|
async function fetchGraphNode(supabase, nodeId) {
|
|
if (!nodeId) {
|
|
throw new Error("nodeId is required to fetch node");
|
|
}
|
|
const {
|
|
data,
|
|
error
|
|
} = await supabase.from("nodes").select("*").eq("id", nodeId).single();
|
|
if (error) {
|
|
if (error.code === "PGRST116") {
|
|
return null;
|
|
}
|
|
debug17.error("Error fetching node %s: %O", nodeId, error);
|
|
throw error;
|
|
}
|
|
return data;
|
|
}
|
|
async function createGraphNode(supabase, node) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await supabase.from("nodes").insert(node).select().single();
|
|
if (error) {
|
|
debug17.error("Error creating node: %O", error);
|
|
throw error;
|
|
}
|
|
return data;
|
|
}
|
|
async function updateGraphNode(supabase, nodeId, updates) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await supabase.from("nodes").update({
|
|
...updates,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
}).eq("id", nodeId).select().single();
|
|
if (error) {
|
|
debug17.error("Error updating node %s: %O", nodeId, error);
|
|
throw error;
|
|
}
|
|
return data;
|
|
}
|
|
async function deleteGraphNode(supabase, nodeId) {
|
|
const {
|
|
error
|
|
} = await supabase.from("nodes").delete().eq("id", nodeId);
|
|
if (error) {
|
|
debug17.error("Error deleting node %s: %O", nodeId, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// src/db/queries/edges.ts
|
|
init_debug();
|
|
var debug18 = createDebug("db:edges");
|
|
async function fetchGraphEdges(supabase, graphId) {
|
|
if (!graphId) {
|
|
throw new Error("graphId is required to fetch edges");
|
|
}
|
|
const {
|
|
data,
|
|
error
|
|
} = await supabase.from("edges").select("*").eq("graph_id", graphId).order("created_at", {
|
|
ascending: true
|
|
});
|
|
if (error) {
|
|
debug18.error("Error fetching edges for graph %s: %O", graphId, error);
|
|
throw error;
|
|
}
|
|
return data || [];
|
|
}
|
|
async function createGraphEdge(supabase, edge) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await supabase.from("edges").insert(edge).select().single();
|
|
if (error) {
|
|
debug18.error("Error creating edge: %O", error);
|
|
throw error;
|
|
}
|
|
return data;
|
|
}
|
|
async function updateGraphEdge(supabase, edgeId, updates) {
|
|
const {
|
|
data,
|
|
error
|
|
} = await supabase.from("edges").update({
|
|
...updates,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
}).eq("id", edgeId).select().single();
|
|
if (error) {
|
|
debug18.error("Error updating edge %s: %O", edgeId, error);
|
|
throw error;
|
|
}
|
|
return data;
|
|
}
|
|
async function deleteGraphEdge(supabase, edgeId) {
|
|
const {
|
|
error
|
|
} = await supabase.from("edges").delete().eq("id", edgeId);
|
|
if (error) {
|
|
debug18.error("Error deleting edge %s: %O", edgeId, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// src/db/hooks/keys.ts
|
|
var canvasKeys = {
|
|
all: ["canvas"],
|
|
graphs: () => [...canvasKeys.all, "graphs"],
|
|
graph: (graphId) => [...canvasKeys.graphs(), graphId],
|
|
nodes: (graphId) => [...canvasKeys.graph(graphId), "nodes"],
|
|
node: (graphId, nodeId) => [...canvasKeys.nodes(graphId), nodeId],
|
|
edges: (graphId) => [...canvasKeys.graph(graphId), "edges"],
|
|
edge: (graphId, edgeId) => [...canvasKeys.edges(graphId), edgeId]
|
|
};
|
|
|
|
// src/db/hooks/useGraphNodes.ts
|
|
import { c as _c22 } from "react/compiler-runtime";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
function useGraphNodes(graphId, t0) {
|
|
const $ = _c22(11);
|
|
let t1;
|
|
if ($[0] !== t0) {
|
|
t1 = t0 === void 0 ? {} : t0;
|
|
$[0] = t0;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const options = t1;
|
|
const {
|
|
enabled: t2
|
|
} = options;
|
|
const enabled = t2 === void 0 ? true : t2;
|
|
const supabase = useSupabaseClient();
|
|
let t3;
|
|
if ($[2] !== graphId) {
|
|
t3 = canvasKeys.nodes(graphId || "");
|
|
$[2] = graphId;
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
let t4;
|
|
if ($[4] !== graphId || $[5] !== supabase) {
|
|
t4 = () => fetchGraphNodes(supabase, graphId);
|
|
$[4] = graphId;
|
|
$[5] = supabase;
|
|
$[6] = t4;
|
|
} else {
|
|
t4 = $[6];
|
|
}
|
|
const t5 = enabled && !!graphId;
|
|
let t6;
|
|
if ($[7] !== t3 || $[8] !== t4 || $[9] !== t5) {
|
|
t6 = {
|
|
queryKey: t3,
|
|
queryFn: t4,
|
|
enabled: t5
|
|
};
|
|
$[7] = t3;
|
|
$[8] = t4;
|
|
$[9] = t5;
|
|
$[10] = t6;
|
|
} else {
|
|
t6 = $[10];
|
|
}
|
|
return useQuery(t6);
|
|
}
|
|
|
|
// src/db/hooks/useGraphEdges.ts
|
|
import { c as _c23 } from "react/compiler-runtime";
|
|
import { useQuery as useQuery2 } from "@tanstack/react-query";
|
|
function useGraphEdges(graphId, t0) {
|
|
const $ = _c23(11);
|
|
let t1;
|
|
if ($[0] !== t0) {
|
|
t1 = t0 === void 0 ? {} : t0;
|
|
$[0] = t0;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const options = t1;
|
|
const {
|
|
enabled: t2
|
|
} = options;
|
|
const enabled = t2 === void 0 ? true : t2;
|
|
const supabase = useSupabaseClient();
|
|
let t3;
|
|
if ($[2] !== graphId) {
|
|
t3 = canvasKeys.edges(graphId || "");
|
|
$[2] = graphId;
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
let t4;
|
|
if ($[4] !== graphId || $[5] !== supabase) {
|
|
t4 = () => fetchGraphEdges(supabase, graphId);
|
|
$[4] = graphId;
|
|
$[5] = supabase;
|
|
$[6] = t4;
|
|
} else {
|
|
t4 = $[6];
|
|
}
|
|
const t5 = enabled && !!graphId;
|
|
let t6;
|
|
if ($[7] !== t3 || $[8] !== t4 || $[9] !== t5) {
|
|
t6 = {
|
|
queryKey: t3,
|
|
queryFn: t4,
|
|
enabled: t5
|
|
};
|
|
$[7] = t3;
|
|
$[8] = t4;
|
|
$[9] = t5;
|
|
$[10] = t6;
|
|
} else {
|
|
t6 = $[10];
|
|
}
|
|
return useQuery2(t6);
|
|
}
|
|
|
|
// src/db/hooks/useUpdateNode.ts
|
|
init_sync_store();
|
|
import { c as _c24 } from "react/compiler-runtime";
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { useSetAtom as useSetAtom14 } from "jotai";
|
|
function useUpdateNode() {
|
|
const $ = _c24(18);
|
|
const queryClient = useQueryClient();
|
|
const supabase = useSupabaseClient();
|
|
const startMutation = useSetAtom14(startMutationAtom);
|
|
const completeMutation = useSetAtom14(completeMutationAtom);
|
|
let t0;
|
|
if ($[0] !== supabase) {
|
|
t0 = async (t12) => {
|
|
const {
|
|
nodeId,
|
|
updates
|
|
} = t12;
|
|
return updateGraphNode(supabase, nodeId, updates);
|
|
};
|
|
$[0] = supabase;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
let t1;
|
|
if ($[2] !== queryClient || $[3] !== startMutation) {
|
|
t1 = async (t22) => {
|
|
const {
|
|
nodeId: nodeId_0,
|
|
graphId,
|
|
updates: updates_0
|
|
} = t22;
|
|
startMutation();
|
|
await queryClient.cancelQueries({
|
|
queryKey: canvasKeys.nodes(graphId)
|
|
});
|
|
const previousNodes = queryClient.getQueryData(canvasKeys.nodes(graphId));
|
|
if (previousNodes) {
|
|
queryClient.setQueryData(canvasKeys.nodes(graphId), (old) => old?.map((node) => node.id === nodeId_0 ? {
|
|
...node,
|
|
...updates_0,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
} : node));
|
|
}
|
|
return {
|
|
previousNodes,
|
|
graphId
|
|
};
|
|
};
|
|
$[2] = queryClient;
|
|
$[3] = startMutation;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
let t2;
|
|
if ($[5] !== completeMutation || $[6] !== queryClient) {
|
|
t2 = (_err, _variables, context) => {
|
|
if (context?.previousNodes) {
|
|
queryClient.setQueryData(canvasKeys.nodes(context.graphId), context.previousNodes);
|
|
}
|
|
completeMutation(false);
|
|
};
|
|
$[5] = completeMutation;
|
|
$[6] = queryClient;
|
|
$[7] = t2;
|
|
} else {
|
|
t2 = $[7];
|
|
}
|
|
let t3;
|
|
if ($[8] !== completeMutation) {
|
|
t3 = () => {
|
|
completeMutation(true);
|
|
};
|
|
$[8] = completeMutation;
|
|
$[9] = t3;
|
|
} else {
|
|
t3 = $[9];
|
|
}
|
|
let t4;
|
|
if ($[10] !== queryClient) {
|
|
t4 = (_data, _error, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: canvasKeys.nodes(variables.graphId)
|
|
});
|
|
};
|
|
$[10] = queryClient;
|
|
$[11] = t4;
|
|
} else {
|
|
t4 = $[11];
|
|
}
|
|
let t5;
|
|
if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) {
|
|
t5 = {
|
|
mutationFn: t0,
|
|
onMutate: t1,
|
|
onError: t2,
|
|
onSuccess: t3,
|
|
onSettled: t4
|
|
};
|
|
$[12] = t0;
|
|
$[13] = t1;
|
|
$[14] = t2;
|
|
$[15] = t3;
|
|
$[16] = t4;
|
|
$[17] = t5;
|
|
} else {
|
|
t5 = $[17];
|
|
}
|
|
return useMutation(t5);
|
|
}
|
|
|
|
// src/db/hooks/useCreateNode.ts
|
|
init_sync_store();
|
|
import { c as _c25 } from "react/compiler-runtime";
|
|
import { useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
import { useSetAtom as useSetAtom15 } from "jotai";
|
|
function useCreateNode() {
|
|
const $ = _c25(17);
|
|
const queryClient = useQueryClient2();
|
|
const supabase = useSupabaseClient();
|
|
const startMutation = useSetAtom15(startMutationAtom);
|
|
const completeMutation = useSetAtom15(completeMutationAtom);
|
|
let t0;
|
|
if ($[0] !== supabase) {
|
|
t0 = async (node) => createGraphNode(supabase, node);
|
|
$[0] = supabase;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
let t1;
|
|
if ($[2] !== queryClient || $[3] !== startMutation) {
|
|
t1 = async (node_0) => {
|
|
startMutation();
|
|
await queryClient.cancelQueries({
|
|
queryKey: canvasKeys.nodes(node_0.graph_id)
|
|
});
|
|
const previousNodes = queryClient.getQueryData(canvasKeys.nodes(node_0.graph_id));
|
|
const optimisticNode = {
|
|
...node_0,
|
|
id: `temp-${Date.now()}`,
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
};
|
|
queryClient.setQueryData(canvasKeys.nodes(node_0.graph_id), (old) => [...old || [], optimisticNode]);
|
|
return {
|
|
previousNodes,
|
|
graphId: node_0.graph_id,
|
|
optimisticId: optimisticNode.id
|
|
};
|
|
};
|
|
$[2] = queryClient;
|
|
$[3] = startMutation;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
let t2;
|
|
let t3;
|
|
if ($[5] !== completeMutation || $[6] !== queryClient) {
|
|
t2 = (_err, _variables, context) => {
|
|
if (context?.previousNodes) {
|
|
queryClient.setQueryData(canvasKeys.nodes(context.graphId), context.previousNodes);
|
|
}
|
|
completeMutation(false);
|
|
};
|
|
t3 = (newNode, _variables_0, context_0) => {
|
|
if (context_0) {
|
|
queryClient.setQueryData(canvasKeys.nodes(context_0.graphId), (old_0) => old_0?.map((node_1) => node_1.id === context_0.optimisticId ? newNode : node_1));
|
|
}
|
|
completeMutation(true);
|
|
};
|
|
$[5] = completeMutation;
|
|
$[6] = queryClient;
|
|
$[7] = t2;
|
|
$[8] = t3;
|
|
} else {
|
|
t2 = $[7];
|
|
t3 = $[8];
|
|
}
|
|
let t4;
|
|
if ($[9] !== queryClient) {
|
|
t4 = (_data, _error, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: canvasKeys.nodes(variables.graph_id)
|
|
});
|
|
};
|
|
$[9] = queryClient;
|
|
$[10] = t4;
|
|
} else {
|
|
t4 = $[10];
|
|
}
|
|
let t5;
|
|
if ($[11] !== t0 || $[12] !== t1 || $[13] !== t2 || $[14] !== t3 || $[15] !== t4) {
|
|
t5 = {
|
|
mutationFn: t0,
|
|
onMutate: t1,
|
|
onError: t2,
|
|
onSuccess: t3,
|
|
onSettled: t4
|
|
};
|
|
$[11] = t0;
|
|
$[12] = t1;
|
|
$[13] = t2;
|
|
$[14] = t3;
|
|
$[15] = t4;
|
|
$[16] = t5;
|
|
} else {
|
|
t5 = $[16];
|
|
}
|
|
return useMutation2(t5);
|
|
}
|
|
|
|
// src/db/hooks/useDeleteNode.ts
|
|
init_sync_store();
|
|
import { c as _c26 } from "react/compiler-runtime";
|
|
import { useMutation as useMutation3, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
import { useSetAtom as useSetAtom16 } from "jotai";
|
|
function useDeleteNode() {
|
|
const $ = _c26(18);
|
|
const queryClient = useQueryClient3();
|
|
const supabase = useSupabaseClient();
|
|
const startMutation = useSetAtom16(startMutationAtom);
|
|
const completeMutation = useSetAtom16(completeMutationAtom);
|
|
let t0;
|
|
if ($[0] !== supabase) {
|
|
t0 = async (t12) => {
|
|
const {
|
|
nodeId
|
|
} = t12;
|
|
return deleteGraphNode(supabase, nodeId);
|
|
};
|
|
$[0] = supabase;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
let t1;
|
|
if ($[2] !== queryClient || $[3] !== startMutation) {
|
|
t1 = async (t22) => {
|
|
const {
|
|
nodeId: nodeId_0,
|
|
graphId
|
|
} = t22;
|
|
startMutation();
|
|
await queryClient.cancelQueries({
|
|
queryKey: canvasKeys.nodes(graphId)
|
|
});
|
|
const previousNodes = queryClient.getQueryData(canvasKeys.nodes(graphId));
|
|
if (previousNodes) {
|
|
queryClient.setQueryData(canvasKeys.nodes(graphId), (old) => old?.filter((node) => node.id !== nodeId_0));
|
|
}
|
|
return {
|
|
previousNodes,
|
|
graphId
|
|
};
|
|
};
|
|
$[2] = queryClient;
|
|
$[3] = startMutation;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
let t2;
|
|
if ($[5] !== completeMutation || $[6] !== queryClient) {
|
|
t2 = (_err, _variables, context) => {
|
|
if (context?.previousNodes) {
|
|
queryClient.setQueryData(canvasKeys.nodes(context.graphId), context.previousNodes);
|
|
}
|
|
completeMutation(false);
|
|
};
|
|
$[5] = completeMutation;
|
|
$[6] = queryClient;
|
|
$[7] = t2;
|
|
} else {
|
|
t2 = $[7];
|
|
}
|
|
let t3;
|
|
if ($[8] !== completeMutation) {
|
|
t3 = () => {
|
|
completeMutation(true);
|
|
};
|
|
$[8] = completeMutation;
|
|
$[9] = t3;
|
|
} else {
|
|
t3 = $[9];
|
|
}
|
|
let t4;
|
|
if ($[10] !== queryClient) {
|
|
t4 = (_data, _error, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: canvasKeys.nodes(variables.graphId)
|
|
});
|
|
};
|
|
$[10] = queryClient;
|
|
$[11] = t4;
|
|
} else {
|
|
t4 = $[11];
|
|
}
|
|
let t5;
|
|
if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) {
|
|
t5 = {
|
|
mutationFn: t0,
|
|
onMutate: t1,
|
|
onError: t2,
|
|
onSuccess: t3,
|
|
onSettled: t4
|
|
};
|
|
$[12] = t0;
|
|
$[13] = t1;
|
|
$[14] = t2;
|
|
$[15] = t3;
|
|
$[16] = t4;
|
|
$[17] = t5;
|
|
} else {
|
|
t5 = $[17];
|
|
}
|
|
return useMutation3(t5);
|
|
}
|
|
|
|
// src/db/hooks/useUpdateEdge.ts
|
|
init_sync_store();
|
|
import { c as _c27 } from "react/compiler-runtime";
|
|
import { useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
|
|
import { useSetAtom as useSetAtom17 } from "jotai";
|
|
function useUpdateEdge() {
|
|
const $ = _c27(18);
|
|
const queryClient = useQueryClient4();
|
|
const supabase = useSupabaseClient();
|
|
const startMutation = useSetAtom17(startMutationAtom);
|
|
const completeMutation = useSetAtom17(completeMutationAtom);
|
|
let t0;
|
|
if ($[0] !== supabase) {
|
|
t0 = async (t12) => {
|
|
const {
|
|
edgeId,
|
|
updates
|
|
} = t12;
|
|
return updateGraphEdge(supabase, edgeId, updates);
|
|
};
|
|
$[0] = supabase;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
let t1;
|
|
if ($[2] !== queryClient || $[3] !== startMutation) {
|
|
t1 = async (t22) => {
|
|
const {
|
|
edgeId: edgeId_0,
|
|
graphId,
|
|
updates: updates_0
|
|
} = t22;
|
|
startMutation();
|
|
await queryClient.cancelQueries({
|
|
queryKey: canvasKeys.edges(graphId)
|
|
});
|
|
const previousEdges = queryClient.getQueryData(canvasKeys.edges(graphId));
|
|
if (previousEdges) {
|
|
queryClient.setQueryData(canvasKeys.edges(graphId), (old) => old?.map((edge) => edge.id === edgeId_0 ? {
|
|
...edge,
|
|
...updates_0,
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
} : edge));
|
|
}
|
|
return {
|
|
previousEdges,
|
|
graphId
|
|
};
|
|
};
|
|
$[2] = queryClient;
|
|
$[3] = startMutation;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
let t2;
|
|
if ($[5] !== completeMutation || $[6] !== queryClient) {
|
|
t2 = (_err, _variables, context) => {
|
|
if (context?.previousEdges) {
|
|
queryClient.setQueryData(canvasKeys.edges(context.graphId), context.previousEdges);
|
|
}
|
|
completeMutation(false);
|
|
};
|
|
$[5] = completeMutation;
|
|
$[6] = queryClient;
|
|
$[7] = t2;
|
|
} else {
|
|
t2 = $[7];
|
|
}
|
|
let t3;
|
|
if ($[8] !== completeMutation) {
|
|
t3 = () => {
|
|
completeMutation(true);
|
|
};
|
|
$[8] = completeMutation;
|
|
$[9] = t3;
|
|
} else {
|
|
t3 = $[9];
|
|
}
|
|
let t4;
|
|
if ($[10] !== queryClient) {
|
|
t4 = (_data, _error, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: canvasKeys.edges(variables.graphId)
|
|
});
|
|
};
|
|
$[10] = queryClient;
|
|
$[11] = t4;
|
|
} else {
|
|
t4 = $[11];
|
|
}
|
|
let t5;
|
|
if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) {
|
|
t5 = {
|
|
mutationFn: t0,
|
|
onMutate: t1,
|
|
onError: t2,
|
|
onSuccess: t3,
|
|
onSettled: t4
|
|
};
|
|
$[12] = t0;
|
|
$[13] = t1;
|
|
$[14] = t2;
|
|
$[15] = t3;
|
|
$[16] = t4;
|
|
$[17] = t5;
|
|
} else {
|
|
t5 = $[17];
|
|
}
|
|
return useMutation4(t5);
|
|
}
|
|
|
|
// src/db/hooks/useCreateEdge.ts
|
|
init_sync_store();
|
|
import { c as _c28 } from "react/compiler-runtime";
|
|
import { useMutation as useMutation5, useQueryClient as useQueryClient5 } from "@tanstack/react-query";
|
|
import { useSetAtom as useSetAtom18 } from "jotai";
|
|
function useCreateEdge() {
|
|
const $ = _c28(17);
|
|
const queryClient = useQueryClient5();
|
|
const supabase = useSupabaseClient();
|
|
const startMutation = useSetAtom18(startMutationAtom);
|
|
const completeMutation = useSetAtom18(completeMutationAtom);
|
|
let t0;
|
|
if ($[0] !== supabase) {
|
|
t0 = async (edge) => createGraphEdge(supabase, edge);
|
|
$[0] = supabase;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
let t1;
|
|
if ($[2] !== queryClient || $[3] !== startMutation) {
|
|
t1 = async (edge_0) => {
|
|
startMutation();
|
|
await queryClient.cancelQueries({
|
|
queryKey: canvasKeys.edges(edge_0.graph_id)
|
|
});
|
|
const previousEdges = queryClient.getQueryData(canvasKeys.edges(edge_0.graph_id));
|
|
const optimisticEdge = {
|
|
...edge_0,
|
|
id: `temp-${Date.now()}`,
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
};
|
|
queryClient.setQueryData(canvasKeys.edges(edge_0.graph_id), (old) => [...old || [], optimisticEdge]);
|
|
return {
|
|
previousEdges,
|
|
graphId: edge_0.graph_id,
|
|
optimisticId: optimisticEdge.id
|
|
};
|
|
};
|
|
$[2] = queryClient;
|
|
$[3] = startMutation;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
let t2;
|
|
let t3;
|
|
if ($[5] !== completeMutation || $[6] !== queryClient) {
|
|
t2 = (_err, _variables, context) => {
|
|
if (context?.previousEdges) {
|
|
queryClient.setQueryData(canvasKeys.edges(context.graphId), context.previousEdges);
|
|
}
|
|
completeMutation(false);
|
|
};
|
|
t3 = (newEdge, _variables_0, context_0) => {
|
|
if (context_0) {
|
|
queryClient.setQueryData(canvasKeys.edges(context_0.graphId), (old_0) => old_0?.map((edge_1) => edge_1.id === context_0.optimisticId ? newEdge : edge_1));
|
|
}
|
|
completeMutation(true);
|
|
};
|
|
$[5] = completeMutation;
|
|
$[6] = queryClient;
|
|
$[7] = t2;
|
|
$[8] = t3;
|
|
} else {
|
|
t2 = $[7];
|
|
t3 = $[8];
|
|
}
|
|
let t4;
|
|
if ($[9] !== queryClient) {
|
|
t4 = (_data, _error, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: canvasKeys.edges(variables.graph_id)
|
|
});
|
|
};
|
|
$[9] = queryClient;
|
|
$[10] = t4;
|
|
} else {
|
|
t4 = $[10];
|
|
}
|
|
let t5;
|
|
if ($[11] !== t0 || $[12] !== t1 || $[13] !== t2 || $[14] !== t3 || $[15] !== t4) {
|
|
t5 = {
|
|
mutationFn: t0,
|
|
onMutate: t1,
|
|
onError: t2,
|
|
onSuccess: t3,
|
|
onSettled: t4
|
|
};
|
|
$[11] = t0;
|
|
$[12] = t1;
|
|
$[13] = t2;
|
|
$[14] = t3;
|
|
$[15] = t4;
|
|
$[16] = t5;
|
|
} else {
|
|
t5 = $[16];
|
|
}
|
|
return useMutation5(t5);
|
|
}
|
|
|
|
// src/db/hooks/useDeleteEdge.ts
|
|
init_sync_store();
|
|
import { c as _c29 } from "react/compiler-runtime";
|
|
import { useMutation as useMutation6, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
|
|
import { useSetAtom as useSetAtom19 } from "jotai";
|
|
function useDeleteEdge() {
|
|
const $ = _c29(18);
|
|
const queryClient = useQueryClient6();
|
|
const supabase = useSupabaseClient();
|
|
const startMutation = useSetAtom19(startMutationAtom);
|
|
const completeMutation = useSetAtom19(completeMutationAtom);
|
|
let t0;
|
|
if ($[0] !== supabase) {
|
|
t0 = async (t12) => {
|
|
const {
|
|
edgeId
|
|
} = t12;
|
|
return deleteGraphEdge(supabase, edgeId);
|
|
};
|
|
$[0] = supabase;
|
|
$[1] = t0;
|
|
} else {
|
|
t0 = $[1];
|
|
}
|
|
let t1;
|
|
if ($[2] !== queryClient || $[3] !== startMutation) {
|
|
t1 = async (t22) => {
|
|
const {
|
|
edgeId: edgeId_0,
|
|
graphId
|
|
} = t22;
|
|
startMutation();
|
|
await queryClient.cancelQueries({
|
|
queryKey: canvasKeys.edges(graphId)
|
|
});
|
|
const previousEdges = queryClient.getQueryData(canvasKeys.edges(graphId));
|
|
if (previousEdges) {
|
|
queryClient.setQueryData(canvasKeys.edges(graphId), (old) => old?.filter((edge) => edge.id !== edgeId_0));
|
|
}
|
|
return {
|
|
previousEdges,
|
|
graphId
|
|
};
|
|
};
|
|
$[2] = queryClient;
|
|
$[3] = startMutation;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
let t2;
|
|
if ($[5] !== completeMutation || $[6] !== queryClient) {
|
|
t2 = (_err, _variables, context) => {
|
|
if (context?.previousEdges) {
|
|
queryClient.setQueryData(canvasKeys.edges(context.graphId), context.previousEdges);
|
|
}
|
|
completeMutation(false);
|
|
};
|
|
$[5] = completeMutation;
|
|
$[6] = queryClient;
|
|
$[7] = t2;
|
|
} else {
|
|
t2 = $[7];
|
|
}
|
|
let t3;
|
|
if ($[8] !== completeMutation) {
|
|
t3 = () => {
|
|
completeMutation(true);
|
|
};
|
|
$[8] = completeMutation;
|
|
$[9] = t3;
|
|
} else {
|
|
t3 = $[9];
|
|
}
|
|
let t4;
|
|
if ($[10] !== queryClient) {
|
|
t4 = (_data, _error, variables) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: canvasKeys.edges(variables.graphId)
|
|
});
|
|
};
|
|
$[10] = queryClient;
|
|
$[11] = t4;
|
|
} else {
|
|
t4 = $[11];
|
|
}
|
|
let t5;
|
|
if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) {
|
|
t5 = {
|
|
mutationFn: t0,
|
|
onMutate: t1,
|
|
onError: t2,
|
|
onSuccess: t3,
|
|
onSettled: t4
|
|
};
|
|
$[12] = t0;
|
|
$[13] = t1;
|
|
$[14] = t2;
|
|
$[15] = t3;
|
|
$[16] = t4;
|
|
$[17] = t5;
|
|
} else {
|
|
t5 = $[17];
|
|
}
|
|
return useMutation6(t5);
|
|
}
|
|
|
|
// src/styles/canvas-styles.ts
|
|
var defaultLightStyles = {
|
|
background: {
|
|
color: "#f0f0f0",
|
|
gradient: {
|
|
from: "#f0f0f0",
|
|
to: "#e0e0e0",
|
|
angle: 180
|
|
},
|
|
pattern: "none"
|
|
},
|
|
grid: {
|
|
visible: true,
|
|
minorLineColor: "rgba(0, 0, 0, 0.05)",
|
|
majorLineColor: "rgba(0, 0, 0, 0.1)",
|
|
opacity: 1
|
|
},
|
|
axes: {
|
|
visible: true,
|
|
color: "rgba(0, 0, 0, 0.3)",
|
|
labelColor: "rgba(0, 0, 0, 0.7)",
|
|
tickColor: "rgba(0, 0, 0, 0.3)"
|
|
},
|
|
nodes: {
|
|
defaultBackground: "#FAFAFA",
|
|
defaultBorderColor: "rgba(0, 0, 0, 0.06)",
|
|
defaultBorderRadius: 12,
|
|
selectedBorderColor: "#FF9800",
|
|
selectedGlowColor: "rgba(255, 152, 0, 0.5)",
|
|
shadowColor: "rgba(0, 0, 0, 0.1)",
|
|
shadowOpacity: 1,
|
|
draggingBackgroundColor: "#FAFAFAd9",
|
|
draggingBorderColor: "#CCCCCC",
|
|
resizeHandleColor: "rgba(0, 0, 0, 0.2)",
|
|
resizeHandleActiveColor: "rgba(59, 130, 246, 0.8)"
|
|
},
|
|
edges: {
|
|
defaultColor: "#cccccc",
|
|
defaultWeight: 1.5,
|
|
labelColor: "#FFFFFF",
|
|
labelStrokeColor: "#000000",
|
|
pathType: "bezier"
|
|
}
|
|
};
|
|
var defaultDarkStyles = {
|
|
background: {
|
|
color: "#0f172a",
|
|
gradient: {
|
|
from: "#0f172a",
|
|
to: "#1e293b",
|
|
angle: 180
|
|
},
|
|
pattern: "none"
|
|
},
|
|
grid: {
|
|
visible: true,
|
|
minorLineColor: "rgba(255, 255, 255, 0.06)",
|
|
majorLineColor: "rgba(255, 255, 255, 0.12)",
|
|
opacity: 1
|
|
},
|
|
axes: {
|
|
visible: true,
|
|
color: "rgba(255, 255, 255, 0.35)",
|
|
labelColor: "rgba(255, 255, 255, 0.7)",
|
|
tickColor: "rgba(255, 255, 255, 0.35)"
|
|
},
|
|
nodes: {
|
|
defaultBackground: "#FAFAFA",
|
|
defaultBorderColor: "rgba(0, 0, 0, 0.06)",
|
|
defaultBorderRadius: 12,
|
|
selectedBorderColor: "#FF9800",
|
|
selectedGlowColor: "rgba(255, 152, 0, 0.5)",
|
|
shadowColor: "rgba(0, 0, 0, 0.1)",
|
|
shadowOpacity: 1,
|
|
draggingBackgroundColor: "#FAFAFAd9",
|
|
draggingBorderColor: "#666666",
|
|
resizeHandleColor: "rgba(255, 255, 255, 0.2)",
|
|
resizeHandleActiveColor: "rgba(96, 165, 250, 0.8)"
|
|
},
|
|
edges: {
|
|
defaultColor: "#cccccc",
|
|
defaultWeight: 1.5,
|
|
labelColor: "#FFFFFF",
|
|
labelStrokeColor: "#000000",
|
|
pathType: "bezier"
|
|
}
|
|
};
|
|
function mergeWithDefaults(userStyles, isDark) {
|
|
const defaults = isDark ? defaultDarkStyles : defaultLightStyles;
|
|
if (!userStyles) return defaults;
|
|
return {
|
|
background: {
|
|
...defaults.background,
|
|
...userStyles.background
|
|
},
|
|
grid: {
|
|
...defaults.grid,
|
|
...userStyles.grid
|
|
},
|
|
axes: {
|
|
...defaults.axes,
|
|
...userStyles.axes
|
|
},
|
|
nodes: {
|
|
...defaults.nodes,
|
|
...userStyles.nodes
|
|
},
|
|
edges: {
|
|
...defaults.edges,
|
|
...userStyles.edges
|
|
}
|
|
};
|
|
}
|
|
|
|
// src/providers/CanvasProvider.tsx
|
|
import { c as _c31 } from "react/compiler-runtime";
|
|
import React5, { useEffect as useEffect5 } from "react";
|
|
import { Provider as JotaiProvider, useSetAtom as useSetAtom21 } from "jotai";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
|
// src/providers/CanvasStyleProvider.tsx
|
|
import { c as _c30 } from "react/compiler-runtime";
|
|
import React4, { createContext as createContext2, useContext as useContext2, useRef as useRef12, useLayoutEffect } from "react";
|
|
import { atom as atom31, useAtom as useAtom3, useSetAtom as useSetAtom20 } from "jotai";
|
|
import { jsx as _jsx4 } from "react/jsx-runtime";
|
|
var canvasStylesAtom = atom31(defaultLightStyles);
|
|
var canvasIsDarkAtom = atom31(false);
|
|
var canvasStyleOverridesAtom = atom31(void 0);
|
|
var CanvasStyleContext = /* @__PURE__ */ createContext2(null);
|
|
var CSS_VARIABLE_NAMES = ["--canvas-bg", "--node-bg", "--node-border", "--node-radius", "--node-selected-border", "--node-selected-glow", "--node-shadow", "--node-shadow-opacity", "--node-dragging-bg", "--node-dragging-border", "--node-resize-handle", "--node-resize-handle-active", "--edge-color", "--edge-weight", "--edge-label-color", "--edge-label-stroke"];
|
|
function applyStylesToElement(element, styles4) {
|
|
const {
|
|
nodes,
|
|
edges,
|
|
background
|
|
} = styles4;
|
|
element.style.setProperty("--canvas-bg", background.color || "");
|
|
element.style.setProperty("--node-bg", nodes.defaultBackground || "");
|
|
element.style.setProperty("--node-border", nodes.defaultBorderColor || "");
|
|
element.style.setProperty("--node-radius", nodes.defaultBorderRadius ? `${nodes.defaultBorderRadius}px` : "8px");
|
|
element.style.setProperty("--node-selected-border", nodes.selectedBorderColor || "");
|
|
element.style.setProperty("--node-selected-glow", nodes.selectedGlowColor || "");
|
|
element.style.setProperty("--node-shadow", nodes.shadowColor || "");
|
|
element.style.setProperty("--node-shadow-opacity", nodes.shadowOpacity?.toString() || "0.2");
|
|
element.style.setProperty("--node-dragging-bg", nodes.draggingBackgroundColor || "");
|
|
element.style.setProperty("--node-dragging-border", nodes.draggingBorderColor || "");
|
|
element.style.setProperty("--node-resize-handle", nodes.resizeHandleColor || "");
|
|
element.style.setProperty("--node-resize-handle-active", nodes.resizeHandleActiveColor || "");
|
|
element.style.setProperty("--edge-color", edges.defaultColor || "");
|
|
element.style.setProperty("--edge-weight", edges.defaultWeight?.toString() || "1.5");
|
|
element.style.setProperty("--edge-label-color", edges.labelColor || "");
|
|
element.style.setProperty("--edge-label-stroke", edges.labelStrokeColor || "");
|
|
}
|
|
function removeStylesFromElement(element) {
|
|
CSS_VARIABLE_NAMES.forEach((name) => {
|
|
element.style.removeProperty(name);
|
|
});
|
|
}
|
|
function CanvasStyleProvider(t0) {
|
|
const $ = _c30(24);
|
|
const {
|
|
children,
|
|
styles: propStyles,
|
|
isDark: t1
|
|
} = t0;
|
|
const propIsDark = t1 === void 0 ? false : t1;
|
|
const containerRef = useRef12(null);
|
|
const [styleOverrides, setStyleOverrides] = useAtom3(canvasStyleOverridesAtom);
|
|
const [isDarkAtom, setIsDarkAtom] = useAtom3(canvasIsDarkAtom);
|
|
const setCanvasStyles = useSetAtom20(canvasStylesAtom);
|
|
const effectiveStyles = styleOverrides ?? propStyles;
|
|
const effectiveIsDark = isDarkAtom || propIsDark;
|
|
let t2;
|
|
if ($[0] !== effectiveIsDark || $[1] !== effectiveStyles) {
|
|
t2 = mergeWithDefaults(effectiveStyles, effectiveIsDark);
|
|
$[0] = effectiveIsDark;
|
|
$[1] = effectiveStyles;
|
|
$[2] = t2;
|
|
} else {
|
|
t2 = $[2];
|
|
}
|
|
const mergedStyles = t2;
|
|
let t3;
|
|
let t4;
|
|
if ($[3] !== mergedStyles || $[4] !== setCanvasStyles) {
|
|
t3 = () => {
|
|
setCanvasStyles(mergedStyles);
|
|
};
|
|
t4 = [mergedStyles, setCanvasStyles];
|
|
$[3] = mergedStyles;
|
|
$[4] = setCanvasStyles;
|
|
$[5] = t3;
|
|
$[6] = t4;
|
|
} else {
|
|
t3 = $[5];
|
|
t4 = $[6];
|
|
}
|
|
useLayoutEffect(t3, t4);
|
|
let t5;
|
|
if ($[7] !== mergedStyles) {
|
|
t5 = (node) => {
|
|
containerRef.current = node;
|
|
if (!node) {
|
|
return;
|
|
}
|
|
applyStylesToElement(node, mergedStyles);
|
|
return () => removeStylesFromElement(node);
|
|
};
|
|
$[7] = mergedStyles;
|
|
$[8] = t5;
|
|
} else {
|
|
t5 = $[8];
|
|
}
|
|
const containerRefCallback = t5;
|
|
let t6;
|
|
if ($[9] !== setStyleOverrides) {
|
|
t6 = (newStyles) => {
|
|
setStyleOverrides(newStyles);
|
|
};
|
|
$[9] = setStyleOverrides;
|
|
$[10] = t6;
|
|
} else {
|
|
t6 = $[10];
|
|
}
|
|
const setStyles = t6;
|
|
let t7;
|
|
if ($[11] !== setIsDarkAtom) {
|
|
t7 = (isDark) => {
|
|
setIsDarkAtom(isDark);
|
|
};
|
|
$[11] = setIsDarkAtom;
|
|
$[12] = t7;
|
|
} else {
|
|
t7 = $[12];
|
|
}
|
|
const setDarkMode = t7;
|
|
let t8;
|
|
if ($[13] !== mergedStyles || $[14] !== setDarkMode || $[15] !== setStyles) {
|
|
t8 = {
|
|
styles: mergedStyles,
|
|
containerRef,
|
|
setStyles,
|
|
setDarkMode
|
|
};
|
|
$[13] = mergedStyles;
|
|
$[14] = setDarkMode;
|
|
$[15] = setStyles;
|
|
$[16] = t8;
|
|
} else {
|
|
t8 = $[16];
|
|
}
|
|
const contextValue = t8;
|
|
let t9;
|
|
if ($[17] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t9 = {
|
|
display: "contents"
|
|
};
|
|
$[17] = t9;
|
|
} else {
|
|
t9 = $[17];
|
|
}
|
|
let t10;
|
|
if ($[18] !== children || $[19] !== containerRefCallback) {
|
|
t10 = /* @__PURE__ */ _jsx4("div", {
|
|
ref: containerRefCallback,
|
|
className: "canvas-style-scope",
|
|
style: t9,
|
|
children
|
|
});
|
|
$[18] = children;
|
|
$[19] = containerRefCallback;
|
|
$[20] = t10;
|
|
} else {
|
|
t10 = $[20];
|
|
}
|
|
let t11;
|
|
if ($[21] !== contextValue || $[22] !== t10) {
|
|
t11 = /* @__PURE__ */ _jsx4(CanvasStyleContext, {
|
|
value: contextValue,
|
|
children: t10
|
|
});
|
|
$[21] = contextValue;
|
|
$[22] = t10;
|
|
$[23] = t11;
|
|
} else {
|
|
t11 = $[23];
|
|
}
|
|
return t11;
|
|
}
|
|
function useCanvasStyle() {
|
|
const context = useContext2(CanvasStyleContext);
|
|
if (!context) {
|
|
throw new Error("useCanvasStyle must be used within a CanvasStyleProvider");
|
|
}
|
|
return context.styles;
|
|
}
|
|
function useCanvasTheme() {
|
|
const $ = _c30(10);
|
|
const context = useContext2(CanvasStyleContext);
|
|
const [isDark, setIsDarkAtom] = useAtom3(canvasIsDarkAtom);
|
|
const [styleOverrides, setStyleOverrides] = useAtom3(canvasStyleOverridesAtom);
|
|
if (!context) {
|
|
throw new Error("useCanvasTheme must be used within a CanvasStyleProvider");
|
|
}
|
|
let t0;
|
|
if ($[0] !== setIsDarkAtom || $[1] !== setStyleOverrides) {
|
|
t0 = () => {
|
|
setStyleOverrides(void 0);
|
|
setIsDarkAtom(false);
|
|
};
|
|
$[0] = setIsDarkAtom;
|
|
$[1] = setStyleOverrides;
|
|
$[2] = t0;
|
|
} else {
|
|
t0 = $[2];
|
|
}
|
|
let t1;
|
|
if ($[3] !== context.setDarkMode || $[4] !== context.setStyles || $[5] !== context.styles || $[6] !== isDark || $[7] !== styleOverrides || $[8] !== t0) {
|
|
t1 = {
|
|
styles: context.styles,
|
|
isDark,
|
|
styleOverrides,
|
|
setDarkMode: context.setDarkMode,
|
|
setStyles: context.setStyles,
|
|
resetStyles: t0
|
|
};
|
|
$[3] = context.setDarkMode;
|
|
$[4] = context.setStyles;
|
|
$[5] = context.styles;
|
|
$[6] = isDark;
|
|
$[7] = styleOverrides;
|
|
$[8] = t0;
|
|
$[9] = t1;
|
|
} else {
|
|
t1 = $[9];
|
|
}
|
|
return t1;
|
|
}
|
|
|
|
// src/providers/CanvasProvider.tsx
|
|
init_graph_store();
|
|
init_graph_mutations();
|
|
import { Fragment as _Fragment, jsx as _jsx5 } from "react/jsx-runtime";
|
|
var defaultQueryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 1e3 * 60,
|
|
// 1 minute
|
|
refetchOnWindowFocus: false
|
|
}
|
|
}
|
|
});
|
|
function GraphLoader(t0) {
|
|
const $ = _c31(19);
|
|
const {
|
|
graphId,
|
|
onGraphLoad,
|
|
onGraphError,
|
|
children
|
|
} = t0;
|
|
const setCurrentGraphId = useSetAtom21(currentGraphIdAtom);
|
|
const loadGraphFromDb = useSetAtom21(loadGraphFromDbAtom);
|
|
const {
|
|
data: nodes,
|
|
error: nodesError,
|
|
isLoading: nodesLoading
|
|
} = useGraphNodes(graphId);
|
|
const {
|
|
data: edges,
|
|
error: edgesError,
|
|
isLoading: edgesLoading
|
|
} = useGraphEdges(graphId);
|
|
let t1;
|
|
let t2;
|
|
if ($[0] !== graphId || $[1] !== setCurrentGraphId) {
|
|
t1 = () => {
|
|
setCurrentGraphId(graphId);
|
|
};
|
|
t2 = [graphId, setCurrentGraphId];
|
|
$[0] = graphId;
|
|
$[1] = setCurrentGraphId;
|
|
$[2] = t1;
|
|
$[3] = t2;
|
|
} else {
|
|
t1 = $[2];
|
|
t2 = $[3];
|
|
}
|
|
useEffect5(t1, t2);
|
|
let t3;
|
|
let t4;
|
|
if ($[4] !== edges || $[5] !== edgesLoading || $[6] !== loadGraphFromDb || $[7] !== nodes || $[8] !== nodesLoading || $[9] !== onGraphLoad) {
|
|
t3 = () => {
|
|
if (nodes && edges && !nodesLoading && !edgesLoading) {
|
|
loadGraphFromDb(nodes, edges);
|
|
onGraphLoad?.(nodes, edges);
|
|
}
|
|
};
|
|
t4 = [nodes, edges, nodesLoading, edgesLoading, loadGraphFromDb, onGraphLoad];
|
|
$[4] = edges;
|
|
$[5] = edgesLoading;
|
|
$[6] = loadGraphFromDb;
|
|
$[7] = nodes;
|
|
$[8] = nodesLoading;
|
|
$[9] = onGraphLoad;
|
|
$[10] = t3;
|
|
$[11] = t4;
|
|
} else {
|
|
t3 = $[10];
|
|
t4 = $[11];
|
|
}
|
|
useEffect5(t3, t4);
|
|
let t5;
|
|
let t6;
|
|
if ($[12] !== edgesError || $[13] !== nodesError || $[14] !== onGraphError) {
|
|
t5 = () => {
|
|
const error = nodesError || edgesError;
|
|
if (error) {
|
|
onGraphError?.(error);
|
|
}
|
|
};
|
|
t6 = [nodesError, edgesError, onGraphError];
|
|
$[12] = edgesError;
|
|
$[13] = nodesError;
|
|
$[14] = onGraphError;
|
|
$[15] = t5;
|
|
$[16] = t6;
|
|
} else {
|
|
t5 = $[15];
|
|
t6 = $[16];
|
|
}
|
|
useEffect5(t5, t6);
|
|
let t7;
|
|
if ($[17] !== children) {
|
|
t7 = /* @__PURE__ */ _jsx5(_Fragment, {
|
|
children
|
|
});
|
|
$[17] = children;
|
|
$[18] = t7;
|
|
} else {
|
|
t7 = $[18];
|
|
}
|
|
return t7;
|
|
}
|
|
function CanvasProvider(t0) {
|
|
const $ = _c31(15);
|
|
const {
|
|
adapter,
|
|
graphId,
|
|
styles: styles4,
|
|
isDark: t1,
|
|
queryClient: t2,
|
|
children,
|
|
onGraphLoad,
|
|
onGraphError
|
|
} = t0;
|
|
const isDark = t1 === void 0 ? false : t1;
|
|
const queryClient = t2 === void 0 ? defaultQueryClient : t2;
|
|
let t3;
|
|
if ($[0] !== children || $[1] !== graphId || $[2] !== onGraphError || $[3] !== onGraphLoad) {
|
|
t3 = /* @__PURE__ */ _jsx5(GraphLoader, {
|
|
graphId,
|
|
onGraphLoad,
|
|
onGraphError,
|
|
children
|
|
});
|
|
$[0] = children;
|
|
$[1] = graphId;
|
|
$[2] = onGraphError;
|
|
$[3] = onGraphLoad;
|
|
$[4] = t3;
|
|
} else {
|
|
t3 = $[4];
|
|
}
|
|
let t4;
|
|
if ($[5] !== isDark || $[6] !== styles4 || $[7] !== t3) {
|
|
t4 = /* @__PURE__ */ _jsx5(CanvasStyleProvider, {
|
|
styles: styles4,
|
|
isDark,
|
|
children: t3
|
|
});
|
|
$[5] = isDark;
|
|
$[6] = styles4;
|
|
$[7] = t3;
|
|
$[8] = t4;
|
|
} else {
|
|
t4 = $[8];
|
|
}
|
|
let t5;
|
|
if ($[9] !== adapter || $[10] !== t4) {
|
|
t5 = /* @__PURE__ */ _jsx5(CanvasDbProvider, {
|
|
adapter,
|
|
children: t4
|
|
});
|
|
$[9] = adapter;
|
|
$[10] = t4;
|
|
$[11] = t5;
|
|
} else {
|
|
t5 = $[11];
|
|
}
|
|
let t6;
|
|
if ($[12] !== queryClient || $[13] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx5(JotaiProvider, {
|
|
children: /* @__PURE__ */ _jsx5(QueryClientProvider, {
|
|
client: queryClient,
|
|
children: t5
|
|
})
|
|
});
|
|
$[12] = queryClient;
|
|
$[13] = t5;
|
|
$[14] = t6;
|
|
} else {
|
|
t6 = $[14];
|
|
}
|
|
return t6;
|
|
}
|
|
|
|
// src/components/Canvas.tsx
|
|
import { c as _c46 } from "react/compiler-runtime";
|
|
import React16 from "react";
|
|
|
|
// src/components/Viewport.tsx
|
|
init_viewport_store();
|
|
import { c as _c36 } from "react/compiler-runtime";
|
|
import React8, { useEffect as useEffect11, useRef as useRef17 } from "react";
|
|
import { useAtomValue as useAtomValue25 } from "jotai";
|
|
|
|
// src/gestures/useCanvasGestures.ts
|
|
init_viewport_store();
|
|
init_selection_store();
|
|
init_input_classifier();
|
|
init_input_store();
|
|
init_reduced_motion_store();
|
|
init_selection_path_store();
|
|
import { useRef as useRef15, useEffect as useEffect7, useMemo } from "react";
|
|
import { useAtom as useAtom4, useSetAtom as useSetAtom22, useAtomValue as useAtomValue23 } from "jotai";
|
|
import { useGesture as useGesture2 } from "@use-gesture/react";
|
|
|
|
// src/gestures/normalize.ts
|
|
init_input_classifier();
|
|
init_types2();
|
|
function extractModifiers(e) {
|
|
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
|
return NO_MODIFIERS;
|
|
}
|
|
return {
|
|
shift: e.shiftKey,
|
|
ctrl: e.ctrlKey,
|
|
alt: e.altKey,
|
|
meta: e.metaKey
|
|
};
|
|
}
|
|
function clampButton(raw) {
|
|
if (raw === 1) return 1;
|
|
if (raw === 2) return 2;
|
|
return 0;
|
|
}
|
|
function lifecycleFromEventType(type) {
|
|
switch (type) {
|
|
case "pointerdown":
|
|
return "down";
|
|
case "pointerup":
|
|
return "up";
|
|
case "pointercancel":
|
|
return "cancel";
|
|
default:
|
|
return "move";
|
|
}
|
|
}
|
|
function normalizePointer(e) {
|
|
const classified = classifyPointer(e);
|
|
return {
|
|
pointerId: e.pointerId,
|
|
lifecycle: lifecycleFromEventType(e.type),
|
|
source: classified.source,
|
|
button: clampButton(e.button),
|
|
modifiers: extractModifiers(e),
|
|
screenX: e.clientX,
|
|
screenY: e.clientY,
|
|
pressure: e.pressure,
|
|
timestamp: e.timeStamp
|
|
};
|
|
}
|
|
|
|
// src/gestures/timed-state.ts
|
|
var LONG_PRESS_TIMER = "long-press";
|
|
var SETTLE_TIMER = "settle";
|
|
var DEFAULT_LONG_PRESS_MS = 600;
|
|
var DEFAULT_MULTI_TAP_WINDOW_MS = 300;
|
|
var IDLE = {
|
|
tag: "idle",
|
|
tapCount: 0
|
|
};
|
|
var DEFAULT_CONFIG = {
|
|
longPressMs: DEFAULT_LONG_PRESS_MS,
|
|
multiTapWindowMs: DEFAULT_MULTI_TAP_WINDOW_MS
|
|
};
|
|
function transition(state, event, config = DEFAULT_CONFIG) {
|
|
switch (event) {
|
|
case "down":
|
|
return onDown(state, config);
|
|
case "up":
|
|
return onUp(state, config);
|
|
case "move-beyond-threshold":
|
|
return onMoveBeyond(state);
|
|
case "cancel":
|
|
return onCancel();
|
|
case "timer:long-press":
|
|
return onLongPressTimer(state);
|
|
case "timer:settle":
|
|
return onSettleTimer();
|
|
default:
|
|
return {
|
|
state
|
|
};
|
|
}
|
|
}
|
|
function onDown(state, config) {
|
|
switch (state.tag) {
|
|
case "idle":
|
|
return {
|
|
state: {
|
|
tag: "pressed",
|
|
tapCount: 0
|
|
},
|
|
cancelTimer: SETTLE_TIMER,
|
|
scheduleTimer: {
|
|
id: LONG_PRESS_TIMER,
|
|
delayMs: config.longPressMs
|
|
}
|
|
};
|
|
case "released":
|
|
return {
|
|
state: {
|
|
tag: "pressed",
|
|
tapCount: state.tapCount
|
|
},
|
|
cancelTimer: SETTLE_TIMER,
|
|
scheduleTimer: {
|
|
id: LONG_PRESS_TIMER,
|
|
delayMs: config.longPressMs
|
|
}
|
|
};
|
|
default:
|
|
return {
|
|
state
|
|
};
|
|
}
|
|
}
|
|
function onUp(state, config) {
|
|
if (state.tag !== "pressed") {
|
|
if (state.tag === "long-pressed") {
|
|
return {
|
|
state: IDLE,
|
|
cancelTimer: LONG_PRESS_TIMER
|
|
};
|
|
}
|
|
return {
|
|
state
|
|
};
|
|
}
|
|
const newCount = state.tapCount + 1;
|
|
const emit = tapTypeForCount(newCount);
|
|
return {
|
|
state: {
|
|
tag: "released",
|
|
tapCount: newCount
|
|
},
|
|
emit,
|
|
cancelTimer: LONG_PRESS_TIMER,
|
|
scheduleTimer: {
|
|
id: SETTLE_TIMER,
|
|
delayMs: config.multiTapWindowMs
|
|
}
|
|
};
|
|
}
|
|
function onMoveBeyond(state) {
|
|
if (state.tag === "pressed") {
|
|
return {
|
|
state: IDLE,
|
|
cancelTimer: LONG_PRESS_TIMER
|
|
};
|
|
}
|
|
return {
|
|
state
|
|
};
|
|
}
|
|
function onCancel() {
|
|
return {
|
|
state: IDLE,
|
|
cancelTimer: LONG_PRESS_TIMER
|
|
};
|
|
}
|
|
function onLongPressTimer(state) {
|
|
if (state.tag === "pressed") {
|
|
return {
|
|
state: {
|
|
tag: "long-pressed",
|
|
tapCount: 0
|
|
},
|
|
emit: "long-press"
|
|
};
|
|
}
|
|
return {
|
|
state
|
|
};
|
|
}
|
|
function onSettleTimer() {
|
|
return {
|
|
state: IDLE
|
|
};
|
|
}
|
|
function tapTypeForCount(count) {
|
|
switch (count) {
|
|
case 2:
|
|
return "double-tap";
|
|
case 3:
|
|
return "triple-tap";
|
|
default:
|
|
return "tap";
|
|
}
|
|
}
|
|
|
|
// src/gestures/timed-state-runner.ts
|
|
var TimedStateRunner = class {
|
|
constructor(config) {
|
|
__publicField(this, "state", IDLE);
|
|
__publicField(this, "timers", /* @__PURE__ */ new Map());
|
|
/** Called when the state machine emits a gesture (tap, double-tap, long-press, etc.) */
|
|
__publicField(this, "onEmit", null);
|
|
this.config = {
|
|
longPressMs: config?.longPressMs ?? DEFAULT_LONG_PRESS_MS,
|
|
multiTapWindowMs: config?.multiTapWindowMs ?? DEFAULT_MULTI_TAP_WINDOW_MS
|
|
};
|
|
}
|
|
/**
|
|
* Feed a lifecycle event into the state machine.
|
|
* Returns the emitted gesture type if any (synchronous emit only).
|
|
* Timer-based emits are delivered asynchronously via `onEmit`.
|
|
*/
|
|
feed(event) {
|
|
return this.apply(event);
|
|
}
|
|
/** Current state tag (for debugging/testing). */
|
|
get tag() {
|
|
return this.state.tag;
|
|
}
|
|
/** Destroy: cancel all pending timers. */
|
|
destroy() {
|
|
for (const timer of this.timers.values()) {
|
|
clearTimeout(timer);
|
|
}
|
|
this.timers.clear();
|
|
this.state = IDLE;
|
|
this.onEmit = null;
|
|
}
|
|
apply(event) {
|
|
const result = transition(this.state, event, this.config);
|
|
this.state = result.state;
|
|
if (result.cancelTimer) {
|
|
const timer = this.timers.get(result.cancelTimer);
|
|
if (timer !== void 0) {
|
|
clearTimeout(timer);
|
|
this.timers.delete(result.cancelTimer);
|
|
}
|
|
}
|
|
if (result.scheduleTimer) {
|
|
const {
|
|
id,
|
|
delayMs
|
|
} = result.scheduleTimer;
|
|
const existing = this.timers.get(id);
|
|
if (existing !== void 0) {
|
|
clearTimeout(existing);
|
|
}
|
|
this.timers.set(id, setTimeout(() => {
|
|
this.timers.delete(id);
|
|
const timerEvent = `timer:${id}`;
|
|
const timerResult = transition(this.state, timerEvent, this.config);
|
|
this.state = timerResult.state;
|
|
if (timerResult.emit) {
|
|
this.onEmit?.(timerResult.emit);
|
|
}
|
|
}, delayMs));
|
|
}
|
|
return result.emit;
|
|
}
|
|
};
|
|
|
|
// src/gestures/specificity.ts
|
|
init_types2();
|
|
var SCORE_TYPE = 128;
|
|
var SCORE_KEY = 64;
|
|
var SCORE_CODE = 64;
|
|
var SCORE_SUBJECT = 32;
|
|
var SCORE_MODIFIER_POSITIVE = 16;
|
|
var SCORE_MODIFIER_NEGATIVE = 8;
|
|
var SCORE_HELD_KEY_POSITIVE = 16;
|
|
var SCORE_HELD_KEY_NEGATIVE = 8;
|
|
var SCORE_SOURCE = 4;
|
|
var SCORE_BUTTON = 2;
|
|
function specificity(pattern, event) {
|
|
let score = 0;
|
|
if (pattern.kind !== void 0) {
|
|
const actualKind = isKeyInputEvent(event) ? "key" : "pointer";
|
|
if (pattern.kind !== actualKind) return -1;
|
|
}
|
|
if (pattern.type !== void 0) {
|
|
if (!isPointerGestureEvent(event) || pattern.type !== event.type) return -1;
|
|
score += SCORE_TYPE;
|
|
}
|
|
if (pattern.subjectKind !== void 0) {
|
|
if (event.subject === void 0 || pattern.subjectKind !== event.subject.kind) return -1;
|
|
score += SCORE_SUBJECT;
|
|
}
|
|
if (pattern.phase !== void 0 && pattern.phase !== event.phase) {
|
|
return -1;
|
|
}
|
|
if (pattern.source !== void 0) {
|
|
if (!isPointerGestureEvent(event)) return -1;
|
|
if (pattern.source !== event.source) return -1;
|
|
score += SCORE_SOURCE;
|
|
}
|
|
if (pattern.button !== void 0) {
|
|
if (!isPointerGestureEvent(event)) return -1;
|
|
if (pattern.button !== event.button) return -1;
|
|
score += SCORE_BUTTON;
|
|
}
|
|
if (pattern.key !== void 0) {
|
|
if (!isKeyInputEvent(event) || pattern.key !== event.key) return -1;
|
|
score += SCORE_KEY;
|
|
}
|
|
if (pattern.code !== void 0) {
|
|
if (!isKeyInputEvent(event) || pattern.code !== event.code) return -1;
|
|
score += SCORE_CODE;
|
|
}
|
|
if (pattern.modifiers !== void 0) {
|
|
const ms = modifierScore(pattern.modifiers, event.modifiers);
|
|
if (ms === -1) return -1;
|
|
score += ms;
|
|
}
|
|
if (pattern.heldKeys !== void 0) {
|
|
const hs = heldKeyScore(pattern.heldKeys, event.heldKeys);
|
|
if (hs === -1) return -1;
|
|
score += hs;
|
|
}
|
|
return score;
|
|
}
|
|
function modifierScore(pattern, actual) {
|
|
let score = 0;
|
|
const keys = ["shift", "ctrl", "alt", "meta"];
|
|
for (const key of keys) {
|
|
const required = pattern[key];
|
|
if (required === void 0) continue;
|
|
if (required !== actual[key]) return -1;
|
|
score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE;
|
|
}
|
|
if (pattern.custom) {
|
|
const actualCustom = actual.custom ?? {};
|
|
for (const [key, required] of Object.entries(pattern.custom)) {
|
|
const actualVal = actualCustom[key] ?? false;
|
|
if (required !== actualVal) return -1;
|
|
score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE;
|
|
}
|
|
}
|
|
return score;
|
|
}
|
|
function heldKeyScore(pattern, actual) {
|
|
let score = 0;
|
|
for (const [key, required] of Object.entries(pattern.byKey ?? {})) {
|
|
const actualVal = actual.byKey[key] ?? false;
|
|
if (required !== actualVal) return -1;
|
|
score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE;
|
|
}
|
|
for (const [code, required] of Object.entries(pattern.byCode ?? {})) {
|
|
const actualVal = actual.byCode[code] ?? false;
|
|
if (required !== actualVal) return -1;
|
|
score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE;
|
|
}
|
|
return score;
|
|
}
|
|
|
|
// src/gestures/mapper.ts
|
|
init_types2();
|
|
var WILDCARD = "__wildcard__";
|
|
function getPatternBucketKey(binding) {
|
|
const {
|
|
pattern
|
|
} = binding;
|
|
if (pattern.type !== void 0) {
|
|
return `pointer:${pattern.type}`;
|
|
}
|
|
if (pattern.kind === "key" || pattern.key !== void 0 || pattern.code !== void 0) {
|
|
return pattern.phase === "down" || pattern.phase === "up" ? `key:${pattern.phase}` : null;
|
|
}
|
|
return null;
|
|
}
|
|
function getEventBucketKey(event) {
|
|
return isKeyInputEvent(event) ? `key:${event.phase}` : `pointer:${event.type}`;
|
|
}
|
|
function indexContext(ctx) {
|
|
const typed = /* @__PURE__ */ new Map();
|
|
const wildcards = [];
|
|
for (const binding of ctx.bindings) {
|
|
const key = getPatternBucketKey(binding);
|
|
if (key === null) {
|
|
wildcards.push(binding);
|
|
} else {
|
|
let bucket = typed.get(key);
|
|
if (!bucket) {
|
|
bucket = [];
|
|
typed.set(key, bucket);
|
|
}
|
|
bucket.push(binding);
|
|
}
|
|
}
|
|
const buckets = /* @__PURE__ */ new Map();
|
|
for (const [key, bucket] of typed) {
|
|
buckets.set(key, bucket);
|
|
}
|
|
if (wildcards.length > 0) {
|
|
buckets.set(WILDCARD, wildcards);
|
|
}
|
|
return {
|
|
contextId: ctx.id,
|
|
priority: ctx.priority,
|
|
enabled: ctx.enabled,
|
|
buckets
|
|
};
|
|
}
|
|
function buildMappingIndex(contexts) {
|
|
return contexts.map(indexContext).sort((a, b) => a.priority - b.priority);
|
|
}
|
|
function resolve(event, index, guard) {
|
|
let winner = null;
|
|
for (const ctx of index) {
|
|
if (!ctx.enabled) continue;
|
|
const bucket = ctx.buckets.get(getEventBucketKey(event));
|
|
const wildcardBucket = ctx.buckets.get(WILDCARD);
|
|
if (!bucket && !wildcardBucket) continue;
|
|
let bestScore = -1;
|
|
let bestBinding = null;
|
|
for (const binding of [...bucket ?? [], ...wildcardBucket ?? []]) {
|
|
const score = specificity(binding.pattern, event);
|
|
if (score === -1) continue;
|
|
if (binding.when && !binding.when(guard)) continue;
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
bestBinding = binding;
|
|
}
|
|
}
|
|
if (bestBinding !== null) {
|
|
const action = {
|
|
actionId: bestBinding.actionId,
|
|
binding: bestBinding,
|
|
contextId: ctx.contextId,
|
|
score: bestScore,
|
|
consumed: bestBinding.consumeInput === true
|
|
};
|
|
if (action.consumed) {
|
|
return action;
|
|
}
|
|
if (winner === null) {
|
|
winner = action;
|
|
}
|
|
}
|
|
}
|
|
return winner;
|
|
}
|
|
|
|
// src/gestures/useCanvasGestures.ts
|
|
init_dispatcher();
|
|
|
|
// src/gestures/keyboard-contexts.ts
|
|
var SEARCH_CONTEXT = {
|
|
id: "search-active",
|
|
priority: 25,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "search-next-enter",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Enter",
|
|
modifiers: {
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "search-next-result",
|
|
when: (ctx) => ctx.isSearchActive && !ctx.commandLineVisible,
|
|
consumeInput: true
|
|
}, {
|
|
id: "search-prev-enter",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Enter",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "search-prev-result",
|
|
when: (ctx) => ctx.isSearchActive && !ctx.commandLineVisible,
|
|
consumeInput: true
|
|
}, {
|
|
id: "search-next-ctrl-g",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "g",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "search-next-result",
|
|
when: (ctx) => ctx.isSearchActive,
|
|
consumeInput: true
|
|
}, {
|
|
id: "search-prev-ctrl-g",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "g",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "search-prev-result",
|
|
when: (ctx) => ctx.isSearchActive,
|
|
consumeInput: true
|
|
}, {
|
|
id: "search-next-meta-g",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "g",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "search-next-result",
|
|
when: (ctx) => ctx.isSearchActive,
|
|
consumeInput: true
|
|
}, {
|
|
id: "search-prev-meta-g",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "g",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "search-prev-result",
|
|
when: (ctx) => ctx.isSearchActive,
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
var KEYBOARD_MANIPULATE_CONTEXT = {
|
|
id: "keyboard:manipulate",
|
|
priority: 30,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "manipulate-arrow-up",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowUp",
|
|
modifiers: {
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "nudge-selection-up",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-arrow-down",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowDown",
|
|
modifiers: {
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "nudge-selection-down",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-arrow-left",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowLeft",
|
|
modifiers: {
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "nudge-selection-left",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-arrow-right",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowRight",
|
|
modifiers: {
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "nudge-selection-right",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-shift-arrow-up",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowUp",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "nudge-selection-up-large",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-shift-arrow-down",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowDown",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "nudge-selection-down-large",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-shift-arrow-left",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowLeft",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "nudge-selection-left-large",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-shift-arrow-right",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowRight",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "nudge-selection-right-large",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "manipulate-escape",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Escape"
|
|
},
|
|
actionId: "exit-keyboard-manipulate-mode",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
var KEYBOARD_NAVIGATE_CONTEXT = {
|
|
id: "keyboard:navigate",
|
|
priority: 40,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "navigate-arrow-up",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowUp"
|
|
},
|
|
actionId: "navigate-focus-up",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "navigate-arrow-down",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowDown"
|
|
},
|
|
actionId: "navigate-focus-down",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "navigate-arrow-left",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowLeft"
|
|
},
|
|
actionId: "navigate-focus-left",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "navigate-arrow-right",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "ArrowRight"
|
|
},
|
|
actionId: "navigate-focus-right",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "navigate-enter",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Enter"
|
|
},
|
|
actionId: "enter-keyboard-manipulate-mode",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "navigate-space",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
code: "Space"
|
|
},
|
|
actionId: "activate-focused-node",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "navigate-tab",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Tab",
|
|
modifiers: {
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "cycle-focus-forward",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}, {
|
|
id: "navigate-shift-tab",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Tab",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "cycle-focus-backward",
|
|
when: (ctx) => ctx.keyboardInteractionMode === "navigate",
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
|
|
// src/gestures/pointer-bindings.ts
|
|
var POINTER_BINDINGS = [
|
|
// --- Pointer taps ---
|
|
{
|
|
id: "tap-node",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "select-node"
|
|
},
|
|
{
|
|
id: "tap-edge",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "edge"
|
|
},
|
|
actionId: "select-edge"
|
|
},
|
|
{
|
|
id: "tap-bg",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "background"
|
|
},
|
|
actionId: "clear-selection"
|
|
},
|
|
{
|
|
id: "shift-tap-node",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "node",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "toggle-selection"
|
|
},
|
|
// --- Right-click ---
|
|
{
|
|
id: "rc-node",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "node",
|
|
button: 2
|
|
},
|
|
actionId: "context-menu",
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "rc-bg",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "background",
|
|
button: 2
|
|
},
|
|
actionId: "context-menu",
|
|
consumeInput: true
|
|
},
|
|
// --- Double/triple tap ---
|
|
{
|
|
id: "dtap-node",
|
|
pattern: {
|
|
type: "double-tap",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "fit-to-view"
|
|
},
|
|
{
|
|
id: "ttap-node",
|
|
pattern: {
|
|
type: "triple-tap",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "toggle-lock"
|
|
},
|
|
// --- Drags ---
|
|
{
|
|
id: "drag-node",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "move-node"
|
|
},
|
|
{
|
|
id: "drag-bg-finger",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "background",
|
|
source: "finger"
|
|
},
|
|
actionId: "pan"
|
|
},
|
|
{
|
|
id: "drag-bg-mouse",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "background",
|
|
source: "mouse"
|
|
},
|
|
actionId: "pan"
|
|
},
|
|
{
|
|
id: "drag-bg-pencil",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "background",
|
|
source: "pencil"
|
|
},
|
|
actionId: "lasso-select"
|
|
},
|
|
{
|
|
id: "space-drag-pan",
|
|
pattern: {
|
|
type: "drag",
|
|
heldKeys: {
|
|
byCode: {
|
|
Space: true
|
|
}
|
|
}
|
|
},
|
|
actionId: "pan"
|
|
},
|
|
{
|
|
id: "shift-drag-bg",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "background",
|
|
modifiers: {
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "rect-select"
|
|
},
|
|
// Right/middle drag — no-ops by default
|
|
{
|
|
id: "rdrag-node",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "node",
|
|
button: 2
|
|
},
|
|
actionId: "none"
|
|
},
|
|
{
|
|
id: "rdrag-bg",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "background",
|
|
button: 2
|
|
},
|
|
actionId: "none"
|
|
},
|
|
// --- Long-press ---
|
|
{
|
|
id: "lp-node",
|
|
pattern: {
|
|
type: "long-press",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "context-menu"
|
|
},
|
|
{
|
|
id: "lp-bg-finger",
|
|
pattern: {
|
|
type: "long-press",
|
|
subjectKind: "background",
|
|
source: "finger"
|
|
},
|
|
actionId: "create-node"
|
|
},
|
|
// --- Pinch / scroll ---
|
|
{
|
|
id: "pinch-bg",
|
|
pattern: {
|
|
type: "pinch",
|
|
subjectKind: "background"
|
|
},
|
|
actionId: "zoom"
|
|
},
|
|
{
|
|
id: "scroll-any",
|
|
pattern: {
|
|
type: "scroll"
|
|
},
|
|
actionId: "zoom"
|
|
},
|
|
{
|
|
id: "pinch-node",
|
|
pattern: {
|
|
type: "pinch",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "split-node"
|
|
}
|
|
];
|
|
|
|
// src/gestures/keyboard-bindings.ts
|
|
var isCommandShortcut = (ctx) => !ctx.commandLineVisible;
|
|
var KEYBOARD_BINDINGS = [
|
|
{
|
|
id: "slash-open-command-line",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
code: "Slash",
|
|
modifiers: {
|
|
ctrl: false,
|
|
meta: false,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "open-command-line",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "escape-fallback",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Escape"
|
|
},
|
|
actionId: "escape-input",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "delete-selection",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Delete"
|
|
},
|
|
actionId: "delete-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "backspace-delete-selection",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Backspace",
|
|
modifiers: {
|
|
ctrl: false,
|
|
meta: false
|
|
}
|
|
},
|
|
actionId: "delete-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
// Search
|
|
{
|
|
id: "open-search-ctrl-f",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "f",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "open-search",
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "open-search-meta-f",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "f",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "open-search",
|
|
consumeInput: true
|
|
},
|
|
// Copy / Cut / Paste
|
|
{
|
|
id: "copy-selection-ctrl-c",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "c",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "copy-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "copy-selection-meta-c",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "c",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "copy-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "cut-selection-ctrl-x",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "x",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "cut-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "cut-selection-meta-x",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "x",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "cut-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "paste-selection-ctrl-v",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "v",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "paste-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "paste-selection-meta-v",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "v",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "paste-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
// Duplicate
|
|
{
|
|
id: "duplicate-selection-ctrl-d",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "d",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "duplicate-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "duplicate-selection-meta-d",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "d",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "duplicate-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
// Select All
|
|
{
|
|
id: "select-all-ctrl-a",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "a",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "select-all",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "select-all-meta-a",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "a",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "select-all",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
// Merge
|
|
{
|
|
id: "merge-selection-ctrl-m",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "m",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "merge-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "merge-selection-meta-m",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "m",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "merge-selection",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
// Undo / Redo
|
|
{
|
|
id: "undo-ctrl-z",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "z",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "undo",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "undo-meta-z",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "z",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: false
|
|
}
|
|
},
|
|
actionId: "undo",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "redo-ctrl-shift-z",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "z",
|
|
modifiers: {
|
|
ctrl: true,
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "redo",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "redo-meta-shift-z",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "z",
|
|
modifiers: {
|
|
meta: true,
|
|
shift: true
|
|
}
|
|
},
|
|
actionId: "redo",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "redo-ctrl-y",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "y",
|
|
modifiers: {
|
|
ctrl: true
|
|
}
|
|
},
|
|
actionId: "redo",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
},
|
|
{
|
|
id: "redo-meta-y",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "y",
|
|
modifiers: {
|
|
meta: true
|
|
}
|
|
},
|
|
actionId: "redo",
|
|
when: isCommandShortcut,
|
|
consumeInput: true
|
|
}
|
|
];
|
|
|
|
// src/gestures/pointer-contexts.ts
|
|
var DEFAULT_CONTEXT = {
|
|
id: "default",
|
|
priority: 100,
|
|
enabled: true,
|
|
bindings: [...POINTER_BINDINGS, ...KEYBOARD_BINDINGS]
|
|
};
|
|
var PICK_NODE_CONTEXT = {
|
|
id: "input-mode:pickNode",
|
|
priority: 5,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "pick-tap-node",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "resolve-pick-node",
|
|
consumeInput: true
|
|
}, {
|
|
id: "pick-cancel-bg",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "background"
|
|
},
|
|
actionId: "cancel-pick",
|
|
consumeInput: true
|
|
}, {
|
|
id: "pick-cancel-key",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Escape"
|
|
},
|
|
actionId: "cancel-pick",
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
var PICK_NODES_CONTEXT = {
|
|
id: "input-mode:pickNodes",
|
|
priority: 5,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "pick-tap-node",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "node"
|
|
},
|
|
actionId: "resolve-pick-node",
|
|
consumeInput: true
|
|
}, {
|
|
id: "pick-done",
|
|
pattern: {
|
|
type: "double-tap",
|
|
subjectKind: "background"
|
|
},
|
|
actionId: "finish-pick-nodes",
|
|
consumeInput: true
|
|
}, {
|
|
id: "pick-cancel-bg",
|
|
pattern: {
|
|
type: "tap",
|
|
subjectKind: "background",
|
|
button: 2
|
|
},
|
|
actionId: "cancel-pick",
|
|
consumeInput: true
|
|
}, {
|
|
id: "pick-cancel-key",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Escape"
|
|
},
|
|
actionId: "cancel-pick",
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
var PICK_POINT_CONTEXT = {
|
|
id: "input-mode:pickPoint",
|
|
priority: 5,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "pick-tap",
|
|
pattern: {
|
|
type: "tap"
|
|
},
|
|
actionId: "resolve-pick-point",
|
|
consumeInput: true
|
|
}, {
|
|
id: "pick-cancel-key",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Escape"
|
|
},
|
|
actionId: "cancel-pick",
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
var INPUT_MODE_CONTEXTS = {
|
|
normal: null,
|
|
pickNode: PICK_NODE_CONTEXT,
|
|
pickNodes: PICK_NODES_CONTEXT,
|
|
pickPoint: PICK_POINT_CONTEXT,
|
|
text: null,
|
|
select: null
|
|
};
|
|
|
|
// src/gestures/contexts.ts
|
|
var PALM_REJECTION_CONTEXT = {
|
|
id: "palm-rejection",
|
|
priority: 0,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "pr-finger-tap",
|
|
pattern: {
|
|
type: "tap",
|
|
source: "finger"
|
|
},
|
|
actionId: "none",
|
|
when: (ctx) => ctx.isStylusActive,
|
|
consumeInput: true
|
|
}, {
|
|
id: "pr-finger-dtap",
|
|
pattern: {
|
|
type: "double-tap",
|
|
source: "finger"
|
|
},
|
|
actionId: "none",
|
|
when: (ctx) => ctx.isStylusActive,
|
|
consumeInput: true
|
|
}, {
|
|
id: "pr-finger-ttap",
|
|
pattern: {
|
|
type: "triple-tap",
|
|
source: "finger"
|
|
},
|
|
actionId: "none",
|
|
when: (ctx) => ctx.isStylusActive,
|
|
consumeInput: true
|
|
}, {
|
|
id: "pr-finger-lp",
|
|
pattern: {
|
|
type: "long-press",
|
|
source: "finger"
|
|
},
|
|
actionId: "none",
|
|
when: (ctx) => ctx.isStylusActive,
|
|
consumeInput: true
|
|
}, {
|
|
id: "pr-finger-drag-node",
|
|
pattern: {
|
|
type: "drag",
|
|
subjectKind: "node",
|
|
source: "finger"
|
|
},
|
|
actionId: "pan",
|
|
when: (ctx) => ctx.isStylusActive,
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
var ACTIVE_INTERACTION_CONTEXT = {
|
|
id: "active-interaction",
|
|
priority: 15,
|
|
enabled: true,
|
|
bindings: [{
|
|
id: "escape-cancel-active-interaction",
|
|
pattern: {
|
|
kind: "key",
|
|
phase: "down",
|
|
key: "Escape"
|
|
},
|
|
actionId: "cancel-active-input",
|
|
when: (ctx) => ctx.isDragging || ctx.isSplitting || Boolean(ctx.custom.isSelecting) || Boolean(ctx.custom.isCreatingEdge),
|
|
consumeInput: true
|
|
}]
|
|
};
|
|
|
|
// src/gestures/useGuardContext.ts
|
|
init_selection_store();
|
|
init_graph_store();
|
|
init_input_store();
|
|
init_interaction_store();
|
|
init_selection_path_store();
|
|
init_search_store();
|
|
import { useRef as useRef13 } from "react";
|
|
import { useAtomValue as useAtomValue22 } from "jotai";
|
|
init_types2();
|
|
function useGuardContext(heldKeys = NO_HELD_KEYS) {
|
|
const isStylusActive = useAtomValue22(isStylusActiveAtom);
|
|
const fingerCount = useAtomValue22(fingerCountAtom);
|
|
const draggingNodeId = useAtomValue22(draggingNodeIdAtom);
|
|
const focusedNodeId = useAtomValue22(focusedNodeIdAtom);
|
|
const selectedNodeIds = useAtomValue22(selectedNodeIdsAtom);
|
|
const inputMode = useAtomValue22(inputModeAtom);
|
|
const keyboardInteractionMode = useAtomValue22(keyboardInteractionModeAtom);
|
|
const selectionPath = useAtomValue22(selectionPathAtom);
|
|
const edgeCreation = useAtomValue22(edgeCreationAtom);
|
|
const isSearchActive = useAtomValue22(isFilterActiveAtom);
|
|
const commandLineVisible = useAtomValue22(commandLineVisibleAtom);
|
|
const guardRef = useRef13({
|
|
isStylusActive: false,
|
|
fingerCount: 0,
|
|
isDragging: false,
|
|
isResizing: false,
|
|
isSplitting: false,
|
|
inputMode: {
|
|
type: "normal"
|
|
},
|
|
keyboardInteractionMode: "navigate",
|
|
selectedNodeIds: /* @__PURE__ */ new Set(),
|
|
focusedNodeId: null,
|
|
isSearchActive: false,
|
|
commandLineVisible: false,
|
|
heldKeys: NO_HELD_KEYS,
|
|
custom: {}
|
|
});
|
|
guardRef.current = {
|
|
isStylusActive,
|
|
fingerCount,
|
|
isDragging: draggingNodeId !== null,
|
|
isResizing: false,
|
|
isSplitting: false,
|
|
inputMode,
|
|
keyboardInteractionMode,
|
|
selectedNodeIds,
|
|
focusedNodeId,
|
|
isSearchActive,
|
|
commandLineVisible,
|
|
heldKeys,
|
|
custom: {
|
|
isSelecting: selectionPath !== null,
|
|
isCreatingEdge: edgeCreation.isCreating
|
|
}
|
|
};
|
|
return guardRef;
|
|
}
|
|
|
|
// src/gestures/useInertia.ts
|
|
import { useRef as useRef14, useEffect as useEffect6 } from "react";
|
|
|
|
// src/gestures/inertia.ts
|
|
var PAN_FRICTION = 0.92;
|
|
var ZOOM_FRICTION = 0.88;
|
|
var MIN_VELOCITY = 0.5;
|
|
var ZOOM_SNAP_THRESHOLD = 0.03;
|
|
var VELOCITY_SAMPLE_COUNT = 5;
|
|
var VelocitySampler = class {
|
|
constructor(maxSamples = VELOCITY_SAMPLE_COUNT) {
|
|
__publicField(this, "samples", []);
|
|
this.maxSamples = maxSamples;
|
|
}
|
|
sample(vx, vy, t) {
|
|
if (this.samples.length >= this.maxSamples) {
|
|
this.samples.shift();
|
|
}
|
|
this.samples.push({
|
|
vx,
|
|
vy,
|
|
t
|
|
});
|
|
}
|
|
average() {
|
|
const n = this.samples.length;
|
|
if (n === 0) return {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
let sx = 0;
|
|
let sy = 0;
|
|
for (const s of this.samples) {
|
|
sx += s.vx;
|
|
sy += s.vy;
|
|
}
|
|
return {
|
|
x: sx / n,
|
|
y: sy / n
|
|
};
|
|
}
|
|
reset() {
|
|
this.samples.length = 0;
|
|
}
|
|
};
|
|
var PanInertia = class {
|
|
constructor(velocity, friction = PAN_FRICTION, minVelocity = MIN_VELOCITY) {
|
|
this.friction = friction;
|
|
this.minVelocity = minVelocity;
|
|
this.vx = velocity.x;
|
|
this.vy = velocity.y;
|
|
}
|
|
/**
|
|
* Advance one frame. Returns the velocity delta to apply,
|
|
* or null if below threshold (animation complete).
|
|
*/
|
|
tick() {
|
|
this.vx *= this.friction;
|
|
this.vy *= this.friction;
|
|
if (Math.abs(this.vx) < this.minVelocity && Math.abs(this.vy) < this.minVelocity) {
|
|
return null;
|
|
}
|
|
return {
|
|
x: this.vx,
|
|
y: this.vy
|
|
};
|
|
}
|
|
};
|
|
var ZoomInertia = class {
|
|
constructor(velocity, origin, friction = ZOOM_FRICTION, minVelocity = MIN_VELOCITY, snapThreshold = ZOOM_SNAP_THRESHOLD) {
|
|
this.origin = origin;
|
|
this.friction = friction;
|
|
this.minVelocity = minVelocity;
|
|
this.snapThreshold = snapThreshold;
|
|
this.v = velocity;
|
|
}
|
|
/**
|
|
* Advance one frame. Returns zoom delta + origin,
|
|
* or null if below threshold.
|
|
*/
|
|
tick(currentZoom) {
|
|
this.v *= this.friction;
|
|
if (Math.abs(this.v) < this.minVelocity) {
|
|
if (Math.abs(currentZoom - 1) < this.snapThreshold) {
|
|
const snapDelta = 1 - currentZoom;
|
|
return Math.abs(snapDelta) > 1e-3 ? {
|
|
delta: snapDelta,
|
|
origin: this.origin
|
|
} : null;
|
|
}
|
|
return null;
|
|
}
|
|
return {
|
|
delta: this.v,
|
|
origin: this.origin
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/gestures/useInertia.ts
|
|
var ZOOM_SNAP_THRESHOLD2 = 0.03;
|
|
var INERTIA_MIN_SPEED = 2;
|
|
var VELOCITY_SAMPLE_COUNT2 = 5;
|
|
function snapZoom(z) {
|
|
return Math.abs(z - 1) < ZOOM_SNAP_THRESHOLD2 ? 1 : z;
|
|
}
|
|
function useInertia() {
|
|
const panInertiaRef = useRef14(null);
|
|
const zoomInertiaRef = useRef14(null);
|
|
const panSamplerRef = useRef14(new VelocitySampler(VELOCITY_SAMPLE_COUNT2));
|
|
const zoomSamplerRef = useRef14(new VelocitySampler(VELOCITY_SAMPLE_COUNT2));
|
|
const pinchPrevOrigin = useRef14(null);
|
|
const lastPinchZoom = useRef14(null);
|
|
const cancelPan = () => {
|
|
if (panInertiaRef.current) {
|
|
cancelAnimationFrame(panInertiaRef.current.anim);
|
|
panInertiaRef.current = null;
|
|
}
|
|
};
|
|
const cancelZoom = () => {
|
|
if (zoomInertiaRef.current) {
|
|
cancelAnimationFrame(zoomInertiaRef.current.anim);
|
|
zoomInertiaRef.current = null;
|
|
}
|
|
};
|
|
const cancelAll = () => {
|
|
cancelPan();
|
|
cancelZoom();
|
|
};
|
|
const startPanInertia = (velocity, setPan) => {
|
|
cancelPan();
|
|
const engine = new PanInertia(velocity);
|
|
const animate = () => {
|
|
const tick = engine.tick();
|
|
if (!tick) {
|
|
panInertiaRef.current = null;
|
|
return;
|
|
}
|
|
setPan((prev) => ({
|
|
x: prev.x + tick.x,
|
|
y: prev.y + tick.y
|
|
}));
|
|
panInertiaRef.current.anim = requestAnimationFrame(animate);
|
|
};
|
|
panInertiaRef.current = {
|
|
anim: requestAnimationFrame(animate),
|
|
engine
|
|
};
|
|
};
|
|
const startZoomInertia = (velocity_0, origin, currentZoom, minZoom, maxZoom, setZoom, setPan_0) => {
|
|
cancelZoom();
|
|
const engine_0 = new ZoomInertia(velocity_0, origin);
|
|
const animate_0 = () => {
|
|
const tick_0 = engine_0.tick(currentZoom);
|
|
if (!tick_0) {
|
|
zoomInertiaRef.current = null;
|
|
return;
|
|
}
|
|
setZoom((prevZoom) => {
|
|
const newZoom = Math.max(minZoom, Math.min(maxZoom, prevZoom + tick_0.delta));
|
|
setPan_0((prevPan) => {
|
|
const worldX = (tick_0.origin.x - prevPan.x) / prevZoom;
|
|
const worldY = (tick_0.origin.y - prevPan.y) / prevZoom;
|
|
return {
|
|
x: tick_0.origin.x - worldX * newZoom,
|
|
y: tick_0.origin.y - worldY * newZoom
|
|
};
|
|
});
|
|
return snapZoom(newZoom);
|
|
});
|
|
zoomInertiaRef.current.anim = requestAnimationFrame(animate_0);
|
|
};
|
|
zoomInertiaRef.current = {
|
|
anim: requestAnimationFrame(animate_0),
|
|
engine: engine_0
|
|
};
|
|
};
|
|
useEffect6(() => cancelAll, []);
|
|
return {
|
|
startPanInertia,
|
|
startZoomInertia,
|
|
cancelAll,
|
|
cancelPan,
|
|
cancelZoom,
|
|
panSampler: panSamplerRef.current,
|
|
zoomSampler: zoomSamplerRef.current,
|
|
pinchPrevOrigin,
|
|
lastPinchZoom
|
|
};
|
|
}
|
|
|
|
// src/gestures/useWheelZoom.ts
|
|
function createWheelHandler(config) {
|
|
return ({
|
|
event,
|
|
pinching,
|
|
delta: [, dy],
|
|
memo
|
|
}) => {
|
|
if (memo === true || pinching || !config.enableZoom || !config.ref.current) return;
|
|
const target = event.target;
|
|
const noDrag = target.closest('[data-no-drag="true"]');
|
|
if (noDrag) {
|
|
const draggableNode = target.closest('[data-draggable-node="true"]');
|
|
if (draggableNode) {
|
|
const nodeId = draggableNode.getAttribute("data-node-id");
|
|
if (nodeId && config.selectedNodeIds.has(nodeId)) {
|
|
let el = target;
|
|
while (el && noDrag.contains(el)) {
|
|
if (el.scrollHeight > el.clientHeight) {
|
|
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
|
|
const atTop = el.scrollTop <= 1;
|
|
if (!atBottom && dy > 0 || !atTop && dy < 0 || atBottom && dy < 0 || atTop && dy > 0) {
|
|
return;
|
|
}
|
|
}
|
|
el = el.parentElement;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
event.preventDefault();
|
|
const rect = config.ref.current.getBoundingClientRect();
|
|
const mouseX = event.clientX - rect.left;
|
|
const mouseY = event.clientY - rect.top;
|
|
const worldX = (mouseX - config.pan.x) / config.zoom;
|
|
const worldY = (mouseY - config.pan.y) / config.zoom;
|
|
const zoomDelta = -dy * config.zoomSensitivity * config.zoom;
|
|
const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, config.zoom + zoomDelta)));
|
|
config.setZoom(newZoom);
|
|
config.setPan({
|
|
x: mouseX - worldX * newZoom,
|
|
y: mouseY - worldY * newZoom
|
|
});
|
|
};
|
|
}
|
|
|
|
// src/gestures/usePinchZoom.ts
|
|
function createPinchHandlers(config) {
|
|
const onPinchStart = ({
|
|
origin: [ox, oy]
|
|
}) => {
|
|
if (!config.enableZoom || !config.ref.current) return true;
|
|
config.inertia.cancelAll();
|
|
config.inertia.pinchPrevOrigin.current = {
|
|
x: ox,
|
|
y: oy
|
|
};
|
|
config.inertia.zoomSampler.reset();
|
|
config.inertia.lastPinchZoom.current = {
|
|
zoom: config.zoom,
|
|
t: performance.now()
|
|
};
|
|
return false;
|
|
};
|
|
const onPinch = ({
|
|
offset: [d],
|
|
origin: [ox, oy],
|
|
event,
|
|
memo
|
|
}) => {
|
|
if (memo === true || !config.enableZoom || !config.ref.current) return;
|
|
event.preventDefault();
|
|
const rect = config.ref.current.getBoundingClientRect();
|
|
const pinchX = ox - rect.left;
|
|
const pinchY = oy - rect.top;
|
|
let pdx = 0;
|
|
let pdy = 0;
|
|
if (config.inertia.pinchPrevOrigin.current) {
|
|
pdx = ox - config.inertia.pinchPrevOrigin.current.x;
|
|
pdy = oy - config.inertia.pinchPrevOrigin.current.y;
|
|
}
|
|
config.inertia.pinchPrevOrigin.current = {
|
|
x: ox,
|
|
y: oy
|
|
};
|
|
const worldX = (pinchX - config.pan.x) / config.zoom;
|
|
const worldY = (pinchY - config.pan.y) / config.zoom;
|
|
const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, d)));
|
|
const now = performance.now();
|
|
if (config.inertia.lastPinchZoom.current) {
|
|
const dt = (now - config.inertia.lastPinchZoom.current.t) / 1e3;
|
|
if (dt > 0) {
|
|
const v = (newZoom - config.inertia.lastPinchZoom.current.zoom) / dt;
|
|
config.inertia.zoomSampler.sample(v, 0, now);
|
|
}
|
|
}
|
|
config.inertia.lastPinchZoom.current = {
|
|
zoom: newZoom,
|
|
t: now
|
|
};
|
|
config.setZoom(newZoom);
|
|
config.setPan({
|
|
x: pinchX - worldX * newZoom + pdx,
|
|
y: pinchY - worldY * newZoom + pdy
|
|
});
|
|
};
|
|
const onPinchEnd = () => {
|
|
const avg = config.inertia.zoomSampler.average();
|
|
const perFrameV = avg.x / 60;
|
|
if (!config.reducedMotion && Math.abs(perFrameV) > 1e-3 && config.ref.current) {
|
|
const rect = config.ref.current.getBoundingClientRect();
|
|
const origin = config.inertia.pinchPrevOrigin.current ?? {
|
|
x: rect.width / 2,
|
|
y: rect.height / 2
|
|
};
|
|
config.inertia.startZoomInertia(perFrameV, {
|
|
x: origin.x - rect.left,
|
|
y: origin.y - rect.top
|
|
}, config.zoom, config.minZoom, config.maxZoom, config.setZoom, config.setPan);
|
|
}
|
|
config.inertia.pinchPrevOrigin.current = null;
|
|
config.inertia.zoomSampler.reset();
|
|
config.inertia.lastPinchZoom.current = null;
|
|
};
|
|
return {
|
|
onPinchStart,
|
|
onPinch,
|
|
onPinchEnd
|
|
};
|
|
}
|
|
|
|
// src/gestures/useCanvasGestures.ts
|
|
init_types2();
|
|
function useCanvasGestures({
|
|
ref,
|
|
minZoom = 0.1,
|
|
maxZoom = 5,
|
|
zoomSensitivity = 15e-4,
|
|
enablePan = true,
|
|
enableZoom = true,
|
|
mappingIndex: externalIndex,
|
|
contexts: extraContexts,
|
|
palmRejection = true,
|
|
onAction,
|
|
heldKeys = NO_HELD_KEYS
|
|
}) {
|
|
const [panVals, setPan] = useAtom4(panAtom);
|
|
const [zoomVal, setZoom] = useAtom4(zoomAtom);
|
|
const setViewportRect = useSetAtom22(viewportRectAtom);
|
|
const selectedNodeIds = useAtomValue23(selectedNodeIdsAtom);
|
|
const clearSelection = useSetAtom22(clearSelectionAtom);
|
|
const screenToWorld = useAtomValue23(screenToWorldAtom);
|
|
const setPointerDown = useSetAtom22(pointerDownAtom);
|
|
const setPointerUp = useSetAtom22(pointerUpAtom);
|
|
const clearPointers = useSetAtom22(clearPointersAtom);
|
|
const primaryInputSource = useAtomValue23(primaryInputSourceAtom);
|
|
const reducedMotion = useAtomValue23(prefersReducedMotionAtom);
|
|
const startSelection = useSetAtom22(startSelectionAtom);
|
|
const updateSelection = useSetAtom22(updateSelectionAtom);
|
|
const endSelection = useSetAtom22(endSelectionAtom);
|
|
const guardRef = useGuardContext(heldKeys);
|
|
const inertia = useInertia();
|
|
const panStartRef = useRef15({
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
const dragOriginatedOnBg = useRef15(false);
|
|
const dragSourceRef = useRef15("mouse");
|
|
const buttonRef = useRef15(0);
|
|
const dragIntentRef = useRef15("none");
|
|
const timedRunner = useRef15(new TimedStateRunner());
|
|
const internalIndex = useMemo(() => {
|
|
const ctxList = [];
|
|
if (palmRejection) ctxList.push(PALM_REJECTION_CONTEXT);
|
|
if (extraContexts) ctxList.push(...extraContexts);
|
|
ctxList.push(DEFAULT_CONTEXT);
|
|
return buildMappingIndex(ctxList);
|
|
}, [palmRejection, extraContexts]);
|
|
const mappingIndex = externalIndex ?? internalIndex;
|
|
const isBackgroundTarget = (target) => {
|
|
if (!ref.current || !target) return false;
|
|
const child = ref.current.firstChild;
|
|
return target === ref.current || target === child;
|
|
};
|
|
const makeGestureEvent = (type, phase, subject, screenX, screenY, extra) => {
|
|
const worldPos = screenToWorld(screenX, screenY);
|
|
return {
|
|
kind: "pointer",
|
|
type,
|
|
phase,
|
|
subject,
|
|
source: dragSourceRef.current,
|
|
button: buttonRef.current,
|
|
modifiers: NO_MODIFIERS,
|
|
heldKeys,
|
|
screenPosition: {
|
|
x: screenX,
|
|
y: screenY
|
|
},
|
|
worldPosition: worldPos,
|
|
...extra
|
|
};
|
|
};
|
|
const resolveAndDispatch = (event) => {
|
|
const resolution = resolve(event, mappingIndex, guardRef.current);
|
|
if (resolution) {
|
|
dispatch(event, resolution);
|
|
onAction?.(event, resolution);
|
|
}
|
|
return resolution;
|
|
};
|
|
useEffect7(() => {
|
|
const runner = timedRunner.current;
|
|
runner.onEmit = (_gestureType) => {
|
|
};
|
|
return () => {
|
|
runner.destroy();
|
|
};
|
|
}, []);
|
|
useEffect7(() => {
|
|
const el = ref.current;
|
|
if (!el) {
|
|
setViewportRect(null);
|
|
return;
|
|
}
|
|
setViewportRect(el.getBoundingClientRect());
|
|
const observer = new ResizeObserver((entries) => {
|
|
for (const entry of entries) {
|
|
setViewportRect(entry.contentRect);
|
|
}
|
|
});
|
|
observer.observe(el);
|
|
return () => {
|
|
observer.unobserve(el);
|
|
observer.disconnect();
|
|
};
|
|
}, [setViewportRect]);
|
|
useEffect7(() => {
|
|
return () => {
|
|
inertia.cancelAll();
|
|
timedRunner.current.destroy();
|
|
};
|
|
}, []);
|
|
useEffect7(() => {
|
|
const handleVisChange = () => {
|
|
if (document.hidden) {
|
|
inertia.cancelAll();
|
|
clearPointers();
|
|
timedRunner.current.feed("cancel");
|
|
}
|
|
};
|
|
document.addEventListener("visibilitychange", handleVisChange);
|
|
return () => document.removeEventListener("visibilitychange", handleVisChange);
|
|
}, [clearPointers]);
|
|
useGesture2({
|
|
onPointerDown: (state) => {
|
|
if (!ref.current) {
|
|
dragOriginatedOnBg.current = false;
|
|
return;
|
|
}
|
|
const pe = state.event;
|
|
const classified = classifyPointer(pe);
|
|
setPointerDown(classified);
|
|
dragSourceRef.current = classified.source;
|
|
buttonRef.current = pe.button ?? 0;
|
|
inertia.cancelAll();
|
|
if (isBackgroundTarget(state.event.target)) {
|
|
dragOriginatedOnBg.current = true;
|
|
timedRunner.current.feed("down");
|
|
const startX = pe.clientX;
|
|
const startY = pe.clientY;
|
|
timedRunner.current.onEmit = (gestureType) => {
|
|
if (gestureType === "long-press") {
|
|
const worldPos_0 = screenToWorld(startX, startY);
|
|
const event_0 = makeGestureEvent("long-press", "instant", {
|
|
kind: "background"
|
|
}, startX, startY, {
|
|
modifiers: extractModifiers(pe),
|
|
worldPosition: worldPos_0
|
|
});
|
|
resolveAndDispatch(event_0);
|
|
}
|
|
};
|
|
} else {
|
|
dragOriginatedOnBg.current = false;
|
|
}
|
|
},
|
|
onPointerUp: (state_0) => {
|
|
const pe_0 = state_0.event;
|
|
setPointerUp(pe_0.pointerId);
|
|
},
|
|
onDragStart: ({
|
|
event: event_1
|
|
}) => {
|
|
if (!dragOriginatedOnBg.current) return;
|
|
const pe_1 = event_1;
|
|
const modifiers = pe_1 ? extractModifiers(pe_1) : NO_MODIFIERS;
|
|
const gestureEvent = makeGestureEvent("drag", "start", {
|
|
kind: "background"
|
|
}, pe_1?.clientX ?? 0, pe_1?.clientY ?? 0, {
|
|
modifiers
|
|
});
|
|
const resolution_0 = resolve(gestureEvent, mappingIndex, guardRef.current);
|
|
dragIntentRef.current = resolution_0?.actionId ?? "none";
|
|
if (dragIntentRef.current === "pan" && enablePan) {
|
|
panStartRef.current = {
|
|
...panVals
|
|
};
|
|
inertia.panSampler.reset();
|
|
} else if (dragIntentRef.current === "lasso-select" || dragIntentRef.current === "rect-select") {
|
|
const worldPos_1 = screenToWorld(pe_1?.clientX ?? 0, pe_1?.clientY ?? 0);
|
|
startSelection({
|
|
type: dragIntentRef.current === "lasso-select" ? "lasso" : "rect",
|
|
point: worldPos_1
|
|
});
|
|
}
|
|
if (resolution_0) {
|
|
dispatch(gestureEvent, resolution_0);
|
|
onAction?.(gestureEvent, resolution_0);
|
|
}
|
|
},
|
|
onDrag: ({
|
|
movement: [mx, my],
|
|
tap,
|
|
active,
|
|
pinching,
|
|
event: event_2,
|
|
velocity: [vx, vy],
|
|
direction: [dx, dy]
|
|
}) => {
|
|
if (tap && dragOriginatedOnBg.current) {
|
|
const emitted = timedRunner.current.feed("up");
|
|
const pe_2 = event_2;
|
|
const modifiers_0 = extractModifiers(pe_2);
|
|
const tapType = emitted ?? "tap";
|
|
const gestureEvent_0 = makeGestureEvent(tapType, "instant", {
|
|
kind: "background"
|
|
}, pe_2.clientX, pe_2.clientY, {
|
|
modifiers: modifiers_0
|
|
});
|
|
const resolution_1 = resolve(gestureEvent_0, mappingIndex, guardRef.current);
|
|
if (resolution_1) {
|
|
dispatch(gestureEvent_0, resolution_1);
|
|
onAction?.(gestureEvent_0, resolution_1);
|
|
} else {
|
|
clearSelection();
|
|
}
|
|
return;
|
|
}
|
|
if (!tap && active && !pinching && dragOriginatedOnBg.current) {
|
|
timedRunner.current.feed("move-beyond-threshold");
|
|
const intent = dragIntentRef.current;
|
|
if (intent === "pan" && enablePan) {
|
|
setPan({
|
|
x: panStartRef.current.x + mx,
|
|
y: panStartRef.current.y + my
|
|
});
|
|
const now = performance.now();
|
|
inertia.panSampler.sample(vx * dx, vy * dy, now);
|
|
} else if (intent === "lasso-select" || intent === "rect-select") {
|
|
const pe_3 = event_2;
|
|
const worldPos_2 = screenToWorld(pe_3.clientX, pe_3.clientY);
|
|
updateSelection(worldPos_2);
|
|
}
|
|
}
|
|
},
|
|
onDragEnd: () => {
|
|
timedRunner.current.feed("up");
|
|
const intent_0 = dragIntentRef.current;
|
|
if (intent_0 === "lasso-select" || intent_0 === "rect-select") {
|
|
endSelection();
|
|
}
|
|
if (!reducedMotion && dragOriginatedOnBg.current && dragSourceRef.current === "finger" && intent_0 === "pan") {
|
|
const avg = inertia.panSampler.average();
|
|
const speed = Math.sqrt(avg.x ** 2 + avg.y ** 2);
|
|
if (speed > INERTIA_MIN_SPEED) {
|
|
inertia.startPanInertia(avg, setPan);
|
|
}
|
|
}
|
|
dragOriginatedOnBg.current = false;
|
|
dragIntentRef.current = "none";
|
|
inertia.panSampler.reset();
|
|
},
|
|
onWheel: createWheelHandler({
|
|
ref,
|
|
minZoom,
|
|
maxZoom,
|
|
zoomSensitivity,
|
|
enableZoom,
|
|
zoom: zoomVal,
|
|
pan: panVals,
|
|
selectedNodeIds,
|
|
setZoom,
|
|
setPan
|
|
}),
|
|
...createPinchHandlers({
|
|
ref,
|
|
minZoom,
|
|
maxZoom,
|
|
enableZoom,
|
|
reducedMotion,
|
|
zoom: zoomVal,
|
|
pan: panVals,
|
|
setZoom,
|
|
setPan,
|
|
inertia
|
|
})
|
|
}, {
|
|
target: ref,
|
|
eventOptions: {
|
|
passive: false,
|
|
capture: true
|
|
},
|
|
drag: {
|
|
filterTaps: true,
|
|
tapsThreshold: primaryInputSource === "finger" ? 10 : 5,
|
|
pointer: {
|
|
touch: true,
|
|
keys: false,
|
|
capture: false,
|
|
buttons: -1
|
|
}
|
|
},
|
|
wheel: {},
|
|
pinch: {
|
|
scaleBounds: () => ({
|
|
min: minZoom,
|
|
max: maxZoom
|
|
}),
|
|
from: () => [zoomVal, 0]
|
|
}
|
|
});
|
|
}
|
|
|
|
// src/gestures/GestureProvider.tsx
|
|
init_dispatcher();
|
|
import React6, { createContext as createContext3, useContext as useContext3, useEffect as useEffect10, useRef as useRef16, useState as useState3 } from "react";
|
|
import { useStore as useStore4 } from "jotai";
|
|
|
|
// src/gestures/useInputModeGestureContext.ts
|
|
init_interaction_store();
|
|
import { c as _c32 } from "react/compiler-runtime";
|
|
import { useEffect as useEffect8 } from "react";
|
|
import { useAtomValue as useAtomValue24 } from "jotai";
|
|
function useInputModeGestureContext(inputSystem) {
|
|
const $ = _c32(4);
|
|
const inputMode = useAtomValue24(inputModeAtom);
|
|
let t0;
|
|
let t1;
|
|
if ($[0] !== inputMode.type || $[1] !== inputSystem) {
|
|
t0 = () => {
|
|
const modeType = inputMode.type;
|
|
const ctx = INPUT_MODE_CONTEXTS[modeType] ?? null;
|
|
if (ctx) {
|
|
inputSystem.pushContext(ctx);
|
|
} else {
|
|
for (const key of Object.keys(INPUT_MODE_CONTEXTS)) {
|
|
const existing = INPUT_MODE_CONTEXTS[key];
|
|
if (existing) {
|
|
inputSystem.removeContext(existing.id);
|
|
}
|
|
}
|
|
}
|
|
return () => {
|
|
if (ctx) {
|
|
inputSystem.removeContext(ctx.id);
|
|
}
|
|
};
|
|
};
|
|
t1 = [inputMode.type, inputSystem];
|
|
$[0] = inputMode.type;
|
|
$[1] = inputSystem;
|
|
$[2] = t0;
|
|
$[3] = t1;
|
|
} else {
|
|
t0 = $[2];
|
|
t1 = $[3];
|
|
}
|
|
useEffect8(t0, t1);
|
|
}
|
|
|
|
// src/gestures/useRegisterInputActions.ts
|
|
init_core();
|
|
import { c as _c33 } from "react/compiler-runtime";
|
|
import { useEffect as useEffect9 } from "react";
|
|
import { useStore as useStore3 } from "jotai";
|
|
init_search_store();
|
|
init_dispatcher();
|
|
|
|
// src/gestures/input-action-helpers.ts
|
|
init_core();
|
|
init_search_store();
|
|
init_interaction_store();
|
|
init_selection_path_store();
|
|
|
|
// src/gestures/modifier-helpers.ts
|
|
init_core();
|
|
init_types2();
|
|
function isRepeatBlocked(event) {
|
|
return isKeyInputEvent(event) && event.repeat;
|
|
}
|
|
function getSelectedNodeIds(store) {
|
|
return Array.from(store.get(selectedNodeIdsAtom));
|
|
}
|
|
function clearSelectionState(store) {
|
|
store.set(clearSelectionAtom);
|
|
store.set(clearEdgeSelectionAtom);
|
|
}
|
|
function resolveFocusableNodeId(store) {
|
|
const focusedNodeId = store.get(focusedNodeIdAtom);
|
|
if (focusedNodeId) return focusedNodeId;
|
|
const selected = getSelectedNodeIds(store);
|
|
return selected[0] ?? null;
|
|
}
|
|
function getCurrentSubject(store) {
|
|
const draggingNodeId = store.get(draggingNodeIdAtom);
|
|
if (draggingNodeId) return {
|
|
kind: "node",
|
|
nodeId: draggingNodeId
|
|
};
|
|
const edgeCreation = store.get(edgeCreationAtom);
|
|
if (edgeCreation.isCreating && edgeCreation.sourceNodeId) {
|
|
return {
|
|
kind: "node",
|
|
nodeId: edgeCreation.sourceNodeId
|
|
};
|
|
}
|
|
const focusedNodeId = resolveFocusableNodeId(store);
|
|
if (focusedNodeId) return {
|
|
kind: "node",
|
|
nodeId: focusedNodeId
|
|
};
|
|
const selectedEdgeId = store.get(selectedEdgeIdAtom);
|
|
if (selectedEdgeId) return {
|
|
kind: "edge",
|
|
edgeId: selectedEdgeId
|
|
};
|
|
return {
|
|
kind: "background"
|
|
};
|
|
}
|
|
function updateKeySubject(event, store) {
|
|
if (event.kind === "key" && event.subject === void 0) {
|
|
event.subject = getCurrentSubject(store);
|
|
}
|
|
}
|
|
|
|
// src/gestures/gesture-classification.ts
|
|
init_core();
|
|
function findNearestNode(currentNodeId, store, direction) {
|
|
const nodes = store.get(uiNodesAtom);
|
|
if (nodes.length === 0) return null;
|
|
if (!currentNodeId) {
|
|
const sorted = [...nodes].sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x);
|
|
return sorted[0]?.id ?? null;
|
|
}
|
|
const currentNode = nodes.find((node) => node.id === currentNodeId);
|
|
if (!currentNode) return nodes[0]?.id ?? null;
|
|
const cx = currentNode.position.x + (currentNode.width ?? 200) / 2;
|
|
const cy = currentNode.position.y + (currentNode.height ?? 100) / 2;
|
|
let bestId = null;
|
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
for (const candidate of nodes) {
|
|
if (candidate.id === currentNode.id) continue;
|
|
const nx = candidate.position.x + (candidate.width ?? 200) / 2;
|
|
const ny = candidate.position.y + (candidate.height ?? 100) / 2;
|
|
const dx = nx - cx;
|
|
const dy = ny - cy;
|
|
let isValid = false;
|
|
switch (direction) {
|
|
case "right":
|
|
isValid = dx > 0;
|
|
break;
|
|
case "left":
|
|
isValid = dx < 0;
|
|
break;
|
|
case "down":
|
|
isValid = dy > 0;
|
|
break;
|
|
case "up":
|
|
isValid = dy < 0;
|
|
break;
|
|
}
|
|
if (!isValid) continue;
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
const isHorizontal = direction === "left" || direction === "right";
|
|
const perpendicularDistance = isHorizontal ? Math.abs(dy) : Math.abs(dx);
|
|
const score = dist + perpendicularDistance * 0.5;
|
|
if (score < bestScore) {
|
|
bestScore = score;
|
|
bestId = candidate.id;
|
|
}
|
|
}
|
|
return bestId;
|
|
}
|
|
function cycleFocus(store, direction) {
|
|
const nodes = store.get(uiNodesAtom);
|
|
if (nodes.length === 0) return;
|
|
const focusedNodeId = store.get(focusedNodeIdAtom);
|
|
const sorted = [...nodes].sort((a, b) => a.zIndex - b.zIndex);
|
|
const currentIdx = focusedNodeId ? sorted.findIndex((node) => node.id === focusedNodeId) : -1;
|
|
const nextIdx = direction === -1 ? currentIdx <= 0 ? sorted.length - 1 : currentIdx - 1 : (currentIdx + 1) % sorted.length;
|
|
store.set(setFocusedNodeAtom, sorted[nextIdx].id);
|
|
}
|
|
function navigateFocus(store, direction) {
|
|
const nextId = findNearestNode(resolveFocusableNodeId(store), store, direction);
|
|
if (nextId) {
|
|
store.set(setFocusedNodeAtom, nextId);
|
|
}
|
|
}
|
|
function activateFocusedNode(store, enterManipulate) {
|
|
const focusedNodeId = resolveFocusableNodeId(store);
|
|
if (!focusedNodeId) return;
|
|
store.set(setFocusedNodeAtom, focusedNodeId);
|
|
store.set(selectSingleNodeAtom, focusedNodeId);
|
|
if (enterManipulate) {
|
|
store.set(setKeyboardInteractionModeAtom, "manipulate");
|
|
}
|
|
}
|
|
|
|
// src/gestures/input-action-helpers.ts
|
|
var EMPTY_EDGE_CREATION = {
|
|
isCreating: false,
|
|
sourceNodeId: null,
|
|
sourceNodePosition: null,
|
|
targetPosition: null,
|
|
hoveredTargetNodeId: null,
|
|
sourceHandle: null,
|
|
targetHandle: null,
|
|
sourcePort: null,
|
|
targetPort: null,
|
|
snappedTargetPosition: null
|
|
};
|
|
function nudgeSelection(store, dx, dy, label) {
|
|
const selected = getSelectedNodeIds(store);
|
|
if (selected.length === 0) {
|
|
const focused = store.get(focusedNodeIdAtom);
|
|
if (focused) {
|
|
store.set(selectSingleNodeAtom, focused);
|
|
}
|
|
}
|
|
const nodeIds = getSelectedNodeIds(store);
|
|
if (nodeIds.length === 0) return;
|
|
const graph = store.get(graphAtom).copy();
|
|
store.set(pushHistoryAtom, label);
|
|
for (const nodeId of nodeIds) {
|
|
if (!graph.hasNode(nodeId)) continue;
|
|
const x = graph.getNodeAttribute(nodeId, "x");
|
|
const y = graph.getNodeAttribute(nodeId, "y");
|
|
graph.setNodeAttribute(nodeId, "x", x + dx);
|
|
graph.setNodeAttribute(nodeId, "y", y + dy);
|
|
}
|
|
store.set(graphAtom, graph);
|
|
store.set(nodePositionUpdateCounterAtom, (count) => count + 1);
|
|
}
|
|
function deleteSelection(store) {
|
|
const selectedEdgeId = store.get(selectedEdgeIdAtom);
|
|
if (selectedEdgeId) {
|
|
store.set(optimisticDeleteEdgeAtom, {
|
|
edgeKey: selectedEdgeId
|
|
});
|
|
store.set(clearEdgeSelectionAtom);
|
|
}
|
|
const selectedNodeIds = getSelectedNodeIds(store);
|
|
if (selectedNodeIds.length === 0) return;
|
|
store.set(pushHistoryAtom, selectedNodeIds.length > 1 ? `Delete ${selectedNodeIds.length} nodes` : "Delete node");
|
|
const focusedNodeId = store.get(focusedNodeIdAtom);
|
|
for (const nodeId of selectedNodeIds) {
|
|
store.set(optimisticDeleteNodeAtom, {
|
|
nodeId
|
|
});
|
|
}
|
|
if (focusedNodeId && selectedNodeIds.includes(focusedNodeId)) {
|
|
store.set(setFocusedNodeAtom, null);
|
|
}
|
|
store.set(clearSelectionAtom);
|
|
}
|
|
function cutSelection(store) {
|
|
const selectedNodeIds = getSelectedNodeIds(store);
|
|
if (selectedNodeIds.length === 0) return;
|
|
store.set(copyToClipboardAtom, selectedNodeIds);
|
|
deleteSelection(store);
|
|
}
|
|
function cancelActiveInteraction(store) {
|
|
let didCancel = false;
|
|
if (store.get(draggingNodeIdAtom) !== null) {
|
|
store.set(endNodeDragAtom);
|
|
didCancel = true;
|
|
}
|
|
if (store.get(edgeCreationAtom).isCreating) {
|
|
store.set(edgeCreationAtom, EMPTY_EDGE_CREATION);
|
|
didCancel = true;
|
|
}
|
|
if (store.get(inputModeAtom).type === "pickPoint" || store.get(inputModeAtom).type === "pickNode" || store.get(inputModeAtom).type === "pickNodes") {
|
|
store.set(resetInputModeAtom);
|
|
didCancel = true;
|
|
}
|
|
const selectionPath = store.get(selectionPathAtom);
|
|
if (selectionPath !== null) {
|
|
store.set(cancelSelectionAtom);
|
|
didCancel = true;
|
|
}
|
|
return didCancel;
|
|
}
|
|
function escapeInput(store) {
|
|
if (cancelActiveInteraction(store)) return;
|
|
if (store.get(keyboardInteractionModeAtom) === "manipulate") {
|
|
store.set(resetKeyboardInteractionModeAtom);
|
|
return;
|
|
}
|
|
if (store.get(isFilterActiveAtom)) {
|
|
store.set(clearSearchAtom);
|
|
return;
|
|
}
|
|
if (store.get(commandLineVisibleAtom)) {
|
|
store.set(closeCommandLineAtom);
|
|
return;
|
|
}
|
|
if (store.get(selectedNodeIdsAtom).size > 0 || store.get(selectedEdgeIdAtom) !== null) {
|
|
clearSelectionState(store);
|
|
}
|
|
}
|
|
function resolvePickNode(event, store) {
|
|
updateKeySubject(event, store);
|
|
if (event.subject?.kind !== "node") return;
|
|
if (store.get(inputModeAtom).type === "pickNodes") {
|
|
store.set(toggleNodeInSelectionAtom, event.subject.nodeId);
|
|
store.set(setFocusedNodeAtom, event.subject.nodeId);
|
|
return;
|
|
}
|
|
store.set(provideInputAtom, event.subject.nodeId);
|
|
store.set(resetInputModeAtom);
|
|
}
|
|
function finishPickNodes(store) {
|
|
store.set(provideInputAtom, getSelectedNodeIds(store));
|
|
store.set(resetInputModeAtom);
|
|
}
|
|
function resolvePickPoint(event, store) {
|
|
if (event.kind !== "pointer") return;
|
|
store.set(provideInputAtom, event.worldPosition);
|
|
store.set(resetInputModeAtom);
|
|
}
|
|
function selectAll(store) {
|
|
clearSelectionState(store);
|
|
store.set(addNodesToSelectionAtom, store.get(nodeKeysAtom));
|
|
}
|
|
|
|
// src/gestures/useRegisterInputActions.ts
|
|
function register(actionId, handler) {
|
|
registerAction2(actionId, handler);
|
|
return () => unregisterAction2(actionId);
|
|
}
|
|
function useRegisterInputActions() {
|
|
const $ = _c33(3);
|
|
const store = useStore3();
|
|
let t0;
|
|
let t1;
|
|
if ($[0] !== store) {
|
|
t0 = () => {
|
|
const unregister = [register("select-node", (event) => {
|
|
updateKeySubject(event, store);
|
|
if (event.subject?.kind !== "node") {
|
|
return;
|
|
}
|
|
store.set(setFocusedNodeAtom, event.subject.nodeId);
|
|
if (event.kind === "key") {
|
|
store.set(selectSingleNodeAtom, event.subject.nodeId);
|
|
}
|
|
}), register("select-edge", (event_0) => {
|
|
updateKeySubject(event_0, store);
|
|
if (event_0.subject?.kind !== "edge") {
|
|
return;
|
|
}
|
|
store.set(selectEdgeAtom, event_0.subject.edgeId);
|
|
}), register("toggle-selection", (event_1) => {
|
|
updateKeySubject(event_1, store);
|
|
if (event_1.subject?.kind !== "node") {
|
|
return;
|
|
}
|
|
store.set(setFocusedNodeAtom, event_1.subject.nodeId);
|
|
if (event_1.kind === "key") {
|
|
store.set(toggleNodeInSelectionAtom, event_1.subject.nodeId);
|
|
}
|
|
}), register("clear-selection", () => {
|
|
clearSelectionState(store);
|
|
}), register("select-all", (event_2) => {
|
|
if (isRepeatBlocked(event_2)) {
|
|
return;
|
|
}
|
|
selectAll(store);
|
|
}), register("fit-to-view", (event_3) => {
|
|
updateKeySubject(event_3, store);
|
|
if (event_3.subject?.kind === "node") {
|
|
store.set(centerOnNodeAtom, event_3.subject.nodeId);
|
|
}
|
|
}), register("fit-all-to-view", () => {
|
|
store.set(fitToBoundsAtom, {
|
|
mode: "graph"
|
|
});
|
|
}), register("toggle-lock", (event_4) => {
|
|
updateKeySubject(event_4, store);
|
|
if (event_4.subject?.kind !== "node") {
|
|
return;
|
|
}
|
|
if (store.get(lockedNodeIdAtom) === event_4.subject.nodeId) {
|
|
store.set(unlockNodeAtom);
|
|
} else {
|
|
store.set(lockNodeAtom, {
|
|
nodeId: event_4.subject.nodeId
|
|
});
|
|
}
|
|
}), register("open-command-line", (event_5) => {
|
|
if (isRepeatBlocked(event_5)) {
|
|
return;
|
|
}
|
|
store.set(openCommandLineAtom);
|
|
}), register("open-search", (event_6) => {
|
|
if (isRepeatBlocked(event_6)) {
|
|
return;
|
|
}
|
|
store.set(openCommandLineAtom);
|
|
}), register("search-next-result", () => {
|
|
store.set(nextSearchResultAtom);
|
|
}), register("search-prev-result", () => {
|
|
store.set(prevSearchResultAtom);
|
|
}), register("copy-selection", (event_7) => {
|
|
if (isRepeatBlocked(event_7)) {
|
|
return;
|
|
}
|
|
const selectedNodeIds = getSelectedNodeIds(store);
|
|
if (selectedNodeIds.length > 0) {
|
|
store.set(copyToClipboardAtom, selectedNodeIds);
|
|
}
|
|
}), register("cut-selection", (event_8) => {
|
|
if (isRepeatBlocked(event_8)) {
|
|
return;
|
|
}
|
|
cutSelection(store);
|
|
}), register("paste-selection", (event_9) => {
|
|
if (isRepeatBlocked(event_9)) {
|
|
return;
|
|
}
|
|
store.set(pasteFromClipboardAtom);
|
|
}), register("duplicate-selection", (event_10) => {
|
|
if (isRepeatBlocked(event_10)) {
|
|
return;
|
|
}
|
|
store.set(duplicateSelectionAtom);
|
|
}), register("merge-selection", (event_11) => {
|
|
if (isRepeatBlocked(event_11)) {
|
|
return;
|
|
}
|
|
const nodeIds = getSelectedNodeIds(store);
|
|
if (nodeIds.length >= 2) {
|
|
store.set(mergeNodesAtom, {
|
|
nodeIds
|
|
});
|
|
}
|
|
}), register("delete-selection", (event_12) => {
|
|
if (isRepeatBlocked(event_12)) {
|
|
return;
|
|
}
|
|
deleteSelection(store);
|
|
}), register("undo", (event_13) => {
|
|
if (isRepeatBlocked(event_13)) {
|
|
return;
|
|
}
|
|
store.set(undoAtom);
|
|
}), register("redo", (event_14) => {
|
|
if (isRepeatBlocked(event_14)) {
|
|
return;
|
|
}
|
|
store.set(redoAtom);
|
|
}), register("cancel-active-input", () => {
|
|
cancelActiveInteraction(store);
|
|
}), register("escape-input", () => {
|
|
escapeInput(store);
|
|
}), register("navigate-focus-up", () => {
|
|
navigateFocus(store, "up");
|
|
}), register("navigate-focus-down", () => {
|
|
navigateFocus(store, "down");
|
|
}), register("navigate-focus-left", () => {
|
|
navigateFocus(store, "left");
|
|
}), register("navigate-focus-right", () => {
|
|
navigateFocus(store, "right");
|
|
}), register("cycle-focus-forward", (event_15) => {
|
|
if (isRepeatBlocked(event_15)) {
|
|
return;
|
|
}
|
|
cycleFocus(store, 1);
|
|
}), register("cycle-focus-backward", (event_16) => {
|
|
if (isRepeatBlocked(event_16)) {
|
|
return;
|
|
}
|
|
cycleFocus(store, -1);
|
|
}), register("activate-focused-node", (event_17) => {
|
|
if (isRepeatBlocked(event_17)) {
|
|
return;
|
|
}
|
|
activateFocusedNode(store, false);
|
|
}), register("enter-keyboard-manipulate-mode", (event_18) => {
|
|
if (isRepeatBlocked(event_18)) {
|
|
return;
|
|
}
|
|
activateFocusedNode(store, true);
|
|
}), register("exit-keyboard-manipulate-mode", () => {
|
|
store.set(resetKeyboardInteractionModeAtom);
|
|
}), register("nudge-selection-up", () => {
|
|
nudgeSelection(store, 0, -10, "Nudge selection");
|
|
}), register("nudge-selection-down", () => {
|
|
nudgeSelection(store, 0, 10, "Nudge selection");
|
|
}), register("nudge-selection-left", () => {
|
|
nudgeSelection(store, -10, 0, "Nudge selection");
|
|
}), register("nudge-selection-right", () => {
|
|
nudgeSelection(store, 10, 0, "Nudge selection");
|
|
}), register("nudge-selection-up-large", () => {
|
|
nudgeSelection(store, 0, -50, "Nudge selection");
|
|
}), register("nudge-selection-down-large", () => {
|
|
nudgeSelection(store, 0, 50, "Nudge selection");
|
|
}), register("nudge-selection-left-large", () => {
|
|
nudgeSelection(store, -50, 0, "Nudge selection");
|
|
}), register("nudge-selection-right-large", () => {
|
|
nudgeSelection(store, 50, 0, "Nudge selection");
|
|
}), register("resolve-pick-node", (event_19) => {
|
|
resolvePickNode(event_19, store);
|
|
}), register("finish-pick-nodes", () => {
|
|
finishPickNodes(store);
|
|
}), register("resolve-pick-point", (event_20) => {
|
|
resolvePickPoint(event_20, store);
|
|
}), register("cancel-pick", () => {
|
|
store.set(resetInputModeAtom);
|
|
})];
|
|
return () => {
|
|
for (const cleanup of unregister) {
|
|
cleanup();
|
|
}
|
|
};
|
|
};
|
|
t1 = [store];
|
|
$[0] = store;
|
|
$[1] = t0;
|
|
$[2] = t1;
|
|
} else {
|
|
t0 = $[1];
|
|
t1 = $[2];
|
|
}
|
|
useEffect9(t0, t1);
|
|
}
|
|
|
|
// src/gestures/useGestureSystem.ts
|
|
import { c as _c34 } from "react/compiler-runtime";
|
|
import { useState as useState2 } from "react";
|
|
init_types2();
|
|
function useInputSystem(t0) {
|
|
const $ = _c34(19);
|
|
const config = t0 === void 0 ? {} : t0;
|
|
const {
|
|
contexts: staticContexts,
|
|
palmRejection: t1
|
|
} = config;
|
|
const initialPR = t1 === void 0 ? true : t1;
|
|
let t2;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = [];
|
|
$[0] = t2;
|
|
} else {
|
|
t2 = $[0];
|
|
}
|
|
const [dynamicContexts, setDynamicContexts] = useState2(t2);
|
|
const [palmRejection, setPalmRejection] = useState2(initialPR);
|
|
const [heldKeys, setHeldKeys] = useState2(NO_HELD_KEYS);
|
|
const all = [];
|
|
if (palmRejection) {
|
|
all.push(PALM_REJECTION_CONTEXT);
|
|
}
|
|
all.push(ACTIVE_INTERACTION_CONTEXT, SEARCH_CONTEXT, KEYBOARD_MANIPULATE_CONTEXT, KEYBOARD_NAVIGATE_CONTEXT);
|
|
if (staticContexts) {
|
|
all.push(...staticContexts);
|
|
}
|
|
all.push(...dynamicContexts);
|
|
all.push(DEFAULT_CONTEXT);
|
|
const mappingIndex = buildMappingIndex(all);
|
|
let t3;
|
|
if ($[1] !== setDynamicContexts) {
|
|
t3 = (ctx) => {
|
|
setDynamicContexts((prev) => {
|
|
const filtered = prev.filter((c) => c.id !== ctx.id);
|
|
return [...filtered, ctx];
|
|
});
|
|
};
|
|
$[1] = setDynamicContexts;
|
|
$[2] = t3;
|
|
} else {
|
|
t3 = $[2];
|
|
}
|
|
const pushContext = t3;
|
|
let t4;
|
|
if ($[3] !== setDynamicContexts) {
|
|
t4 = (id) => {
|
|
setDynamicContexts((prev_0) => prev_0.filter((c_0) => c_0.id !== id));
|
|
};
|
|
$[3] = setDynamicContexts;
|
|
$[4] = t4;
|
|
} else {
|
|
t4 = $[4];
|
|
}
|
|
const removeContext = t4;
|
|
let t5;
|
|
if ($[5] !== setDynamicContexts) {
|
|
t5 = (id_0, enabled) => {
|
|
setDynamicContexts((prev_1) => prev_1.map((c_1) => c_1.id === id_0 ? {
|
|
...c_1,
|
|
enabled
|
|
} : c_1));
|
|
};
|
|
$[5] = setDynamicContexts;
|
|
$[6] = t5;
|
|
} else {
|
|
t5 = $[6];
|
|
}
|
|
const setContextEnabled = t5;
|
|
let t6;
|
|
if ($[7] !== setHeldKeys) {
|
|
t6 = () => {
|
|
setHeldKeys(NO_HELD_KEYS);
|
|
};
|
|
$[7] = setHeldKeys;
|
|
$[8] = t6;
|
|
} else {
|
|
t6 = $[8];
|
|
}
|
|
const clearHeldKeys = t6;
|
|
let t7;
|
|
if ($[9] !== clearHeldKeys || $[10] !== heldKeys || $[11] !== mappingIndex || $[12] !== palmRejection || $[13] !== pushContext || $[14] !== removeContext || $[15] !== setContextEnabled || $[16] !== setHeldKeys || $[17] !== setPalmRejection) {
|
|
t7 = {
|
|
mappingIndex,
|
|
pushContext,
|
|
removeContext,
|
|
setContextEnabled,
|
|
palmRejection,
|
|
setPalmRejection,
|
|
heldKeys,
|
|
setHeldKeys,
|
|
clearHeldKeys
|
|
};
|
|
$[9] = clearHeldKeys;
|
|
$[10] = heldKeys;
|
|
$[11] = mappingIndex;
|
|
$[12] = palmRejection;
|
|
$[13] = pushContext;
|
|
$[14] = removeContext;
|
|
$[15] = setContextEnabled;
|
|
$[16] = setHeldKeys;
|
|
$[17] = setPalmRejection;
|
|
$[18] = t7;
|
|
} else {
|
|
t7 = $[18];
|
|
}
|
|
return t7;
|
|
}
|
|
var useGestureSystem = useInputSystem;
|
|
|
|
// src/gestures/gesture-provider-utils.ts
|
|
init_core();
|
|
init_search_store();
|
|
init_selection_path_store();
|
|
function isEditableTarget(target) {
|
|
if (!(target instanceof HTMLElement)) {
|
|
return false;
|
|
}
|
|
if (target.isContentEditable) {
|
|
return true;
|
|
}
|
|
const editable = target.closest('input, textarea, [contenteditable="true"], [data-no-canvas-keyboard="true"]');
|
|
return editable !== null;
|
|
}
|
|
function setHeldKeyValue(current, key, isHeld) {
|
|
const previous = current[key] ?? false;
|
|
if (previous === isHeld) {
|
|
return current;
|
|
}
|
|
const next = {
|
|
...current
|
|
};
|
|
if (isHeld) {
|
|
next[key] = true;
|
|
} else {
|
|
delete next[key];
|
|
}
|
|
return next;
|
|
}
|
|
function applyHeldKeyDelta(event, current) {
|
|
const isHeld = event.type === "keydown";
|
|
const nextByKey = setHeldKeyValue(current.byKey, event.key, isHeld);
|
|
const nextByCode = setHeldKeyValue(current.byCode, event.code, isHeld);
|
|
if (nextByKey === current.byKey && nextByCode === current.byCode) {
|
|
return current;
|
|
}
|
|
return {
|
|
byKey: nextByKey,
|
|
byCode: nextByCode
|
|
};
|
|
}
|
|
function getCurrentSubject2(store) {
|
|
const draggingNodeId = store.get(draggingNodeIdAtom);
|
|
if (draggingNodeId) {
|
|
return {
|
|
kind: "node",
|
|
nodeId: draggingNodeId
|
|
};
|
|
}
|
|
const edgeCreation = store.get(edgeCreationAtom);
|
|
if (edgeCreation.isCreating && edgeCreation.sourceNodeId) {
|
|
return {
|
|
kind: "node",
|
|
nodeId: edgeCreation.sourceNodeId
|
|
};
|
|
}
|
|
const focusedNodeId = store.get(focusedNodeIdAtom);
|
|
if (focusedNodeId) {
|
|
return {
|
|
kind: "node",
|
|
nodeId: focusedNodeId
|
|
};
|
|
}
|
|
return {
|
|
kind: "background"
|
|
};
|
|
}
|
|
function getSubjectPosition(store, root, subject) {
|
|
const worldToScreen = store.get(worldToScreenAtom);
|
|
if (subject.kind === "node") {
|
|
const node = store.get(uiNodesAtom).find((entry) => entry.id === subject.nodeId);
|
|
if (!node) {
|
|
return {};
|
|
}
|
|
const worldPosition = {
|
|
x: node.position.x + (node.width ?? 200) / 2,
|
|
y: node.position.y + (node.height ?? 100) / 2
|
|
};
|
|
return {
|
|
worldPosition,
|
|
screenPosition: worldToScreen(worldPosition.x, worldPosition.y)
|
|
};
|
|
}
|
|
if (subject.kind === "background" && root) {
|
|
const rect = root.getBoundingClientRect();
|
|
const screenPosition = {
|
|
x: rect.left + rect.width / 2,
|
|
y: rect.top + rect.height / 2
|
|
};
|
|
return {
|
|
screenPosition,
|
|
worldPosition: store.get(screenToWorldAtom)(screenPosition.x, screenPosition.y)
|
|
};
|
|
}
|
|
return {};
|
|
}
|
|
function buildGuardContext(store, system) {
|
|
const heldKeys = "heldKeys" in system ? system.heldKeys : system;
|
|
const edgeCreation = store.get(edgeCreationAtom);
|
|
return {
|
|
isStylusActive: store.get(isStylusActiveAtom),
|
|
fingerCount: store.get(fingerCountAtom),
|
|
isDragging: store.get(draggingNodeIdAtom) !== null,
|
|
isResizing: false,
|
|
isSplitting: false,
|
|
inputMode: store.get(inputModeAtom),
|
|
keyboardInteractionMode: store.get(keyboardInteractionModeAtom),
|
|
selectedNodeIds: store.get(selectedNodeIdsAtom),
|
|
focusedNodeId: store.get(focusedNodeIdAtom),
|
|
isSearchActive: store.get(isFilterActiveAtom),
|
|
commandLineVisible: store.get(commandLineVisibleAtom),
|
|
heldKeys,
|
|
custom: {
|
|
isSelecting: store.get(selectionPathAtom) !== null,
|
|
isCreatingEdge: edgeCreation.isCreating
|
|
}
|
|
};
|
|
}
|
|
|
|
// src/gestures/GestureProvider.tsx
|
|
import { jsx as _jsx6 } from "react/jsx-runtime";
|
|
var nextOwnerId = 1;
|
|
var activeOwnerId = null;
|
|
var InputContext = /* @__PURE__ */ createContext3(null);
|
|
function InputProvider({
|
|
children,
|
|
gestureConfig,
|
|
onAction
|
|
}) {
|
|
const store = useStore4();
|
|
const system = useInputSystem({
|
|
contexts: gestureConfig?.contexts,
|
|
palmRejection: gestureConfig?.palmRejection ?? true
|
|
});
|
|
useInputModeGestureContext(system);
|
|
useRegisterInputActions();
|
|
const [ownerId] = useState3(() => nextOwnerId++);
|
|
const [canvasRoot, setCanvasRoot] = useState3(null);
|
|
const rootRef = useRef16(null);
|
|
const systemRef = useRef16(system);
|
|
systemRef.current = system;
|
|
const registerCanvasRoot = (node) => {
|
|
rootRef.current = node;
|
|
setCanvasRoot(node);
|
|
};
|
|
useEffect10(() => {
|
|
const root = canvasRoot;
|
|
if (!root) {
|
|
return;
|
|
}
|
|
const activate = (event) => {
|
|
activeOwnerId = ownerId;
|
|
if (!isEditableTarget(event.target)) {
|
|
root.focus({
|
|
preventScroll: true
|
|
});
|
|
}
|
|
};
|
|
root.addEventListener("pointerdown", activate, true);
|
|
root.addEventListener("focusin", activate, true);
|
|
return () => {
|
|
root.removeEventListener("pointerdown", activate, true);
|
|
root.removeEventListener("focusin", activate, true);
|
|
if (activeOwnerId === ownerId) {
|
|
activeOwnerId = null;
|
|
}
|
|
};
|
|
}, [canvasRoot, ownerId]);
|
|
useEffect10(() => {
|
|
const handleKeyboard = (nativeEvent) => {
|
|
if (activeOwnerId !== ownerId) {
|
|
return;
|
|
}
|
|
const root_0 = rootRef.current;
|
|
if (!root_0 || !root_0.isConnected) {
|
|
return;
|
|
}
|
|
const currentSystem = systemRef.current;
|
|
const editableTarget = isEditableTarget(nativeEvent.target);
|
|
const shouldTrackHeldKey = nativeEvent.type === "keyup" || !nativeEvent.isComposing && !editableTarget;
|
|
const nextHeldKeys = shouldTrackHeldKey ? applyHeldKeyDelta(nativeEvent, currentSystem.heldKeys) : currentSystem.heldKeys;
|
|
if (nextHeldKeys !== currentSystem.heldKeys) {
|
|
currentSystem.setHeldKeys(nextHeldKeys);
|
|
}
|
|
if (nativeEvent.isComposing || editableTarget) {
|
|
return;
|
|
}
|
|
const subject = getCurrentSubject2(store);
|
|
const {
|
|
screenPosition,
|
|
worldPosition
|
|
} = getSubjectPosition(store, root_0, subject);
|
|
const inputEvent = {
|
|
kind: "key",
|
|
phase: nativeEvent.type === "keydown" ? "down" : "up",
|
|
key: nativeEvent.key,
|
|
code: nativeEvent.code,
|
|
repeat: nativeEvent.repeat,
|
|
modifiers: {
|
|
shift: nativeEvent.shiftKey,
|
|
ctrl: nativeEvent.ctrlKey,
|
|
alt: nativeEvent.altKey,
|
|
meta: nativeEvent.metaKey
|
|
},
|
|
heldKeys: nextHeldKeys,
|
|
subject,
|
|
screenPosition,
|
|
worldPosition,
|
|
originalEvent: nativeEvent
|
|
};
|
|
const resolution = resolve(inputEvent, currentSystem.mappingIndex, buildGuardContext(store, {
|
|
heldKeys: nextHeldKeys
|
|
}));
|
|
if (!resolution) {
|
|
return;
|
|
}
|
|
nativeEvent.preventDefault();
|
|
dispatch(inputEvent, resolution);
|
|
onAction?.(inputEvent, resolution);
|
|
};
|
|
const clearHeldKeys = () => {
|
|
systemRef.current.clearHeldKeys();
|
|
};
|
|
window.addEventListener("keydown", handleKeyboard, true);
|
|
window.addEventListener("keyup", handleKeyboard, true);
|
|
window.addEventListener("blur", clearHeldKeys);
|
|
document.addEventListener("visibilitychange", clearHeldKeys);
|
|
return () => {
|
|
window.removeEventListener("keydown", handleKeyboard, true);
|
|
window.removeEventListener("keyup", handleKeyboard, true);
|
|
window.removeEventListener("blur", clearHeldKeys);
|
|
document.removeEventListener("visibilitychange", clearHeldKeys);
|
|
};
|
|
}, [onAction, ownerId, store]);
|
|
return /* @__PURE__ */ _jsx6(InputContext.Provider, {
|
|
value: {
|
|
system,
|
|
onAction,
|
|
registerCanvasRoot
|
|
},
|
|
children
|
|
});
|
|
}
|
|
function useInputContext() {
|
|
const ctx = useContext3(InputContext);
|
|
if (!ctx) {
|
|
throw new Error("useInputContext must be used within an InputProvider");
|
|
}
|
|
return ctx;
|
|
}
|
|
var GestureProvider = InputProvider;
|
|
var useGestureContext = useInputContext;
|
|
|
|
// src/components/Grid.tsx
|
|
import { c as _c35 } from "react/compiler-runtime";
|
|
import React7 from "react";
|
|
import { jsx as _jsx7 } from "react/jsx-runtime";
|
|
function Grid(t0) {
|
|
const $ = _c35(19);
|
|
const {
|
|
pan,
|
|
zoom,
|
|
viewportWidth,
|
|
viewportHeight,
|
|
gridSize: t1,
|
|
majorGridSize: t2
|
|
} = t0;
|
|
const gridSize = t1 === void 0 ? 50 : t1;
|
|
const majorGridSize = t2 === void 0 ? 250 : t2;
|
|
const styles4 = useCanvasStyle();
|
|
if (!styles4.grid.visible || !viewportWidth || !viewportHeight || zoom <= 0) {
|
|
return null;
|
|
}
|
|
let gridOpacity;
|
|
let lines;
|
|
if ($[0] !== gridSize || $[1] !== majorGridSize || $[2] !== pan.x || $[3] !== pan.y || $[4] !== styles4.grid.majorLineColor || $[5] !== styles4.grid.minorLineColor || $[6] !== styles4.grid.opacity || $[7] !== viewportHeight || $[8] !== viewportWidth || $[9] !== zoom) {
|
|
lines = [];
|
|
const minorLineColor = styles4.grid.minorLineColor;
|
|
const majorLineColor = styles4.grid.majorLineColor;
|
|
gridOpacity = styles4.grid.opacity;
|
|
const worldLeft = -pan.x / zoom;
|
|
const worldTop = -pan.y / zoom;
|
|
const worldRight = (-pan.x + viewportWidth) / zoom;
|
|
const worldBottom = (-pan.y + viewportHeight) / zoom;
|
|
const firstVerticalLine = Math.ceil(worldLeft / gridSize) * gridSize;
|
|
for (let x = firstVerticalLine; x <= worldRight; x = x + gridSize, x) {
|
|
if (x % majorGridSize !== 0) {
|
|
lines.push(/* @__PURE__ */ _jsx7("line", {
|
|
x1: x,
|
|
y1: worldTop,
|
|
x2: x,
|
|
y2: worldBottom,
|
|
stroke: minorLineColor,
|
|
strokeWidth: 1 / zoom
|
|
}, `v-${x}`));
|
|
}
|
|
}
|
|
const firstHorizontalLine = Math.ceil(worldTop / gridSize) * gridSize;
|
|
for (let y = firstHorizontalLine; y <= worldBottom; y = y + gridSize, y) {
|
|
if (y % majorGridSize !== 0) {
|
|
lines.push(/* @__PURE__ */ _jsx7("line", {
|
|
x1: worldLeft,
|
|
y1: y,
|
|
x2: worldRight,
|
|
y2: y,
|
|
stroke: minorLineColor,
|
|
strokeWidth: 1 / zoom
|
|
}, `h-${y}`));
|
|
}
|
|
}
|
|
const firstMajorVerticalLine = Math.ceil(worldLeft / majorGridSize) * majorGridSize;
|
|
for (let x_0 = firstMajorVerticalLine; x_0 <= worldRight; x_0 = x_0 + majorGridSize, x_0) {
|
|
lines.push(/* @__PURE__ */ _jsx7("line", {
|
|
x1: x_0,
|
|
y1: worldTop,
|
|
x2: x_0,
|
|
y2: worldBottom,
|
|
stroke: majorLineColor,
|
|
strokeWidth: 1.5 / zoom
|
|
}, `major-v-${x_0}`));
|
|
}
|
|
const firstMajorHorizontalLine = Math.ceil(worldTop / majorGridSize) * majorGridSize;
|
|
for (let y_0 = firstMajorHorizontalLine; y_0 <= worldBottom; y_0 = y_0 + majorGridSize, y_0) {
|
|
lines.push(/* @__PURE__ */ _jsx7("line", {
|
|
x1: worldLeft,
|
|
y1: y_0,
|
|
x2: worldRight,
|
|
y2: y_0,
|
|
stroke: majorLineColor,
|
|
strokeWidth: 1.5 / zoom
|
|
}, `major-h-${y_0}`));
|
|
}
|
|
$[0] = gridSize;
|
|
$[1] = majorGridSize;
|
|
$[2] = pan.x;
|
|
$[3] = pan.y;
|
|
$[4] = styles4.grid.majorLineColor;
|
|
$[5] = styles4.grid.minorLineColor;
|
|
$[6] = styles4.grid.opacity;
|
|
$[7] = viewportHeight;
|
|
$[8] = viewportWidth;
|
|
$[9] = zoom;
|
|
$[10] = gridOpacity;
|
|
$[11] = lines;
|
|
} else {
|
|
gridOpacity = $[10];
|
|
lines = $[11];
|
|
}
|
|
let t3;
|
|
if ($[12] !== gridOpacity) {
|
|
t3 = {
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
overflow: "visible",
|
|
pointerEvents: "none",
|
|
opacity: gridOpacity
|
|
};
|
|
$[12] = gridOpacity;
|
|
$[13] = t3;
|
|
} else {
|
|
t3 = $[13];
|
|
}
|
|
let t4;
|
|
if ($[14] !== lines) {
|
|
t4 = /* @__PURE__ */ _jsx7("g", {
|
|
children: lines
|
|
});
|
|
$[14] = lines;
|
|
$[15] = t4;
|
|
} else {
|
|
t4 = $[15];
|
|
}
|
|
let t5;
|
|
if ($[16] !== t3 || $[17] !== t4) {
|
|
t5 = /* @__PURE__ */ _jsx7("svg", {
|
|
width: "100%",
|
|
height: "100%",
|
|
style: t3,
|
|
children: t4
|
|
});
|
|
$[16] = t3;
|
|
$[17] = t4;
|
|
$[18] = t5;
|
|
} else {
|
|
t5 = $[18];
|
|
}
|
|
return t5;
|
|
}
|
|
function Crosshairs(t0) {
|
|
const $ = _c35(31);
|
|
const {
|
|
pan,
|
|
zoom,
|
|
viewportWidth,
|
|
viewportHeight,
|
|
tickSize: t1,
|
|
labelSize: t2
|
|
} = t0;
|
|
const tickSize = t1 === void 0 ? 100 : t1;
|
|
const labelSize = t2 === void 0 ? 12 : t2;
|
|
const styles4 = useCanvasStyle();
|
|
if (!styles4.axes.visible || !viewportWidth || !viewportHeight || zoom <= 0) {
|
|
return null;
|
|
}
|
|
let t3;
|
|
if ($[0] !== tickSize || $[1] !== zoom) {
|
|
t3 = () => {
|
|
const minWorldSpacing = 80 / zoom;
|
|
const niceNumbers = [50, 100, 250, 500, 1e3, 2500, 5e3, 1e4];
|
|
for (const n of niceNumbers) {
|
|
if (n >= minWorldSpacing) {
|
|
return Math.max(n, tickSize);
|
|
}
|
|
}
|
|
return 1e4;
|
|
};
|
|
$[0] = tickSize;
|
|
$[1] = zoom;
|
|
$[2] = t3;
|
|
} else {
|
|
t3 = $[2];
|
|
}
|
|
const getAdjustedTickSize = t3;
|
|
const adjustedTickSize = getAdjustedTickSize();
|
|
let elements;
|
|
if ($[3] !== adjustedTickSize || $[4] !== labelSize || $[5] !== pan.x || $[6] !== pan.y || $[7] !== styles4.axes.color || $[8] !== styles4.axes.labelColor || $[9] !== viewportHeight || $[10] !== viewportWidth || $[11] !== zoom) {
|
|
elements = [];
|
|
const strokeWidth = 1.5 / zoom;
|
|
const tickLength = 5 / zoom;
|
|
const labelOffset = 8 / zoom;
|
|
const finalLabelSize = labelSize / zoom;
|
|
const axisColor = styles4.axes.color;
|
|
const labelColor = styles4.axes.labelColor;
|
|
const worldLeft = -pan.x / zoom;
|
|
const worldTop = -pan.y / zoom;
|
|
const worldRight = (-pan.x + viewportWidth) / zoom;
|
|
const worldBottom = (-pan.y + viewportHeight) / zoom;
|
|
if (worldTop <= 0 && worldBottom >= 0) {
|
|
let t42;
|
|
if ($[13] !== axisColor || $[14] !== strokeWidth || $[15] !== worldLeft || $[16] !== worldRight) {
|
|
t42 = /* @__PURE__ */ _jsx7("line", {
|
|
x1: worldLeft,
|
|
y1: 0,
|
|
x2: worldRight,
|
|
y2: 0,
|
|
stroke: axisColor,
|
|
strokeWidth
|
|
}, "x-axis");
|
|
$[13] = axisColor;
|
|
$[14] = strokeWidth;
|
|
$[15] = worldLeft;
|
|
$[16] = worldRight;
|
|
$[17] = t42;
|
|
} else {
|
|
t42 = $[17];
|
|
}
|
|
elements.push(t42);
|
|
const firstXTick = Math.ceil(worldLeft / adjustedTickSize) * adjustedTickSize;
|
|
for (let x = firstXTick; x <= worldRight; x = x + adjustedTickSize, x) {
|
|
if (x === 0 && worldLeft <= 0 && worldRight >= 0) {
|
|
continue;
|
|
}
|
|
elements.push(/* @__PURE__ */ _jsx7("line", {
|
|
x1: x,
|
|
y1: -tickLength,
|
|
x2: x,
|
|
y2: tickLength,
|
|
stroke: axisColor,
|
|
strokeWidth: strokeWidth / 1.5
|
|
}, `x-tick-${x}`));
|
|
elements.push(/* @__PURE__ */ _jsx7("text", {
|
|
x,
|
|
y: tickLength + labelOffset,
|
|
fill: labelColor,
|
|
fontSize: finalLabelSize,
|
|
textAnchor: "middle",
|
|
dominantBaseline: "hanging",
|
|
children: x
|
|
}, `x-label-${x}`));
|
|
}
|
|
}
|
|
if (worldLeft <= 0 && worldRight >= 0) {
|
|
let t42;
|
|
if ($[18] !== axisColor || $[19] !== strokeWidth || $[20] !== worldBottom || $[21] !== worldTop) {
|
|
t42 = /* @__PURE__ */ _jsx7("line", {
|
|
x1: 0,
|
|
y1: worldTop,
|
|
x2: 0,
|
|
y2: worldBottom,
|
|
stroke: axisColor,
|
|
strokeWidth
|
|
}, "y-axis");
|
|
$[18] = axisColor;
|
|
$[19] = strokeWidth;
|
|
$[20] = worldBottom;
|
|
$[21] = worldTop;
|
|
$[22] = t42;
|
|
} else {
|
|
t42 = $[22];
|
|
}
|
|
elements.push(t42);
|
|
const firstYTick = Math.ceil(worldTop / adjustedTickSize) * adjustedTickSize;
|
|
for (let y = firstYTick; y <= worldBottom; y = y + adjustedTickSize, y) {
|
|
if (y === 0 && worldTop <= 0 && worldBottom >= 0) {
|
|
continue;
|
|
}
|
|
elements.push(/* @__PURE__ */ _jsx7("line", {
|
|
x1: -tickLength,
|
|
y1: y,
|
|
x2: tickLength,
|
|
y2: y,
|
|
stroke: axisColor,
|
|
strokeWidth: strokeWidth / 1.5
|
|
}, `y-tick-${y}`));
|
|
elements.push(/* @__PURE__ */ _jsx7("text", {
|
|
x: tickLength + labelOffset,
|
|
y,
|
|
fill: labelColor,
|
|
fontSize: finalLabelSize,
|
|
textAnchor: "start",
|
|
dominantBaseline: "middle",
|
|
children: y
|
|
}, `y-label-${y}`));
|
|
}
|
|
}
|
|
if (worldLeft <= 0 && worldRight >= 0 && worldTop <= 0 && worldBottom >= 0) {
|
|
const t42 = -labelOffset;
|
|
const t52 = finalLabelSize * 1.1;
|
|
let t6;
|
|
if ($[23] !== labelColor || $[24] !== labelOffset || $[25] !== t42 || $[26] !== t52) {
|
|
t6 = /* @__PURE__ */ _jsx7("text", {
|
|
x: labelOffset,
|
|
y: t42,
|
|
fill: labelColor,
|
|
fontSize: t52,
|
|
textAnchor: "start",
|
|
dominantBaseline: "alphabetic",
|
|
children: "(0,0)"
|
|
}, "origin-label");
|
|
$[23] = labelColor;
|
|
$[24] = labelOffset;
|
|
$[25] = t42;
|
|
$[26] = t52;
|
|
$[27] = t6;
|
|
} else {
|
|
t6 = $[27];
|
|
}
|
|
elements.push(t6);
|
|
}
|
|
$[3] = adjustedTickSize;
|
|
$[4] = labelSize;
|
|
$[5] = pan.x;
|
|
$[6] = pan.y;
|
|
$[7] = styles4.axes.color;
|
|
$[8] = styles4.axes.labelColor;
|
|
$[9] = viewportHeight;
|
|
$[10] = viewportWidth;
|
|
$[11] = zoom;
|
|
$[12] = elements;
|
|
} else {
|
|
elements = $[12];
|
|
}
|
|
let t4;
|
|
if ($[28] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = {
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
overflow: "visible",
|
|
pointerEvents: "none"
|
|
};
|
|
$[28] = t4;
|
|
} else {
|
|
t4 = $[28];
|
|
}
|
|
let t5;
|
|
if ($[29] !== elements) {
|
|
t5 = /* @__PURE__ */ _jsx7("svg", {
|
|
width: "100%",
|
|
height: "100%",
|
|
style: t4,
|
|
children: /* @__PURE__ */ _jsx7("g", {
|
|
children: elements
|
|
})
|
|
});
|
|
$[29] = elements;
|
|
$[30] = t5;
|
|
} else {
|
|
t5 = $[30];
|
|
}
|
|
return t5;
|
|
}
|
|
|
|
// src/components/Viewport.tsx
|
|
import { jsx as _jsx8, Fragment as _Fragment2, jsxs as _jsxs2 } from "react/jsx-runtime";
|
|
function Viewport(t0) {
|
|
const $ = _c36(28);
|
|
const {
|
|
children,
|
|
minZoom: t1,
|
|
maxZoom: t2,
|
|
zoomSensitivity: t3,
|
|
className,
|
|
style,
|
|
enablePan: t4,
|
|
enableZoom: t5
|
|
} = t0;
|
|
const minZoom = t1 === void 0 ? 0.1 : t1;
|
|
const maxZoom = t2 === void 0 ? 5 : t2;
|
|
const zoomSensitivity = t3 === void 0 ? 15e-4 : t3;
|
|
const enablePan = t4 === void 0 ? true : t4;
|
|
const enableZoom = t5 === void 0 ? true : t5;
|
|
const panVals = useAtomValue25(panAtom);
|
|
const zoomVal = useAtomValue25(zoomAtom);
|
|
const viewportRect = useAtomValue25(viewportRectAtom);
|
|
const viewportRef = useRef17(null);
|
|
const {
|
|
system,
|
|
onAction,
|
|
registerCanvasRoot
|
|
} = useInputContext();
|
|
let t6;
|
|
let t7;
|
|
if ($[0] !== registerCanvasRoot) {
|
|
t6 = () => {
|
|
registerCanvasRoot(viewportRef.current);
|
|
return () => registerCanvasRoot(null);
|
|
};
|
|
t7 = [registerCanvasRoot];
|
|
$[0] = registerCanvasRoot;
|
|
$[1] = t6;
|
|
$[2] = t7;
|
|
} else {
|
|
t6 = $[1];
|
|
t7 = $[2];
|
|
}
|
|
useEffect11(t6, t7);
|
|
let t8;
|
|
if ($[3] !== enablePan || $[4] !== enableZoom || $[5] !== maxZoom || $[6] !== minZoom || $[7] !== onAction || $[8] !== system.heldKeys || $[9] !== system.mappingIndex || $[10] !== zoomSensitivity) {
|
|
t8 = {
|
|
ref: viewportRef,
|
|
minZoom,
|
|
maxZoom,
|
|
zoomSensitivity,
|
|
enablePan,
|
|
enableZoom,
|
|
mappingIndex: system.mappingIndex,
|
|
onAction,
|
|
heldKeys: system.heldKeys
|
|
};
|
|
$[3] = enablePan;
|
|
$[4] = enableZoom;
|
|
$[5] = maxZoom;
|
|
$[6] = minZoom;
|
|
$[7] = onAction;
|
|
$[8] = system.heldKeys;
|
|
$[9] = system.mappingIndex;
|
|
$[10] = zoomSensitivity;
|
|
$[11] = t8;
|
|
} else {
|
|
t8 = $[11];
|
|
}
|
|
useCanvasGestures(t8);
|
|
let t9;
|
|
if ($[12] !== style) {
|
|
t9 = {
|
|
width: "100%",
|
|
height: "100%",
|
|
overflow: "hidden",
|
|
position: "relative",
|
|
touchAction: "none",
|
|
userSelect: "none",
|
|
WebkitUserSelect: "none",
|
|
WebkitTouchCallout: "none",
|
|
background: "var(--canvas-bg, #23272a)",
|
|
...style
|
|
};
|
|
$[12] = style;
|
|
$[13] = t9;
|
|
} else {
|
|
t9 = $[13];
|
|
}
|
|
const t10 = `translate(${panVals.x}px, ${panVals.y}px) scale(${zoomVal})`;
|
|
let t11;
|
|
if ($[14] !== t10) {
|
|
t11 = {
|
|
transform: t10,
|
|
transformOrigin: "0 0",
|
|
width: "100%",
|
|
height: "100%"
|
|
};
|
|
$[14] = t10;
|
|
$[15] = t11;
|
|
} else {
|
|
t11 = $[15];
|
|
}
|
|
let t12;
|
|
if ($[16] !== panVals || $[17] !== viewportRect || $[18] !== zoomVal) {
|
|
t12 = viewportRect && viewportRect.width > 0 && viewportRect.height > 0 && /* @__PURE__ */ _jsxs2(_Fragment2, {
|
|
children: [/* @__PURE__ */ _jsx8(Grid, {
|
|
pan: panVals,
|
|
zoom: zoomVal,
|
|
viewportWidth: viewportRect.width,
|
|
viewportHeight: viewportRect.height
|
|
}), /* @__PURE__ */ _jsx8(Crosshairs, {
|
|
pan: panVals,
|
|
zoom: zoomVal,
|
|
viewportWidth: viewportRect.width,
|
|
viewportHeight: viewportRect.height
|
|
})]
|
|
});
|
|
$[16] = panVals;
|
|
$[17] = viewportRect;
|
|
$[18] = zoomVal;
|
|
$[19] = t12;
|
|
} else {
|
|
t12 = $[19];
|
|
}
|
|
let t13;
|
|
if ($[20] !== children || $[21] !== t11 || $[22] !== t12) {
|
|
t13 = /* @__PURE__ */ _jsxs2("div", {
|
|
style: t11,
|
|
children: [t12, children]
|
|
});
|
|
$[20] = children;
|
|
$[21] = t11;
|
|
$[22] = t12;
|
|
$[23] = t13;
|
|
} else {
|
|
t13 = $[23];
|
|
}
|
|
let t14;
|
|
if ($[24] !== className || $[25] !== t13 || $[26] !== t9) {
|
|
t14 = /* @__PURE__ */ _jsx8("div", {
|
|
ref: viewportRef,
|
|
id: "viewport-canvas",
|
|
className,
|
|
"data-viewport": "true",
|
|
"data-canvas-root": "true",
|
|
tabIndex: -1,
|
|
style: t9,
|
|
children: t13
|
|
});
|
|
$[24] = className;
|
|
$[25] = t13;
|
|
$[26] = t9;
|
|
$[27] = t14;
|
|
} else {
|
|
t14 = $[27];
|
|
}
|
|
return t14;
|
|
}
|
|
|
|
// src/components/NodeRenderer.tsx
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_virtualization_store();
|
|
init_node_type_registry();
|
|
import { c as _c39 } from "react/compiler-runtime";
|
|
import React12 from "react";
|
|
import { useAtomValue as useAtomValue28 } from "jotai";
|
|
|
|
// src/components/Node.tsx
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_mutations();
|
|
init_selection_store();
|
|
init_search_store();
|
|
import React10 from "react";
|
|
import { useAtomValue as useAtomValue27, useSetAtom as useSetAtom23 } from "jotai";
|
|
|
|
// src/components/ResizeHandle.tsx
|
|
import { c as _c37 } from "react/compiler-runtime";
|
|
import React9 from "react";
|
|
import { jsx as _jsx9 } from "react/jsx-runtime";
|
|
var HIT_AREA_SIZE = 44;
|
|
var VISUAL_SIZE = 12;
|
|
var VISUAL_RADIUS = "8px";
|
|
function getHitAreaStyles(direction) {
|
|
const base = {
|
|
position: "absolute",
|
|
width: `${HIT_AREA_SIZE}px`,
|
|
height: `${HIT_AREA_SIZE}px`,
|
|
zIndex: 10,
|
|
touchAction: "none",
|
|
// Prevent iOS callout/magnifier on long-press
|
|
WebkitTouchCallout: "none",
|
|
WebkitUserSelect: "none",
|
|
userSelect: "none"
|
|
};
|
|
const halfHit = HIT_AREA_SIZE / 2;
|
|
switch (direction) {
|
|
case "nw":
|
|
return {
|
|
...base,
|
|
top: -halfHit,
|
|
left: -halfHit,
|
|
cursor: "nwse-resize"
|
|
};
|
|
case "ne":
|
|
return {
|
|
...base,
|
|
top: -halfHit,
|
|
right: -halfHit,
|
|
cursor: "nesw-resize"
|
|
};
|
|
case "sw":
|
|
return {
|
|
...base,
|
|
bottom: -halfHit,
|
|
left: -halfHit,
|
|
cursor: "nesw-resize"
|
|
};
|
|
case "se":
|
|
return {
|
|
...base,
|
|
bottom: -halfHit,
|
|
right: -halfHit,
|
|
cursor: "nwse-resize"
|
|
};
|
|
case "n":
|
|
return {
|
|
...base,
|
|
top: -halfHit,
|
|
left: "50%",
|
|
transform: "translateX(-50%)",
|
|
cursor: "ns-resize"
|
|
};
|
|
case "s":
|
|
return {
|
|
...base,
|
|
bottom: -halfHit,
|
|
left: "50%",
|
|
transform: "translateX(-50%)",
|
|
cursor: "ns-resize"
|
|
};
|
|
case "e":
|
|
return {
|
|
...base,
|
|
right: -halfHit,
|
|
top: "50%",
|
|
transform: "translateY(-50%)",
|
|
cursor: "ew-resize"
|
|
};
|
|
case "w":
|
|
return {
|
|
...base,
|
|
left: -halfHit,
|
|
top: "50%",
|
|
transform: "translateY(-50%)",
|
|
cursor: "ew-resize"
|
|
};
|
|
}
|
|
}
|
|
function getVisualStyles(direction, isResizing) {
|
|
const activeColor = "var(--node-resize-handle-active, rgba(59, 130, 246, 0.8))";
|
|
const inactiveColor = "var(--node-resize-handle, rgba(0, 0, 0, 0.2))";
|
|
const borderWidth = isResizing ? "4px" : "3px";
|
|
const borderColor = isResizing ? activeColor : inactiveColor;
|
|
const borderStyle = `${borderWidth} solid ${borderColor}`;
|
|
const base = {
|
|
position: "absolute",
|
|
pointerEvents: "none",
|
|
// Center the visual indicator within the hit area
|
|
top: "50%",
|
|
left: "50%",
|
|
transform: "translate(-50%, -50%)",
|
|
width: `${VISUAL_SIZE}px`,
|
|
height: `${VISUAL_SIZE}px`
|
|
};
|
|
switch (direction) {
|
|
case "nw":
|
|
return {
|
|
...base,
|
|
borderTop: borderStyle,
|
|
borderLeft: borderStyle,
|
|
borderTopLeftRadius: VISUAL_RADIUS
|
|
};
|
|
case "ne":
|
|
return {
|
|
...base,
|
|
borderTop: borderStyle,
|
|
borderRight: borderStyle,
|
|
borderTopRightRadius: VISUAL_RADIUS
|
|
};
|
|
case "sw":
|
|
return {
|
|
...base,
|
|
borderBottom: borderStyle,
|
|
borderLeft: borderStyle,
|
|
borderBottomLeftRadius: VISUAL_RADIUS
|
|
};
|
|
case "se":
|
|
return {
|
|
...base,
|
|
borderBottom: borderStyle,
|
|
borderRight: borderStyle,
|
|
borderBottomRightRadius: VISUAL_RADIUS
|
|
};
|
|
case "n":
|
|
return {
|
|
...base,
|
|
borderTop: borderStyle,
|
|
width: `${VISUAL_SIZE * 2}px`
|
|
};
|
|
case "s":
|
|
return {
|
|
...base,
|
|
borderBottom: borderStyle,
|
|
width: `${VISUAL_SIZE * 2}px`
|
|
};
|
|
case "e":
|
|
return {
|
|
...base,
|
|
borderRight: borderStyle,
|
|
height: `${VISUAL_SIZE * 2}px`
|
|
};
|
|
case "w":
|
|
return {
|
|
...base,
|
|
borderLeft: borderStyle,
|
|
height: `${VISUAL_SIZE * 2}px`
|
|
};
|
|
}
|
|
}
|
|
function ResizeHandle(t0) {
|
|
const $ = _c37(26);
|
|
const {
|
|
direction,
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing,
|
|
alwaysVisible: t1
|
|
} = t0;
|
|
const alwaysVisible = t1 === void 0 ? false : t1;
|
|
let t2;
|
|
if ($[0] !== createResizeStart || $[1] !== direction) {
|
|
t2 = createResizeStart(direction);
|
|
$[0] = createResizeStart;
|
|
$[1] = direction;
|
|
$[2] = t2;
|
|
} else {
|
|
t2 = $[2];
|
|
}
|
|
let t3;
|
|
if ($[3] !== direction) {
|
|
t3 = getHitAreaStyles(direction);
|
|
$[3] = direction;
|
|
$[4] = t3;
|
|
} else {
|
|
t3 = $[4];
|
|
}
|
|
const t4 = alwaysVisible || isResizing ? 1 : 0;
|
|
let t5;
|
|
if ($[5] !== t3 || $[6] !== t4) {
|
|
t5 = {
|
|
...t3,
|
|
opacity: t4,
|
|
transition: "opacity 200ms"
|
|
};
|
|
$[5] = t3;
|
|
$[6] = t4;
|
|
$[7] = t5;
|
|
} else {
|
|
t5 = $[7];
|
|
}
|
|
let t6;
|
|
if ($[8] !== alwaysVisible) {
|
|
t6 = (e) => {
|
|
if (!alwaysVisible) {
|
|
e.currentTarget.style.opacity = "1";
|
|
}
|
|
};
|
|
$[8] = alwaysVisible;
|
|
$[9] = t6;
|
|
} else {
|
|
t6 = $[9];
|
|
}
|
|
let t7;
|
|
if ($[10] !== alwaysVisible || $[11] !== isResizing) {
|
|
t7 = (e_0) => {
|
|
if (!alwaysVisible && !isResizing) {
|
|
e_0.currentTarget.style.opacity = "0";
|
|
}
|
|
};
|
|
$[10] = alwaysVisible;
|
|
$[11] = isResizing;
|
|
$[12] = t7;
|
|
} else {
|
|
t7 = $[12];
|
|
}
|
|
let t8;
|
|
if ($[13] !== direction || $[14] !== isResizing) {
|
|
t8 = getVisualStyles(direction, isResizing);
|
|
$[13] = direction;
|
|
$[14] = isResizing;
|
|
$[15] = t8;
|
|
} else {
|
|
t8 = $[15];
|
|
}
|
|
let t9;
|
|
if ($[16] !== t8) {
|
|
t9 = /* @__PURE__ */ _jsx9("div", {
|
|
style: t8
|
|
});
|
|
$[16] = t8;
|
|
$[17] = t9;
|
|
} else {
|
|
t9 = $[17];
|
|
}
|
|
let t10;
|
|
if ($[18] !== handleResizeEnd || $[19] !== handleResizeMove || $[20] !== t2 || $[21] !== t5 || $[22] !== t6 || $[23] !== t7 || $[24] !== t9) {
|
|
t10 = /* @__PURE__ */ _jsx9("div", {
|
|
"data-no-drag": "true",
|
|
onPointerDown: t2,
|
|
onPointerMove: handleResizeMove,
|
|
onPointerUp: handleResizeEnd,
|
|
onPointerCancel: handleResizeEnd,
|
|
style: t5,
|
|
onPointerEnter: t6,
|
|
onPointerLeave: t7,
|
|
children: t9
|
|
});
|
|
$[18] = handleResizeEnd;
|
|
$[19] = handleResizeMove;
|
|
$[20] = t2;
|
|
$[21] = t5;
|
|
$[22] = t6;
|
|
$[23] = t7;
|
|
$[24] = t9;
|
|
$[25] = t10;
|
|
} else {
|
|
t10 = $[25];
|
|
}
|
|
return t10;
|
|
}
|
|
|
|
// src/gestures/useNodeGestures.ts
|
|
init_viewport_store();
|
|
init_input_store();
|
|
init_interaction_store();
|
|
init_selection_store();
|
|
init_graph_store();
|
|
init_search_store();
|
|
init_selection_path_store();
|
|
import { useRef as useRef18, useEffect as useEffect12 } from "react";
|
|
import { useAtomValue as useAtomValue26 } from "jotai";
|
|
init_input_classifier();
|
|
init_dispatcher();
|
|
init_types2();
|
|
function useNodeGestures({
|
|
nodeId,
|
|
mappingIndex,
|
|
onAction,
|
|
heldKeys = NO_HELD_KEYS
|
|
}) {
|
|
const screenToWorld = useAtomValue26(screenToWorldAtom);
|
|
const isStylusActive = useAtomValue26(isStylusActiveAtom);
|
|
const fingerCount = useAtomValue26(fingerCountAtom);
|
|
const inputMode = useAtomValue26(inputModeAtom);
|
|
const keyboardInteractionMode = useAtomValue26(keyboardInteractionModeAtom);
|
|
const selectedNodeIds = useAtomValue26(selectedNodeIdsAtom);
|
|
const focusedNodeId = useAtomValue26(focusedNodeIdAtom);
|
|
const draggingNodeId = useAtomValue26(draggingNodeIdAtom);
|
|
const edgeCreation = useAtomValue26(edgeCreationAtom);
|
|
const selectionPath = useAtomValue26(selectionPathAtom);
|
|
const isSearchActive = useAtomValue26(isFilterActiveAtom);
|
|
const commandLineVisible = useAtomValue26(commandLineVisibleAtom);
|
|
const runner = useRef18(new TimedStateRunner());
|
|
const sourceRef = useRef18("mouse");
|
|
const buttonRef = useRef18(0);
|
|
const posRef = useRef18({
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
const modifiersRef = useRef18(NO_MODIFIERS);
|
|
const rightDragSeen = useRef18(false);
|
|
const guardRef = useRef18({
|
|
isStylusActive: false,
|
|
fingerCount: 0,
|
|
isDragging: false,
|
|
isResizing: false,
|
|
isSplitting: false,
|
|
inputMode: {
|
|
type: "normal"
|
|
},
|
|
keyboardInteractionMode: "navigate",
|
|
selectedNodeIds: /* @__PURE__ */ new Set(),
|
|
focusedNodeId: null,
|
|
isSearchActive: false,
|
|
commandLineVisible: false,
|
|
heldKeys: NO_HELD_KEYS,
|
|
custom: {}
|
|
});
|
|
guardRef.current = {
|
|
isStylusActive,
|
|
fingerCount,
|
|
isDragging: draggingNodeId !== null,
|
|
isResizing: false,
|
|
isSplitting: false,
|
|
inputMode,
|
|
keyboardInteractionMode,
|
|
selectedNodeIds,
|
|
focusedNodeId,
|
|
isSearchActive,
|
|
commandLineVisible,
|
|
heldKeys,
|
|
custom: {}
|
|
};
|
|
guardRef.current.custom = {
|
|
isSelecting: selectionPath !== null,
|
|
isCreatingEdge: edgeCreation.isCreating
|
|
};
|
|
useEffect12(() => {
|
|
return () => runner.current.destroy();
|
|
}, []);
|
|
const resolveAndDispatch = (event) => {
|
|
const resolution = resolve(event, mappingIndex, guardRef.current);
|
|
if (resolution) {
|
|
dispatch(event, resolution);
|
|
onAction?.(event, resolution);
|
|
}
|
|
return resolution;
|
|
};
|
|
const makeEvent = (type, phase, screenX, screenY) => {
|
|
const worldPos = screenToWorld(screenX, screenY);
|
|
return {
|
|
kind: "pointer",
|
|
type,
|
|
phase,
|
|
subject: {
|
|
kind: "node",
|
|
nodeId
|
|
},
|
|
source: sourceRef.current,
|
|
button: buttonRef.current,
|
|
modifiers: modifiersRef.current,
|
|
heldKeys,
|
|
screenPosition: {
|
|
x: screenX,
|
|
y: screenY
|
|
},
|
|
worldPosition: worldPos
|
|
};
|
|
};
|
|
useEffect12(() => {
|
|
runner.current.onEmit = (gestureType) => {
|
|
if (gestureType === "long-press") {
|
|
const event_0 = makeEvent("long-press", "instant", posRef.current.x, posRef.current.y);
|
|
resolveAndDispatch(event_0);
|
|
}
|
|
};
|
|
});
|
|
const onPointerDown = (e) => {
|
|
const classified = classifyPointer(e.nativeEvent);
|
|
sourceRef.current = classified.source;
|
|
buttonRef.current = e.button ?? 0;
|
|
posRef.current = {
|
|
x: e.clientX,
|
|
y: e.clientY
|
|
};
|
|
modifiersRef.current = extractModifiers(e);
|
|
rightDragSeen.current = false;
|
|
runner.current.feed("down");
|
|
};
|
|
const onPointerUp = (e_0) => {
|
|
const emitted = runner.current.feed("up");
|
|
if (emitted) {
|
|
const event_1 = makeEvent(emitted, "instant", e_0.clientX, e_0.clientY);
|
|
resolveAndDispatch(event_1);
|
|
}
|
|
};
|
|
const onPointerCancel = (_e) => {
|
|
runner.current.feed("cancel");
|
|
};
|
|
const onContextMenu = (e_1) => {
|
|
e_1.preventDefault();
|
|
e_1.stopPropagation();
|
|
if (rightDragSeen.current) {
|
|
rightDragSeen.current = false;
|
|
return;
|
|
}
|
|
const event_2 = makeEvent("tap", "instant", e_1.clientX, e_1.clientY);
|
|
event_2.button = 2;
|
|
event_2.modifiers = extractModifiers(e_1);
|
|
resolveAndDispatch(event_2);
|
|
};
|
|
return {
|
|
onPointerDown,
|
|
onPointerUp,
|
|
onPointerCancel,
|
|
onContextMenu
|
|
};
|
|
}
|
|
|
|
// src/components/Node.tsx
|
|
import { jsx as _jsx10, jsxs as _jsxs3 } from "react/jsx-runtime";
|
|
function Node({
|
|
nodeData,
|
|
renderContent,
|
|
onPersist,
|
|
onPersistError,
|
|
wrapper: Wrapper
|
|
}) {
|
|
const {
|
|
id,
|
|
zIndex
|
|
} = nodeData;
|
|
const graph = useAtomValue27(graphAtom);
|
|
const setHandleNodePointerDown = useSetAtom23(handleNodePointerDownSelectionAtom);
|
|
const {
|
|
isSelected
|
|
} = useNodeSelection(id);
|
|
const selectedNodeIds = useAtomValue27(selectedNodeIdsAtom);
|
|
const activeDraggingNodeId = useAtomValue27(draggingNodeIdAtom);
|
|
const focusedNodeId = useAtomValue27(focusedNodeIdAtom);
|
|
const isFocused = focusedNodeId === id && !isSelected;
|
|
const position = useAtomValue27(nodePositionAtomFamily(id));
|
|
const canvasStyles = useCanvasStyle();
|
|
const isFilterActive = useAtomValue27(isFilterActiveAtom);
|
|
const searchResults = useAtomValue27(searchResultsAtom);
|
|
const highlightedSearchNodeId = useAtomValue27(highlightedSearchNodeIdAtom);
|
|
const isDimmedByFilter = isFilterActive && !searchResults.has(id);
|
|
const isSearchHighlighted = highlightedSearchNodeId === id;
|
|
const dropTargetId = useAtomValue27(dropTargetNodeIdAtom);
|
|
const isDropTarget = dropTargetId === id;
|
|
const {
|
|
system,
|
|
onAction
|
|
} = useInputContext();
|
|
const gestureHandlers = useNodeGestures({
|
|
nodeId: id,
|
|
mappingIndex: system.mappingIndex,
|
|
onAction,
|
|
heldKeys: system.heldKeys
|
|
});
|
|
const {
|
|
bind,
|
|
updateNodePositions
|
|
} = useNodeDrag(id, {
|
|
heldKeys: system.heldKeys,
|
|
onPersist,
|
|
onPersistError
|
|
});
|
|
const resizeOptions = {
|
|
onPersist,
|
|
onPersistError
|
|
};
|
|
const {
|
|
localWidth,
|
|
localHeight,
|
|
isResizing,
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd
|
|
} = useNodeResize({
|
|
id,
|
|
nodeData,
|
|
updateNodePositions,
|
|
options: resizeOptions
|
|
});
|
|
const nodeWidth = localWidth;
|
|
const nodeHeight = localHeight;
|
|
const splitGesture = useSplitGesture(id);
|
|
const isVisuallyDragging = activeDraggingNodeId !== null && selectedNodeIds.has(id);
|
|
const bindGestures = bind();
|
|
const handlePointerDown = (e) => {
|
|
const target = e.target;
|
|
if (target.closest('[data-no-drag="true"]') || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT") {
|
|
return;
|
|
}
|
|
gestureHandlers.onPointerDown(e);
|
|
if (bindGestures.onPointerDown) {
|
|
bindGestures.onPointerDown(e);
|
|
}
|
|
e.stopPropagation();
|
|
setHandleNodePointerDown({
|
|
nodeId: id,
|
|
isShiftPressed: e.shiftKey
|
|
});
|
|
};
|
|
const handlePointerUp = (e_0) => {
|
|
gestureHandlers.onPointerUp(e_0);
|
|
};
|
|
const currentZIndex = graph.hasNode(id) ? graph.getNodeAttribute(id, "zIndex") : zIndex;
|
|
const borderColor = isSelected ? canvasStyles.nodes.selectedBorderColor : isFocused ? canvasStyles.nodes.selectedBorderColor : isVisuallyDragging ? canvasStyles.nodes.draggingBorderColor : canvasStyles.nodes.defaultBorderColor;
|
|
const borderWidth = isSelected || isVisuallyDragging ? "1.5px" : isFocused ? "2px" : "1px";
|
|
const borderRadius = `${canvasStyles.nodes.defaultBorderRadius}px`;
|
|
const defaultBoxShadow = `0px 8px 24px ${canvasStyles.nodes.shadowColor}`;
|
|
const draggingBoxShadow = `0px 12px 32px ${canvasStyles.nodes.shadowColor}`;
|
|
const selectedNodeBoxShadow = `0 0 10px 2px ${canvasStyles.nodes.selectedGlowColor}, ${defaultBoxShadow}`;
|
|
const focusedBoxShadow = `0 0 0 2px ${canvasStyles.nodes.selectedBorderColor}, ${defaultBoxShadow}`;
|
|
const currentBoxShadow = isSelected ? selectedNodeBoxShadow : isFocused ? focusedBoxShadow : isVisuallyDragging ? draggingBoxShadow : defaultBoxShadow;
|
|
const nodeContent = /* @__PURE__ */ _jsxs3("div", {
|
|
"data-draggable-node": "true",
|
|
"data-node-id": id,
|
|
"data-focused": isFocused || void 0,
|
|
className: [isSearchHighlighted && "canvas-search-highlight", isDropTarget && "canvas-drop-target"].filter(Boolean).join(" ") || void 0,
|
|
...bindGestures,
|
|
onPointerDown: (e_1) => {
|
|
splitGesture.onPointerDown(e_1);
|
|
handlePointerDown(e_1);
|
|
},
|
|
onPointerMove: splitGesture.onPointerMove,
|
|
onPointerUp: (e_2) => {
|
|
splitGesture.onPointerUp(e_2);
|
|
handlePointerUp(e_2);
|
|
},
|
|
onPointerCancel: gestureHandlers.onPointerCancel,
|
|
onContextMenu: gestureHandlers.onContextMenu,
|
|
style: {
|
|
width: `${nodeWidth}px`,
|
|
height: `${nodeHeight}px`,
|
|
backgroundColor: isVisuallyDragging ? canvasStyles.nodes.draggingBackgroundColor : canvasStyles.nodes.defaultBackground,
|
|
border: `${borderWidth} solid ${borderColor}`,
|
|
borderRadius,
|
|
boxShadow: currentBoxShadow,
|
|
cursor: isVisuallyDragging ? "grabbing" : "grab",
|
|
opacity: isDimmedByFilter ? 0.2 : isVisuallyDragging ? 0.9 : 1,
|
|
pointerEvents: isDimmedByFilter ? "none" : "auto",
|
|
transition: isVisuallyDragging ? "opacity 0.1s, transform 0.05s, background-color 0.1s, box-shadow 0.1s, border-color 0.1s" : "opacity 0.4s, border-color 0.4s, box-shadow 0.4s, background-color 0.4s, transform 0.4s ease-in-out",
|
|
transform: isVisuallyDragging ? "scale(1.005)" : "scale(1)",
|
|
zIndex: currentZIndex,
|
|
touchAction: "manipulation",
|
|
userSelect: "auto",
|
|
position: "relative",
|
|
overflow: "hidden"
|
|
},
|
|
children: [renderContent({
|
|
node: nodeData,
|
|
isSelected,
|
|
isResizing,
|
|
isDragging: isVisuallyDragging
|
|
}), /* @__PURE__ */ _jsx10(ResizeHandle, {
|
|
direction: "nw",
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing
|
|
}), /* @__PURE__ */ _jsx10(ResizeHandle, {
|
|
direction: "ne",
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing
|
|
}), /* @__PURE__ */ _jsx10(ResizeHandle, {
|
|
direction: "sw",
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing
|
|
}), /* @__PURE__ */ _jsx10(ResizeHandle, {
|
|
direction: "se",
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing,
|
|
alwaysVisible: true
|
|
}), /* @__PURE__ */ _jsx10(ResizeHandle, {
|
|
direction: "n",
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing
|
|
}), /* @__PURE__ */ _jsx10(ResizeHandle, {
|
|
direction: "s",
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing
|
|
}), /* @__PURE__ */ _jsx10(ResizeHandle, {
|
|
direction: "w",
|
|
createResizeStart,
|
|
handleResizeMove,
|
|
handleResizeEnd,
|
|
isResizing
|
|
})]
|
|
});
|
|
if (Wrapper) {
|
|
return /* @__PURE__ */ _jsx10(Wrapper, {
|
|
nodeData,
|
|
children: nodeContent
|
|
});
|
|
}
|
|
return nodeContent;
|
|
}
|
|
|
|
// src/components/NodeErrorBoundary.tsx
|
|
init_debug();
|
|
import { c as _c38 } from "react/compiler-runtime";
|
|
import React11, { Component } from "react";
|
|
import { jsx as _jsx11, jsxs as _jsxs4 } from "react/jsx-runtime";
|
|
var debug19 = createDebug("error-boundary");
|
|
function DefaultFallback(t0) {
|
|
const $ = _c38(11);
|
|
const {
|
|
error,
|
|
reset
|
|
} = t0;
|
|
let t1;
|
|
let t2;
|
|
let t3;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
padding: "16px",
|
|
backgroundColor: "rgba(239, 68, 68, 0.1)",
|
|
border: "1px solid rgba(239, 68, 68, 0.3)",
|
|
borderRadius: "8px",
|
|
color: "#ef4444",
|
|
fontSize: "12px",
|
|
minWidth: "150px"
|
|
};
|
|
t2 = /* @__PURE__ */ _jsx11("div", {
|
|
style: {
|
|
fontWeight: 600,
|
|
marginBottom: "8px"
|
|
},
|
|
children: "\u26A0\uFE0F Node Error"
|
|
});
|
|
t3 = {
|
|
color: "#888",
|
|
marginBottom: "8px",
|
|
wordBreak: "break-word"
|
|
};
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
$[2] = t3;
|
|
} else {
|
|
t1 = $[0];
|
|
t2 = $[1];
|
|
t3 = $[2];
|
|
}
|
|
const t4 = error.message || "Unknown error";
|
|
let t5;
|
|
if ($[3] !== t4) {
|
|
t5 = /* @__PURE__ */ _jsx11("div", {
|
|
style: t3,
|
|
children: t4
|
|
});
|
|
$[3] = t4;
|
|
$[4] = t5;
|
|
} else {
|
|
t5 = $[4];
|
|
}
|
|
let t6;
|
|
if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t6 = {
|
|
padding: "4px 8px",
|
|
fontSize: "11px",
|
|
backgroundColor: "rgba(239, 68, 68, 0.2)",
|
|
border: "1px solid rgba(239, 68, 68, 0.3)",
|
|
borderRadius: "4px",
|
|
color: "#ef4444",
|
|
cursor: "pointer"
|
|
};
|
|
$[5] = t6;
|
|
} else {
|
|
t6 = $[5];
|
|
}
|
|
let t7;
|
|
if ($[6] !== reset) {
|
|
t7 = /* @__PURE__ */ _jsx11("button", {
|
|
onClick: reset,
|
|
style: t6,
|
|
children: "Retry"
|
|
});
|
|
$[6] = reset;
|
|
$[7] = t7;
|
|
} else {
|
|
t7 = $[7];
|
|
}
|
|
let t8;
|
|
if ($[8] !== t5 || $[9] !== t7) {
|
|
t8 = /* @__PURE__ */ _jsxs4("div", {
|
|
style: t1,
|
|
children: [t2, t5, t7]
|
|
});
|
|
$[8] = t5;
|
|
$[9] = t7;
|
|
$[10] = t8;
|
|
} else {
|
|
t8 = $[10];
|
|
}
|
|
return t8;
|
|
}
|
|
var NodeErrorBoundary = class extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
__publicField(this, "reset", () => {
|
|
this.setState({
|
|
hasError: false,
|
|
error: null
|
|
});
|
|
});
|
|
this.state = {
|
|
hasError: false,
|
|
error: null
|
|
};
|
|
}
|
|
static getDerivedStateFromError(error) {
|
|
return {
|
|
hasError: true,
|
|
error
|
|
};
|
|
}
|
|
componentDidCatch(error, errorInfo) {
|
|
const {
|
|
nodeId,
|
|
onError
|
|
} = this.props;
|
|
debug19.error("Error in node %s: %O %O", nodeId, error, errorInfo);
|
|
onError?.(nodeId, error, errorInfo);
|
|
}
|
|
render() {
|
|
const {
|
|
hasError,
|
|
error
|
|
} = this.state;
|
|
const {
|
|
nodeId,
|
|
children,
|
|
renderFallback
|
|
} = this.props;
|
|
if (hasError && error) {
|
|
if (renderFallback) {
|
|
return renderFallback({
|
|
nodeId,
|
|
error,
|
|
reset: this.reset
|
|
});
|
|
}
|
|
return /* @__PURE__ */ _jsx11(DefaultFallback, {
|
|
nodeId,
|
|
error,
|
|
reset: this.reset
|
|
});
|
|
}
|
|
return children;
|
|
}
|
|
};
|
|
|
|
// src/components/NodeRenderer.tsx
|
|
import { jsx as _jsx12, Fragment as _Fragment3 } from "react/jsx-runtime";
|
|
function NodeRenderer(t0) {
|
|
const $ = _c39(17);
|
|
const {
|
|
renderNode,
|
|
onNodePersist,
|
|
onNodePersistError,
|
|
onNodeRenderError,
|
|
renderErrorFallback,
|
|
nodeWrapper
|
|
} = t0;
|
|
const nodeKeys = useAtomValue28(visibleNodeKeysAtom);
|
|
let t1;
|
|
if ($[0] !== nodeKeys || $[1] !== nodeWrapper || $[2] !== onNodePersist || $[3] !== onNodePersistError || $[4] !== onNodeRenderError || $[5] !== renderErrorFallback || $[6] !== renderNode) {
|
|
let t22;
|
|
if ($[8] !== nodeWrapper || $[9] !== onNodePersist || $[10] !== onNodePersistError || $[11] !== onNodeRenderError || $[12] !== renderErrorFallback || $[13] !== renderNode) {
|
|
t22 = (key) => /* @__PURE__ */ _jsx12(NodeItem, {
|
|
nodeKey: key,
|
|
renderNode,
|
|
onNodePersist,
|
|
onNodePersistError,
|
|
onNodeRenderError,
|
|
renderErrorFallback,
|
|
nodeWrapper
|
|
}, key);
|
|
$[8] = nodeWrapper;
|
|
$[9] = onNodePersist;
|
|
$[10] = onNodePersistError;
|
|
$[11] = onNodeRenderError;
|
|
$[12] = renderErrorFallback;
|
|
$[13] = renderNode;
|
|
$[14] = t22;
|
|
} else {
|
|
t22 = $[14];
|
|
}
|
|
t1 = nodeKeys.map(t22);
|
|
$[0] = nodeKeys;
|
|
$[1] = nodeWrapper;
|
|
$[2] = onNodePersist;
|
|
$[3] = onNodePersistError;
|
|
$[4] = onNodeRenderError;
|
|
$[5] = renderErrorFallback;
|
|
$[6] = renderNode;
|
|
$[7] = t1;
|
|
} else {
|
|
t1 = $[7];
|
|
}
|
|
let t2;
|
|
if ($[15] !== t1) {
|
|
t2 = /* @__PURE__ */ _jsx12(_Fragment3, {
|
|
children: t1
|
|
});
|
|
$[15] = t1;
|
|
$[16] = t2;
|
|
} else {
|
|
t2 = $[16];
|
|
}
|
|
return t2;
|
|
}
|
|
function resolveRenderNode(nodeData) {
|
|
const Component2 = getNodeTypeComponent(nodeData.dbData.node_type) ?? FallbackNodeTypeComponent;
|
|
return (props) => /* @__PURE__ */ _jsx12(Component2, {
|
|
nodeData: props.node,
|
|
isResizing: props.isResizing
|
|
});
|
|
}
|
|
function NodeItem(t0) {
|
|
const $ = _c39(24);
|
|
const {
|
|
nodeKey,
|
|
renderNode,
|
|
onNodePersist,
|
|
onNodePersistError,
|
|
onNodeRenderError,
|
|
renderErrorFallback,
|
|
nodeWrapper
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== nodeKey) {
|
|
t1 = nodeFamilyAtom(nodeKey);
|
|
$[0] = nodeKey;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const nodeData = useAtomValue28(t1);
|
|
let t2;
|
|
if ($[2] !== nodeKey) {
|
|
t2 = nodePositionAtomFamily(nodeKey);
|
|
$[2] = nodeKey;
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
const position = useAtomValue28(t2);
|
|
if (!nodeData) {
|
|
return null;
|
|
}
|
|
let t3;
|
|
if ($[4] !== nodeData || $[5] !== renderNode) {
|
|
t3 = renderNode ?? resolveRenderNode(nodeData);
|
|
$[4] = nodeData;
|
|
$[5] = renderNode;
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
const effectiveRenderNode = t3;
|
|
let t4;
|
|
if ($[7] !== position.x || $[8] !== position.y) {
|
|
t4 = {
|
|
position: "absolute",
|
|
left: position.x,
|
|
top: position.y,
|
|
transform: "translate(0, 0)"
|
|
};
|
|
$[7] = position.x;
|
|
$[8] = position.y;
|
|
$[9] = t4;
|
|
} else {
|
|
t4 = $[9];
|
|
}
|
|
let t5;
|
|
if ($[10] !== effectiveRenderNode || $[11] !== nodeData || $[12] !== nodeWrapper || $[13] !== onNodePersist || $[14] !== onNodePersistError) {
|
|
t5 = /* @__PURE__ */ _jsx12(Node, {
|
|
nodeData,
|
|
renderContent: effectiveRenderNode,
|
|
onPersist: onNodePersist,
|
|
onPersistError: onNodePersistError,
|
|
wrapper: nodeWrapper
|
|
});
|
|
$[10] = effectiveRenderNode;
|
|
$[11] = nodeData;
|
|
$[12] = nodeWrapper;
|
|
$[13] = onNodePersist;
|
|
$[14] = onNodePersistError;
|
|
$[15] = t5;
|
|
} else {
|
|
t5 = $[15];
|
|
}
|
|
let t6;
|
|
if ($[16] !== nodeKey || $[17] !== onNodeRenderError || $[18] !== renderErrorFallback || $[19] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx12(NodeErrorBoundary, {
|
|
nodeId: nodeKey,
|
|
onError: onNodeRenderError,
|
|
renderFallback: renderErrorFallback,
|
|
children: t5
|
|
});
|
|
$[16] = nodeKey;
|
|
$[17] = onNodeRenderError;
|
|
$[18] = renderErrorFallback;
|
|
$[19] = t5;
|
|
$[20] = t6;
|
|
} else {
|
|
t6 = $[20];
|
|
}
|
|
let t7;
|
|
if ($[21] !== t4 || $[22] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsx12("div", {
|
|
style: t4,
|
|
children: t6
|
|
});
|
|
$[21] = t4;
|
|
$[22] = t6;
|
|
$[23] = t7;
|
|
} else {
|
|
t7 = $[23];
|
|
}
|
|
return t7;
|
|
}
|
|
|
|
// src/components/EdgeRenderer.tsx
|
|
init_graph_derived();
|
|
init_graph_mutations();
|
|
init_virtualization_store();
|
|
init_selection_store();
|
|
init_viewport_store();
|
|
init_search_store();
|
|
import { c as _c40 } from "react/compiler-runtime";
|
|
import React13 from "react";
|
|
import { useAtomValue as useAtomValue29, useSetAtom as useSetAtom24 } from "jotai";
|
|
init_edge_path_calculators();
|
|
import { jsx as _jsx13, jsxs as _jsxs5 } from "react/jsx-runtime";
|
|
var SELECTED_EDGE_COLOR = "#3b82f6";
|
|
function EdgeRenderer(t0) {
|
|
const $ = _c40(19);
|
|
const {
|
|
onEdgeClick,
|
|
onEdgeDoubleClick,
|
|
onEdgeRightClick,
|
|
onEdgeHover,
|
|
onEdgeLeave
|
|
} = t0;
|
|
const edgeKeys = useAtomValue29(visibleEdgeKeysAtom);
|
|
const departingEdges = useAtomValue29(departingEdgesAtom);
|
|
let t1;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
pointerEvents: "none",
|
|
zIndex: 0,
|
|
overflow: "visible"
|
|
};
|
|
$[0] = t1;
|
|
} else {
|
|
t1 = $[0];
|
|
}
|
|
let t2;
|
|
if ($[1] !== edgeKeys || $[2] !== onEdgeClick || $[3] !== onEdgeDoubleClick || $[4] !== onEdgeHover || $[5] !== onEdgeLeave || $[6] !== onEdgeRightClick) {
|
|
let t32;
|
|
if ($[8] !== onEdgeClick || $[9] !== onEdgeDoubleClick || $[10] !== onEdgeHover || $[11] !== onEdgeLeave || $[12] !== onEdgeRightClick) {
|
|
t32 = (key) => /* @__PURE__ */ _jsx13(EdgeItem, {
|
|
edgeKey: key,
|
|
onEdgeClick,
|
|
onEdgeDoubleClick,
|
|
onEdgeRightClick,
|
|
onEdgeHover,
|
|
onEdgeLeave
|
|
}, key);
|
|
$[8] = onEdgeClick;
|
|
$[9] = onEdgeDoubleClick;
|
|
$[10] = onEdgeHover;
|
|
$[11] = onEdgeLeave;
|
|
$[12] = onEdgeRightClick;
|
|
$[13] = t32;
|
|
} else {
|
|
t32 = $[13];
|
|
}
|
|
t2 = edgeKeys.map(t32);
|
|
$[1] = edgeKeys;
|
|
$[2] = onEdgeClick;
|
|
$[3] = onEdgeDoubleClick;
|
|
$[4] = onEdgeHover;
|
|
$[5] = onEdgeLeave;
|
|
$[6] = onEdgeRightClick;
|
|
$[7] = t2;
|
|
} else {
|
|
t2 = $[7];
|
|
}
|
|
let t3;
|
|
if ($[14] !== departingEdges) {
|
|
t3 = Array.from(departingEdges.entries()).map(_temp8);
|
|
$[14] = departingEdges;
|
|
$[15] = t3;
|
|
} else {
|
|
t3 = $[15];
|
|
}
|
|
let t4;
|
|
if ($[16] !== t2 || $[17] !== t3) {
|
|
t4 = /* @__PURE__ */ _jsxs5("svg", {
|
|
style: t1,
|
|
children: [t2, t3]
|
|
});
|
|
$[16] = t2;
|
|
$[17] = t3;
|
|
$[18] = t4;
|
|
} else {
|
|
t4 = $[18];
|
|
}
|
|
return t4;
|
|
}
|
|
function _temp8(t0) {
|
|
const [key_0, edgeData] = t0;
|
|
return /* @__PURE__ */ _jsx13(DepartingEdgeItem, {
|
|
edgeData
|
|
}, `departing-${key_0}`);
|
|
}
|
|
function EdgeItem(t0) {
|
|
const $ = _c40(77);
|
|
const {
|
|
edgeKey,
|
|
onEdgeClick,
|
|
onEdgeDoubleClick,
|
|
onEdgeRightClick,
|
|
onEdgeHover,
|
|
onEdgeLeave
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== edgeKey) {
|
|
t1 = edgeFamilyAtom(edgeKey);
|
|
$[0] = edgeKey;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const edgeData = useAtomValue29(t1);
|
|
const styles4 = useCanvasStyle();
|
|
const selectedEdgeId = useAtomValue29(selectedEdgeIdAtom);
|
|
const selectEdge = useSetAtom24(selectEdgeAtom);
|
|
const zoom = useAtomValue29(zoomAtom);
|
|
const setEditingEdgeLabel = useSetAtom24(editingEdgeLabelAtom);
|
|
const isFilterActive = useAtomValue29(isFilterActiveAtom);
|
|
const searchResults = useAtomValue29(searchResultsAtom);
|
|
const searchEdgeResults = useAtomValue29(searchEdgeResultsAtom);
|
|
const isSelected = selectedEdgeId === edgeKey;
|
|
let color;
|
|
let handleClick;
|
|
let handleContextMenu;
|
|
let handleDoubleClick;
|
|
let handlePointerEnter;
|
|
let handlePointerLeave;
|
|
let label;
|
|
let t2;
|
|
let t3;
|
|
let type;
|
|
let weight;
|
|
if ($[2] !== edgeData || $[3] !== edgeKey || $[4] !== onEdgeClick || $[5] !== onEdgeDoubleClick || $[6] !== onEdgeHover || $[7] !== onEdgeLeave || $[8] !== onEdgeRightClick || $[9] !== selectEdge || $[10] !== styles4.edges.pathType || $[11] !== zoom) {
|
|
t3 = /* @__PURE__ */ Symbol.for("react.early_return_sentinel");
|
|
bb0: {
|
|
const pathCalculator = getEdgePathCalculator(styles4.edges.pathType || "bezier");
|
|
let t42;
|
|
if ($[23] !== edgeData || $[24] !== edgeKey || $[25] !== onEdgeClick || $[26] !== selectEdge) {
|
|
t42 = (e) => {
|
|
e.stopPropagation();
|
|
if (onEdgeClick && edgeData) {
|
|
onEdgeClick(edgeKey, edgeData, e);
|
|
} else {
|
|
selectEdge(edgeKey);
|
|
}
|
|
};
|
|
$[23] = edgeData;
|
|
$[24] = edgeKey;
|
|
$[25] = onEdgeClick;
|
|
$[26] = selectEdge;
|
|
$[27] = t42;
|
|
} else {
|
|
t42 = $[27];
|
|
}
|
|
handleClick = t42;
|
|
let t52;
|
|
if ($[28] !== edgeData || $[29] !== edgeKey || $[30] !== onEdgeDoubleClick) {
|
|
t52 = (e_0) => {
|
|
if (onEdgeDoubleClick && edgeData) {
|
|
e_0.stopPropagation();
|
|
e_0.preventDefault();
|
|
onEdgeDoubleClick(edgeKey, edgeData, e_0);
|
|
}
|
|
};
|
|
$[28] = edgeData;
|
|
$[29] = edgeKey;
|
|
$[30] = onEdgeDoubleClick;
|
|
$[31] = t52;
|
|
} else {
|
|
t52 = $[31];
|
|
}
|
|
handleDoubleClick = t52;
|
|
let t62;
|
|
if ($[32] !== edgeData || $[33] !== edgeKey || $[34] !== onEdgeRightClick) {
|
|
t62 = (e_1) => {
|
|
if (onEdgeRightClick && edgeData) {
|
|
e_1.preventDefault();
|
|
e_1.stopPropagation();
|
|
onEdgeRightClick(edgeKey, edgeData, e_1);
|
|
}
|
|
};
|
|
$[32] = edgeData;
|
|
$[33] = edgeKey;
|
|
$[34] = onEdgeRightClick;
|
|
$[35] = t62;
|
|
} else {
|
|
t62 = $[35];
|
|
}
|
|
handleContextMenu = t62;
|
|
let t72;
|
|
if ($[36] !== edgeData || $[37] !== edgeKey || $[38] !== onEdgeHover) {
|
|
t72 = () => {
|
|
if (onEdgeHover && edgeData) {
|
|
onEdgeHover(edgeKey, edgeData);
|
|
}
|
|
};
|
|
$[36] = edgeData;
|
|
$[37] = edgeKey;
|
|
$[38] = onEdgeHover;
|
|
$[39] = t72;
|
|
} else {
|
|
t72 = $[39];
|
|
}
|
|
handlePointerEnter = t72;
|
|
let t82;
|
|
if ($[40] !== edgeKey || $[41] !== onEdgeLeave) {
|
|
t82 = () => {
|
|
if (onEdgeLeave) {
|
|
onEdgeLeave(edgeKey);
|
|
}
|
|
};
|
|
$[40] = edgeKey;
|
|
$[41] = onEdgeLeave;
|
|
$[42] = t82;
|
|
} else {
|
|
t82 = $[42];
|
|
}
|
|
handlePointerLeave = t82;
|
|
if (!edgeData) {
|
|
t3 = null;
|
|
break bb0;
|
|
}
|
|
const {
|
|
sourcePosition,
|
|
targetPosition,
|
|
color: t92,
|
|
weight: t102,
|
|
type: t112,
|
|
label: t122,
|
|
sourceEndpoint,
|
|
targetEndpoint
|
|
} = edgeData;
|
|
color = t92;
|
|
weight = t102;
|
|
type = t112;
|
|
label = t122;
|
|
const x1 = sourceEndpoint?.x ?? sourcePosition.x + edgeData.sourceNodeWidth / 2;
|
|
const y1 = sourceEndpoint?.y ?? sourcePosition.y + edgeData.sourceNodeHeight / 2;
|
|
const x2 = targetEndpoint?.x ?? targetPosition.x + edgeData.targetNodeWidth / 2;
|
|
const y2 = targetEndpoint?.y ?? targetPosition.y + edgeData.targetNodeHeight / 2;
|
|
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2) || zoom === 0) {
|
|
t3 = null;
|
|
break bb0;
|
|
}
|
|
t2 = pathCalculator({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
sourceWidth: edgeData.sourceNodeWidth,
|
|
sourceHeight: edgeData.sourceNodeHeight,
|
|
targetWidth: edgeData.targetNodeWidth,
|
|
targetHeight: edgeData.targetNodeHeight
|
|
});
|
|
}
|
|
$[2] = edgeData;
|
|
$[3] = edgeKey;
|
|
$[4] = onEdgeClick;
|
|
$[5] = onEdgeDoubleClick;
|
|
$[6] = onEdgeHover;
|
|
$[7] = onEdgeLeave;
|
|
$[8] = onEdgeRightClick;
|
|
$[9] = selectEdge;
|
|
$[10] = styles4.edges.pathType;
|
|
$[11] = zoom;
|
|
$[12] = color;
|
|
$[13] = handleClick;
|
|
$[14] = handleContextMenu;
|
|
$[15] = handleDoubleClick;
|
|
$[16] = handlePointerEnter;
|
|
$[17] = handlePointerLeave;
|
|
$[18] = label;
|
|
$[19] = t2;
|
|
$[20] = t3;
|
|
$[21] = type;
|
|
$[22] = weight;
|
|
} else {
|
|
color = $[12];
|
|
handleClick = $[13];
|
|
handleContextMenu = $[14];
|
|
handleDoubleClick = $[15];
|
|
handlePointerEnter = $[16];
|
|
handlePointerLeave = $[17];
|
|
label = $[18];
|
|
t2 = $[19];
|
|
t3 = $[20];
|
|
type = $[21];
|
|
weight = $[22];
|
|
}
|
|
if (t3 !== /* @__PURE__ */ Symbol.for("react.early_return_sentinel")) {
|
|
return t3;
|
|
}
|
|
const {
|
|
path: pathD,
|
|
labelX,
|
|
labelY
|
|
} = t2;
|
|
const edgeColor = isSelected ? SELECTED_EDGE_COLOR : color || styles4.edges.defaultColor;
|
|
const baseStrokeWidth = weight || styles4.edges.defaultWeight;
|
|
const effectiveStrokeWidth = (isSelected ? baseStrokeWidth * 1.5 : baseStrokeWidth) / zoom;
|
|
const effectiveFontSize = 10 / zoom;
|
|
const hitboxStrokeWidth = Math.max(10, baseStrokeWidth * 3) / zoom;
|
|
const isDimmedByFilter = isFilterActive && !searchResults.has(edgeData.sourceId) && !searchResults.has(edgeData.targetId) && !searchEdgeResults.has(edgeKey);
|
|
const edgeOpacity = isDimmedByFilter ? 0.2 : 1;
|
|
let t4;
|
|
if ($[43] !== edgeOpacity) {
|
|
t4 = {
|
|
cursor: "pointer",
|
|
opacity: edgeOpacity,
|
|
transition: "opacity 150ms ease"
|
|
};
|
|
$[43] = edgeOpacity;
|
|
$[44] = t4;
|
|
} else {
|
|
t4 = $[44];
|
|
}
|
|
const t5 = isDimmedByFilter ? "none" : "stroke";
|
|
let t6;
|
|
if ($[45] !== t5) {
|
|
t6 = {
|
|
pointerEvents: t5
|
|
};
|
|
$[45] = t5;
|
|
$[46] = t6;
|
|
} else {
|
|
t6 = $[46];
|
|
}
|
|
let t7;
|
|
if ($[47] !== handleClick || $[48] !== handleContextMenu || $[49] !== handleDoubleClick || $[50] !== handlePointerEnter || $[51] !== handlePointerLeave || $[52] !== hitboxStrokeWidth || $[53] !== pathD || $[54] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsx13("path", {
|
|
d: pathD,
|
|
stroke: "transparent",
|
|
strokeWidth: hitboxStrokeWidth,
|
|
fill: "none",
|
|
style: t6,
|
|
onClick: handleClick,
|
|
onDoubleClick: handleDoubleClick,
|
|
onContextMenu: handleContextMenu,
|
|
onPointerEnter: handlePointerEnter,
|
|
onPointerLeave: handlePointerLeave
|
|
});
|
|
$[47] = handleClick;
|
|
$[48] = handleContextMenu;
|
|
$[49] = handleDoubleClick;
|
|
$[50] = handlePointerEnter;
|
|
$[51] = handlePointerLeave;
|
|
$[52] = hitboxStrokeWidth;
|
|
$[53] = pathD;
|
|
$[54] = t6;
|
|
$[55] = t7;
|
|
} else {
|
|
t7 = $[55];
|
|
}
|
|
const t8 = type === "dashed" ? `calc(5px / ${zoom}) calc(5px / ${zoom})` : void 0;
|
|
let t9;
|
|
if ($[56] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t9 = {
|
|
pointerEvents: "none"
|
|
};
|
|
$[56] = t9;
|
|
} else {
|
|
t9 = $[56];
|
|
}
|
|
let t10;
|
|
if ($[57] !== edgeColor || $[58] !== effectiveStrokeWidth || $[59] !== pathD || $[60] !== t8) {
|
|
t10 = /* @__PURE__ */ _jsx13("path", {
|
|
d: pathD,
|
|
stroke: edgeColor,
|
|
strokeWidth: effectiveStrokeWidth,
|
|
fill: "none",
|
|
strokeDasharray: t8,
|
|
style: t9
|
|
});
|
|
$[57] = edgeColor;
|
|
$[58] = effectiveStrokeWidth;
|
|
$[59] = pathD;
|
|
$[60] = t8;
|
|
$[61] = t10;
|
|
} else {
|
|
t10 = $[61];
|
|
}
|
|
let t11;
|
|
if ($[62] !== edgeKey || $[63] !== effectiveFontSize || $[64] !== label || $[65] !== labelX || $[66] !== labelY || $[67] !== setEditingEdgeLabel || $[68] !== styles4.edges.labelColor || $[69] !== styles4.edges.labelStrokeColor || $[70] !== zoom) {
|
|
t11 = label && /* @__PURE__ */ _jsx13("text", {
|
|
x: labelX,
|
|
y: labelY,
|
|
fontSize: `${effectiveFontSize}px`,
|
|
fill: styles4.edges.labelColor,
|
|
stroke: styles4.edges.labelStrokeColor,
|
|
strokeWidth: `${0.2 / zoom}px`,
|
|
textAnchor: "middle",
|
|
dy: `${-5 / zoom}px`,
|
|
style: {
|
|
pointerEvents: "visiblePainted",
|
|
cursor: "text"
|
|
},
|
|
onDoubleClick: (e_2) => {
|
|
e_2.stopPropagation();
|
|
e_2.preventDefault();
|
|
setEditingEdgeLabel(edgeKey);
|
|
},
|
|
children: label
|
|
});
|
|
$[62] = edgeKey;
|
|
$[63] = effectiveFontSize;
|
|
$[64] = label;
|
|
$[65] = labelX;
|
|
$[66] = labelY;
|
|
$[67] = setEditingEdgeLabel;
|
|
$[68] = styles4.edges.labelColor;
|
|
$[69] = styles4.edges.labelStrokeColor;
|
|
$[70] = zoom;
|
|
$[71] = t11;
|
|
} else {
|
|
t11 = $[71];
|
|
}
|
|
let t12;
|
|
if ($[72] !== t10 || $[73] !== t11 || $[74] !== t4 || $[75] !== t7) {
|
|
t12 = /* @__PURE__ */ _jsxs5("g", {
|
|
style: t4,
|
|
children: [t7, t10, t11]
|
|
});
|
|
$[72] = t10;
|
|
$[73] = t11;
|
|
$[74] = t4;
|
|
$[75] = t7;
|
|
$[76] = t12;
|
|
} else {
|
|
t12 = $[76];
|
|
}
|
|
return t12;
|
|
}
|
|
function DepartingEdgeItem(t0) {
|
|
const $ = _c40(14);
|
|
const {
|
|
edgeData
|
|
} = t0;
|
|
const styles4 = useCanvasStyle();
|
|
const zoom = useAtomValue29(zoomAtom);
|
|
let color;
|
|
let t1;
|
|
let t2;
|
|
let type;
|
|
let weight;
|
|
if ($[0] !== edgeData || $[1] !== styles4.edges.pathType || $[2] !== zoom) {
|
|
t2 = /* @__PURE__ */ Symbol.for("react.early_return_sentinel");
|
|
bb0: {
|
|
const pathCalculator = getEdgePathCalculator(styles4.edges.pathType || "bezier");
|
|
const {
|
|
sourcePosition,
|
|
targetPosition,
|
|
color: t32,
|
|
weight: t42,
|
|
type: t52,
|
|
sourceEndpoint,
|
|
targetEndpoint
|
|
} = edgeData;
|
|
color = t32;
|
|
weight = t42;
|
|
type = t52;
|
|
const x1 = sourceEndpoint?.x ?? sourcePosition.x + edgeData.sourceNodeWidth / 2;
|
|
const y1 = sourceEndpoint?.y ?? sourcePosition.y + edgeData.sourceNodeHeight / 2;
|
|
const x2 = targetEndpoint?.x ?? targetPosition.x + edgeData.targetNodeWidth / 2;
|
|
const y2 = targetEndpoint?.y ?? targetPosition.y + edgeData.targetNodeHeight / 2;
|
|
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2) || zoom === 0) {
|
|
t2 = null;
|
|
break bb0;
|
|
}
|
|
t1 = pathCalculator({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
sourceWidth: edgeData.sourceNodeWidth,
|
|
sourceHeight: edgeData.sourceNodeHeight,
|
|
targetWidth: edgeData.targetNodeWidth,
|
|
targetHeight: edgeData.targetNodeHeight
|
|
});
|
|
}
|
|
$[0] = edgeData;
|
|
$[1] = styles4.edges.pathType;
|
|
$[2] = zoom;
|
|
$[3] = color;
|
|
$[4] = t1;
|
|
$[5] = t2;
|
|
$[6] = type;
|
|
$[7] = weight;
|
|
} else {
|
|
color = $[3];
|
|
t1 = $[4];
|
|
t2 = $[5];
|
|
type = $[6];
|
|
weight = $[7];
|
|
}
|
|
if (t2 !== /* @__PURE__ */ Symbol.for("react.early_return_sentinel")) {
|
|
return t2;
|
|
}
|
|
const {
|
|
path: pathD
|
|
} = t1;
|
|
const edgeColor = color || styles4.edges.defaultColor;
|
|
const baseStrokeWidth = weight || styles4.edges.defaultWeight;
|
|
const effectiveStrokeWidth = baseStrokeWidth / zoom;
|
|
const t3 = type === "dashed" ? `calc(5px / ${zoom}) calc(5px / ${zoom})` : void 0;
|
|
let t4;
|
|
if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = {
|
|
pointerEvents: "none"
|
|
};
|
|
$[8] = t4;
|
|
} else {
|
|
t4 = $[8];
|
|
}
|
|
let t5;
|
|
if ($[9] !== edgeColor || $[10] !== effectiveStrokeWidth || $[11] !== pathD || $[12] !== t3) {
|
|
t5 = /* @__PURE__ */ _jsx13("g", {
|
|
className: "canvas-edge-exit",
|
|
children: /* @__PURE__ */ _jsx13("path", {
|
|
d: pathD,
|
|
stroke: edgeColor,
|
|
strokeWidth: effectiveStrokeWidth,
|
|
fill: "none",
|
|
strokeDasharray: t3,
|
|
style: t4
|
|
})
|
|
});
|
|
$[9] = edgeColor;
|
|
$[10] = effectiveStrokeWidth;
|
|
$[11] = pathD;
|
|
$[12] = t3;
|
|
$[13] = t5;
|
|
} else {
|
|
t5 = $[13];
|
|
}
|
|
return t5;
|
|
}
|
|
|
|
// src/components/EdgeLabelEditor.tsx
|
|
init_graph_derived();
|
|
init_graph_mutations();
|
|
init_viewport_store();
|
|
init_edge_path_calculators();
|
|
import { c as _c41 } from "react/compiler-runtime";
|
|
import React14, { useRef as useRef19, useEffect as useEffect13 } from "react";
|
|
import { useAtomValue as useAtomValue30, useSetAtom as useSetAtom25 } from "jotai";
|
|
import { jsx as _jsx14 } from "react/jsx-runtime";
|
|
function EdgeLabelEditor(t0) {
|
|
const $ = _c41(3);
|
|
const {
|
|
className
|
|
} = t0;
|
|
const editingEdgeKey = useAtomValue30(editingEdgeLabelAtom);
|
|
if (!editingEdgeKey) {
|
|
return null;
|
|
}
|
|
let t1;
|
|
if ($[0] !== className || $[1] !== editingEdgeKey) {
|
|
t1 = /* @__PURE__ */ _jsx14(EdgeLabelInput, {
|
|
edgeKey: editingEdgeKey,
|
|
className
|
|
});
|
|
$[0] = className;
|
|
$[1] = editingEdgeKey;
|
|
$[2] = t1;
|
|
} else {
|
|
t1 = $[2];
|
|
}
|
|
return t1;
|
|
}
|
|
function EdgeLabelInput(t0) {
|
|
const $ = _c41(29);
|
|
const {
|
|
edgeKey,
|
|
className
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== edgeKey) {
|
|
t1 = edgeFamilyAtom(edgeKey);
|
|
$[0] = edgeKey;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const edgeData = useAtomValue30(t1);
|
|
const setEditingEdge = useSetAtom25(editingEdgeLabelAtom);
|
|
const updateLabel = useSetAtom25(updateEdgeLabelAtom);
|
|
const pan = useAtomValue30(panAtom);
|
|
const zoom = useAtomValue30(zoomAtom);
|
|
const styles4 = useCanvasStyle();
|
|
const inputRef = useRef19(null);
|
|
let t2;
|
|
let t3;
|
|
if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = () => {
|
|
if (inputRef.current) {
|
|
inputRef.current.focus();
|
|
inputRef.current.select();
|
|
}
|
|
};
|
|
t3 = [];
|
|
$[2] = t2;
|
|
$[3] = t3;
|
|
} else {
|
|
t2 = $[2];
|
|
t3 = $[3];
|
|
}
|
|
useEffect13(t2, t3);
|
|
if (!edgeData) {
|
|
setEditingEdge(null);
|
|
return null;
|
|
}
|
|
const {
|
|
sourcePosition,
|
|
targetPosition,
|
|
sourceEndpoint,
|
|
targetEndpoint
|
|
} = edgeData;
|
|
const x1 = sourceEndpoint?.x ?? sourcePosition.x + edgeData.sourceNodeWidth / 2;
|
|
const y1 = sourceEndpoint?.y ?? sourcePosition.y + edgeData.sourceNodeHeight / 2;
|
|
const x2 = targetEndpoint?.x ?? targetPosition.x + edgeData.targetNodeWidth / 2;
|
|
const y2 = targetEndpoint?.y ?? targetPosition.y + edgeData.targetNodeHeight / 2;
|
|
let t4;
|
|
if ($[4] !== edgeData.sourceNodeHeight || $[5] !== edgeData.sourceNodeWidth || $[6] !== edgeData.targetNodeHeight || $[7] !== edgeData.targetNodeWidth || $[8] !== styles4.edges.pathType || $[9] !== x1 || $[10] !== x2 || $[11] !== y1 || $[12] !== y2) {
|
|
const pathCalculator = getEdgePathCalculator(styles4.edges.pathType || "bezier");
|
|
t4 = pathCalculator({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
sourceWidth: edgeData.sourceNodeWidth,
|
|
sourceHeight: edgeData.sourceNodeHeight,
|
|
targetWidth: edgeData.targetNodeWidth,
|
|
targetHeight: edgeData.targetNodeHeight
|
|
});
|
|
$[4] = edgeData.sourceNodeHeight;
|
|
$[5] = edgeData.sourceNodeWidth;
|
|
$[6] = edgeData.targetNodeHeight;
|
|
$[7] = edgeData.targetNodeWidth;
|
|
$[8] = styles4.edges.pathType;
|
|
$[9] = x1;
|
|
$[10] = x2;
|
|
$[11] = y1;
|
|
$[12] = y2;
|
|
$[13] = t4;
|
|
} else {
|
|
t4 = $[13];
|
|
}
|
|
const {
|
|
labelX,
|
|
labelY
|
|
} = t4;
|
|
const screenX = labelX * zoom + pan.x;
|
|
const screenY = labelY * zoom + pan.y;
|
|
let t5;
|
|
if ($[14] !== edgeKey || $[15] !== setEditingEdge || $[16] !== updateLabel) {
|
|
t5 = (e) => {
|
|
updateLabel({
|
|
edgeKey,
|
|
label: e.target.value
|
|
});
|
|
setEditingEdge(null);
|
|
};
|
|
$[14] = edgeKey;
|
|
$[15] = setEditingEdge;
|
|
$[16] = updateLabel;
|
|
$[17] = t5;
|
|
} else {
|
|
t5 = $[17];
|
|
}
|
|
const handleBlur = t5;
|
|
let t6;
|
|
if ($[18] !== setEditingEdge) {
|
|
t6 = (e_0) => {
|
|
if (e_0.key === "Enter") {
|
|
e_0.currentTarget.blur();
|
|
} else {
|
|
if (e_0.key === "Escape") {
|
|
setEditingEdge(null);
|
|
}
|
|
}
|
|
};
|
|
$[18] = setEditingEdge;
|
|
$[19] = t6;
|
|
} else {
|
|
t6 = $[19];
|
|
}
|
|
const handleKeyDown = t6;
|
|
const t7 = edgeData.label || "";
|
|
let t8;
|
|
if ($[20] !== screenX || $[21] !== screenY) {
|
|
t8 = {
|
|
position: "absolute",
|
|
left: screenX,
|
|
top: screenY,
|
|
transform: "translate(-50%, -50%)",
|
|
zIndex: 1e4,
|
|
fontSize: "10px",
|
|
padding: "2px 6px",
|
|
border: "1px solid #3b82f6",
|
|
borderRadius: "3px",
|
|
outline: "none",
|
|
background: "white",
|
|
textAlign: "center",
|
|
minWidth: "60px"
|
|
};
|
|
$[20] = screenX;
|
|
$[21] = screenY;
|
|
$[22] = t8;
|
|
} else {
|
|
t8 = $[22];
|
|
}
|
|
let t9;
|
|
if ($[23] !== className || $[24] !== handleBlur || $[25] !== handleKeyDown || $[26] !== t7 || $[27] !== t8) {
|
|
t9 = /* @__PURE__ */ _jsx14("input", {
|
|
ref: inputRef,
|
|
"data-no-drag": "true",
|
|
className,
|
|
defaultValue: t7,
|
|
onBlur: handleBlur,
|
|
onKeyDown: handleKeyDown,
|
|
style: t8
|
|
});
|
|
$[23] = className;
|
|
$[24] = handleBlur;
|
|
$[25] = handleKeyDown;
|
|
$[26] = t7;
|
|
$[27] = t8;
|
|
$[28] = t9;
|
|
} else {
|
|
t9 = $[28];
|
|
}
|
|
return t9;
|
|
}
|
|
|
|
// src/components/EdgePreviewLine.tsx
|
|
init_graph_store();
|
|
init_edge_path_calculators();
|
|
import { c as _c42 } from "react/compiler-runtime";
|
|
import React15 from "react";
|
|
import { useAtomValue as useAtomValue31 } from "jotai";
|
|
import { jsx as _jsx15, jsxs as _jsxs6 } from "react/jsx-runtime";
|
|
var DASH_ARRAY = "8 4";
|
|
var IDLE_COLOR = "rgba(150, 150, 150, 0.6)";
|
|
var VALID_TARGET_COLOR = "var(--canvas-edge-preview-valid, #4ade80)";
|
|
var SNAP_COLOR = "var(--canvas-edge-preview-snap, #60a5fa)";
|
|
function EdgePreviewLine() {
|
|
const $ = _c42(28);
|
|
const edgeCreation = useAtomValue31(edgeCreationAtom);
|
|
if (!edgeCreation.isCreating || !edgeCreation.sourceNodePosition) {
|
|
return null;
|
|
}
|
|
const source = edgeCreation.sourceNodePosition;
|
|
const target = edgeCreation.snappedTargetPosition ?? edgeCreation.targetPosition;
|
|
if (!target) {
|
|
return null;
|
|
}
|
|
const hasValidTarget = edgeCreation.hoveredTargetNodeId !== null;
|
|
const isSnapped = edgeCreation.snappedTargetPosition !== null;
|
|
let strokeColor = IDLE_COLOR;
|
|
if (isSnapped) {
|
|
strokeColor = SNAP_COLOR;
|
|
} else {
|
|
if (hasValidTarget) {
|
|
strokeColor = VALID_TARGET_COLOR;
|
|
}
|
|
}
|
|
let t0;
|
|
if ($[0] !== source.x || $[1] !== source.y || $[2] !== target.x || $[3] !== target.y) {
|
|
const pathInput = {
|
|
x1: source.x,
|
|
y1: source.y,
|
|
x2: target.x,
|
|
y2: target.y,
|
|
sourceWidth: 0,
|
|
sourceHeight: 0,
|
|
targetWidth: 0,
|
|
targetHeight: 0
|
|
};
|
|
t0 = bezierSmart(pathInput);
|
|
$[0] = source.x;
|
|
$[1] = source.y;
|
|
$[2] = target.x;
|
|
$[3] = target.y;
|
|
$[4] = t0;
|
|
} else {
|
|
t0 = $[4];
|
|
}
|
|
const {
|
|
path
|
|
} = t0;
|
|
const dotRadius = isSnapped ? 6 : 4;
|
|
let t1;
|
|
if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
pointerEvents: "none",
|
|
overflow: "visible",
|
|
zIndex: 5
|
|
};
|
|
$[5] = t1;
|
|
} else {
|
|
t1 = $[5];
|
|
}
|
|
const t2 = isSnapped ? 2.5 : 2;
|
|
let t3;
|
|
let t4;
|
|
if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t3 = {
|
|
transition: "stroke 150ms, stroke-width 150ms"
|
|
};
|
|
t4 = /* @__PURE__ */ _jsx15("animate", {
|
|
attributeName: "stroke-dashoffset",
|
|
from: "24",
|
|
to: "0",
|
|
dur: "0.8s",
|
|
repeatCount: "indefinite"
|
|
});
|
|
$[6] = t3;
|
|
$[7] = t4;
|
|
} else {
|
|
t3 = $[6];
|
|
t4 = $[7];
|
|
}
|
|
let t5;
|
|
if ($[8] !== path || $[9] !== strokeColor || $[10] !== t2) {
|
|
t5 = /* @__PURE__ */ _jsx15("path", {
|
|
d: path,
|
|
fill: "none",
|
|
stroke: strokeColor,
|
|
strokeWidth: t2,
|
|
strokeDasharray: DASH_ARRAY,
|
|
strokeLinecap: "round",
|
|
style: t3,
|
|
children: t4
|
|
});
|
|
$[8] = path;
|
|
$[9] = strokeColor;
|
|
$[10] = t2;
|
|
$[11] = t5;
|
|
} else {
|
|
t5 = $[11];
|
|
}
|
|
let t6;
|
|
if ($[12] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t6 = {
|
|
transition: "fill 150ms"
|
|
};
|
|
$[12] = t6;
|
|
} else {
|
|
t6 = $[12];
|
|
}
|
|
let t7;
|
|
if ($[13] !== source.x || $[14] !== source.y || $[15] !== strokeColor) {
|
|
t7 = /* @__PURE__ */ _jsx15("circle", {
|
|
cx: source.x,
|
|
cy: source.y,
|
|
r: 4,
|
|
fill: strokeColor,
|
|
style: t6
|
|
});
|
|
$[13] = source.x;
|
|
$[14] = source.y;
|
|
$[15] = strokeColor;
|
|
$[16] = t7;
|
|
} else {
|
|
t7 = $[16];
|
|
}
|
|
const t8 = isSnapped ? SNAP_COLOR : "transparent";
|
|
let t9;
|
|
if ($[17] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t9 = {
|
|
transition: "r 150ms, fill 150ms"
|
|
};
|
|
$[17] = t9;
|
|
} else {
|
|
t9 = $[17];
|
|
}
|
|
let t10;
|
|
if ($[18] !== dotRadius || $[19] !== strokeColor || $[20] !== t8 || $[21] !== target.x || $[22] !== target.y) {
|
|
t10 = /* @__PURE__ */ _jsx15("circle", {
|
|
cx: target.x,
|
|
cy: target.y,
|
|
r: dotRadius,
|
|
fill: t8,
|
|
stroke: strokeColor,
|
|
strokeWidth: 1.5,
|
|
style: t9
|
|
});
|
|
$[18] = dotRadius;
|
|
$[19] = strokeColor;
|
|
$[20] = t8;
|
|
$[21] = target.x;
|
|
$[22] = target.y;
|
|
$[23] = t10;
|
|
} else {
|
|
t10 = $[23];
|
|
}
|
|
let t11;
|
|
if ($[24] !== t10 || $[25] !== t5 || $[26] !== t7) {
|
|
t11 = /* @__PURE__ */ _jsxs6("svg", {
|
|
style: t1,
|
|
children: [t5, t7, t10]
|
|
});
|
|
$[24] = t10;
|
|
$[25] = t5;
|
|
$[26] = t7;
|
|
$[27] = t11;
|
|
} else {
|
|
t11 = $[27];
|
|
}
|
|
return t11;
|
|
}
|
|
|
|
// src/hooks/useSelectionChangeEffect.ts
|
|
init_selection_store();
|
|
import { c as _c43 } from "react/compiler-runtime";
|
|
import { useEffect as useEffect14, useRef as useRef20 } from "react";
|
|
import { useAtomValue as useAtomValue32 } from "jotai";
|
|
function useSelectionChangeEffect(onSelectionChange) {
|
|
const $ = _c43(5);
|
|
const selectedNodeIds = useAtomValue32(selectedNodeIdsAtom);
|
|
const selectedEdgeId = useAtomValue32(selectedEdgeIdAtom);
|
|
const isFirstRender = useRef20(true);
|
|
let t0;
|
|
let t1;
|
|
if ($[0] !== onSelectionChange || $[1] !== selectedEdgeId || $[2] !== selectedNodeIds) {
|
|
t0 = () => {
|
|
if (isFirstRender.current) {
|
|
isFirstRender.current = false;
|
|
return;
|
|
}
|
|
onSelectionChange?.(selectedNodeIds, selectedEdgeId);
|
|
};
|
|
t1 = [selectedNodeIds, selectedEdgeId, onSelectionChange];
|
|
$[0] = onSelectionChange;
|
|
$[1] = selectedEdgeId;
|
|
$[2] = selectedNodeIds;
|
|
$[3] = t0;
|
|
$[4] = t1;
|
|
} else {
|
|
t0 = $[3];
|
|
t1 = $[4];
|
|
}
|
|
useEffect14(t0, t1);
|
|
}
|
|
|
|
// src/hooks/useViewportChangeEffect.ts
|
|
init_viewport_store();
|
|
import { c as _c44 } from "react/compiler-runtime";
|
|
import { useEffect as useEffect15, useRef as useRef21 } from "react";
|
|
import { useAtomValue as useAtomValue33 } from "jotai";
|
|
function useViewportChangeEffect(onViewportChange) {
|
|
const $ = _c44(5);
|
|
const zoom = useAtomValue33(zoomAtom);
|
|
const pan = useAtomValue33(panAtom);
|
|
const rafRef = useRef21(null);
|
|
const isFirstRender = useRef21(true);
|
|
let t0;
|
|
let t1;
|
|
if ($[0] !== onViewportChange || $[1] !== pan || $[2] !== zoom) {
|
|
t0 = () => {
|
|
if (isFirstRender.current) {
|
|
isFirstRender.current = false;
|
|
return;
|
|
}
|
|
if (!onViewportChange) {
|
|
return;
|
|
}
|
|
if (rafRef.current !== null) {
|
|
cancelAnimationFrame(rafRef.current);
|
|
}
|
|
rafRef.current = requestAnimationFrame(() => {
|
|
onViewportChange({
|
|
zoom,
|
|
pan
|
|
});
|
|
rafRef.current = null;
|
|
});
|
|
return () => {
|
|
if (rafRef.current !== null) {
|
|
cancelAnimationFrame(rafRef.current);
|
|
}
|
|
};
|
|
};
|
|
t1 = [zoom, pan, onViewportChange];
|
|
$[0] = onViewportChange;
|
|
$[1] = pan;
|
|
$[2] = zoom;
|
|
$[3] = t0;
|
|
$[4] = t1;
|
|
} else {
|
|
t0 = $[3];
|
|
t1 = $[4];
|
|
}
|
|
useEffect15(t0, t1);
|
|
}
|
|
|
|
// src/hooks/useDragLifecycleEffect.ts
|
|
init_graph_store();
|
|
init_selection_store();
|
|
import { c as _c45 } from "react/compiler-runtime";
|
|
import { useEffect as useEffect16, useRef as useRef22 } from "react";
|
|
import { useAtomValue as useAtomValue34, useStore as useStore5 } from "jotai";
|
|
function useDragLifecycleEffect(onDragStart, onDragEnd) {
|
|
const $ = _c45(7);
|
|
const draggingNodeId = useAtomValue34(draggingNodeIdAtom);
|
|
const store = useStore5();
|
|
const prevDraggingRef = useRef22(null);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = [];
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
const draggedNodeIdsRef = useRef22(t0);
|
|
let t1;
|
|
let t2;
|
|
if ($[1] !== draggingNodeId || $[2] !== onDragEnd || $[3] !== onDragStart || $[4] !== store) {
|
|
t1 = () => {
|
|
const prev = prevDraggingRef.current;
|
|
prevDraggingRef.current = draggingNodeId;
|
|
if (prev === null && draggingNodeId !== null) {
|
|
const selectedIds = Array.from(store.get(selectedNodeIdsAtom));
|
|
draggedNodeIdsRef.current = selectedIds;
|
|
onDragStart?.(selectedIds);
|
|
} else {
|
|
if (prev !== null && draggingNodeId === null) {
|
|
const nodeIds = draggedNodeIdsRef.current;
|
|
if (onDragEnd && nodeIds.length > 0) {
|
|
const graph = store.get(graphAtom);
|
|
const positions = /* @__PURE__ */ new Map();
|
|
for (const id of nodeIds) {
|
|
if (graph.hasNode(id)) {
|
|
const attrs = graph.getNodeAttributes(id);
|
|
positions.set(id, {
|
|
x: attrs.x,
|
|
y: attrs.y
|
|
});
|
|
}
|
|
}
|
|
onDragEnd(nodeIds, positions);
|
|
}
|
|
draggedNodeIdsRef.current = [];
|
|
}
|
|
}
|
|
};
|
|
t2 = [draggingNodeId, store, onDragStart, onDragEnd];
|
|
$[1] = draggingNodeId;
|
|
$[2] = onDragEnd;
|
|
$[3] = onDragStart;
|
|
$[4] = store;
|
|
$[5] = t1;
|
|
$[6] = t2;
|
|
} else {
|
|
t1 = $[5];
|
|
t2 = $[6];
|
|
}
|
|
useEffect16(t1, t2);
|
|
}
|
|
|
|
// src/components/Canvas.tsx
|
|
import { jsx as _jsx16, jsxs as _jsxs7 } from "react/jsx-runtime";
|
|
function Canvas(t0) {
|
|
const $ = _c46(44);
|
|
let children;
|
|
let gestureConfig;
|
|
let nodeWrapper;
|
|
let onAction;
|
|
let onDragEnd;
|
|
let onDragStart;
|
|
let onEdgeClick;
|
|
let onEdgeDoubleClick;
|
|
let onEdgeHover;
|
|
let onEdgeLeave;
|
|
let onEdgeRightClick;
|
|
let onNodePersist;
|
|
let onNodePersistError;
|
|
let onNodeRenderError;
|
|
let onSelectionChange;
|
|
let onViewportChange;
|
|
let renderErrorFallback;
|
|
let renderNode;
|
|
let viewportProps;
|
|
if ($[0] !== t0) {
|
|
({
|
|
renderNode,
|
|
onEdgeClick,
|
|
onEdgeDoubleClick,
|
|
onEdgeRightClick,
|
|
onEdgeHover,
|
|
onEdgeLeave,
|
|
onSelectionChange,
|
|
onViewportChange,
|
|
onDragStart,
|
|
onDragEnd,
|
|
onNodePersist,
|
|
onNodePersistError,
|
|
onNodeRenderError,
|
|
renderErrorFallback,
|
|
gestureConfig,
|
|
onAction,
|
|
nodeWrapper,
|
|
children,
|
|
...viewportProps
|
|
} = t0);
|
|
$[0] = t0;
|
|
$[1] = children;
|
|
$[2] = gestureConfig;
|
|
$[3] = nodeWrapper;
|
|
$[4] = onAction;
|
|
$[5] = onDragEnd;
|
|
$[6] = onDragStart;
|
|
$[7] = onEdgeClick;
|
|
$[8] = onEdgeDoubleClick;
|
|
$[9] = onEdgeHover;
|
|
$[10] = onEdgeLeave;
|
|
$[11] = onEdgeRightClick;
|
|
$[12] = onNodePersist;
|
|
$[13] = onNodePersistError;
|
|
$[14] = onNodeRenderError;
|
|
$[15] = onSelectionChange;
|
|
$[16] = onViewportChange;
|
|
$[17] = renderErrorFallback;
|
|
$[18] = renderNode;
|
|
$[19] = viewportProps;
|
|
} else {
|
|
children = $[1];
|
|
gestureConfig = $[2];
|
|
nodeWrapper = $[3];
|
|
onAction = $[4];
|
|
onDragEnd = $[5];
|
|
onDragStart = $[6];
|
|
onEdgeClick = $[7];
|
|
onEdgeDoubleClick = $[8];
|
|
onEdgeHover = $[9];
|
|
onEdgeLeave = $[10];
|
|
onEdgeRightClick = $[11];
|
|
onNodePersist = $[12];
|
|
onNodePersistError = $[13];
|
|
onNodeRenderError = $[14];
|
|
onSelectionChange = $[15];
|
|
onViewportChange = $[16];
|
|
renderErrorFallback = $[17];
|
|
renderNode = $[18];
|
|
viewportProps = $[19];
|
|
}
|
|
useSelectionChangeEffect(onSelectionChange);
|
|
useViewportChangeEffect(onViewportChange);
|
|
useDragLifecycleEffect(onDragStart, onDragEnd);
|
|
let t1;
|
|
if ($[20] !== onEdgeClick || $[21] !== onEdgeDoubleClick || $[22] !== onEdgeHover || $[23] !== onEdgeLeave || $[24] !== onEdgeRightClick) {
|
|
t1 = /* @__PURE__ */ _jsx16(EdgeRenderer, {
|
|
onEdgeClick,
|
|
onEdgeDoubleClick,
|
|
onEdgeRightClick,
|
|
onEdgeHover,
|
|
onEdgeLeave
|
|
});
|
|
$[20] = onEdgeClick;
|
|
$[21] = onEdgeDoubleClick;
|
|
$[22] = onEdgeHover;
|
|
$[23] = onEdgeLeave;
|
|
$[24] = onEdgeRightClick;
|
|
$[25] = t1;
|
|
} else {
|
|
t1 = $[25];
|
|
}
|
|
let t2;
|
|
if ($[26] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = /* @__PURE__ */ _jsx16(EdgePreviewLine, {});
|
|
$[26] = t2;
|
|
} else {
|
|
t2 = $[26];
|
|
}
|
|
let t3;
|
|
if ($[27] !== nodeWrapper || $[28] !== onNodePersist || $[29] !== onNodePersistError || $[30] !== onNodeRenderError || $[31] !== renderErrorFallback || $[32] !== renderNode) {
|
|
t3 = /* @__PURE__ */ _jsx16(NodeRenderer, {
|
|
renderNode,
|
|
onNodePersist,
|
|
onNodePersistError,
|
|
onNodeRenderError,
|
|
renderErrorFallback,
|
|
nodeWrapper
|
|
});
|
|
$[27] = nodeWrapper;
|
|
$[28] = onNodePersist;
|
|
$[29] = onNodePersistError;
|
|
$[30] = onNodeRenderError;
|
|
$[31] = renderErrorFallback;
|
|
$[32] = renderNode;
|
|
$[33] = t3;
|
|
} else {
|
|
t3 = $[33];
|
|
}
|
|
let t4;
|
|
if ($[34] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = /* @__PURE__ */ _jsx16(EdgeLabelEditor, {});
|
|
$[34] = t4;
|
|
} else {
|
|
t4 = $[34];
|
|
}
|
|
let t5;
|
|
if ($[35] !== children || $[36] !== t1 || $[37] !== t3 || $[38] !== viewportProps) {
|
|
t5 = /* @__PURE__ */ _jsxs7(Viewport, {
|
|
...viewportProps,
|
|
children: [t1, t2, t3, t4, children]
|
|
});
|
|
$[35] = children;
|
|
$[36] = t1;
|
|
$[37] = t3;
|
|
$[38] = viewportProps;
|
|
$[39] = t5;
|
|
} else {
|
|
t5 = $[39];
|
|
}
|
|
let t6;
|
|
if ($[40] !== gestureConfig || $[41] !== onAction || $[42] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx16(InputProvider, {
|
|
gestureConfig,
|
|
onAction,
|
|
children: t5
|
|
});
|
|
$[40] = gestureConfig;
|
|
$[41] = onAction;
|
|
$[42] = t5;
|
|
$[43] = t6;
|
|
} else {
|
|
t6 = $[43];
|
|
}
|
|
return t6;
|
|
}
|
|
|
|
// src/components/ConnectedNode.tsx
|
|
import { c as _c47 } from "react/compiler-runtime";
|
|
import React17 from "react";
|
|
init_debug();
|
|
import { jsx as _jsx17 } from "react/jsx-runtime";
|
|
var debug20 = createDebug("connected-node");
|
|
function ConnectedNode(t0) {
|
|
const $ = _c47(10);
|
|
const {
|
|
nodeData,
|
|
renderContent,
|
|
onPersistError: customErrorHandler,
|
|
wrapper
|
|
} = t0;
|
|
const {
|
|
mutateAsync: updateNode
|
|
} = useUpdateNode();
|
|
let t1;
|
|
if ($[0] !== updateNode) {
|
|
t1 = async (nodeId, graphId, uiProperties) => {
|
|
debug20("Persisting node %s to graph %s", nodeId, graphId);
|
|
await updateNode({
|
|
nodeId,
|
|
graphId,
|
|
updates: {
|
|
ui_properties: uiProperties
|
|
}
|
|
});
|
|
};
|
|
$[0] = updateNode;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const handlePersist = t1;
|
|
let t2;
|
|
if ($[2] !== customErrorHandler) {
|
|
t2 = (nodeId_0, error) => {
|
|
debug20("Failed to persist node %s: %o", nodeId_0, error);
|
|
customErrorHandler?.(nodeId_0, error);
|
|
};
|
|
$[2] = customErrorHandler;
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
const handlePersistError = t2;
|
|
let t3;
|
|
if ($[4] !== handlePersist || $[5] !== handlePersistError || $[6] !== nodeData || $[7] !== renderContent || $[8] !== wrapper) {
|
|
t3 = /* @__PURE__ */ _jsx17(Node, {
|
|
nodeData,
|
|
renderContent,
|
|
onPersist: handlePersist,
|
|
onPersistError: handlePersistError,
|
|
wrapper
|
|
});
|
|
$[4] = handlePersist;
|
|
$[5] = handlePersistError;
|
|
$[6] = nodeData;
|
|
$[7] = renderContent;
|
|
$[8] = wrapper;
|
|
$[9] = t3;
|
|
} else {
|
|
t3 = $[9];
|
|
}
|
|
return t3;
|
|
}
|
|
|
|
// src/components/ConnectedNodeRenderer.tsx
|
|
init_graph_position();
|
|
init_graph_derived();
|
|
init_virtualization_store();
|
|
import { c as _c48 } from "react/compiler-runtime";
|
|
import React18 from "react";
|
|
import { useAtomValue as useAtomValue35 } from "jotai";
|
|
import { jsx as _jsx18, Fragment as _Fragment4 } from "react/jsx-runtime";
|
|
function ConnectedNodeRenderer(t0) {
|
|
const $ = _c48(11);
|
|
const {
|
|
renderNode,
|
|
onNodePersistError,
|
|
nodeWrapper
|
|
} = t0;
|
|
const nodeKeys = useAtomValue35(visibleNodeKeysAtom);
|
|
let t1;
|
|
if ($[0] !== nodeKeys || $[1] !== nodeWrapper || $[2] !== onNodePersistError || $[3] !== renderNode) {
|
|
let t22;
|
|
if ($[5] !== nodeWrapper || $[6] !== onNodePersistError || $[7] !== renderNode) {
|
|
t22 = (key) => /* @__PURE__ */ _jsx18(ConnectedNodeItem, {
|
|
nodeKey: key,
|
|
renderNode,
|
|
onNodePersistError,
|
|
nodeWrapper
|
|
}, key);
|
|
$[5] = nodeWrapper;
|
|
$[6] = onNodePersistError;
|
|
$[7] = renderNode;
|
|
$[8] = t22;
|
|
} else {
|
|
t22 = $[8];
|
|
}
|
|
t1 = nodeKeys.map(t22);
|
|
$[0] = nodeKeys;
|
|
$[1] = nodeWrapper;
|
|
$[2] = onNodePersistError;
|
|
$[3] = renderNode;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
let t2;
|
|
if ($[9] !== t1) {
|
|
t2 = /* @__PURE__ */ _jsx18(_Fragment4, {
|
|
children: t1
|
|
});
|
|
$[9] = t1;
|
|
$[10] = t2;
|
|
} else {
|
|
t2 = $[10];
|
|
}
|
|
return t2;
|
|
}
|
|
function ConnectedNodeItem(t0) {
|
|
const $ = _c48(15);
|
|
const {
|
|
nodeKey,
|
|
renderNode,
|
|
onNodePersistError,
|
|
nodeWrapper
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== nodeKey) {
|
|
t1 = nodeFamilyAtom(nodeKey);
|
|
$[0] = nodeKey;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const nodeData = useAtomValue35(t1);
|
|
let t2;
|
|
if ($[2] !== nodeKey) {
|
|
t2 = nodePositionAtomFamily(nodeKey);
|
|
$[2] = nodeKey;
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
const position = useAtomValue35(t2);
|
|
if (!nodeData) {
|
|
return null;
|
|
}
|
|
let t3;
|
|
if ($[4] !== position.x || $[5] !== position.y) {
|
|
t3 = {
|
|
position: "absolute",
|
|
left: position.x,
|
|
top: position.y,
|
|
transform: "translate(0, 0)"
|
|
};
|
|
$[4] = position.x;
|
|
$[5] = position.y;
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
let t4;
|
|
if ($[7] !== nodeData || $[8] !== nodeWrapper || $[9] !== onNodePersistError || $[10] !== renderNode) {
|
|
t4 = /* @__PURE__ */ _jsx18(ConnectedNode, {
|
|
nodeData,
|
|
renderContent: renderNode,
|
|
onPersistError: onNodePersistError,
|
|
wrapper: nodeWrapper
|
|
});
|
|
$[7] = nodeData;
|
|
$[8] = nodeWrapper;
|
|
$[9] = onNodePersistError;
|
|
$[10] = renderNode;
|
|
$[11] = t4;
|
|
} else {
|
|
t4 = $[11];
|
|
}
|
|
let t5;
|
|
if ($[12] !== t3 || $[13] !== t4) {
|
|
t5 = /* @__PURE__ */ _jsx18("div", {
|
|
style: t3,
|
|
children: t4
|
|
});
|
|
$[12] = t3;
|
|
$[13] = t4;
|
|
$[14] = t5;
|
|
} else {
|
|
t5 = $[14];
|
|
}
|
|
return t5;
|
|
}
|
|
|
|
// src/components/NodeContextMenu.tsx
|
|
init_graph_store();
|
|
init_selection_store();
|
|
init_input_store();
|
|
import { c as _c51 } from "react/compiler-runtime";
|
|
import React21, { useRef as useRef23 } from "react";
|
|
import { useAtomValue as useAtomValue36, useSetAtom as useSetAtom26 } from "jotai";
|
|
|
|
// src/components/ContextMenuAction.tsx
|
|
import { c as _c49 } from "react/compiler-runtime";
|
|
import React19 from "react";
|
|
import { jsx as _jsx19, jsxs as _jsxs8 } from "react/jsx-runtime";
|
|
function ContextMenuAction(t0) {
|
|
const $ = _c49(18);
|
|
const {
|
|
label,
|
|
icon,
|
|
onClick,
|
|
destructive: t1,
|
|
disabled: t2
|
|
} = t0;
|
|
const destructive = t1 === void 0 ? false : t1;
|
|
const disabled = t2 === void 0 ? false : t2;
|
|
const t3 = disabled ? "not-allowed" : "pointer";
|
|
const t4 = disabled ? 0.5 : 1;
|
|
const t5 = destructive ? "#ef4444" : "var(--canvas-text, #fff)";
|
|
let t6;
|
|
if ($[0] !== t3 || $[1] !== t4 || $[2] !== t5) {
|
|
t6 = {
|
|
width: "100%",
|
|
minHeight: "48px",
|
|
padding: "12px 16px",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "12px",
|
|
background: "none",
|
|
border: "none",
|
|
borderRadius: "8px",
|
|
cursor: t3,
|
|
opacity: t4,
|
|
color: t5,
|
|
fontSize: "16px",
|
|
textAlign: "left",
|
|
touchAction: "manipulation",
|
|
WebkitTapHighlightColor: "transparent"
|
|
};
|
|
$[0] = t3;
|
|
$[1] = t4;
|
|
$[2] = t5;
|
|
$[3] = t6;
|
|
} else {
|
|
t6 = $[3];
|
|
}
|
|
let t7;
|
|
if ($[4] !== destructive || $[5] !== disabled) {
|
|
t7 = (e) => {
|
|
if (!disabled) {
|
|
e.currentTarget.style.backgroundColor = destructive ? "rgba(239, 68, 68, 0.1)" : "rgba(255, 255, 255, 0.1)";
|
|
}
|
|
};
|
|
$[4] = destructive;
|
|
$[5] = disabled;
|
|
$[6] = t7;
|
|
} else {
|
|
t7 = $[6];
|
|
}
|
|
let t8;
|
|
if ($[7] !== icon) {
|
|
t8 = icon && /* @__PURE__ */ _jsx19("span", {
|
|
style: {
|
|
display: "flex"
|
|
},
|
|
children: icon
|
|
});
|
|
$[7] = icon;
|
|
$[8] = t8;
|
|
} else {
|
|
t8 = $[8];
|
|
}
|
|
let t9;
|
|
if ($[9] !== label) {
|
|
t9 = /* @__PURE__ */ _jsx19("span", {
|
|
children: label
|
|
});
|
|
$[9] = label;
|
|
$[10] = t9;
|
|
} else {
|
|
t9 = $[10];
|
|
}
|
|
let t10;
|
|
if ($[11] !== disabled || $[12] !== onClick || $[13] !== t6 || $[14] !== t7 || $[15] !== t8 || $[16] !== t9) {
|
|
t10 = /* @__PURE__ */ _jsxs8("button", {
|
|
onClick,
|
|
disabled,
|
|
style: t6,
|
|
onPointerEnter: t7,
|
|
onPointerLeave: _temp9,
|
|
children: [t8, t9]
|
|
});
|
|
$[11] = disabled;
|
|
$[12] = onClick;
|
|
$[13] = t6;
|
|
$[14] = t7;
|
|
$[15] = t8;
|
|
$[16] = t9;
|
|
$[17] = t10;
|
|
} else {
|
|
t10 = $[17];
|
|
}
|
|
return t10;
|
|
}
|
|
function _temp9(e_0) {
|
|
e_0.currentTarget.style.backgroundColor = "transparent";
|
|
}
|
|
|
|
// src/components/ContextMenuDivider.tsx
|
|
import { c as _c50 } from "react/compiler-runtime";
|
|
import React20 from "react";
|
|
import { jsx as _jsx20 } from "react/jsx-runtime";
|
|
function ContextMenuDivider(t0) {
|
|
const $ = _c50(7);
|
|
const {
|
|
label
|
|
} = t0;
|
|
const t1 = label ? "8px 12px" : "0";
|
|
let t2;
|
|
if ($[0] !== t1) {
|
|
t2 = {
|
|
padding: t1,
|
|
borderBottom: "1px solid var(--canvas-border, #333)",
|
|
marginBottom: "4px"
|
|
};
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
} else {
|
|
t2 = $[1];
|
|
}
|
|
let t3;
|
|
if ($[2] !== label) {
|
|
t3 = label && /* @__PURE__ */ _jsx20("div", {
|
|
style: {
|
|
fontSize: "14px",
|
|
fontWeight: 500,
|
|
color: "var(--canvas-text, #fff)"
|
|
},
|
|
children: label
|
|
});
|
|
$[2] = label;
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
let t4;
|
|
if ($[4] !== t2 || $[5] !== t3) {
|
|
t4 = /* @__PURE__ */ _jsx20("div", {
|
|
style: t2,
|
|
children: t3
|
|
});
|
|
$[4] = t2;
|
|
$[5] = t3;
|
|
$[6] = t4;
|
|
} else {
|
|
t4 = $[6];
|
|
}
|
|
return t4;
|
|
}
|
|
|
|
// src/components/NodeContextMenu.tsx
|
|
import { jsx as _jsx21, jsxs as _jsxs9 } from "react/jsx-runtime";
|
|
var DefaultDialog = (t0) => {
|
|
const $ = _c51(14);
|
|
const {
|
|
open,
|
|
onOpenChange,
|
|
children
|
|
} = t0;
|
|
const inputSource = useAtomValue36(primaryInputSourceAtom);
|
|
const isTouchInput = inputSource === "finger" || inputSource === "pencil";
|
|
if (!open) {
|
|
return null;
|
|
}
|
|
if (isTouchInput) {
|
|
let t12;
|
|
if ($[0] !== onOpenChange) {
|
|
t12 = () => onOpenChange(false);
|
|
$[0] = onOpenChange;
|
|
$[1] = t12;
|
|
} else {
|
|
t12 = $[1];
|
|
}
|
|
let t22;
|
|
if ($[2] !== children || $[3] !== t12) {
|
|
t22 = /* @__PURE__ */ _jsx21(BottomSheet, {
|
|
onClose: t12,
|
|
children
|
|
});
|
|
$[2] = children;
|
|
$[3] = t12;
|
|
$[4] = t22;
|
|
} else {
|
|
t22 = $[4];
|
|
}
|
|
return t22;
|
|
}
|
|
let t1;
|
|
if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
position: "fixed",
|
|
inset: 0,
|
|
zIndex: 9999,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
backgroundColor: "rgba(0, 0, 0, 0.5)"
|
|
};
|
|
$[5] = t1;
|
|
} else {
|
|
t1 = $[5];
|
|
}
|
|
let t2;
|
|
if ($[6] !== onOpenChange) {
|
|
t2 = () => onOpenChange(false);
|
|
$[6] = onOpenChange;
|
|
$[7] = t2;
|
|
} else {
|
|
t2 = $[7];
|
|
}
|
|
let t3;
|
|
if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t3 = {
|
|
backgroundColor: "var(--canvas-bg, #1a1a1a)",
|
|
border: "1px solid var(--canvas-border, #333)",
|
|
borderRadius: "8px",
|
|
padding: "8px 0",
|
|
minWidth: "200px",
|
|
maxWidth: "280px",
|
|
boxShadow: "0 4px 20px rgba(0, 0, 0, 0.3)"
|
|
};
|
|
$[8] = t3;
|
|
} else {
|
|
t3 = $[8];
|
|
}
|
|
let t4;
|
|
if ($[9] !== children) {
|
|
t4 = /* @__PURE__ */ _jsx21("div", {
|
|
style: t3,
|
|
onClick: _temp10,
|
|
onPointerDown: _temp23,
|
|
children
|
|
});
|
|
$[9] = children;
|
|
$[10] = t4;
|
|
} else {
|
|
t4 = $[10];
|
|
}
|
|
let t5;
|
|
if ($[11] !== t2 || $[12] !== t4) {
|
|
t5 = /* @__PURE__ */ _jsx21("div", {
|
|
style: t1,
|
|
onClick: t2,
|
|
children: t4
|
|
});
|
|
$[11] = t2;
|
|
$[12] = t4;
|
|
$[13] = t5;
|
|
} else {
|
|
t5 = $[13];
|
|
}
|
|
return t5;
|
|
};
|
|
function BottomSheet(t0) {
|
|
const $ = _c51(17);
|
|
const {
|
|
onClose,
|
|
children
|
|
} = t0;
|
|
const sheetRef = useRef23(null);
|
|
const startYRef = useRef23(null);
|
|
let t1;
|
|
if ($[0] !== onClose) {
|
|
t1 = () => {
|
|
onClose();
|
|
};
|
|
$[0] = onClose;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const handleBackdropClick = t1;
|
|
let t2;
|
|
if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = (e) => {
|
|
startYRef.current = e.clientY;
|
|
};
|
|
$[2] = t2;
|
|
} else {
|
|
t2 = $[2];
|
|
}
|
|
const handlePointerDown = t2;
|
|
let t3;
|
|
if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t3 = (e_0) => {
|
|
if (startYRef.current === null || !sheetRef.current) {
|
|
return;
|
|
}
|
|
const dy = e_0.clientY - startYRef.current;
|
|
if (dy > 0) {
|
|
sheetRef.current.style.transform = `translateY(${dy}px)`;
|
|
}
|
|
};
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
const handlePointerMove = t3;
|
|
let t4;
|
|
if ($[4] !== onClose) {
|
|
t4 = (e_1) => {
|
|
if (startYRef.current === null) {
|
|
return;
|
|
}
|
|
const dy_0 = e_1.clientY - startYRef.current;
|
|
startYRef.current = null;
|
|
if (dy_0 > 60) {
|
|
onClose();
|
|
} else {
|
|
if (sheetRef.current) {
|
|
sheetRef.current.style.transform = "translateY(0)";
|
|
}
|
|
}
|
|
};
|
|
$[4] = onClose;
|
|
$[5] = t4;
|
|
} else {
|
|
t4 = $[5];
|
|
}
|
|
const handlePointerUp = t4;
|
|
let t5;
|
|
if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t5 = {
|
|
position: "fixed",
|
|
inset: 0,
|
|
zIndex: 9999,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
justifyContent: "flex-end",
|
|
backgroundColor: "rgba(0, 0, 0, 0.4)"
|
|
};
|
|
$[6] = t5;
|
|
} else {
|
|
t5 = $[6];
|
|
}
|
|
let t6;
|
|
if ($[7] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t6 = {
|
|
backgroundColor: "var(--canvas-bg, #1a1a1a)",
|
|
borderTop: "1px solid var(--canvas-border, #333)",
|
|
borderRadius: "16px 16px 0 0",
|
|
padding: "0 0 env(safe-area-inset-bottom, 16px)",
|
|
maxHeight: "60vh",
|
|
overflowY: "auto",
|
|
transition: "transform 200ms ease-out",
|
|
WebkitOverflowScrolling: "touch"
|
|
};
|
|
$[7] = t6;
|
|
} else {
|
|
t6 = $[7];
|
|
}
|
|
let t7;
|
|
if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t7 = (e_3) => {
|
|
e_3.stopPropagation();
|
|
handlePointerDown(e_3);
|
|
};
|
|
$[8] = t7;
|
|
} else {
|
|
t7 = $[8];
|
|
}
|
|
let t8;
|
|
let t9;
|
|
if ($[9] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t8 = (e_4) => {
|
|
startYRef.current = null;
|
|
if (sheetRef.current) {
|
|
sheetRef.current.style.transform = "translateY(0)";
|
|
}
|
|
};
|
|
t9 = /* @__PURE__ */ _jsx21("div", {
|
|
style: {
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
padding: "8px 0 4px",
|
|
touchAction: "none"
|
|
},
|
|
children: /* @__PURE__ */ _jsx21("div", {
|
|
style: {
|
|
width: "36px",
|
|
height: "4px",
|
|
borderRadius: "2px",
|
|
backgroundColor: "var(--canvas-border, #666)"
|
|
}
|
|
})
|
|
});
|
|
$[9] = t8;
|
|
$[10] = t9;
|
|
} else {
|
|
t8 = $[9];
|
|
t9 = $[10];
|
|
}
|
|
let t10;
|
|
if ($[11] !== children || $[12] !== handlePointerUp) {
|
|
t10 = /* @__PURE__ */ _jsxs9("div", {
|
|
ref: sheetRef,
|
|
style: t6,
|
|
onClick: _temp32,
|
|
onPointerDown: t7,
|
|
onPointerMove: handlePointerMove,
|
|
onPointerUp: handlePointerUp,
|
|
onPointerCancel: t8,
|
|
children: [t9, children]
|
|
});
|
|
$[11] = children;
|
|
$[12] = handlePointerUp;
|
|
$[13] = t10;
|
|
} else {
|
|
t10 = $[13];
|
|
}
|
|
let t11;
|
|
if ($[14] !== handleBackdropClick || $[15] !== t10) {
|
|
t11 = /* @__PURE__ */ _jsx21("div", {
|
|
style: t5,
|
|
onClick: handleBackdropClick,
|
|
children: t10
|
|
});
|
|
$[14] = handleBackdropClick;
|
|
$[15] = t10;
|
|
$[16] = t11;
|
|
} else {
|
|
t11 = $[16];
|
|
}
|
|
return t11;
|
|
}
|
|
function _temp32(e_2) {
|
|
return e_2.stopPropagation();
|
|
}
|
|
function NodeContextMenu(t0) {
|
|
const $ = _c51(13);
|
|
const {
|
|
open,
|
|
onOpenChange,
|
|
nodeId,
|
|
actions: t1,
|
|
renderContent,
|
|
DialogComponent: t2
|
|
} = t0;
|
|
const actions = t1 === void 0 ? [] : t1;
|
|
const DialogComponent = t2 === void 0 ? DefaultDialog : t2;
|
|
const currentGraphId = useAtomValue36(currentGraphIdAtom);
|
|
useSetAtom26(clearSelectionAtom);
|
|
let t3;
|
|
if ($[0] !== onOpenChange) {
|
|
t3 = () => {
|
|
onOpenChange(false);
|
|
};
|
|
$[0] = onOpenChange;
|
|
$[1] = t3;
|
|
} else {
|
|
t3 = $[1];
|
|
}
|
|
const closeMenu = t3;
|
|
const ctx = {
|
|
nodeId,
|
|
graphId: currentGraphId,
|
|
closeMenu
|
|
};
|
|
if (renderContent) {
|
|
return /* @__PURE__ */ _jsx21(DialogComponent, {
|
|
open,
|
|
onOpenChange,
|
|
children: renderContent(ctx)
|
|
});
|
|
}
|
|
let t4;
|
|
let t5;
|
|
if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = /* @__PURE__ */ _jsx21(ContextMenuDivider, {
|
|
label: "Node Options"
|
|
});
|
|
t5 = {
|
|
padding: "0 4px"
|
|
};
|
|
$[2] = t4;
|
|
$[3] = t5;
|
|
} else {
|
|
t4 = $[2];
|
|
t5 = $[3];
|
|
}
|
|
const t6 = actions.map((action) => /* @__PURE__ */ _jsx21(ContextMenuAction, {
|
|
label: action.label,
|
|
icon: action.icon,
|
|
onClick: () => action.onClick(ctx),
|
|
destructive: action.destructive,
|
|
disabled: action.disabled
|
|
}, action.key));
|
|
let t7;
|
|
if ($[4] !== t5 || $[5] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsx21("div", {
|
|
style: t5,
|
|
children: t6
|
|
});
|
|
$[4] = t5;
|
|
$[5] = t6;
|
|
$[6] = t7;
|
|
} else {
|
|
t7 = $[6];
|
|
}
|
|
let t8;
|
|
if ($[7] !== DialogComponent || $[8] !== onOpenChange || $[9] !== open || $[10] !== t4 || $[11] !== t7) {
|
|
t8 = /* @__PURE__ */ _jsxs9(DialogComponent, {
|
|
open,
|
|
onOpenChange,
|
|
children: [t4, t7]
|
|
});
|
|
$[7] = DialogComponent;
|
|
$[8] = onOpenChange;
|
|
$[9] = open;
|
|
$[10] = t4;
|
|
$[11] = t7;
|
|
$[12] = t8;
|
|
} else {
|
|
t8 = $[12];
|
|
}
|
|
return t8;
|
|
}
|
|
var DeleteIcon = () => {
|
|
const $ = _c51(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsx21("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
children: /* @__PURE__ */ _jsx21("path", {
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
})
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
};
|
|
var FitToViewIcon = () => {
|
|
const $ = _c51(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsx21("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
children: /* @__PURE__ */ _jsx21("path", {
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7"
|
|
})
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
};
|
|
function _temp10(e) {
|
|
return e.stopPropagation();
|
|
}
|
|
function _temp23(e_0) {
|
|
return e_0.stopPropagation();
|
|
}
|
|
|
|
// src/components/LockedNodeOverlay.tsx
|
|
init_locked_node_store();
|
|
import { c as _c52 } from "react/compiler-runtime";
|
|
import React22, { useEffect as useEffect17 } from "react";
|
|
import { useAtomValue as useAtomValue37, useSetAtom as useSetAtom27 } from "jotai";
|
|
import { jsx as _jsx22, jsxs as _jsxs10, Fragment as _Fragment5 } from "react/jsx-runtime";
|
|
var DefaultCloseButton = (t0) => {
|
|
const $ = _c52(4);
|
|
const {
|
|
onClick
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
color: "var(--hud-text, #fff)",
|
|
opacity: 0.6
|
|
};
|
|
$[0] = t1;
|
|
} else {
|
|
t1 = $[0];
|
|
}
|
|
let t2;
|
|
if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = /* @__PURE__ */ _jsxs10("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
children: [/* @__PURE__ */ _jsx22("line", {
|
|
x1: "18",
|
|
y1: "6",
|
|
x2: "6",
|
|
y2: "18"
|
|
}), /* @__PURE__ */ _jsx22("line", {
|
|
x1: "6",
|
|
y1: "6",
|
|
x2: "18",
|
|
y2: "18"
|
|
})]
|
|
});
|
|
$[1] = t2;
|
|
} else {
|
|
t2 = $[1];
|
|
}
|
|
let t3;
|
|
if ($[2] !== onClick) {
|
|
t3 = /* @__PURE__ */ _jsx22("button", {
|
|
onClick,
|
|
className: "absolute top-4 right-4 z-10 p-2 rounded hover:bg-white/10 transition-colors",
|
|
style: t1,
|
|
"aria-label": "Close",
|
|
children: t2
|
|
});
|
|
$[2] = onClick;
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
return t3;
|
|
};
|
|
var DefaultFooter = (t0) => {
|
|
const $ = _c52(13);
|
|
const {
|
|
pages,
|
|
currentIndex,
|
|
onPageSelect
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
backgroundColor: "var(--hud-header-bg, rgba(20, 20, 30, 0.9))",
|
|
borderColor: "var(--hud-border, rgba(255, 255, 255, 0.1))"
|
|
};
|
|
$[0] = t1;
|
|
} else {
|
|
t1 = $[0];
|
|
}
|
|
let t2;
|
|
if ($[1] !== currentIndex || $[2] !== onPageSelect || $[3] !== pages) {
|
|
let t32;
|
|
if ($[5] !== currentIndex || $[6] !== onPageSelect) {
|
|
t32 = (page, idx) => /* @__PURE__ */ _jsx22("button", {
|
|
onClick: () => onPageSelect(idx),
|
|
className: "w-2.5 h-2.5 rounded-full transition-all hover:scale-110",
|
|
style: {
|
|
backgroundColor: idx === currentIndex ? "var(--hud-accent, #3b82f6)" : "var(--hud-border, rgba(255, 255, 255, 0.3))",
|
|
transform: idx === currentIndex ? "scale(1.3)" : "scale(1)"
|
|
},
|
|
title: page.label
|
|
}, page.key);
|
|
$[5] = currentIndex;
|
|
$[6] = onPageSelect;
|
|
$[7] = t32;
|
|
} else {
|
|
t32 = $[7];
|
|
}
|
|
t2 = pages.map(t32);
|
|
$[1] = currentIndex;
|
|
$[2] = onPageSelect;
|
|
$[3] = pages;
|
|
$[4] = t2;
|
|
} else {
|
|
t2 = $[4];
|
|
}
|
|
let t3;
|
|
if ($[8] !== t2) {
|
|
t3 = /* @__PURE__ */ _jsx22("div", {
|
|
className: "flex items-center gap-2",
|
|
children: t2
|
|
});
|
|
$[8] = t2;
|
|
$[9] = t3;
|
|
} else {
|
|
t3 = $[9];
|
|
}
|
|
let t4;
|
|
if ($[10] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = /* @__PURE__ */ _jsxs10("div", {
|
|
className: "flex items-center gap-4 text-xs",
|
|
style: {
|
|
color: "var(--hud-text, #fff)",
|
|
opacity: 0.5
|
|
},
|
|
children: [/* @__PURE__ */ _jsx22("span", {
|
|
children: "\u2190 \u2192 Navigate"
|
|
}), /* @__PURE__ */ _jsx22("span", {
|
|
children: "ESC Exit"
|
|
})]
|
|
});
|
|
$[10] = t4;
|
|
} else {
|
|
t4 = $[10];
|
|
}
|
|
let t5;
|
|
if ($[11] !== t3) {
|
|
t5 = /* @__PURE__ */ _jsxs10("footer", {
|
|
className: "flex items-center justify-center gap-8 py-4 border-t",
|
|
style: t1,
|
|
children: [t3, t4]
|
|
});
|
|
$[11] = t3;
|
|
$[12] = t5;
|
|
} else {
|
|
t5 = $[12];
|
|
}
|
|
return t5;
|
|
};
|
|
var DefaultWrapper = (t0) => {
|
|
const $ = _c52(2);
|
|
const {
|
|
children
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== children) {
|
|
t1 = /* @__PURE__ */ _jsx22(_Fragment5, {
|
|
children
|
|
});
|
|
$[0] = children;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
return t1;
|
|
};
|
|
function LockedNodeOverlay(t0) {
|
|
const $ = _c52(50);
|
|
const {
|
|
pages,
|
|
renderContent,
|
|
renderCloseButton,
|
|
renderFooter,
|
|
WrapperComponent: t1,
|
|
className: t2,
|
|
style
|
|
} = t0;
|
|
const WrapperComponent = t1 === void 0 ? DefaultWrapper : t1;
|
|
const className = t2 === void 0 ? "" : t2;
|
|
const lockedNodeId = useAtomValue37(lockedNodeIdAtom);
|
|
const nodeData = useAtomValue37(lockedNodeDataAtom);
|
|
const pageIndex = useAtomValue37(lockedNodePageIndexAtom);
|
|
const setPageCount = useSetAtom27(lockedNodePageCountAtom);
|
|
const unlock = useSetAtom27(unlockNodeAtom);
|
|
let t3;
|
|
let t4;
|
|
if ($[0] !== pages.length || $[1] !== setPageCount) {
|
|
t3 = () => {
|
|
setPageCount(pages.length);
|
|
};
|
|
t4 = [pages.length, setPageCount];
|
|
$[0] = pages.length;
|
|
$[1] = setPageCount;
|
|
$[2] = t3;
|
|
$[3] = t4;
|
|
} else {
|
|
t3 = $[2];
|
|
t4 = $[3];
|
|
}
|
|
useEffect17(t3, t4);
|
|
const pageIndexSetter = useSetAtom27(lockedNodePageIndexAtom);
|
|
let t5;
|
|
if ($[4] !== pageIndexSetter || $[5] !== pages.length) {
|
|
t5 = () => {
|
|
pageIndexSetter((current) => (current + 1) % pages.length);
|
|
};
|
|
$[4] = pageIndexSetter;
|
|
$[5] = pages.length;
|
|
$[6] = t5;
|
|
} else {
|
|
t5 = $[6];
|
|
}
|
|
const nextPage = t5;
|
|
let t6;
|
|
if ($[7] !== pageIndexSetter || $[8] !== pages.length) {
|
|
t6 = () => {
|
|
pageIndexSetter((current_0) => (current_0 - 1 + pages.length) % pages.length);
|
|
};
|
|
$[7] = pageIndexSetter;
|
|
$[8] = pages.length;
|
|
$[9] = t6;
|
|
} else {
|
|
t6 = $[9];
|
|
}
|
|
const prevPage = t6;
|
|
let t7;
|
|
if ($[10] !== pageIndexSetter || $[11] !== pages.length) {
|
|
t7 = (index_0) => {
|
|
if (index_0 >= 0 && index_0 < pages.length) {
|
|
pageIndexSetter(index_0);
|
|
}
|
|
};
|
|
$[10] = pageIndexSetter;
|
|
$[11] = pages.length;
|
|
$[12] = t7;
|
|
} else {
|
|
t7 = $[12];
|
|
}
|
|
const handleGoToPage = t7;
|
|
let t8;
|
|
let t9;
|
|
if ($[13] !== lockedNodeId || $[14] !== nextPage || $[15] !== prevPage || $[16] !== unlock) {
|
|
t8 = () => {
|
|
if (!lockedNodeId) {
|
|
return;
|
|
}
|
|
const handleKeyDown = (e) => {
|
|
const target = e.target;
|
|
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
|
|
return;
|
|
}
|
|
bb46: switch (e.key) {
|
|
case "Escape": {
|
|
e.preventDefault();
|
|
unlock();
|
|
break bb46;
|
|
}
|
|
case "ArrowRight":
|
|
case "ArrowDown": {
|
|
e.preventDefault();
|
|
nextPage();
|
|
break bb46;
|
|
}
|
|
case "ArrowLeft":
|
|
case "ArrowUp": {
|
|
e.preventDefault();
|
|
prevPage();
|
|
}
|
|
}
|
|
};
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
};
|
|
t9 = [lockedNodeId, unlock, nextPage, prevPage];
|
|
$[13] = lockedNodeId;
|
|
$[14] = nextPage;
|
|
$[15] = prevPage;
|
|
$[16] = unlock;
|
|
$[17] = t8;
|
|
$[18] = t9;
|
|
} else {
|
|
t8 = $[17];
|
|
t9 = $[18];
|
|
}
|
|
useEffect17(t8, t9);
|
|
if (!lockedNodeId || !nodeData) {
|
|
return null;
|
|
}
|
|
const currentPage = pages[pageIndex]?.key || pages[0]?.key;
|
|
const totalPages = pages.length;
|
|
let T0;
|
|
let t10;
|
|
let t11;
|
|
let t12;
|
|
let t13;
|
|
if ($[19] !== WrapperComponent || $[20] !== className || $[21] !== currentPage || $[22] !== handleGoToPage || $[23] !== lockedNodeId || $[24] !== nextPage || $[25] !== nodeData || $[26] !== pageIndex || $[27] !== pages || $[28] !== prevPage || $[29] !== renderCloseButton || $[30] !== renderContent || $[31] !== renderFooter || $[32] !== style || $[33] !== totalPages || $[34] !== unlock) {
|
|
const ctx = {
|
|
nodeId: lockedNodeId,
|
|
nodeData,
|
|
currentPage,
|
|
pageIndex,
|
|
totalPages,
|
|
close: unlock,
|
|
nextPage,
|
|
prevPage,
|
|
goToPage: handleGoToPage
|
|
};
|
|
T0 = WrapperComponent;
|
|
t10 = `fixed inset-0 z-[9999] flex flex-col ${className}`;
|
|
if ($[40] !== style) {
|
|
t11 = {
|
|
backgroundColor: "#0f0f14",
|
|
isolation: "isolate",
|
|
...style
|
|
};
|
|
$[40] = style;
|
|
$[41] = t11;
|
|
} else {
|
|
t11 = $[41];
|
|
}
|
|
t12 = /* @__PURE__ */ _jsxs10("div", {
|
|
className: "flex-1 overflow-hidden relative",
|
|
children: [renderCloseButton ? renderCloseButton(ctx) : /* @__PURE__ */ _jsx22(DefaultCloseButton, {
|
|
onClick: unlock
|
|
}), renderContent(ctx)]
|
|
});
|
|
t13 = renderFooter ? renderFooter(ctx) : /* @__PURE__ */ _jsx22(DefaultFooter, {
|
|
pages,
|
|
currentIndex: pageIndex,
|
|
onPageSelect: handleGoToPage
|
|
});
|
|
$[19] = WrapperComponent;
|
|
$[20] = className;
|
|
$[21] = currentPage;
|
|
$[22] = handleGoToPage;
|
|
$[23] = lockedNodeId;
|
|
$[24] = nextPage;
|
|
$[25] = nodeData;
|
|
$[26] = pageIndex;
|
|
$[27] = pages;
|
|
$[28] = prevPage;
|
|
$[29] = renderCloseButton;
|
|
$[30] = renderContent;
|
|
$[31] = renderFooter;
|
|
$[32] = style;
|
|
$[33] = totalPages;
|
|
$[34] = unlock;
|
|
$[35] = T0;
|
|
$[36] = t10;
|
|
$[37] = t11;
|
|
$[38] = t12;
|
|
$[39] = t13;
|
|
} else {
|
|
T0 = $[35];
|
|
t10 = $[36];
|
|
t11 = $[37];
|
|
t12 = $[38];
|
|
t13 = $[39];
|
|
}
|
|
let t14;
|
|
if ($[42] !== t10 || $[43] !== t11 || $[44] !== t12 || $[45] !== t13) {
|
|
t14 = /* @__PURE__ */ _jsxs10("div", {
|
|
className: t10,
|
|
style: t11,
|
|
children: [t12, t13]
|
|
});
|
|
$[42] = t10;
|
|
$[43] = t11;
|
|
$[44] = t12;
|
|
$[45] = t13;
|
|
$[46] = t14;
|
|
} else {
|
|
t14 = $[46];
|
|
}
|
|
let t15;
|
|
if ($[47] !== T0 || $[48] !== t14) {
|
|
t15 = /* @__PURE__ */ _jsx22(T0, {
|
|
children: t14
|
|
});
|
|
$[47] = T0;
|
|
$[48] = t14;
|
|
$[49] = t15;
|
|
} else {
|
|
t15 = $[49];
|
|
}
|
|
return t15;
|
|
}
|
|
|
|
// src/components/EdgeOverlay.tsx
|
|
init_graph_store();
|
|
init_graph_position();
|
|
init_graph_mutations();
|
|
init_graph_mutations_edges();
|
|
init_selection_store();
|
|
import React25, { useRef as useRef24 } from "react";
|
|
import { useSetAtom as useSetAtom28, useAtomValue as useAtomValue38 } from "jotai";
|
|
import { useGesture as useGesture3 } from "@use-gesture/react";
|
|
|
|
// src/components/EdgePath.tsx
|
|
import { c as _c53 } from "react/compiler-runtime";
|
|
import React23 from "react";
|
|
import { jsx as _jsx23 } from "react/jsx-runtime";
|
|
function EdgePath(t0) {
|
|
const $ = _c53(7);
|
|
const {
|
|
handleRef,
|
|
className,
|
|
style
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== style) {
|
|
t1 = {
|
|
position: "absolute",
|
|
top: "50%",
|
|
right: "-22px",
|
|
height: "44px",
|
|
width: "44px",
|
|
transform: "translateY(-50%)",
|
|
cursor: "crosshair",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
touchAction: "none",
|
|
...style
|
|
};
|
|
$[0] = style;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
let t2;
|
|
if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = /* @__PURE__ */ _jsx23("div", {
|
|
style: {
|
|
height: "32px",
|
|
width: "12px",
|
|
borderRadius: "3px",
|
|
backgroundColor: "var(--list-item-border, #666)",
|
|
transition: "background-color 0.2s",
|
|
pointerEvents: "none"
|
|
}
|
|
});
|
|
$[2] = t2;
|
|
} else {
|
|
t2 = $[2];
|
|
}
|
|
let t3;
|
|
if ($[3] !== className || $[4] !== handleRef || $[5] !== t1) {
|
|
t3 = /* @__PURE__ */ _jsx23("div", {
|
|
ref: handleRef,
|
|
className,
|
|
style: t1,
|
|
onPointerEnter: _temp11,
|
|
onPointerLeave: _temp24,
|
|
children: t2
|
|
});
|
|
$[3] = className;
|
|
$[4] = handleRef;
|
|
$[5] = t1;
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
return t3;
|
|
}
|
|
function _temp24(e_0) {
|
|
const visual_0 = e_0.currentTarget.firstChild;
|
|
if (visual_0) {
|
|
visual_0.style.backgroundColor = "var(--list-item-border, #666)";
|
|
}
|
|
}
|
|
function _temp11(e) {
|
|
const visual = e.currentTarget.firstChild;
|
|
if (visual) {
|
|
visual.style.backgroundColor = "var(--list-item-border-active, #888)";
|
|
}
|
|
}
|
|
|
|
// src/components/EdgeOverlay.tsx
|
|
import { jsx as _jsx25, jsxs as _jsxs11 } from "react/jsx-runtime";
|
|
|
|
// src/components/EdgeLabel.tsx
|
|
import { c as _c54 } from "react/compiler-runtime";
|
|
import React24 from "react";
|
|
import { jsx as _jsx24 } from "react/jsx-runtime";
|
|
function EdgeLabel(t0) {
|
|
const $ = _c54(13);
|
|
const {
|
|
label,
|
|
x,
|
|
y,
|
|
onClick,
|
|
className,
|
|
style
|
|
} = t0;
|
|
if (!label) {
|
|
return null;
|
|
}
|
|
let t1;
|
|
if ($[0] !== onClick) {
|
|
t1 = (e) => {
|
|
e.stopPropagation();
|
|
onClick?.();
|
|
};
|
|
$[0] = onClick;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const t2 = onClick ? "pointer" : "default";
|
|
const t3 = onClick ? "auto" : "none";
|
|
let t4;
|
|
if ($[2] !== style || $[3] !== t2 || $[4] !== t3 || $[5] !== x || $[6] !== y) {
|
|
t4 = {
|
|
position: "absolute",
|
|
left: x,
|
|
top: y,
|
|
transform: "translate(-50%, -50%)",
|
|
padding: "2px 8px",
|
|
fontSize: "12px",
|
|
fontWeight: 500,
|
|
color: "var(--canvas-text, #fff)",
|
|
backgroundColor: "var(--canvas-bg, #1a1a1a)",
|
|
border: "1px solid var(--canvas-border, #333)",
|
|
borderRadius: "4px",
|
|
whiteSpace: "nowrap",
|
|
cursor: t2,
|
|
userSelect: "none",
|
|
pointerEvents: t3,
|
|
...style
|
|
};
|
|
$[2] = style;
|
|
$[3] = t2;
|
|
$[4] = t3;
|
|
$[5] = x;
|
|
$[6] = y;
|
|
$[7] = t4;
|
|
} else {
|
|
t4 = $[7];
|
|
}
|
|
let t5;
|
|
if ($[8] !== className || $[9] !== label || $[10] !== t1 || $[11] !== t4) {
|
|
t5 = /* @__PURE__ */ _jsx24("div", {
|
|
className,
|
|
onClick: t1,
|
|
style: t4,
|
|
children: label
|
|
});
|
|
$[8] = className;
|
|
$[9] = label;
|
|
$[10] = t1;
|
|
$[11] = t4;
|
|
$[12] = t5;
|
|
} else {
|
|
t5 = $[12];
|
|
}
|
|
return t5;
|
|
}
|
|
|
|
// src/components/EdgeOverlay.tsx
|
|
var defaultGetSourceHandle = (nodeId) => `widget:${nodeId}:output:data`;
|
|
var defaultGetTargetHandle = (targetNodeId) => `widget:${targetNodeId}:input:default`;
|
|
function EdgeOverlay({
|
|
nodeId,
|
|
children,
|
|
getSourceHandle = defaultGetSourceHandle,
|
|
getTargetHandle = defaultGetTargetHandle,
|
|
onEdgeCreate,
|
|
onEdgeCreateError,
|
|
onEdgeCreated,
|
|
handleStyle,
|
|
handleClassName,
|
|
canConnect
|
|
}) {
|
|
const setEdgeCreationState = useSetAtom28(edgeCreationAtom);
|
|
const graph = useAtomValue38(graphAtom);
|
|
const addEdgeToLocalGraph = useSetAtom28(addEdgeToLocalGraphAtom);
|
|
const removeEdgeFromLocalGraph = useSetAtom28(removeEdgeFromLocalGraphAtom);
|
|
const swapEdgeAtomic = useSetAtom28(swapEdgeAtomicAtom);
|
|
const addNodeToSelection = useSetAtom28(addNodesToSelectionAtom);
|
|
const removeNodeFromSelection = useSetAtom28(removeNodesFromSelectionAtom);
|
|
const currentGraphId = useAtomValue38(currentGraphIdAtom);
|
|
const handleRef = useRef24(null);
|
|
const wrapperRef = useRef24(null);
|
|
const position = useAtomValue38(nodePositionAtomFamily(nodeId));
|
|
const nodeAttributes = graph.hasNode(nodeId) ? graph.getNodeAttributes(nodeId) : void 0;
|
|
const width = nodeAttributes?.width ?? 500;
|
|
const height = nodeAttributes?.height ?? 500;
|
|
const handleDragStart = (x, y) => {
|
|
if (!graph.hasNode(nodeId)) return;
|
|
const sourceNodeAttributes = graph.getNodeAttributes(nodeId);
|
|
if (!sourceNodeAttributes) return;
|
|
const nodeRect = wrapperRef.current?.querySelector('[data-draggable-node="true"]')?.getBoundingClientRect();
|
|
if (!nodeRect) return;
|
|
const sourceNodePosition = {
|
|
x: sourceNodeAttributes.x + nodeRect.width / 2,
|
|
y: sourceNodeAttributes.y + nodeRect.height / 2
|
|
};
|
|
const sourceHandle = getSourceHandle(nodeId);
|
|
setEdgeCreationState({
|
|
isCreating: true,
|
|
sourceNodeId: nodeId,
|
|
sourceNodePosition,
|
|
targetPosition: {
|
|
x,
|
|
y
|
|
},
|
|
hoveredTargetNodeId: null,
|
|
sourceHandle,
|
|
targetHandle: null,
|
|
sourcePort: null,
|
|
targetPort: null,
|
|
snappedTargetPosition: null
|
|
});
|
|
};
|
|
const handleDrag = (x_0, y_0) => {
|
|
setEdgeCreationState((prev) => {
|
|
if (!prev.isCreating) return prev;
|
|
const {
|
|
nodeId: hitNodeId
|
|
} = hitTestNode(x_0, y_0);
|
|
let hoveredNodeId = null;
|
|
let targetPort = null;
|
|
const portInfo = hitTestPort(x_0, y_0);
|
|
if (portInfo && portInfo.nodeId !== nodeId) {
|
|
hoveredNodeId = portInfo.nodeId;
|
|
targetPort = portInfo.portId;
|
|
} else if (hitNodeId) {
|
|
hoveredNodeId = hitNodeId;
|
|
}
|
|
if (hoveredNodeId && hoveredNodeId !== nodeId && hoveredNodeId !== prev.hoveredTargetNodeId) {
|
|
if (prev.hoveredTargetNodeId) {
|
|
removeNodeFromSelection([prev.hoveredTargetNodeId]);
|
|
}
|
|
addNodeToSelection([hoveredNodeId]);
|
|
}
|
|
return {
|
|
...prev,
|
|
targetPosition: {
|
|
x: x_0,
|
|
y: y_0
|
|
},
|
|
hoveredTargetNodeId: hoveredNodeId && hoveredNodeId !== nodeId ? hoveredNodeId : null,
|
|
targetPort
|
|
};
|
|
});
|
|
};
|
|
const handleDragEnd = async (x_1, y_1) => {
|
|
let targetNodeId = null;
|
|
const {
|
|
nodeId: hitNodeId_0
|
|
} = hitTestNode(x_1, y_1);
|
|
if (hitNodeId_0) {
|
|
targetNodeId = hitNodeId_0;
|
|
}
|
|
setEdgeCreationState((prev_0) => {
|
|
if (prev_0.hoveredTargetNodeId) {
|
|
removeNodeFromSelection([prev_0.hoveredTargetNodeId]);
|
|
}
|
|
return {
|
|
isCreating: false,
|
|
sourceNodeId: null,
|
|
sourceNodePosition: null,
|
|
targetPosition: null,
|
|
hoveredTargetNodeId: null,
|
|
sourceHandle: null,
|
|
targetHandle: null,
|
|
sourcePort: null,
|
|
targetPort: null,
|
|
snappedTargetPosition: null
|
|
};
|
|
});
|
|
if (targetNodeId && targetNodeId !== nodeId && currentGraphId) {
|
|
if (canConnect) {
|
|
const allowed = canConnect({
|
|
nodeId,
|
|
portId: null
|
|
}, {
|
|
nodeId: targetNodeId,
|
|
portId: null
|
|
});
|
|
if (!allowed) return;
|
|
}
|
|
const sourceHandle_0 = getSourceHandle(nodeId);
|
|
const targetHandle = getTargetHandle(targetNodeId);
|
|
const tempEdgeId = crypto.randomUUID();
|
|
const tempEdge = {
|
|
id: tempEdgeId,
|
|
graph_id: currentGraphId,
|
|
source_node_id: nodeId,
|
|
target_node_id: targetNodeId,
|
|
edge_type: "data_connection",
|
|
filter_condition: null,
|
|
ui_properties: {
|
|
style: "solid",
|
|
color: "#999",
|
|
sourceHandle: sourceHandle_0,
|
|
targetHandle
|
|
},
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
};
|
|
addEdgeToLocalGraph(tempEdge);
|
|
if (!onEdgeCreate) {
|
|
return;
|
|
}
|
|
try {
|
|
const newEdge = await onEdgeCreate({
|
|
graphId: currentGraphId,
|
|
sourceNodeId: nodeId,
|
|
targetNodeId,
|
|
sourceHandle: sourceHandle_0,
|
|
targetHandle,
|
|
tempEdge
|
|
});
|
|
swapEdgeAtomic({
|
|
tempEdgeId,
|
|
newEdge
|
|
});
|
|
onEdgeCreated?.({
|
|
edge: newEdge,
|
|
sourceNodeId: nodeId,
|
|
targetNodeId,
|
|
sourceHandle: sourceHandle_0,
|
|
targetHandle
|
|
});
|
|
} catch (error) {
|
|
removeEdgeFromLocalGraph(tempEdgeId);
|
|
onEdgeCreateError?.(error, tempEdgeId);
|
|
}
|
|
}
|
|
};
|
|
useGesture3({
|
|
onDragStart: ({
|
|
event,
|
|
xy: [x_2, y_2]
|
|
}) => {
|
|
event.stopPropagation();
|
|
handleDragStart(x_2, y_2);
|
|
},
|
|
onDrag: ({
|
|
xy: [x_3, y_3]
|
|
}) => {
|
|
handleDrag(x_3, y_3);
|
|
},
|
|
onDragEnd: ({
|
|
xy: [x_4, y_4]
|
|
}) => {
|
|
handleDragEnd(x_4, y_4);
|
|
}
|
|
}, {
|
|
target: handleRef,
|
|
eventOptions: {
|
|
passive: false
|
|
}
|
|
});
|
|
if (!position || !nodeAttributes) {
|
|
return null;
|
|
}
|
|
return /* @__PURE__ */ _jsxs11("div", {
|
|
ref: wrapperRef,
|
|
style: {
|
|
position: "absolute",
|
|
top: position.y,
|
|
left: position.x,
|
|
width: `${width}px`,
|
|
height: `${height}px`
|
|
},
|
|
children: [children, /* @__PURE__ */ _jsx25(EdgePath, {
|
|
handleRef,
|
|
className: handleClassName,
|
|
style: handleStyle
|
|
})]
|
|
});
|
|
}
|
|
|
|
// src/components/NodeTypeCombobox.tsx
|
|
init_node_type_registry();
|
|
import { c as _c57 } from "react/compiler-runtime";
|
|
import React28, { useState as useState4, useRef as useRef25, useEffect as useEffect18 } from "react";
|
|
import { useAtomValue as useAtomValue39 } from "jotai";
|
|
init_core();
|
|
init_debug();
|
|
|
|
// src/components/ComboboxSearch.tsx
|
|
import { c as _c55 } from "react/compiler-runtime";
|
|
import React26 from "react";
|
|
import { jsx as _jsx26 } from "react/jsx-runtime";
|
|
var defaultInputStyle = {
|
|
width: "100%",
|
|
padding: "8px 12px",
|
|
fontSize: "14px",
|
|
border: "1px solid #ddd",
|
|
borderRadius: "6px",
|
|
outline: "none",
|
|
backgroundColor: "#fff"
|
|
};
|
|
function ComboboxSearch(t0) {
|
|
const $ = _c55(14);
|
|
const {
|
|
inputRef,
|
|
value,
|
|
onChange,
|
|
onKeyDown,
|
|
onFocus,
|
|
placeholder: t1,
|
|
disabled: t2,
|
|
isOpen,
|
|
style
|
|
} = t0;
|
|
const placeholder = t1 === void 0 ? "Search..." : t1;
|
|
const disabled = t2 === void 0 ? false : t2;
|
|
let t3;
|
|
if ($[0] !== onChange) {
|
|
t3 = (e) => onChange(e.target.value);
|
|
$[0] = onChange;
|
|
$[1] = t3;
|
|
} else {
|
|
t3 = $[1];
|
|
}
|
|
let t4;
|
|
if ($[2] !== style) {
|
|
t4 = {
|
|
...defaultInputStyle,
|
|
...style
|
|
};
|
|
$[2] = style;
|
|
$[3] = t4;
|
|
} else {
|
|
t4 = $[3];
|
|
}
|
|
let t5;
|
|
if ($[4] !== disabled || $[5] !== inputRef || $[6] !== isOpen || $[7] !== onFocus || $[8] !== onKeyDown || $[9] !== placeholder || $[10] !== t3 || $[11] !== t4 || $[12] !== value) {
|
|
t5 = /* @__PURE__ */ _jsx26("input", {
|
|
ref: inputRef,
|
|
type: "text",
|
|
value,
|
|
onChange: t3,
|
|
onFocus,
|
|
onKeyDown,
|
|
placeholder,
|
|
disabled,
|
|
style: t4,
|
|
"aria-expanded": isOpen,
|
|
"aria-haspopup": "listbox",
|
|
"aria-autocomplete": "list",
|
|
role: "combobox"
|
|
});
|
|
$[4] = disabled;
|
|
$[5] = inputRef;
|
|
$[6] = isOpen;
|
|
$[7] = onFocus;
|
|
$[8] = onKeyDown;
|
|
$[9] = placeholder;
|
|
$[10] = t3;
|
|
$[11] = t4;
|
|
$[12] = value;
|
|
$[13] = t5;
|
|
} else {
|
|
t5 = $[13];
|
|
}
|
|
return t5;
|
|
}
|
|
|
|
// src/components/ComboboxOption.tsx
|
|
import { c as _c56 } from "react/compiler-runtime";
|
|
import React27 from "react";
|
|
import { jsx as _jsx27, jsxs as _jsxs12 } from "react/jsx-runtime";
|
|
var styles = {
|
|
option: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "8px",
|
|
padding: "8px 12px",
|
|
cursor: "pointer",
|
|
transition: "background-color 0.1s"
|
|
},
|
|
optionHighlighted: {
|
|
backgroundColor: "#f0f0f0"
|
|
},
|
|
icon: {
|
|
fontSize: "16px",
|
|
width: "20px",
|
|
textAlign: "center"
|
|
},
|
|
content: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: "2px"
|
|
},
|
|
label: {
|
|
fontSize: "14px",
|
|
fontWeight: 500
|
|
},
|
|
description: {
|
|
fontSize: "12px",
|
|
color: "#666"
|
|
}
|
|
};
|
|
function ComboboxOption(t0) {
|
|
const $ = _c56(20);
|
|
const {
|
|
label,
|
|
icon,
|
|
description,
|
|
highlighted: t1,
|
|
onClick,
|
|
onMouseEnter
|
|
} = t0;
|
|
const highlighted = t1 === void 0 ? false : t1;
|
|
let t2;
|
|
if ($[0] !== highlighted) {
|
|
t2 = highlighted ? styles.optionHighlighted : {};
|
|
$[0] = highlighted;
|
|
$[1] = t2;
|
|
} else {
|
|
t2 = $[1];
|
|
}
|
|
let t3;
|
|
if ($[2] !== t2) {
|
|
t3 = {
|
|
...styles.option,
|
|
...t2
|
|
};
|
|
$[2] = t2;
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
let t4;
|
|
if ($[4] !== icon) {
|
|
t4 = icon && /* @__PURE__ */ _jsx27("span", {
|
|
style: styles.icon,
|
|
children: icon
|
|
});
|
|
$[4] = icon;
|
|
$[5] = t4;
|
|
} else {
|
|
t4 = $[5];
|
|
}
|
|
let t5;
|
|
if ($[6] !== label) {
|
|
t5 = /* @__PURE__ */ _jsx27("span", {
|
|
style: styles.label,
|
|
children: label
|
|
});
|
|
$[6] = label;
|
|
$[7] = t5;
|
|
} else {
|
|
t5 = $[7];
|
|
}
|
|
let t6;
|
|
if ($[8] !== description) {
|
|
t6 = description && /* @__PURE__ */ _jsx27("span", {
|
|
style: styles.description,
|
|
children: description
|
|
});
|
|
$[8] = description;
|
|
$[9] = t6;
|
|
} else {
|
|
t6 = $[9];
|
|
}
|
|
let t7;
|
|
if ($[10] !== t5 || $[11] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsxs12("div", {
|
|
style: styles.content,
|
|
children: [t5, t6]
|
|
});
|
|
$[10] = t5;
|
|
$[11] = t6;
|
|
$[12] = t7;
|
|
} else {
|
|
t7 = $[12];
|
|
}
|
|
let t8;
|
|
if ($[13] !== highlighted || $[14] !== onClick || $[15] !== onMouseEnter || $[16] !== t3 || $[17] !== t4 || $[18] !== t7) {
|
|
t8 = /* @__PURE__ */ _jsxs12("li", {
|
|
onClick,
|
|
onMouseEnter,
|
|
style: t3,
|
|
role: "option",
|
|
"aria-selected": highlighted,
|
|
children: [t4, t7]
|
|
});
|
|
$[13] = highlighted;
|
|
$[14] = onClick;
|
|
$[15] = onMouseEnter;
|
|
$[16] = t3;
|
|
$[17] = t4;
|
|
$[18] = t7;
|
|
$[19] = t8;
|
|
} else {
|
|
t8 = $[19];
|
|
}
|
|
return t8;
|
|
}
|
|
|
|
// src/components/NodeTypeCombobox.tsx
|
|
import { jsx as _jsx28, jsxs as _jsxs13 } from "react/jsx-runtime";
|
|
var debug21 = createDebug("node-type-combobox");
|
|
function NodeTypeCombobox(t0) {
|
|
const $ = _c57(53);
|
|
const {
|
|
nodeTypes: customNodeTypes,
|
|
onNodeCreated,
|
|
createPosition: t1,
|
|
placeholder: t2,
|
|
clearOnCreate: t3,
|
|
className,
|
|
style,
|
|
disabled: t4
|
|
} = t0;
|
|
const createPosition = t1 === void 0 ? "center" : t1;
|
|
const placeholder = t2 === void 0 ? "Add node..." : t2;
|
|
const clearOnCreate = t3 === void 0 ? true : t3;
|
|
const disabled = t4 === void 0 ? false : t4;
|
|
const [isOpen, setIsOpen] = useState4(false);
|
|
const [searchQuery, setSearchQuery] = useState4("");
|
|
const [highlightedIndex, setHighlightedIndex] = useState4(0);
|
|
const inputRef = useRef25(null);
|
|
const listRef = useRef25(null);
|
|
const createNode = useCreateNode();
|
|
const graphId = useAtomValue39(currentGraphIdAtom);
|
|
const viewportRect = useAtomValue39(viewportRectAtom);
|
|
const pan = useAtomValue39(panAtom);
|
|
const zoom = useAtomValue39(zoomAtom);
|
|
let t5;
|
|
if ($[0] !== customNodeTypes || $[1] !== searchQuery) {
|
|
const nodeTypes = customNodeTypes ?? getRegisteredNodeTypes().map(_temp12);
|
|
bb0: {
|
|
if (!searchQuery.trim()) {
|
|
t5 = nodeTypes;
|
|
break bb0;
|
|
}
|
|
const query = searchQuery.toLowerCase();
|
|
t5 = nodeTypes.filter((opt) => opt.type.toLowerCase().includes(query) || opt.label?.toLowerCase().includes(query) || opt.description?.toLowerCase().includes(query));
|
|
}
|
|
$[0] = customNodeTypes;
|
|
$[1] = searchQuery;
|
|
$[2] = t5;
|
|
} else {
|
|
t5 = $[2];
|
|
}
|
|
const filteredTypes = t5;
|
|
let t6;
|
|
if ($[3] !== createPosition || $[4] !== pan || $[5] !== viewportRect?.height || $[6] !== viewportRect?.width || $[7] !== zoom) {
|
|
t6 = () => {
|
|
if (typeof createPosition === "object") {
|
|
return createPosition;
|
|
}
|
|
const vpWidth = viewportRect?.width ?? 800;
|
|
const vpHeight = viewportRect?.height ?? 600;
|
|
if (createPosition === "random") {
|
|
const centerX = (vpWidth / 2 - pan.x) / zoom;
|
|
const centerY = (vpHeight / 2 - pan.y) / zoom;
|
|
const offsetX = (Math.random() - 0.5) * 400;
|
|
const offsetY = (Math.random() - 0.5) * 400;
|
|
return {
|
|
x: centerX + offsetX,
|
|
y: centerY + offsetY
|
|
};
|
|
}
|
|
const centerX_0 = (vpWidth / 2 - pan.x) / zoom;
|
|
const centerY_0 = (vpHeight / 2 - pan.y) / zoom;
|
|
return {
|
|
x: centerX_0,
|
|
y: centerY_0
|
|
};
|
|
};
|
|
$[3] = createPosition;
|
|
$[4] = pan;
|
|
$[5] = viewportRect?.height;
|
|
$[6] = viewportRect?.width;
|
|
$[7] = zoom;
|
|
$[8] = t6;
|
|
} else {
|
|
t6 = $[8];
|
|
}
|
|
const getCreatePosition = t6;
|
|
let t7;
|
|
if ($[9] !== clearOnCreate || $[10] !== createNode || $[11] !== getCreatePosition || $[12] !== graphId || $[13] !== onNodeCreated) {
|
|
t7 = (option) => {
|
|
if (!graphId) {
|
|
debug21.warn("No graphId available");
|
|
return;
|
|
}
|
|
const position = getCreatePosition();
|
|
const width = option.defaultWidth ?? 200;
|
|
const height = option.defaultHeight ?? 150;
|
|
createNode.mutate({
|
|
graph_id: graphId,
|
|
label: option.label || option.type,
|
|
node_type: option.type,
|
|
configuration: null,
|
|
ui_properties: {
|
|
x: position.x - width / 2,
|
|
y: position.y - height / 2,
|
|
width,
|
|
height
|
|
}
|
|
}, {
|
|
onSuccess: (data) => {
|
|
onNodeCreated?.(data.id, option.type);
|
|
}
|
|
});
|
|
if (clearOnCreate) {
|
|
setSearchQuery("");
|
|
}
|
|
setIsOpen(false);
|
|
};
|
|
$[9] = clearOnCreate;
|
|
$[10] = createNode;
|
|
$[11] = getCreatePosition;
|
|
$[12] = graphId;
|
|
$[13] = onNodeCreated;
|
|
$[14] = t7;
|
|
} else {
|
|
t7 = $[14];
|
|
}
|
|
const handleSelect = t7;
|
|
let t8;
|
|
if ($[15] !== filteredTypes || $[16] !== handleSelect || $[17] !== highlightedIndex || $[18] !== isOpen) {
|
|
t8 = (e) => {
|
|
if (!isOpen) {
|
|
if (e.key === "ArrowDown" || e.key === "Enter") {
|
|
setIsOpen(true);
|
|
e.preventDefault();
|
|
}
|
|
return;
|
|
}
|
|
bb121: switch (e.key) {
|
|
case "ArrowDown": {
|
|
e.preventDefault();
|
|
setHighlightedIndex((prev_0) => Math.min(prev_0 + 1, filteredTypes.length - 1));
|
|
break bb121;
|
|
}
|
|
case "ArrowUp": {
|
|
e.preventDefault();
|
|
setHighlightedIndex(_temp25);
|
|
break bb121;
|
|
}
|
|
case "Enter": {
|
|
e.preventDefault();
|
|
if (filteredTypes[highlightedIndex]) {
|
|
handleSelect(filteredTypes[highlightedIndex]);
|
|
}
|
|
break bb121;
|
|
}
|
|
case "Escape": {
|
|
e.preventDefault();
|
|
setIsOpen(false);
|
|
}
|
|
}
|
|
};
|
|
$[15] = filteredTypes;
|
|
$[16] = handleSelect;
|
|
$[17] = highlightedIndex;
|
|
$[18] = isOpen;
|
|
$[19] = t8;
|
|
} else {
|
|
t8 = $[19];
|
|
}
|
|
const handleKeyDown = t8;
|
|
let t9;
|
|
if ($[20] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t9 = () => {
|
|
setHighlightedIndex(0);
|
|
};
|
|
$[20] = t9;
|
|
} else {
|
|
t9 = $[20];
|
|
}
|
|
let t10;
|
|
if ($[21] !== filteredTypes.length) {
|
|
t10 = [filteredTypes.length];
|
|
$[21] = filteredTypes.length;
|
|
$[22] = t10;
|
|
} else {
|
|
t10 = $[22];
|
|
}
|
|
useEffect18(t9, t10);
|
|
let t11;
|
|
let t12;
|
|
if ($[23] !== highlightedIndex || $[24] !== isOpen) {
|
|
t11 = () => {
|
|
if (isOpen && listRef.current) {
|
|
const highlightedElement = listRef.current.children[highlightedIndex];
|
|
highlightedElement?.scrollIntoView({
|
|
block: "nearest"
|
|
});
|
|
}
|
|
};
|
|
t12 = [highlightedIndex, isOpen];
|
|
$[23] = highlightedIndex;
|
|
$[24] = isOpen;
|
|
$[25] = t11;
|
|
$[26] = t12;
|
|
} else {
|
|
t11 = $[25];
|
|
t12 = $[26];
|
|
}
|
|
useEffect18(t11, t12);
|
|
let t13;
|
|
let t14;
|
|
if ($[27] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t13 = () => {
|
|
const handleClickOutside = (e_0) => {
|
|
if (inputRef.current && !inputRef.current.parentElement?.contains(e_0.target)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
};
|
|
t14 = [];
|
|
$[27] = t13;
|
|
$[28] = t14;
|
|
} else {
|
|
t13 = $[27];
|
|
t14 = $[28];
|
|
}
|
|
useEffect18(t13, t14);
|
|
let t15;
|
|
if ($[29] !== style) {
|
|
t15 = {
|
|
...styles2.container,
|
|
...style
|
|
};
|
|
$[29] = style;
|
|
$[30] = t15;
|
|
} else {
|
|
t15 = $[30];
|
|
}
|
|
let t16;
|
|
let t17;
|
|
if ($[31] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t16 = (v) => {
|
|
setSearchQuery(v);
|
|
setIsOpen(true);
|
|
};
|
|
t17 = () => setIsOpen(true);
|
|
$[31] = t16;
|
|
$[32] = t17;
|
|
} else {
|
|
t16 = $[31];
|
|
t17 = $[32];
|
|
}
|
|
let t18;
|
|
if ($[33] !== disabled || $[34] !== handleKeyDown || $[35] !== isOpen || $[36] !== placeholder || $[37] !== searchQuery) {
|
|
t18 = /* @__PURE__ */ _jsx28(ComboboxSearch, {
|
|
inputRef,
|
|
value: searchQuery,
|
|
onChange: t16,
|
|
onFocus: t17,
|
|
onKeyDown: handleKeyDown,
|
|
placeholder,
|
|
disabled,
|
|
isOpen
|
|
});
|
|
$[33] = disabled;
|
|
$[34] = handleKeyDown;
|
|
$[35] = isOpen;
|
|
$[36] = placeholder;
|
|
$[37] = searchQuery;
|
|
$[38] = t18;
|
|
} else {
|
|
t18 = $[38];
|
|
}
|
|
let t19;
|
|
if ($[39] !== filteredTypes || $[40] !== handleSelect || $[41] !== highlightedIndex || $[42] !== isOpen) {
|
|
t19 = isOpen && filteredTypes.length > 0 && /* @__PURE__ */ _jsx28("ul", {
|
|
ref: listRef,
|
|
style: styles2.dropdown,
|
|
role: "listbox",
|
|
children: filteredTypes.map((option_0, index) => /* @__PURE__ */ _jsx28(ComboboxOption, {
|
|
type: option_0.type,
|
|
label: option_0.label || option_0.type,
|
|
icon: option_0.icon,
|
|
description: option_0.description,
|
|
highlighted: index === highlightedIndex,
|
|
onClick: () => handleSelect(option_0),
|
|
onMouseEnter: () => setHighlightedIndex(index)
|
|
}, option_0.type))
|
|
});
|
|
$[39] = filteredTypes;
|
|
$[40] = handleSelect;
|
|
$[41] = highlightedIndex;
|
|
$[42] = isOpen;
|
|
$[43] = t19;
|
|
} else {
|
|
t19 = $[43];
|
|
}
|
|
let t20;
|
|
if ($[44] !== filteredTypes.length || $[45] !== isOpen) {
|
|
t20 = isOpen && filteredTypes.length === 0 && /* @__PURE__ */ _jsx28("div", {
|
|
style: styles2.emptyState,
|
|
children: "No node types found"
|
|
});
|
|
$[44] = filteredTypes.length;
|
|
$[45] = isOpen;
|
|
$[46] = t20;
|
|
} else {
|
|
t20 = $[46];
|
|
}
|
|
let t21;
|
|
if ($[47] !== className || $[48] !== t15 || $[49] !== t18 || $[50] !== t19 || $[51] !== t20) {
|
|
t21 = /* @__PURE__ */ _jsxs13("div", {
|
|
className,
|
|
style: t15,
|
|
children: [t18, t19, t20]
|
|
});
|
|
$[47] = className;
|
|
$[48] = t15;
|
|
$[49] = t18;
|
|
$[50] = t19;
|
|
$[51] = t20;
|
|
$[52] = t21;
|
|
} else {
|
|
t21 = $[52];
|
|
}
|
|
return t21;
|
|
}
|
|
function _temp25(prev) {
|
|
return Math.max(prev - 1, 0);
|
|
}
|
|
function _temp12(type) {
|
|
return {
|
|
type,
|
|
label: formatTypeLabel(type)
|
|
};
|
|
}
|
|
function formatTypeLabel(type) {
|
|
return type.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
}
|
|
var styles2 = {
|
|
container: {
|
|
position: "relative",
|
|
width: "200px"
|
|
},
|
|
input: {
|
|
width: "100%",
|
|
padding: "8px 12px",
|
|
fontSize: "14px",
|
|
border: "1px solid #ddd",
|
|
borderRadius: "6px",
|
|
outline: "none",
|
|
backgroundColor: "#fff"
|
|
},
|
|
dropdown: {
|
|
position: "absolute",
|
|
top: "100%",
|
|
left: 0,
|
|
right: 0,
|
|
marginTop: "4px",
|
|
padding: "4px 0",
|
|
backgroundColor: "#fff",
|
|
border: "1px solid #ddd",
|
|
borderRadius: "6px",
|
|
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
maxHeight: "240px",
|
|
overflowY: "auto",
|
|
zIndex: 1e3,
|
|
listStyle: "none"
|
|
},
|
|
option: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "8px",
|
|
padding: "8px 12px",
|
|
cursor: "pointer",
|
|
transition: "background-color 0.1s"
|
|
},
|
|
optionHighlighted: {
|
|
backgroundColor: "#f0f0f0"
|
|
},
|
|
icon: {
|
|
fontSize: "16px",
|
|
width: "20px",
|
|
textAlign: "center"
|
|
},
|
|
optionContent: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: "2px"
|
|
},
|
|
optionLabel: {
|
|
fontSize: "14px",
|
|
fontWeight: 500
|
|
},
|
|
optionDescription: {
|
|
fontSize: "12px",
|
|
color: "#666"
|
|
},
|
|
emptyState: {
|
|
position: "absolute",
|
|
top: "100%",
|
|
left: 0,
|
|
right: 0,
|
|
marginTop: "4px",
|
|
padding: "12px",
|
|
backgroundColor: "#fff",
|
|
border: "1px solid #ddd",
|
|
borderRadius: "6px",
|
|
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
color: "#666",
|
|
fontSize: "14px",
|
|
textAlign: "center"
|
|
}
|
|
};
|
|
|
|
// src/components/SettingsPanel.tsx
|
|
import { c as _c60 } from "react/compiler-runtime";
|
|
import React31 from "react";
|
|
|
|
// src/components/SettingsPresets.tsx
|
|
import { c as _c58 } from "react/compiler-runtime";
|
|
import React29, { useState as useState5 } from "react";
|
|
import { jsx as _jsx29, jsxs as _jsxs14, Fragment as _Fragment6 } from "react/jsx-runtime";
|
|
function SettingsPresets(t0) {
|
|
const $ = _c58(36);
|
|
const {
|
|
className,
|
|
selectClassName,
|
|
buttonClassName,
|
|
buttonsClassName,
|
|
inputClassName
|
|
} = t0;
|
|
const {
|
|
allPresets,
|
|
activePresetId,
|
|
hasUnsavedChanges,
|
|
applyPreset,
|
|
saveAsPreset,
|
|
deletePreset,
|
|
resetSettings
|
|
} = useCanvasSettings();
|
|
const [showSaveDialog, setShowSaveDialog] = useState5(false);
|
|
const [newPresetName, setNewPresetName] = useState5("");
|
|
let t1;
|
|
if ($[0] !== newPresetName || $[1] !== saveAsPreset) {
|
|
t1 = () => {
|
|
if (newPresetName.trim()) {
|
|
saveAsPreset(newPresetName.trim());
|
|
setNewPresetName("");
|
|
setShowSaveDialog(false);
|
|
}
|
|
};
|
|
$[0] = newPresetName;
|
|
$[1] = saveAsPreset;
|
|
$[2] = t1;
|
|
} else {
|
|
t1 = $[2];
|
|
}
|
|
const handleSave = t1;
|
|
let t2;
|
|
if ($[3] !== activePresetId || $[4] !== allPresets || $[5] !== deletePreset) {
|
|
t2 = () => {
|
|
if (activePresetId && !allPresets.find((p) => p.id === activePresetId)?.isBuiltIn) {
|
|
deletePreset(activePresetId);
|
|
}
|
|
};
|
|
$[3] = activePresetId;
|
|
$[4] = allPresets;
|
|
$[5] = deletePreset;
|
|
$[6] = t2;
|
|
} else {
|
|
t2 = $[6];
|
|
}
|
|
const handleDelete = t2;
|
|
const t3 = activePresetId || "";
|
|
let t4;
|
|
if ($[7] !== applyPreset) {
|
|
t4 = (e) => e.target.value && applyPreset(e.target.value);
|
|
$[7] = applyPreset;
|
|
$[8] = t4;
|
|
} else {
|
|
t4 = $[8];
|
|
}
|
|
const t5 = hasUnsavedChanges ? "Custom (modified)" : "Select preset...";
|
|
let t6;
|
|
if ($[9] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx29("option", {
|
|
value: "",
|
|
disabled: true,
|
|
children: t5
|
|
});
|
|
$[9] = t5;
|
|
$[10] = t6;
|
|
} else {
|
|
t6 = $[10];
|
|
}
|
|
let t7;
|
|
if ($[11] !== allPresets) {
|
|
t7 = allPresets.map(_temp13);
|
|
$[11] = allPresets;
|
|
$[12] = t7;
|
|
} else {
|
|
t7 = $[12];
|
|
}
|
|
let t8;
|
|
if ($[13] !== selectClassName || $[14] !== t3 || $[15] !== t4 || $[16] !== t6 || $[17] !== t7) {
|
|
t8 = /* @__PURE__ */ _jsxs14("select", {
|
|
className: selectClassName,
|
|
value: t3,
|
|
onChange: t4,
|
|
children: [t6, t7]
|
|
});
|
|
$[13] = selectClassName;
|
|
$[14] = t3;
|
|
$[15] = t4;
|
|
$[16] = t6;
|
|
$[17] = t7;
|
|
$[18] = t8;
|
|
} else {
|
|
t8 = $[18];
|
|
}
|
|
let t9;
|
|
if ($[19] !== activePresetId || $[20] !== allPresets || $[21] !== buttonClassName || $[22] !== handleDelete || $[23] !== handleSave || $[24] !== inputClassName || $[25] !== newPresetName || $[26] !== resetSettings || $[27] !== showSaveDialog) {
|
|
t9 = showSaveDialog ? /* @__PURE__ */ _jsxs14(_Fragment6, {
|
|
children: [/* @__PURE__ */ _jsx29("input", {
|
|
type: "text",
|
|
className: inputClassName,
|
|
placeholder: "Preset name",
|
|
value: newPresetName,
|
|
onChange: (e_0) => setNewPresetName(e_0.target.value),
|
|
onKeyDown: (e_1) => e_1.key === "Enter" && handleSave(),
|
|
autoFocus: true
|
|
}), /* @__PURE__ */ _jsx29("button", {
|
|
className: buttonClassName,
|
|
onClick: handleSave,
|
|
children: "Save"
|
|
}), /* @__PURE__ */ _jsx29("button", {
|
|
className: buttonClassName,
|
|
onClick: () => setShowSaveDialog(false),
|
|
children: "Cancel"
|
|
})]
|
|
}) : /* @__PURE__ */ _jsxs14(_Fragment6, {
|
|
children: [/* @__PURE__ */ _jsx29("button", {
|
|
className: buttonClassName,
|
|
onClick: () => setShowSaveDialog(true),
|
|
children: "Save as..."
|
|
}), activePresetId && !allPresets.find((p_0) => p_0.id === activePresetId)?.isBuiltIn && /* @__PURE__ */ _jsx29("button", {
|
|
className: buttonClassName,
|
|
onClick: handleDelete,
|
|
children: "Delete"
|
|
}), /* @__PURE__ */ _jsx29("button", {
|
|
className: buttonClassName,
|
|
onClick: resetSettings,
|
|
children: "Reset"
|
|
})]
|
|
});
|
|
$[19] = activePresetId;
|
|
$[20] = allPresets;
|
|
$[21] = buttonClassName;
|
|
$[22] = handleDelete;
|
|
$[23] = handleSave;
|
|
$[24] = inputClassName;
|
|
$[25] = newPresetName;
|
|
$[26] = resetSettings;
|
|
$[27] = showSaveDialog;
|
|
$[28] = t9;
|
|
} else {
|
|
t9 = $[28];
|
|
}
|
|
let t10;
|
|
if ($[29] !== buttonsClassName || $[30] !== t9) {
|
|
t10 = /* @__PURE__ */ _jsx29("div", {
|
|
className: buttonsClassName,
|
|
children: t9
|
|
});
|
|
$[29] = buttonsClassName;
|
|
$[30] = t9;
|
|
$[31] = t10;
|
|
} else {
|
|
t10 = $[31];
|
|
}
|
|
let t11;
|
|
if ($[32] !== className || $[33] !== t10 || $[34] !== t8) {
|
|
t11 = /* @__PURE__ */ _jsxs14("div", {
|
|
className,
|
|
children: [t8, t10]
|
|
});
|
|
$[32] = className;
|
|
$[33] = t10;
|
|
$[34] = t8;
|
|
$[35] = t11;
|
|
} else {
|
|
t11 = $[35];
|
|
}
|
|
return t11;
|
|
}
|
|
function _temp13(preset) {
|
|
return /* @__PURE__ */ _jsxs14("option", {
|
|
value: preset.id,
|
|
children: [preset.name, preset.isBuiltIn ? "" : " (custom)"]
|
|
}, preset.id);
|
|
}
|
|
|
|
// src/components/SettingsEventMap.tsx
|
|
init_settings_types();
|
|
init_action_registry();
|
|
import { c as _c59 } from "react/compiler-runtime";
|
|
import React30 from "react";
|
|
import { jsx as _jsx30, jsxs as _jsxs15 } from "react/jsx-runtime";
|
|
function EventMappingRow(t0) {
|
|
const $ = _c59(28);
|
|
const {
|
|
event,
|
|
currentActionId,
|
|
onActionChange,
|
|
rowClassName,
|
|
labelClassName,
|
|
selectClassName
|
|
} = t0;
|
|
let t1;
|
|
let t2;
|
|
let t3;
|
|
let t4;
|
|
let t5;
|
|
let t6;
|
|
if ($[0] !== currentActionId || $[1] !== event.label || $[2] !== event.type || $[3] !== labelClassName || $[4] !== onActionChange || $[5] !== rowClassName || $[6] !== selectClassName) {
|
|
const categories = getActionsByCategories();
|
|
t5 = rowClassName;
|
|
if ($[13] !== event.label || $[14] !== labelClassName) {
|
|
t6 = /* @__PURE__ */ _jsx30("span", {
|
|
className: labelClassName,
|
|
children: event.label
|
|
});
|
|
$[13] = event.label;
|
|
$[14] = labelClassName;
|
|
$[15] = t6;
|
|
} else {
|
|
t6 = $[15];
|
|
}
|
|
t1 = selectClassName;
|
|
t2 = currentActionId;
|
|
if ($[16] !== event.type || $[17] !== onActionChange) {
|
|
t3 = (e) => onActionChange(event.type, e.target.value);
|
|
$[16] = event.type;
|
|
$[17] = onActionChange;
|
|
$[18] = t3;
|
|
} else {
|
|
t3 = $[18];
|
|
}
|
|
t4 = categories.map(_temp26);
|
|
$[0] = currentActionId;
|
|
$[1] = event.label;
|
|
$[2] = event.type;
|
|
$[3] = labelClassName;
|
|
$[4] = onActionChange;
|
|
$[5] = rowClassName;
|
|
$[6] = selectClassName;
|
|
$[7] = t1;
|
|
$[8] = t2;
|
|
$[9] = t3;
|
|
$[10] = t4;
|
|
$[11] = t5;
|
|
$[12] = t6;
|
|
} else {
|
|
t1 = $[7];
|
|
t2 = $[8];
|
|
t3 = $[9];
|
|
t4 = $[10];
|
|
t5 = $[11];
|
|
t6 = $[12];
|
|
}
|
|
let t7;
|
|
if ($[19] !== t1 || $[20] !== t2 || $[21] !== t3 || $[22] !== t4) {
|
|
t7 = /* @__PURE__ */ _jsx30("select", {
|
|
className: t1,
|
|
value: t2,
|
|
onChange: t3,
|
|
children: t4
|
|
});
|
|
$[19] = t1;
|
|
$[20] = t2;
|
|
$[21] = t3;
|
|
$[22] = t4;
|
|
$[23] = t7;
|
|
} else {
|
|
t7 = $[23];
|
|
}
|
|
let t8;
|
|
if ($[24] !== t5 || $[25] !== t6 || $[26] !== t7) {
|
|
t8 = /* @__PURE__ */ _jsxs15("div", {
|
|
className: t5,
|
|
children: [t6, t7]
|
|
});
|
|
$[24] = t5;
|
|
$[25] = t6;
|
|
$[26] = t7;
|
|
$[27] = t8;
|
|
} else {
|
|
t8 = $[27];
|
|
}
|
|
return t8;
|
|
}
|
|
function _temp26(category) {
|
|
return /* @__PURE__ */ _jsx30("optgroup", {
|
|
label: category.label,
|
|
children: category.actions.map(_temp14)
|
|
}, category.category);
|
|
}
|
|
function _temp14(action) {
|
|
return /* @__PURE__ */ _jsx30("option", {
|
|
value: action.id,
|
|
children: action.label
|
|
}, action.id);
|
|
}
|
|
function SettingsEventMap(t0) {
|
|
const $ = _c59(17);
|
|
const {
|
|
mappings,
|
|
onActionChange,
|
|
contentClassName,
|
|
groupClassName,
|
|
groupHeadingClassName,
|
|
rowClassName,
|
|
labelClassName,
|
|
selectClassName
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== contentClassName || $[1] !== groupClassName || $[2] !== groupHeadingClassName || $[3] !== labelClassName || $[4] !== mappings || $[5] !== onActionChange || $[6] !== rowClassName || $[7] !== selectClassName) {
|
|
const nodeEvents = Object.values(CanvasEventType).filter(_temp33);
|
|
const edgeEvents = Object.values(CanvasEventType).filter(_temp42);
|
|
const backgroundEvents = Object.values(CanvasEventType).filter(_temp52);
|
|
let t2;
|
|
if ($[9] !== groupClassName || $[10] !== groupHeadingClassName || $[11] !== labelClassName || $[12] !== mappings || $[13] !== onActionChange || $[14] !== rowClassName || $[15] !== selectClassName) {
|
|
t2 = (heading, events) => /* @__PURE__ */ _jsxs15("div", {
|
|
className: groupClassName,
|
|
children: [/* @__PURE__ */ _jsx30("h3", {
|
|
className: groupHeadingClassName,
|
|
children: heading
|
|
}), events.map((event_2) => /* @__PURE__ */ _jsx30(EventMappingRow, {
|
|
event: EVENT_TYPE_INFO[event_2],
|
|
currentActionId: mappings[event_2],
|
|
onActionChange,
|
|
rowClassName,
|
|
labelClassName,
|
|
selectClassName
|
|
}, event_2))]
|
|
});
|
|
$[9] = groupClassName;
|
|
$[10] = groupHeadingClassName;
|
|
$[11] = labelClassName;
|
|
$[12] = mappings;
|
|
$[13] = onActionChange;
|
|
$[14] = rowClassName;
|
|
$[15] = selectClassName;
|
|
$[16] = t2;
|
|
} else {
|
|
t2 = $[16];
|
|
}
|
|
const renderGroup = t2;
|
|
t1 = /* @__PURE__ */ _jsxs15("div", {
|
|
className: contentClassName,
|
|
children: [renderGroup("Node Events", nodeEvents), renderGroup("Edge Events", edgeEvents), renderGroup("Background Events", backgroundEvents)]
|
|
});
|
|
$[0] = contentClassName;
|
|
$[1] = groupClassName;
|
|
$[2] = groupHeadingClassName;
|
|
$[3] = labelClassName;
|
|
$[4] = mappings;
|
|
$[5] = onActionChange;
|
|
$[6] = rowClassName;
|
|
$[7] = selectClassName;
|
|
$[8] = t1;
|
|
} else {
|
|
t1 = $[8];
|
|
}
|
|
return t1;
|
|
}
|
|
function _temp52(event_1) {
|
|
return EVENT_TYPE_INFO[event_1].category === "background";
|
|
}
|
|
function _temp42(event_0) {
|
|
return EVENT_TYPE_INFO[event_0].category === "edge";
|
|
}
|
|
function _temp33(event) {
|
|
return EVENT_TYPE_INFO[event].category === "node";
|
|
}
|
|
|
|
// src/components/SettingsPanel.tsx
|
|
import { jsx as _jsx31, jsxs as _jsxs16 } from "react/jsx-runtime";
|
|
function QuickActions(t0) {
|
|
const $ = _c60(17);
|
|
const {
|
|
className,
|
|
checkboxClassName,
|
|
virtualizationClassName,
|
|
virtualizationStatsClassName
|
|
} = t0;
|
|
const {
|
|
enabled,
|
|
toggle,
|
|
visibleNodes,
|
|
totalNodes,
|
|
culledNodes
|
|
} = useVirtualization();
|
|
let t1;
|
|
if ($[0] !== checkboxClassName || $[1] !== enabled || $[2] !== toggle) {
|
|
t1 = /* @__PURE__ */ _jsx31("input", {
|
|
type: "checkbox",
|
|
className: checkboxClassName,
|
|
checked: enabled,
|
|
onChange: toggle
|
|
});
|
|
$[0] = checkboxClassName;
|
|
$[1] = enabled;
|
|
$[2] = toggle;
|
|
$[3] = t1;
|
|
} else {
|
|
t1 = $[3];
|
|
}
|
|
let t2;
|
|
if ($[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = /* @__PURE__ */ _jsx31("span", {
|
|
children: "Virtualization"
|
|
});
|
|
$[4] = t2;
|
|
} else {
|
|
t2 = $[4];
|
|
}
|
|
let t3;
|
|
if ($[5] !== t1) {
|
|
t3 = /* @__PURE__ */ _jsxs16("label", {
|
|
children: [t1, t2]
|
|
});
|
|
$[5] = t1;
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
const t4 = enabled ? `${visibleNodes}/${totalNodes} (${culledNodes} culled)` : "Off";
|
|
let t5;
|
|
if ($[7] !== t4 || $[8] !== virtualizationStatsClassName) {
|
|
t5 = /* @__PURE__ */ _jsx31("span", {
|
|
className: virtualizationStatsClassName,
|
|
children: t4
|
|
});
|
|
$[7] = t4;
|
|
$[8] = virtualizationStatsClassName;
|
|
$[9] = t5;
|
|
} else {
|
|
t5 = $[9];
|
|
}
|
|
let t6;
|
|
if ($[10] !== t3 || $[11] !== t5 || $[12] !== virtualizationClassName) {
|
|
t6 = /* @__PURE__ */ _jsxs16("div", {
|
|
className: virtualizationClassName,
|
|
children: [t3, t5]
|
|
});
|
|
$[10] = t3;
|
|
$[11] = t5;
|
|
$[12] = virtualizationClassName;
|
|
$[13] = t6;
|
|
} else {
|
|
t6 = $[13];
|
|
}
|
|
let t7;
|
|
if ($[14] !== className || $[15] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsx31("div", {
|
|
className,
|
|
children: t6
|
|
});
|
|
$[14] = className;
|
|
$[15] = t6;
|
|
$[16] = t7;
|
|
} else {
|
|
t7 = $[16];
|
|
}
|
|
return t7;
|
|
}
|
|
function SettingsPanel(t0) {
|
|
const $ = _c60(35);
|
|
const {
|
|
className,
|
|
headerClassName,
|
|
contentClassName,
|
|
footerClassName,
|
|
rowClassName,
|
|
labelClassName,
|
|
selectClassName,
|
|
buttonClassName,
|
|
presetClassName,
|
|
presetSelectClassName,
|
|
presetButtonsClassName,
|
|
checkboxClassName,
|
|
groupClassName,
|
|
groupHeadingClassName,
|
|
quickActionsClassName,
|
|
virtualizationClassName,
|
|
virtualizationStatsClassName,
|
|
inputClassName,
|
|
renderHeader,
|
|
renderFooter,
|
|
onClose
|
|
} = t0;
|
|
const {
|
|
mappings,
|
|
setEventMapping
|
|
} = useCanvasSettings();
|
|
let t1;
|
|
if ($[0] !== buttonClassName || $[1] !== headerClassName || $[2] !== onClose || $[3] !== renderHeader) {
|
|
t1 = renderHeader ? renderHeader() : /* @__PURE__ */ _jsxs16("div", {
|
|
className: headerClassName,
|
|
children: [/* @__PURE__ */ _jsx31("h2", {
|
|
children: "Canvas Settings"
|
|
}), onClose && /* @__PURE__ */ _jsx31("button", {
|
|
className: buttonClassName,
|
|
onClick: onClose,
|
|
children: "\xD7"
|
|
})]
|
|
});
|
|
$[0] = buttonClassName;
|
|
$[1] = headerClassName;
|
|
$[2] = onClose;
|
|
$[3] = renderHeader;
|
|
$[4] = t1;
|
|
} else {
|
|
t1 = $[4];
|
|
}
|
|
const t2 = presetSelectClassName || selectClassName;
|
|
let t3;
|
|
if ($[5] !== buttonClassName || $[6] !== inputClassName || $[7] !== presetButtonsClassName || $[8] !== presetClassName || $[9] !== t2) {
|
|
t3 = /* @__PURE__ */ _jsx31(SettingsPresets, {
|
|
className: presetClassName,
|
|
selectClassName: t2,
|
|
buttonClassName,
|
|
buttonsClassName: presetButtonsClassName,
|
|
inputClassName
|
|
});
|
|
$[5] = buttonClassName;
|
|
$[6] = inputClassName;
|
|
$[7] = presetButtonsClassName;
|
|
$[8] = presetClassName;
|
|
$[9] = t2;
|
|
$[10] = t3;
|
|
} else {
|
|
t3 = $[10];
|
|
}
|
|
let t4;
|
|
if ($[11] !== checkboxClassName || $[12] !== quickActionsClassName || $[13] !== virtualizationClassName || $[14] !== virtualizationStatsClassName) {
|
|
t4 = /* @__PURE__ */ _jsx31(QuickActions, {
|
|
className: quickActionsClassName,
|
|
checkboxClassName,
|
|
virtualizationClassName,
|
|
virtualizationStatsClassName
|
|
});
|
|
$[11] = checkboxClassName;
|
|
$[12] = quickActionsClassName;
|
|
$[13] = virtualizationClassName;
|
|
$[14] = virtualizationStatsClassName;
|
|
$[15] = t4;
|
|
} else {
|
|
t4 = $[15];
|
|
}
|
|
let t5;
|
|
if ($[16] !== contentClassName || $[17] !== groupClassName || $[18] !== groupHeadingClassName || $[19] !== labelClassName || $[20] !== mappings || $[21] !== rowClassName || $[22] !== selectClassName || $[23] !== setEventMapping) {
|
|
t5 = /* @__PURE__ */ _jsx31(SettingsEventMap, {
|
|
mappings,
|
|
onActionChange: setEventMapping,
|
|
contentClassName,
|
|
groupClassName,
|
|
groupHeadingClassName,
|
|
rowClassName,
|
|
labelClassName,
|
|
selectClassName
|
|
});
|
|
$[16] = contentClassName;
|
|
$[17] = groupClassName;
|
|
$[18] = groupHeadingClassName;
|
|
$[19] = labelClassName;
|
|
$[20] = mappings;
|
|
$[21] = rowClassName;
|
|
$[22] = selectClassName;
|
|
$[23] = setEventMapping;
|
|
$[24] = t5;
|
|
} else {
|
|
t5 = $[24];
|
|
}
|
|
let t6;
|
|
if ($[25] !== footerClassName || $[26] !== renderFooter) {
|
|
t6 = renderFooter ? renderFooter() : /* @__PURE__ */ _jsx31("div", {
|
|
className: footerClassName,
|
|
children: "Changes auto-saved to localStorage."
|
|
});
|
|
$[25] = footerClassName;
|
|
$[26] = renderFooter;
|
|
$[27] = t6;
|
|
} else {
|
|
t6 = $[27];
|
|
}
|
|
let t7;
|
|
if ($[28] !== className || $[29] !== t1 || $[30] !== t3 || $[31] !== t4 || $[32] !== t5 || $[33] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsxs16("div", {
|
|
className,
|
|
children: [t1, t3, t4, t5, t6]
|
|
});
|
|
$[28] = className;
|
|
$[29] = t1;
|
|
$[30] = t3;
|
|
$[31] = t4;
|
|
$[32] = t5;
|
|
$[33] = t6;
|
|
$[34] = t7;
|
|
} else {
|
|
t7 = $[34];
|
|
}
|
|
return t7;
|
|
}
|
|
|
|
// src/components/CommandLine/CommandLine.tsx
|
|
import { c as _c65 } from "react/compiler-runtime";
|
|
import React36, { useEffect as useEffect21, useRef as useRef28, useState as useState8 } from "react";
|
|
import { useAtom as useAtom7, useAtomValue as useAtomValue42, useSetAtom as useSetAtom31 } from "jotai";
|
|
init_registry();
|
|
|
|
// src/components/CommandLine/CommandSearch.tsx
|
|
import { c as _c61 } from "react/compiler-runtime";
|
|
import React32, { useRef as useRef26, useEffect as useEffect19, useState as useState6 } from "react";
|
|
import { useAtom as useAtom5, useSetAtom as useSetAtom29, useAtomValue as useAtomValue40 } from "jotai";
|
|
init_registry();
|
|
import { jsx as _jsx32, jsxs as _jsxs17 } from "react/jsx-runtime";
|
|
function CommandSearch() {
|
|
const $ = _c61(24);
|
|
const [state] = useAtom5(commandLineStateAtom);
|
|
const updateSearchQuery = useSetAtom29(updateSearchQueryAtom);
|
|
const selectCommand = useSetAtom29(selectCommandAtom);
|
|
useAtomValue40(commandHistoryAtom);
|
|
const [selectedIndex, setSelectedIndex] = useAtom5(selectedSuggestionIndexAtom);
|
|
const inputRef = useRef26(null);
|
|
const [inputValue, setInputValue] = useState6("");
|
|
let t0;
|
|
let t1;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = () => {
|
|
inputRef.current?.focus();
|
|
};
|
|
t1 = [];
|
|
$[0] = t0;
|
|
$[1] = t1;
|
|
} else {
|
|
t0 = $[0];
|
|
t1 = $[1];
|
|
}
|
|
useEffect19(t0, t1);
|
|
let t2;
|
|
if ($[2] !== state.phase || $[3] !== state.suggestions) {
|
|
t2 = state.phase === "searching" ? state.suggestions : commandRegistry.all();
|
|
$[2] = state.phase;
|
|
$[3] = state.suggestions;
|
|
$[4] = t2;
|
|
} else {
|
|
t2 = $[4];
|
|
}
|
|
const suggestions = t2;
|
|
let t3;
|
|
if ($[5] !== updateSearchQuery) {
|
|
t3 = (e) => {
|
|
const value = e.target.value;
|
|
setInputValue(value);
|
|
updateSearchQuery(value);
|
|
};
|
|
$[5] = updateSearchQuery;
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
const handleChange = t3;
|
|
let t4;
|
|
if ($[7] !== selectCommand || $[8] !== selectedIndex || $[9] !== setSelectedIndex || $[10] !== suggestions || $[11] !== updateSearchQuery) {
|
|
t4 = (e_0) => {
|
|
bb15: switch (e_0.key) {
|
|
case "ArrowDown": {
|
|
e_0.preventDefault();
|
|
setSelectedIndex((prev_0) => Math.min(prev_0 + 1, suggestions.length - 1));
|
|
break bb15;
|
|
}
|
|
case "ArrowUp": {
|
|
e_0.preventDefault();
|
|
setSelectedIndex(_temp15);
|
|
break bb15;
|
|
}
|
|
case "Enter": {
|
|
e_0.preventDefault();
|
|
if (suggestions[selectedIndex]) {
|
|
selectCommand(suggestions[selectedIndex]);
|
|
}
|
|
break bb15;
|
|
}
|
|
case "Tab": {
|
|
e_0.preventDefault();
|
|
if (suggestions[selectedIndex]) {
|
|
setInputValue(suggestions[selectedIndex].name);
|
|
updateSearchQuery(suggestions[selectedIndex].name);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
$[7] = selectCommand;
|
|
$[8] = selectedIndex;
|
|
$[9] = setSelectedIndex;
|
|
$[10] = suggestions;
|
|
$[11] = updateSearchQuery;
|
|
$[12] = t4;
|
|
} else {
|
|
t4 = $[12];
|
|
}
|
|
const handleKeyDown = t4;
|
|
let t5;
|
|
if ($[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t5 = {
|
|
color: "var(--cmd-input-text)"
|
|
};
|
|
$[13] = t5;
|
|
} else {
|
|
t5 = $[13];
|
|
}
|
|
let t6;
|
|
if ($[14] !== handleChange || $[15] !== handleKeyDown || $[16] !== inputValue) {
|
|
t6 = /* @__PURE__ */ _jsx32("input", {
|
|
ref: inputRef,
|
|
type: "text",
|
|
value: inputValue,
|
|
onChange: handleChange,
|
|
onKeyDown: handleKeyDown,
|
|
placeholder: "Type command name...",
|
|
className: "flex-1 bg-transparent outline-none",
|
|
style: t5,
|
|
autoComplete: "off",
|
|
autoCorrect: "off",
|
|
autoCapitalize: "off",
|
|
spellCheck: "false"
|
|
});
|
|
$[14] = handleChange;
|
|
$[15] = handleKeyDown;
|
|
$[16] = inputValue;
|
|
$[17] = t6;
|
|
} else {
|
|
t6 = $[17];
|
|
}
|
|
let t7;
|
|
if ($[18] !== inputValue || $[19] !== suggestions) {
|
|
t7 = inputValue && /* @__PURE__ */ _jsxs17("span", {
|
|
className: "ml-2 text-xs",
|
|
style: {
|
|
color: "var(--hud-text-secondary)",
|
|
opacity: 0.6
|
|
},
|
|
children: [suggestions.length, " matches"]
|
|
});
|
|
$[18] = inputValue;
|
|
$[19] = suggestions;
|
|
$[20] = t7;
|
|
} else {
|
|
t7 = $[20];
|
|
}
|
|
let t8;
|
|
if ($[21] !== t6 || $[22] !== t7) {
|
|
t8 = /* @__PURE__ */ _jsxs17("div", {
|
|
className: "flex flex-1 items-center",
|
|
children: [t6, t7]
|
|
});
|
|
$[21] = t6;
|
|
$[22] = t7;
|
|
$[23] = t8;
|
|
} else {
|
|
t8 = $[23];
|
|
}
|
|
return t8;
|
|
}
|
|
function _temp15(prev) {
|
|
return Math.max(prev - 1, 0);
|
|
}
|
|
|
|
// src/components/CommandLine/CommandInputCollector.tsx
|
|
import { c as _c64 } from "react/compiler-runtime";
|
|
import React35, { useRef as useRef27, useEffect as useEffect20, useState as useState7 } from "react";
|
|
import { useAtom as useAtom6, useSetAtom as useSetAtom30, useAtomValue as useAtomValue41 } from "jotai";
|
|
init_viewport_store();
|
|
|
|
// src/components/CommandLine/CollectorInputPhase.tsx
|
|
import { c as _c62 } from "react/compiler-runtime";
|
|
import React33 from "react";
|
|
import { jsx as _jsx33, jsxs as _jsxs18, Fragment as _Fragment7 } from "react/jsx-runtime";
|
|
function CollectorInputPhase(t0) {
|
|
const $ = _c62(15);
|
|
const {
|
|
command,
|
|
currentInput,
|
|
progress,
|
|
inputModeType
|
|
} = t0;
|
|
const isPickMode = inputModeType === "pickPoint" || inputModeType === "pickNode" || inputModeType === "pickNodes";
|
|
let t1;
|
|
if ($[0] !== command || $[1] !== progress) {
|
|
t1 = progress && /* @__PURE__ */ _jsxs18("div", {
|
|
className: "flex items-center gap-2 text-xs",
|
|
style: {
|
|
color: "var(--hud-text-secondary)"
|
|
},
|
|
children: [/* @__PURE__ */ _jsx33("span", {
|
|
className: "font-medium",
|
|
style: {
|
|
color: "var(--hud-text)"
|
|
},
|
|
children: command.name
|
|
}), /* @__PURE__ */ _jsx33("span", {
|
|
children: "\u2022"
|
|
}), /* @__PURE__ */ _jsxs18("span", {
|
|
children: ["Step ", progress.current, "/", progress.total]
|
|
})]
|
|
});
|
|
$[0] = command;
|
|
$[1] = progress;
|
|
$[2] = t1;
|
|
} else {
|
|
t1 = $[2];
|
|
}
|
|
let t2;
|
|
if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = {
|
|
color: "var(--hud-text-secondary)"
|
|
};
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
let t3;
|
|
if ($[4] !== currentInput.prompt) {
|
|
t3 = /* @__PURE__ */ _jsxs18("span", {
|
|
style: t2,
|
|
children: [currentInput.prompt, ":"]
|
|
});
|
|
$[4] = currentInput.prompt;
|
|
$[5] = t3;
|
|
} else {
|
|
t3 = $[5];
|
|
}
|
|
let t4;
|
|
if ($[6] !== inputModeType || $[7] !== isPickMode) {
|
|
t4 = isPickMode && /* @__PURE__ */ _jsxs18("span", {
|
|
className: "animate-pulse text-sm",
|
|
style: {
|
|
color: "var(--badge-info-text)"
|
|
},
|
|
children: [inputModeType === "pickPoint" && "Click on canvas or enter x,y", inputModeType === "pickNode" && "Click on a node", inputModeType === "pickNodes" && "Click nodes (Enter when done)"]
|
|
});
|
|
$[6] = inputModeType;
|
|
$[7] = isPickMode;
|
|
$[8] = t4;
|
|
} else {
|
|
t4 = $[8];
|
|
}
|
|
let t5;
|
|
if ($[9] !== t3 || $[10] !== t4) {
|
|
t5 = /* @__PURE__ */ _jsxs18("div", {
|
|
className: "flex items-center gap-2",
|
|
children: [t3, t4]
|
|
});
|
|
$[9] = t3;
|
|
$[10] = t4;
|
|
$[11] = t5;
|
|
} else {
|
|
t5 = $[11];
|
|
}
|
|
let t6;
|
|
if ($[12] !== t1 || $[13] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsxs18(_Fragment7, {
|
|
children: [t1, t5]
|
|
});
|
|
$[12] = t1;
|
|
$[13] = t5;
|
|
$[14] = t6;
|
|
} else {
|
|
t6 = $[14];
|
|
}
|
|
return t6;
|
|
}
|
|
|
|
// src/components/CommandLine/CollectorSelectInput.tsx
|
|
import { c as _c63 } from "react/compiler-runtime";
|
|
import React34 from "react";
|
|
import { jsx as _jsx34, jsxs as _jsxs19 } from "react/jsx-runtime";
|
|
function ShortcutButton(t0) {
|
|
const $ = _c63(13);
|
|
const {
|
|
shortcut,
|
|
label,
|
|
description,
|
|
onClick
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
backgroundColor: "var(--list-item-bg-hover)",
|
|
color: "var(--hud-text)"
|
|
};
|
|
$[0] = t1;
|
|
} else {
|
|
t1 = $[0];
|
|
}
|
|
let t2;
|
|
if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = {
|
|
backgroundColor: "var(--cmd-bg)",
|
|
color: "var(--badge-info-text)"
|
|
};
|
|
$[1] = t2;
|
|
} else {
|
|
t2 = $[1];
|
|
}
|
|
let t3;
|
|
if ($[2] !== shortcut) {
|
|
t3 = shortcut.toUpperCase();
|
|
$[2] = shortcut;
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
let t4;
|
|
if ($[4] !== t3) {
|
|
t4 = /* @__PURE__ */ _jsx34("kbd", {
|
|
className: "rounded px-1 py-0.5 font-mono text-xs",
|
|
style: t2,
|
|
children: t3
|
|
});
|
|
$[4] = t3;
|
|
$[5] = t4;
|
|
} else {
|
|
t4 = $[5];
|
|
}
|
|
let t5;
|
|
if ($[6] !== label) {
|
|
t5 = /* @__PURE__ */ _jsx34("span", {
|
|
children: label
|
|
});
|
|
$[6] = label;
|
|
$[7] = t5;
|
|
} else {
|
|
t5 = $[7];
|
|
}
|
|
let t6;
|
|
if ($[8] !== description || $[9] !== onClick || $[10] !== t4 || $[11] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsxs19("button", {
|
|
onClick,
|
|
className: "flex items-center gap-1 rounded px-2 py-0.5 text-sm transition-colors",
|
|
style: t1,
|
|
onMouseEnter: _temp16,
|
|
onMouseLeave: _temp27,
|
|
title: description,
|
|
children: [t4, t5]
|
|
});
|
|
$[8] = description;
|
|
$[9] = onClick;
|
|
$[10] = t4;
|
|
$[11] = t5;
|
|
$[12] = t6;
|
|
} else {
|
|
t6 = $[12];
|
|
}
|
|
return t6;
|
|
}
|
|
function _temp27(e_0) {
|
|
return e_0.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)";
|
|
}
|
|
function _temp16(e) {
|
|
return e.currentTarget.style.backgroundColor = "var(--list-item-border)";
|
|
}
|
|
function CollectorSelectInput(t0) {
|
|
const $ = _c63(7);
|
|
const {
|
|
options,
|
|
onSelect
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== onSelect || $[1] !== options) {
|
|
let t22;
|
|
if ($[3] !== onSelect) {
|
|
t22 = (option) => /* @__PURE__ */ _jsx34(ShortcutButton, {
|
|
shortcut: option.shortcut,
|
|
label: option.label,
|
|
description: option.description,
|
|
onClick: () => onSelect(option.value)
|
|
}, option.value);
|
|
$[3] = onSelect;
|
|
$[4] = t22;
|
|
} else {
|
|
t22 = $[4];
|
|
}
|
|
t1 = options.map(t22);
|
|
$[0] = onSelect;
|
|
$[1] = options;
|
|
$[2] = t1;
|
|
} else {
|
|
t1 = $[2];
|
|
}
|
|
let t2;
|
|
if ($[5] !== t1) {
|
|
t2 = /* @__PURE__ */ _jsx34("div", {
|
|
className: "flex items-center gap-2",
|
|
children: t1
|
|
});
|
|
$[5] = t1;
|
|
$[6] = t2;
|
|
} else {
|
|
t2 = $[6];
|
|
}
|
|
return t2;
|
|
}
|
|
function CollectorBooleanInput(t0) {
|
|
const $ = _c63(7);
|
|
const {
|
|
onSelect
|
|
} = t0;
|
|
let t1;
|
|
if ($[0] !== onSelect) {
|
|
t1 = /* @__PURE__ */ _jsx34(ShortcutButton, {
|
|
shortcut: "Y",
|
|
label: "Yes",
|
|
onClick: () => onSelect(true)
|
|
});
|
|
$[0] = onSelect;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
let t2;
|
|
if ($[2] !== onSelect) {
|
|
t2 = /* @__PURE__ */ _jsx34(ShortcutButton, {
|
|
shortcut: "N",
|
|
label: "No",
|
|
onClick: () => onSelect(false)
|
|
});
|
|
$[2] = onSelect;
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
let t3;
|
|
if ($[4] !== t1 || $[5] !== t2) {
|
|
t3 = /* @__PURE__ */ _jsxs19("div", {
|
|
className: "flex items-center gap-2",
|
|
children: [t1, t2]
|
|
});
|
|
$[4] = t1;
|
|
$[5] = t2;
|
|
$[6] = t3;
|
|
} else {
|
|
t3 = $[6];
|
|
}
|
|
return t3;
|
|
}
|
|
|
|
// src/components/CommandLine/CommandInputCollector.tsx
|
|
import { jsx as _jsx35, jsxs as _jsxs20 } from "react/jsx-runtime";
|
|
function CommandInputCollector() {
|
|
const $ = _c64(71);
|
|
const [state] = useAtom6(commandLineStateAtom);
|
|
const currentInput = useAtomValue41(currentInputAtom);
|
|
const progress = useAtomValue41(commandProgressAtom);
|
|
const inputMode = useAtomValue41(inputModeAtom2);
|
|
const provideInput = useSetAtom30(provideInputAtom2);
|
|
const skipInput = useSetAtom30(skipInputAtom);
|
|
const goBack = useSetAtom30(goBackInputAtom);
|
|
const inputRef = useRef27(null);
|
|
const [textValue, setTextValue] = useState7("");
|
|
const zoom = useAtomValue41(zoomAtom);
|
|
const pan = useAtomValue41(panAtom);
|
|
const viewportRect = useAtomValue41(viewportRectAtom);
|
|
let t0;
|
|
let t1;
|
|
if ($[0] !== currentInput) {
|
|
t0 = () => {
|
|
if (currentInput && (currentInput.type === "text" || currentInput.type === "number" || currentInput.type === "point")) {
|
|
inputRef.current?.focus();
|
|
}
|
|
};
|
|
t1 = [currentInput];
|
|
$[0] = currentInput;
|
|
$[1] = t0;
|
|
$[2] = t1;
|
|
} else {
|
|
t0 = $[1];
|
|
t1 = $[2];
|
|
}
|
|
useEffect20(t0, t1);
|
|
let t2;
|
|
if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t2 = () => {
|
|
setTextValue("");
|
|
};
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
const t3 = currentInput?.name;
|
|
let t4;
|
|
if ($[4] !== t3) {
|
|
t4 = [t3];
|
|
$[4] = t3;
|
|
$[5] = t4;
|
|
} else {
|
|
t4 = $[5];
|
|
}
|
|
useEffect20(t2, t4);
|
|
let t5;
|
|
if ($[6] !== pan.x || $[7] !== pan.y || $[8] !== viewportRect || $[9] !== zoom) {
|
|
t5 = () => {
|
|
const screenCenterX = viewportRect ? viewportRect.width / 2 : window.innerWidth / 2;
|
|
const screenCenterY = viewportRect ? viewportRect.height / 2 : window.innerHeight / 2;
|
|
return {
|
|
x: Math.round((screenCenterX - pan.x) / zoom),
|
|
y: Math.round((screenCenterY - pan.y) / zoom)
|
|
};
|
|
};
|
|
$[6] = pan.x;
|
|
$[7] = pan.y;
|
|
$[8] = viewportRect;
|
|
$[9] = zoom;
|
|
$[10] = t5;
|
|
} else {
|
|
t5 = $[10];
|
|
}
|
|
const getWorldCenter = t5;
|
|
let t6;
|
|
let t7;
|
|
if ($[11] !== currentInput || $[12] !== getWorldCenter || $[13] !== provideInput) {
|
|
t6 = () => {
|
|
if (!currentInput) {
|
|
return;
|
|
}
|
|
const handleGlobalKeyDown = (e) => {
|
|
const target = e.target;
|
|
const isInputField = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
|
|
if (e.key === "Enter" && !isInputField) {
|
|
if (currentInput.type === "point") {
|
|
e.preventDefault();
|
|
provideInput(getWorldCenter());
|
|
return;
|
|
}
|
|
if (currentInput.default !== void 0) {
|
|
e.preventDefault();
|
|
provideInput(currentInput.default);
|
|
return;
|
|
}
|
|
}
|
|
if (currentInput.type === "select" && currentInput.options) {
|
|
const option = currentInput.options.find((opt) => opt.shortcut.toLowerCase() === e.key.toLowerCase());
|
|
if (option) {
|
|
e.preventDefault();
|
|
provideInput(option.value);
|
|
}
|
|
}
|
|
if (currentInput.type === "boolean") {
|
|
if (e.key.toLowerCase() === "y") {
|
|
e.preventDefault();
|
|
provideInput(true);
|
|
}
|
|
if (e.key.toLowerCase() === "n") {
|
|
e.preventDefault();
|
|
provideInput(false);
|
|
}
|
|
}
|
|
};
|
|
window.addEventListener("keydown", handleGlobalKeyDown);
|
|
return () => window.removeEventListener("keydown", handleGlobalKeyDown);
|
|
};
|
|
t7 = [currentInput, provideInput, getWorldCenter];
|
|
$[11] = currentInput;
|
|
$[12] = getWorldCenter;
|
|
$[13] = provideInput;
|
|
$[14] = t6;
|
|
$[15] = t7;
|
|
} else {
|
|
t6 = $[14];
|
|
t7 = $[15];
|
|
}
|
|
useEffect20(t6, t7);
|
|
let t8;
|
|
if ($[16] !== currentInput || $[17] !== provideInput || $[18] !== textValue) {
|
|
t8 = (e_0) => {
|
|
e_0.preventDefault();
|
|
if (!currentInput) {
|
|
return;
|
|
}
|
|
if (textValue === "" && currentInput.default !== void 0) {
|
|
provideInput(currentInput.default);
|
|
setTextValue("");
|
|
return;
|
|
}
|
|
if (currentInput.type === "number") {
|
|
const num = parseFloat(textValue);
|
|
if (!isNaN(num)) {
|
|
provideInput(num);
|
|
}
|
|
} else {
|
|
provideInput(textValue);
|
|
}
|
|
setTextValue("");
|
|
};
|
|
$[16] = currentInput;
|
|
$[17] = provideInput;
|
|
$[18] = textValue;
|
|
$[19] = t8;
|
|
} else {
|
|
t8 = $[19];
|
|
}
|
|
const handleSubmit = t8;
|
|
let t9;
|
|
if ($[20] !== currentInput || $[21] !== goBack || $[22] !== progress || $[23] !== provideInput || $[24] !== skipInput || $[25] !== textValue) {
|
|
t9 = (e_1) => {
|
|
if (e_1.key === "Escape") {
|
|
e_1.preventDefault();
|
|
if (currentInput?.required === false) {
|
|
skipInput();
|
|
} else {
|
|
if (progress && progress.current > 1) {
|
|
goBack();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (e_1.key === "Backspace" && textValue === "" && progress && progress.current > 1) {
|
|
e_1.preventDefault();
|
|
goBack();
|
|
return;
|
|
}
|
|
if (currentInput?.type === "select" && currentInput.options) {
|
|
const option_0 = currentInput.options.find((opt_0) => opt_0.shortcut.toLowerCase() === e_1.key.toLowerCase());
|
|
if (option_0) {
|
|
e_1.preventDefault();
|
|
provideInput(option_0.value);
|
|
}
|
|
}
|
|
};
|
|
$[20] = currentInput;
|
|
$[21] = goBack;
|
|
$[22] = progress;
|
|
$[23] = provideInput;
|
|
$[24] = skipInput;
|
|
$[25] = textValue;
|
|
$[26] = t9;
|
|
} else {
|
|
t9 = $[26];
|
|
}
|
|
const handleKeyDown = t9;
|
|
if (state.phase !== "collecting" || !currentInput) {
|
|
return null;
|
|
}
|
|
const isSelectMode = currentInput.type === "select";
|
|
let t10;
|
|
if ($[27] !== currentInput || $[28] !== inputMode.type || $[29] !== progress || $[30] !== state.command) {
|
|
t10 = /* @__PURE__ */ _jsx35(CollectorInputPhase, {
|
|
command: state.command,
|
|
currentInput,
|
|
progress,
|
|
inputModeType: inputMode.type
|
|
});
|
|
$[27] = currentInput;
|
|
$[28] = inputMode.type;
|
|
$[29] = progress;
|
|
$[30] = state.command;
|
|
$[31] = t10;
|
|
} else {
|
|
t10 = $[31];
|
|
}
|
|
let t11;
|
|
if ($[32] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t11 = {
|
|
color: "var(--hud-text-secondary)"
|
|
};
|
|
$[32] = t11;
|
|
} else {
|
|
t11 = $[32];
|
|
}
|
|
let t12;
|
|
if ($[33] !== currentInput.prompt) {
|
|
t12 = /* @__PURE__ */ _jsxs20("span", {
|
|
style: t11,
|
|
children: [currentInput.prompt, ":"]
|
|
});
|
|
$[33] = currentInput.prompt;
|
|
$[34] = t12;
|
|
} else {
|
|
t12 = $[34];
|
|
}
|
|
let t13;
|
|
if ($[35] !== currentInput.options || $[36] !== isSelectMode || $[37] !== provideInput) {
|
|
t13 = isSelectMode && currentInput.options && /* @__PURE__ */ _jsx35(CollectorSelectInput, {
|
|
options: currentInput.options,
|
|
onSelect: (v) => provideInput(v)
|
|
});
|
|
$[35] = currentInput.options;
|
|
$[36] = isSelectMode;
|
|
$[37] = provideInput;
|
|
$[38] = t13;
|
|
} else {
|
|
t13 = $[38];
|
|
}
|
|
let t14;
|
|
if ($[39] !== currentInput.default || $[40] !== currentInput.type || $[41] !== handleKeyDown || $[42] !== handleSubmit || $[43] !== textValue) {
|
|
t14 = (currentInput.type === "text" || currentInput.type === "number") && /* @__PURE__ */ _jsx35("form", {
|
|
onSubmit: handleSubmit,
|
|
className: "flex flex-1 items-center",
|
|
children: /* @__PURE__ */ _jsx35("input", {
|
|
ref: inputRef,
|
|
type: currentInput.type === "number" ? "number" : "text",
|
|
value: textValue,
|
|
onChange: (e_2) => setTextValue(e_2.target.value),
|
|
onKeyDown: handleKeyDown,
|
|
placeholder: currentInput.default !== void 0 ? `Default: ${currentInput.default}` : void 0,
|
|
className: "flex-1 bg-transparent outline-none",
|
|
style: {
|
|
color: "var(--cmd-input-text)"
|
|
},
|
|
autoComplete: "off",
|
|
autoCorrect: "off",
|
|
autoCapitalize: "off",
|
|
spellCheck: "false"
|
|
})
|
|
});
|
|
$[39] = currentInput.default;
|
|
$[40] = currentInput.type;
|
|
$[41] = handleKeyDown;
|
|
$[42] = handleSubmit;
|
|
$[43] = textValue;
|
|
$[44] = t14;
|
|
} else {
|
|
t14 = $[44];
|
|
}
|
|
let t15;
|
|
if ($[45] !== currentInput.type || $[46] !== getWorldCenter || $[47] !== handleKeyDown || $[48] !== provideInput || $[49] !== textValue) {
|
|
t15 = currentInput.type === "point" && /* @__PURE__ */ _jsx35("form", {
|
|
onSubmit: (e_3) => {
|
|
e_3.preventDefault();
|
|
if (textValue.trim() === "") {
|
|
provideInput(getWorldCenter());
|
|
setTextValue("");
|
|
return;
|
|
}
|
|
const match = textValue.match(/^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/);
|
|
if (match) {
|
|
provideInput({
|
|
x: parseFloat(match[1]),
|
|
y: parseFloat(match[2])
|
|
});
|
|
setTextValue("");
|
|
}
|
|
},
|
|
className: "flex flex-1 items-center",
|
|
children: /* @__PURE__ */ _jsx35("input", {
|
|
ref: inputRef,
|
|
type: "text",
|
|
value: textValue,
|
|
onChange: (e_4) => setTextValue(e_4.target.value),
|
|
onKeyDown: handleKeyDown,
|
|
placeholder: "x, y or click on canvas (Enter for center)",
|
|
className: "flex-1 bg-transparent outline-none",
|
|
style: {
|
|
color: "var(--cmd-input-text)"
|
|
},
|
|
autoComplete: "off"
|
|
})
|
|
});
|
|
$[45] = currentInput.type;
|
|
$[46] = getWorldCenter;
|
|
$[47] = handleKeyDown;
|
|
$[48] = provideInput;
|
|
$[49] = textValue;
|
|
$[50] = t15;
|
|
} else {
|
|
t15 = $[50];
|
|
}
|
|
let t16;
|
|
if ($[51] !== currentInput.type || $[52] !== provideInput) {
|
|
t16 = currentInput.type === "boolean" && /* @__PURE__ */ _jsx35(CollectorBooleanInput, {
|
|
onSelect: (v_0) => provideInput(v_0)
|
|
});
|
|
$[51] = currentInput.type;
|
|
$[52] = provideInput;
|
|
$[53] = t16;
|
|
} else {
|
|
t16 = $[53];
|
|
}
|
|
let t17;
|
|
if ($[54] !== currentInput.required || $[55] !== skipInput) {
|
|
t17 = currentInput.required === false && /* @__PURE__ */ _jsx35("button", {
|
|
onClick: () => skipInput(),
|
|
className: "rounded px-2 py-0.5 text-xs transition-colors",
|
|
style: {
|
|
color: "var(--hud-text-secondary)",
|
|
opacity: 0.8
|
|
},
|
|
onMouseEnter: _temp17,
|
|
onMouseLeave: _temp28,
|
|
children: "Skip (Esc)"
|
|
});
|
|
$[54] = currentInput.required;
|
|
$[55] = skipInput;
|
|
$[56] = t17;
|
|
} else {
|
|
t17 = $[56];
|
|
}
|
|
let t18;
|
|
if ($[57] !== goBack || $[58] !== progress) {
|
|
t18 = progress && progress.current > 1 && /* @__PURE__ */ _jsx35("button", {
|
|
onClick: () => goBack(),
|
|
className: "rounded px-2 py-0.5 text-xs transition-colors",
|
|
style: {
|
|
color: "var(--hud-text-secondary)",
|
|
opacity: 0.8
|
|
},
|
|
onMouseEnter: _temp34,
|
|
onMouseLeave: _temp43,
|
|
children: "Back"
|
|
});
|
|
$[57] = goBack;
|
|
$[58] = progress;
|
|
$[59] = t18;
|
|
} else {
|
|
t18 = $[59];
|
|
}
|
|
let t19;
|
|
if ($[60] !== t12 || $[61] !== t13 || $[62] !== t14 || $[63] !== t15 || $[64] !== t16 || $[65] !== t17 || $[66] !== t18) {
|
|
t19 = /* @__PURE__ */ _jsxs20("div", {
|
|
className: "flex items-center gap-2",
|
|
children: [t12, t13, t14, t15, t16, t17, t18]
|
|
});
|
|
$[60] = t12;
|
|
$[61] = t13;
|
|
$[62] = t14;
|
|
$[63] = t15;
|
|
$[64] = t16;
|
|
$[65] = t17;
|
|
$[66] = t18;
|
|
$[67] = t19;
|
|
} else {
|
|
t19 = $[67];
|
|
}
|
|
let t20;
|
|
if ($[68] !== t10 || $[69] !== t19) {
|
|
t20 = /* @__PURE__ */ _jsxs20("div", {
|
|
className: "flex flex-1 flex-col gap-1",
|
|
children: [t10, t19]
|
|
});
|
|
$[68] = t10;
|
|
$[69] = t19;
|
|
$[70] = t20;
|
|
} else {
|
|
t20 = $[70];
|
|
}
|
|
return t20;
|
|
}
|
|
function _temp43(e_8) {
|
|
e_8.currentTarget.style.backgroundColor = "transparent";
|
|
e_8.currentTarget.style.color = "var(--hud-text-secondary)";
|
|
}
|
|
function _temp34(e_7) {
|
|
e_7.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)";
|
|
e_7.currentTarget.style.color = "var(--hud-text)";
|
|
}
|
|
function _temp28(e_6) {
|
|
e_6.currentTarget.style.backgroundColor = "transparent";
|
|
e_6.currentTarget.style.color = "var(--hud-text-secondary)";
|
|
}
|
|
function _temp17(e_5) {
|
|
e_5.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)";
|
|
e_5.currentTarget.style.color = "var(--hud-text)";
|
|
}
|
|
|
|
// src/components/CommandLine/CommandLine.tsx
|
|
import { jsx as _jsx36, jsxs as _jsxs21 } from "react/jsx-runtime";
|
|
var MIN_HEIGHT = 60;
|
|
var MAX_HEIGHT = 400;
|
|
var DEFAULT_HEIGHT = 200;
|
|
function CommandLine(t0) {
|
|
const $ = _c65(58);
|
|
const {
|
|
isModalOpen
|
|
} = t0;
|
|
const visible = useAtomValue42(commandLineVisibleAtom);
|
|
const [state, setState] = useAtom7(commandLineStateAtom);
|
|
useSetAtom31(closeCommandLineAtom);
|
|
const progress = useAtomValue42(commandProgressAtom);
|
|
const selectedIndex = useAtomValue42(selectedSuggestionIndexAtom);
|
|
const setInputMode = useSetAtom31(inputModeAtom2);
|
|
const containerRef = useRef28(null);
|
|
const [height, setHeight] = useState8(DEFAULT_HEIGHT);
|
|
const [isDragging, setIsDragging] = useState8(false);
|
|
const dragStartY = useRef28(0);
|
|
const dragStartHeight = useRef28(DEFAULT_HEIGHT);
|
|
let t1;
|
|
let t2;
|
|
if ($[0] !== isModalOpen || $[1] !== setInputMode || $[2] !== setState || $[3] !== state.phase || $[4] !== visible) {
|
|
t1 = () => {
|
|
if (!visible) {
|
|
return;
|
|
}
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === "Escape") {
|
|
if (isModalOpen?.()) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (state.phase === "collecting" || state.phase === "executing" || state.phase === "error") {
|
|
setState({
|
|
phase: "searching",
|
|
query: "",
|
|
suggestions: commandRegistry.all()
|
|
});
|
|
setInputMode({
|
|
type: "normal"
|
|
});
|
|
}
|
|
}
|
|
};
|
|
window.addEventListener("keydown", handleKeyDown, {
|
|
capture: true
|
|
});
|
|
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
capture: true
|
|
});
|
|
};
|
|
t2 = [visible, state.phase, setState, setInputMode, isModalOpen];
|
|
$[0] = isModalOpen;
|
|
$[1] = setInputMode;
|
|
$[2] = setState;
|
|
$[3] = state.phase;
|
|
$[4] = visible;
|
|
$[5] = t1;
|
|
$[6] = t2;
|
|
} else {
|
|
t1 = $[5];
|
|
t2 = $[6];
|
|
}
|
|
useEffect21(t1, t2);
|
|
let t3;
|
|
if ($[7] !== height) {
|
|
t3 = (e_0) => {
|
|
e_0.preventDefault();
|
|
setIsDragging(true);
|
|
dragStartY.current = e_0.clientY;
|
|
dragStartHeight.current = height;
|
|
};
|
|
$[7] = height;
|
|
$[8] = t3;
|
|
} else {
|
|
t3 = $[8];
|
|
}
|
|
const handleDragStart = t3;
|
|
let t4;
|
|
let t5;
|
|
if ($[9] !== isDragging) {
|
|
t4 = () => {
|
|
if (!isDragging) {
|
|
return;
|
|
}
|
|
const handleMouseMove = (e_1) => {
|
|
const deltaY = dragStartY.current - e_1.clientY;
|
|
const newHeight = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, dragStartHeight.current + deltaY));
|
|
setHeight(newHeight);
|
|
};
|
|
const handleMouseUp = () => {
|
|
setIsDragging(false);
|
|
};
|
|
window.addEventListener("mousemove", handleMouseMove);
|
|
window.addEventListener("mouseup", handleMouseUp);
|
|
return () => {
|
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
};
|
|
};
|
|
t5 = [isDragging];
|
|
$[9] = isDragging;
|
|
$[10] = t4;
|
|
$[11] = t5;
|
|
} else {
|
|
t4 = $[10];
|
|
t5 = $[11];
|
|
}
|
|
useEffect21(t4, t5);
|
|
if (!visible) {
|
|
return null;
|
|
}
|
|
typeof window !== "undefined" ? Math.min(400, window.innerHeight * 0.5) : 300;
|
|
const t6 = `${height}px`;
|
|
let t7;
|
|
if ($[12] !== t6) {
|
|
t7 = {
|
|
height: t6,
|
|
maxHeight: "50vh",
|
|
backgroundColor: "var(--cmd-bg)"
|
|
};
|
|
$[12] = t6;
|
|
$[13] = t7;
|
|
} else {
|
|
t7 = $[13];
|
|
}
|
|
const t8 = isDragging ? "var(--cmd-resize-hover)" : "var(--cmd-resize-bg)";
|
|
let t9;
|
|
if ($[14] !== t8) {
|
|
t9 = {
|
|
borderColor: "var(--cmd-border)",
|
|
backgroundColor: t8
|
|
};
|
|
$[14] = t8;
|
|
$[15] = t9;
|
|
} else {
|
|
t9 = $[15];
|
|
}
|
|
let t10;
|
|
let t11;
|
|
if ($[16] !== isDragging) {
|
|
t10 = (e_2) => !isDragging && (e_2.currentTarget.style.backgroundColor = "var(--cmd-resize-hover)");
|
|
t11 = (e_3) => !isDragging && (e_3.currentTarget.style.backgroundColor = "var(--cmd-resize-bg)");
|
|
$[16] = isDragging;
|
|
$[17] = t10;
|
|
$[18] = t11;
|
|
} else {
|
|
t10 = $[17];
|
|
t11 = $[18];
|
|
}
|
|
let t12;
|
|
if ($[19] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t12 = /* @__PURE__ */ _jsx36("div", {
|
|
className: "h-1 w-12 rounded-full opacity-50",
|
|
style: {
|
|
backgroundColor: "var(--hud-icon)"
|
|
}
|
|
});
|
|
$[19] = t12;
|
|
} else {
|
|
t12 = $[19];
|
|
}
|
|
let t13;
|
|
if ($[20] !== handleDragStart || $[21] !== t10 || $[22] !== t11 || $[23] !== t9) {
|
|
t13 = /* @__PURE__ */ _jsx36("div", {
|
|
onMouseDown: handleDragStart,
|
|
className: "flex h-3 w-full shrink-0 cursor-ns-resize items-center justify-center border-t transition-colors",
|
|
style: t9,
|
|
onMouseEnter: t10,
|
|
onMouseLeave: t11,
|
|
children: t12
|
|
});
|
|
$[20] = handleDragStart;
|
|
$[21] = t10;
|
|
$[22] = t11;
|
|
$[23] = t9;
|
|
$[24] = t13;
|
|
} else {
|
|
t13 = $[24];
|
|
}
|
|
let t14;
|
|
if ($[25] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t14 = /* @__PURE__ */ _jsx36("span", {
|
|
className: "mr-2 text-lg font-bold select-none",
|
|
style: {
|
|
color: "var(--badge-info-text)"
|
|
},
|
|
children: ">"
|
|
});
|
|
$[25] = t14;
|
|
} else {
|
|
t14 = $[25];
|
|
}
|
|
let t15;
|
|
if ($[26] !== state.phase) {
|
|
t15 = (state.phase === "idle" || state.phase === "searching") && /* @__PURE__ */ _jsx36(CommandSearch, {});
|
|
$[26] = state.phase;
|
|
$[27] = t15;
|
|
} else {
|
|
t15 = $[27];
|
|
}
|
|
let t16;
|
|
if ($[28] !== state.phase) {
|
|
t16 = state.phase === "collecting" && /* @__PURE__ */ _jsx36(CommandInputCollector, {});
|
|
$[28] = state.phase;
|
|
$[29] = t16;
|
|
} else {
|
|
t16 = $[29];
|
|
}
|
|
let t17;
|
|
if ($[30] !== state.command || $[31] !== state.phase) {
|
|
t17 = state.phase === "executing" && /* @__PURE__ */ _jsxs21("div", {
|
|
className: "flex items-center gap-2",
|
|
style: {
|
|
color: "var(--badge-warning-text)"
|
|
},
|
|
children: [/* @__PURE__ */ _jsxs21("svg", {
|
|
className: "h-5 w-5 animate-spin",
|
|
viewBox: "0 0 24 24",
|
|
children: [/* @__PURE__ */ _jsx36("circle", {
|
|
className: "opacity-25",
|
|
cx: "12",
|
|
cy: "12",
|
|
r: "10",
|
|
stroke: "currentColor",
|
|
strokeWidth: "4",
|
|
fill: "none"
|
|
}), /* @__PURE__ */ _jsx36("path", {
|
|
className: "opacity-75",
|
|
fill: "currentColor",
|
|
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
})]
|
|
}), /* @__PURE__ */ _jsxs21("span", {
|
|
className: "text-base",
|
|
children: ["Executing ", state.command.name, "..."]
|
|
})]
|
|
});
|
|
$[30] = state.command;
|
|
$[31] = state.phase;
|
|
$[32] = t17;
|
|
} else {
|
|
t17 = $[32];
|
|
}
|
|
let t18;
|
|
if ($[33] !== setState || $[34] !== state.message || $[35] !== state.phase) {
|
|
t18 = state.phase === "error" && /* @__PURE__ */ _jsxs21("div", {
|
|
className: "flex items-center gap-2",
|
|
style: {
|
|
color: "var(--badge-error-text)"
|
|
},
|
|
children: [/* @__PURE__ */ _jsxs21("svg", {
|
|
className: "h-5 w-5",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
children: [/* @__PURE__ */ _jsx36("circle", {
|
|
cx: "12",
|
|
cy: "12",
|
|
r: "10"
|
|
}), /* @__PURE__ */ _jsx36("line", {
|
|
x1: "15",
|
|
y1: "9",
|
|
x2: "9",
|
|
y2: "15"
|
|
}), /* @__PURE__ */ _jsx36("line", {
|
|
x1: "9",
|
|
y1: "9",
|
|
x2: "15",
|
|
y2: "15"
|
|
})]
|
|
}), /* @__PURE__ */ _jsx36("span", {
|
|
className: "text-base",
|
|
children: state.message
|
|
}), /* @__PURE__ */ _jsx36("button", {
|
|
onClick: () => setState({
|
|
phase: "idle"
|
|
}),
|
|
className: "ml-2 rounded px-2 py-1 text-sm transition-colors",
|
|
style: {
|
|
backgroundColor: "var(--list-item-bg-hover)",
|
|
color: "var(--hud-text-secondary)"
|
|
},
|
|
onMouseEnter: _temp18,
|
|
onMouseLeave: _temp29,
|
|
children: "Try again"
|
|
})]
|
|
});
|
|
$[33] = setState;
|
|
$[34] = state.message;
|
|
$[35] = state.phase;
|
|
$[36] = t18;
|
|
} else {
|
|
t18 = $[36];
|
|
}
|
|
let t19;
|
|
if ($[37] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t19 = /* @__PURE__ */ _jsx36("div", {
|
|
className: "flex-1"
|
|
});
|
|
$[37] = t19;
|
|
} else {
|
|
t19 = $[37];
|
|
}
|
|
let t20;
|
|
if ($[38] !== progress) {
|
|
t20 = progress && /* @__PURE__ */ _jsxs21("span", {
|
|
className: "mr-4 rounded px-2 py-1 text-sm",
|
|
style: {
|
|
backgroundColor: "var(--list-item-bg-hover)",
|
|
color: "var(--hud-text-secondary)"
|
|
},
|
|
children: ["Step ", progress.current, "/", progress.total]
|
|
});
|
|
$[38] = progress;
|
|
$[39] = t20;
|
|
} else {
|
|
t20 = $[39];
|
|
}
|
|
let t21;
|
|
if ($[40] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t21 = /* @__PURE__ */ _jsx36("span", {
|
|
className: "text-sm select-none",
|
|
style: {
|
|
color: "var(--hud-text-secondary)",
|
|
opacity: 0.6
|
|
},
|
|
children: "/ to close \u2022 ESC to cancel"
|
|
});
|
|
$[40] = t21;
|
|
} else {
|
|
t21 = $[40];
|
|
}
|
|
let t22;
|
|
if ($[41] !== t15 || $[42] !== t16 || $[43] !== t17 || $[44] !== t18 || $[45] !== t20) {
|
|
t22 = /* @__PURE__ */ _jsxs21("div", {
|
|
className: "flex items-center px-4 py-3 font-mono text-sm",
|
|
children: [t14, t15, t16, t17, t18, t19, t20, t21]
|
|
});
|
|
$[41] = t15;
|
|
$[42] = t16;
|
|
$[43] = t17;
|
|
$[44] = t18;
|
|
$[45] = t20;
|
|
$[46] = t22;
|
|
} else {
|
|
t22 = $[46];
|
|
}
|
|
let t23;
|
|
if ($[47] !== selectedIndex || $[48] !== state.phase || $[49] !== state.suggestions) {
|
|
t23 = state.phase === "searching" && state.suggestions.length > 0 && /* @__PURE__ */ _jsx36("div", {
|
|
className: "min-h-0 shrink overflow-y-auto border-t",
|
|
style: {
|
|
maxHeight: "40vh",
|
|
borderColor: "var(--cmd-border)"
|
|
},
|
|
children: state.suggestions.map((cmd, index) => /* @__PURE__ */ _jsx36(CommandSuggestionItem, {
|
|
command: cmd,
|
|
isHighlighted: index === selectedIndex
|
|
}, cmd.name))
|
|
});
|
|
$[47] = selectedIndex;
|
|
$[48] = state.phase;
|
|
$[49] = state.suggestions;
|
|
$[50] = t23;
|
|
} else {
|
|
t23 = $[50];
|
|
}
|
|
let t24;
|
|
if ($[51] !== t22 || $[52] !== t23) {
|
|
t24 = /* @__PURE__ */ _jsxs21("div", {
|
|
className: "flex min-h-0 flex-1 flex-col overflow-hidden",
|
|
children: [t22, t23]
|
|
});
|
|
$[51] = t22;
|
|
$[52] = t23;
|
|
$[53] = t24;
|
|
} else {
|
|
t24 = $[53];
|
|
}
|
|
let t25;
|
|
if ($[54] !== t13 || $[55] !== t24 || $[56] !== t7) {
|
|
t25 = /* @__PURE__ */ _jsxs21("div", {
|
|
ref: containerRef,
|
|
className: "fixed right-0 bottom-0 left-0 z-50 flex flex-col shadow-2xl",
|
|
style: t7,
|
|
children: [t13, t24]
|
|
});
|
|
$[54] = t13;
|
|
$[55] = t24;
|
|
$[56] = t7;
|
|
$[57] = t25;
|
|
} else {
|
|
t25 = $[57];
|
|
}
|
|
return t25;
|
|
}
|
|
function _temp29(e_5) {
|
|
return e_5.currentTarget.style.color = "var(--hud-text-secondary)";
|
|
}
|
|
function _temp18(e_4) {
|
|
return e_4.currentTarget.style.color = "var(--hud-text)";
|
|
}
|
|
function CommandSuggestionItem(t0) {
|
|
const $ = _c65(24);
|
|
const {
|
|
command,
|
|
isHighlighted
|
|
} = t0;
|
|
const selectCommand = useSetAtom31(selectCommandAtom);
|
|
let t1;
|
|
if ($[0] !== command || $[1] !== selectCommand) {
|
|
t1 = () => selectCommand(command);
|
|
$[0] = command;
|
|
$[1] = selectCommand;
|
|
$[2] = t1;
|
|
} else {
|
|
t1 = $[2];
|
|
}
|
|
const t2 = isHighlighted ? "var(--list-item-bg-hover)" : "transparent";
|
|
let t3;
|
|
if ($[3] !== t2) {
|
|
t3 = {
|
|
backgroundColor: t2
|
|
};
|
|
$[3] = t2;
|
|
$[4] = t3;
|
|
} else {
|
|
t3 = $[4];
|
|
}
|
|
let t4;
|
|
let t5;
|
|
if ($[5] !== isHighlighted) {
|
|
t4 = (e) => !isHighlighted && (e.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)");
|
|
t5 = (e_0) => !isHighlighted && (e_0.currentTarget.style.backgroundColor = "transparent");
|
|
$[5] = isHighlighted;
|
|
$[6] = t4;
|
|
$[7] = t5;
|
|
} else {
|
|
t4 = $[6];
|
|
t5 = $[7];
|
|
}
|
|
let t6;
|
|
if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t6 = {
|
|
color: "var(--hud-text)"
|
|
};
|
|
$[8] = t6;
|
|
} else {
|
|
t6 = $[8];
|
|
}
|
|
let t7;
|
|
if ($[9] !== command.name) {
|
|
t7 = /* @__PURE__ */ _jsx36("span", {
|
|
className: "font-mono",
|
|
style: t6,
|
|
children: command.name
|
|
});
|
|
$[9] = command.name;
|
|
$[10] = t7;
|
|
} else {
|
|
t7 = $[10];
|
|
}
|
|
let t8;
|
|
if ($[11] !== command.aliases) {
|
|
t8 = command.aliases && command.aliases.length > 0 && /* @__PURE__ */ _jsxs21("span", {
|
|
className: "text-xs",
|
|
style: {
|
|
color: "var(--hud-text-secondary)",
|
|
opacity: 0.5
|
|
},
|
|
children: ["(", command.aliases.join(", "), ")"]
|
|
});
|
|
$[11] = command.aliases;
|
|
$[12] = t8;
|
|
} else {
|
|
t8 = $[12];
|
|
}
|
|
let t9;
|
|
if ($[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t9 = {
|
|
color: "var(--hud-text-secondary)"
|
|
};
|
|
$[13] = t9;
|
|
} else {
|
|
t9 = $[13];
|
|
}
|
|
let t10;
|
|
if ($[14] !== command.description) {
|
|
t10 = /* @__PURE__ */ _jsx36("span", {
|
|
className: "text-sm",
|
|
style: t9,
|
|
children: command.description
|
|
});
|
|
$[14] = command.description;
|
|
$[15] = t10;
|
|
} else {
|
|
t10 = $[15];
|
|
}
|
|
let t11;
|
|
if ($[16] !== t1 || $[17] !== t10 || $[18] !== t3 || $[19] !== t4 || $[20] !== t5 || $[21] !== t7 || $[22] !== t8) {
|
|
t11 = /* @__PURE__ */ _jsxs21("button", {
|
|
onClick: t1,
|
|
className: "flex w-full items-center gap-3 px-4 py-2 text-left transition-colors",
|
|
style: t3,
|
|
onMouseEnter: t4,
|
|
onMouseLeave: t5,
|
|
children: [t7, t8, t10]
|
|
});
|
|
$[16] = t1;
|
|
$[17] = t10;
|
|
$[18] = t3;
|
|
$[19] = t4;
|
|
$[20] = t5;
|
|
$[21] = t7;
|
|
$[22] = t8;
|
|
$[23] = t11;
|
|
} else {
|
|
t11 = $[23];
|
|
}
|
|
return t11;
|
|
}
|
|
|
|
// src/components/CommandFeedbackOverlay.tsx
|
|
import { c as _c66 } from "react/compiler-runtime";
|
|
import { useAtomValue as useAtomValue43 } from "jotai";
|
|
init_viewport_store();
|
|
import { jsx as _jsx37, jsxs as _jsxs22 } from "react/jsx-runtime";
|
|
function CommandFeedbackOverlay() {
|
|
const $ = _c66(27);
|
|
const inputMode = useAtomValue43(inputModeAtom2);
|
|
const feedback = useAtomValue43(commandFeedbackAtom);
|
|
const currentInput = useAtomValue43(currentInputAtom);
|
|
const zoom = useAtomValue43(zoomAtom);
|
|
const pan = useAtomValue43(panAtom);
|
|
if (inputMode.type === "normal") {
|
|
return null;
|
|
}
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = {};
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
let t1;
|
|
if ($[1] !== feedback || $[2] !== inputMode.type || $[3] !== pan || $[4] !== zoom) {
|
|
t1 = (inputMode.type === "pickPoint" || feedback?.crosshair) && feedback?.cursorWorldPos && /* @__PURE__ */ _jsx37(Crosshair, {
|
|
position: feedback.cursorWorldPos,
|
|
zoom,
|
|
pan
|
|
});
|
|
$[1] = feedback;
|
|
$[2] = inputMode.type;
|
|
$[3] = pan;
|
|
$[4] = zoom;
|
|
$[5] = t1;
|
|
} else {
|
|
t1 = $[5];
|
|
}
|
|
let t2;
|
|
if ($[6] !== feedback || $[7] !== pan || $[8] !== zoom) {
|
|
t2 = feedback?.hoveredNodeId && /* @__PURE__ */ _jsx37(NodeHighlight, {
|
|
nodeId: feedback.hoveredNodeId,
|
|
zoom,
|
|
pan
|
|
});
|
|
$[6] = feedback;
|
|
$[7] = pan;
|
|
$[8] = zoom;
|
|
$[9] = t2;
|
|
} else {
|
|
t2 = $[9];
|
|
}
|
|
let t3;
|
|
if ($[10] !== feedback || $[11] !== pan || $[12] !== zoom) {
|
|
t3 = feedback?.previewEdge && /* @__PURE__ */ _jsx37(PreviewEdge, {
|
|
edge: feedback.previewEdge,
|
|
zoom,
|
|
pan
|
|
});
|
|
$[10] = feedback;
|
|
$[11] = pan;
|
|
$[12] = zoom;
|
|
$[13] = t3;
|
|
} else {
|
|
t3 = $[13];
|
|
}
|
|
let t4;
|
|
if ($[14] !== feedback || $[15] !== pan || $[16] !== zoom) {
|
|
t4 = feedback?.ghostNode && /* @__PURE__ */ _jsx37(GhostNode, {
|
|
ghost: feedback.ghostNode,
|
|
zoom,
|
|
pan
|
|
});
|
|
$[14] = feedback;
|
|
$[15] = pan;
|
|
$[16] = zoom;
|
|
$[17] = t4;
|
|
} else {
|
|
t4 = $[17];
|
|
}
|
|
const t5 = currentInput?.prompt;
|
|
let t6;
|
|
if ($[18] !== inputMode || $[19] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx37(ModeIndicator, {
|
|
inputMode,
|
|
prompt: t5
|
|
});
|
|
$[18] = inputMode;
|
|
$[19] = t5;
|
|
$[20] = t6;
|
|
} else {
|
|
t6 = $[20];
|
|
}
|
|
let t7;
|
|
if ($[21] !== t1 || $[22] !== t2 || $[23] !== t3 || $[24] !== t4 || $[25] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsxs22("div", {
|
|
className: "pointer-events-none fixed inset-0 z-50",
|
|
style: t0,
|
|
children: [t1, t2, t3, t4, t6]
|
|
});
|
|
$[21] = t1;
|
|
$[22] = t2;
|
|
$[23] = t3;
|
|
$[24] = t4;
|
|
$[25] = t6;
|
|
$[26] = t7;
|
|
} else {
|
|
t7 = $[26];
|
|
}
|
|
return t7;
|
|
}
|
|
function Crosshair(t0) {
|
|
const $ = _c66(10);
|
|
const {
|
|
position,
|
|
zoom,
|
|
pan
|
|
} = t0;
|
|
const screenX = position.x * zoom + pan.x;
|
|
const screenY = position.y * zoom + pan.y;
|
|
const t1 = screenX - 20;
|
|
const t2 = screenY - 20;
|
|
let t3;
|
|
if ($[0] !== t1 || $[1] !== t2) {
|
|
t3 = {
|
|
left: t1,
|
|
top: t2,
|
|
width: 40,
|
|
height: 40
|
|
};
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
$[2] = t3;
|
|
} else {
|
|
t3 = $[2];
|
|
}
|
|
let t4;
|
|
let t5;
|
|
let t6;
|
|
let t7;
|
|
let t8;
|
|
if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = /* @__PURE__ */ _jsx37("line", {
|
|
x1: "0",
|
|
y1: "20",
|
|
x2: "15",
|
|
y2: "20",
|
|
stroke: "var(--list-item-border-active)",
|
|
strokeWidth: "2"
|
|
});
|
|
t5 = /* @__PURE__ */ _jsx37("line", {
|
|
x1: "25",
|
|
y1: "20",
|
|
x2: "40",
|
|
y2: "20",
|
|
stroke: "var(--list-item-border-active)",
|
|
strokeWidth: "2"
|
|
});
|
|
t6 = /* @__PURE__ */ _jsx37("line", {
|
|
x1: "20",
|
|
y1: "0",
|
|
x2: "20",
|
|
y2: "15",
|
|
stroke: "var(--list-item-border-active)",
|
|
strokeWidth: "2"
|
|
});
|
|
t7 = /* @__PURE__ */ _jsx37("line", {
|
|
x1: "20",
|
|
y1: "25",
|
|
x2: "20",
|
|
y2: "40",
|
|
stroke: "var(--list-item-border-active)",
|
|
strokeWidth: "2"
|
|
});
|
|
t8 = /* @__PURE__ */ _jsx37("circle", {
|
|
cx: "20",
|
|
cy: "20",
|
|
r: "3",
|
|
fill: "var(--list-item-border-active)"
|
|
});
|
|
$[3] = t4;
|
|
$[4] = t5;
|
|
$[5] = t6;
|
|
$[6] = t7;
|
|
$[7] = t8;
|
|
} else {
|
|
t4 = $[3];
|
|
t5 = $[4];
|
|
t6 = $[5];
|
|
t7 = $[6];
|
|
t8 = $[7];
|
|
}
|
|
let t9;
|
|
if ($[8] !== t3) {
|
|
t9 = /* @__PURE__ */ _jsxs22("svg", {
|
|
className: "pointer-events-none absolute",
|
|
style: t3,
|
|
children: [t4, t5, t6, t7, t8]
|
|
});
|
|
$[8] = t3;
|
|
$[9] = t9;
|
|
} else {
|
|
t9 = $[9];
|
|
}
|
|
return t9;
|
|
}
|
|
function NodeHighlight({
|
|
nodeId,
|
|
zoom,
|
|
pan
|
|
}) {
|
|
return null;
|
|
}
|
|
function PreviewEdge(t0) {
|
|
const $ = _c66(15);
|
|
const {
|
|
edge,
|
|
zoom,
|
|
pan
|
|
} = t0;
|
|
const t1 = edge.from.x * zoom + pan.x;
|
|
const t2 = edge.from.y * zoom + pan.y;
|
|
let t3;
|
|
if ($[0] !== t1 || $[1] !== t2) {
|
|
t3 = {
|
|
x: t1,
|
|
y: t2
|
|
};
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
$[2] = t3;
|
|
} else {
|
|
t3 = $[2];
|
|
}
|
|
const fromScreen = t3;
|
|
const t4 = edge.to.x * zoom + pan.x;
|
|
const t5 = edge.to.y * zoom + pan.y;
|
|
let t6;
|
|
if ($[3] !== t4 || $[4] !== t5) {
|
|
t6 = {
|
|
x: t4,
|
|
y: t5
|
|
};
|
|
$[3] = t4;
|
|
$[4] = t5;
|
|
$[5] = t6;
|
|
} else {
|
|
t6 = $[5];
|
|
}
|
|
const toScreen = t6;
|
|
let t7;
|
|
if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t7 = {
|
|
width: "100%",
|
|
height: "100%"
|
|
};
|
|
$[6] = t7;
|
|
} else {
|
|
t7 = $[6];
|
|
}
|
|
let t8;
|
|
if ($[7] !== fromScreen.x || $[8] !== fromScreen.y || $[9] !== toScreen.x || $[10] !== toScreen.y) {
|
|
t8 = /* @__PURE__ */ _jsx37("line", {
|
|
x1: fromScreen.x,
|
|
y1: fromScreen.y,
|
|
x2: toScreen.x,
|
|
y2: toScreen.y,
|
|
stroke: "var(--list-item-border-active)",
|
|
strokeWidth: "2",
|
|
strokeDasharray: "5,5",
|
|
markerEnd: "url(#arrowhead)"
|
|
});
|
|
$[7] = fromScreen.x;
|
|
$[8] = fromScreen.y;
|
|
$[9] = toScreen.x;
|
|
$[10] = toScreen.y;
|
|
$[11] = t8;
|
|
} else {
|
|
t8 = $[11];
|
|
}
|
|
let t9;
|
|
if ($[12] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t9 = /* @__PURE__ */ _jsx37("defs", {
|
|
children: /* @__PURE__ */ _jsx37("marker", {
|
|
id: "arrowhead",
|
|
markerWidth: "10",
|
|
markerHeight: "7",
|
|
refX: "9",
|
|
refY: "3.5",
|
|
orient: "auto",
|
|
children: /* @__PURE__ */ _jsx37("polygon", {
|
|
points: "0 0, 10 3.5, 0 7",
|
|
fill: "var(--list-item-border-active)"
|
|
})
|
|
})
|
|
});
|
|
$[12] = t9;
|
|
} else {
|
|
t9 = $[12];
|
|
}
|
|
let t10;
|
|
if ($[13] !== t8) {
|
|
t10 = /* @__PURE__ */ _jsxs22("svg", {
|
|
className: "pointer-events-none absolute inset-0",
|
|
style: t7,
|
|
children: [t8, t9]
|
|
});
|
|
$[13] = t8;
|
|
$[14] = t10;
|
|
} else {
|
|
t10 = $[14];
|
|
}
|
|
return t10;
|
|
}
|
|
function GhostNode(t0) {
|
|
const $ = _c66(7);
|
|
const {
|
|
ghost,
|
|
zoom,
|
|
pan
|
|
} = t0;
|
|
const t1 = ghost.position.x * zoom + pan.x;
|
|
const t2 = ghost.position.y * zoom + pan.y;
|
|
let t3;
|
|
if ($[0] !== t1 || $[1] !== t2) {
|
|
t3 = {
|
|
x: t1,
|
|
y: t2
|
|
};
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
$[2] = t3;
|
|
} else {
|
|
t3 = $[2];
|
|
}
|
|
const screenPos = t3;
|
|
const t4 = screenPos.x - 50;
|
|
const t5 = screenPos.y - 50;
|
|
let t6;
|
|
if ($[3] !== ghost.opacity || $[4] !== t4 || $[5] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx37("div", {
|
|
className: "absolute rounded-lg border-2 border-dashed",
|
|
style: {
|
|
left: t4,
|
|
top: t5,
|
|
width: 100,
|
|
height: 100,
|
|
opacity: ghost.opacity,
|
|
borderColor: "var(--badge-info-border)",
|
|
backgroundColor: "var(--badge-info-bg)"
|
|
}
|
|
});
|
|
$[3] = ghost.opacity;
|
|
$[4] = t4;
|
|
$[5] = t5;
|
|
$[6] = t6;
|
|
} else {
|
|
t6 = $[6];
|
|
}
|
|
return t6;
|
|
}
|
|
function ModeIndicator(t0) {
|
|
const $ = _c66(7);
|
|
const {
|
|
inputMode,
|
|
prompt
|
|
} = t0;
|
|
const displayPrompt = prompt || inputMode.prompt || "Waiting for input...";
|
|
let modeLabel;
|
|
bb0: switch (inputMode.type) {
|
|
case "pickPoint": {
|
|
modeLabel = "Click to pick point";
|
|
break bb0;
|
|
}
|
|
case "pickNode": {
|
|
modeLabel = "Click a node to select";
|
|
break bb0;
|
|
}
|
|
case "pickNodes": {
|
|
modeLabel = "Click nodes to select (Shift+click for multiple)";
|
|
break bb0;
|
|
}
|
|
case "select": {
|
|
modeLabel = "Choose an option";
|
|
break bb0;
|
|
}
|
|
case "text": {
|
|
modeLabel = "Type your input";
|
|
break bb0;
|
|
}
|
|
default: {
|
|
modeLabel = inputMode.type;
|
|
}
|
|
}
|
|
let t1;
|
|
let t2;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
backgroundColor: "var(--modal-bg)",
|
|
borderColor: "var(--modal-border)",
|
|
color: "var(--modal-text-primary)",
|
|
opacity: 0.9
|
|
};
|
|
t2 = {
|
|
color: "var(--badge-info-text)"
|
|
};
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
} else {
|
|
t1 = $[0];
|
|
t2 = $[1];
|
|
}
|
|
let t3;
|
|
if ($[2] !== modeLabel) {
|
|
t3 = /* @__PURE__ */ _jsxs22("span", {
|
|
style: t2,
|
|
children: [modeLabel, ":"]
|
|
});
|
|
$[2] = modeLabel;
|
|
$[3] = t3;
|
|
} else {
|
|
t3 = $[3];
|
|
}
|
|
let t4;
|
|
if ($[4] !== displayPrompt || $[5] !== t3) {
|
|
t4 = /* @__PURE__ */ _jsxs22("div", {
|
|
className: "fixed bottom-16 left-1/2 -translate-x-1/2 rounded-lg border px-4 py-2 font-mono text-sm shadow-lg",
|
|
style: t1,
|
|
children: [t3, " ", displayPrompt]
|
|
});
|
|
$[4] = displayPrompt;
|
|
$[5] = t3;
|
|
$[6] = t4;
|
|
} else {
|
|
t4 = $[6];
|
|
}
|
|
return t4;
|
|
}
|
|
|
|
// src/components/NodePorts.tsx
|
|
init_port_types();
|
|
import { c as _c69 } from "react/compiler-runtime";
|
|
import React39 from "react";
|
|
|
|
// src/components/PortBar.tsx
|
|
init_port_types();
|
|
init_graph_store();
|
|
import { c as _c68 } from "react/compiler-runtime";
|
|
import React38, { useState as useState9 } from "react";
|
|
import { useSetAtom as useSetAtom32 } from "jotai";
|
|
|
|
// src/components/PortHandle.tsx
|
|
import { c as _c67 } from "react/compiler-runtime";
|
|
import React37 from "react";
|
|
import { jsx as _jsx38, jsxs as _jsxs23 } from "react/jsx-runtime";
|
|
var PORT_ROW_HEIGHT = 24;
|
|
var TRANSITION_DURATION = "150ms";
|
|
function PortHandle(t0) {
|
|
const $ = _c67(38);
|
|
const {
|
|
port,
|
|
side,
|
|
isHovered,
|
|
showExpanded,
|
|
isCompatible,
|
|
isDragTarget,
|
|
onClick,
|
|
onPointerDown,
|
|
onMouseEnter,
|
|
onMouseLeave
|
|
} = t0;
|
|
const canvasStyles = useCanvasStyle();
|
|
const t1 = isDragTarget && isCompatible ? port.id : void 0;
|
|
let t2;
|
|
if ($[0] !== onClick || $[1] !== port) {
|
|
t2 = () => onClick(port.id, port);
|
|
$[0] = onClick;
|
|
$[1] = port;
|
|
$[2] = t2;
|
|
} else {
|
|
t2 = $[2];
|
|
}
|
|
let t3;
|
|
if ($[3] !== onPointerDown || $[4] !== port) {
|
|
t3 = (e) => onPointerDown(port.id, port, e);
|
|
$[3] = onPointerDown;
|
|
$[4] = port;
|
|
$[5] = t3;
|
|
} else {
|
|
t3 = $[5];
|
|
}
|
|
let t4;
|
|
if ($[6] !== onMouseEnter || $[7] !== port) {
|
|
t4 = () => onMouseEnter(port.id, port);
|
|
$[6] = onMouseEnter;
|
|
$[7] = port;
|
|
$[8] = t4;
|
|
} else {
|
|
t4 = $[8];
|
|
}
|
|
const t5 = side === "top" || side === "bottom" ? `${PORT_ROW_HEIGHT}px` : void 0;
|
|
const t6 = isCompatible ? "pointer" : "not-allowed";
|
|
const t7 = isHovered && isCompatible ? canvasStyles.nodes.selectedBorderColor : isDragTarget && !isCompatible ? "rgba(255, 0, 0, 0.1)" : "transparent";
|
|
const t8 = isDragTarget && !isCompatible ? 0.5 : 1;
|
|
let t9;
|
|
if ($[9] !== t5 || $[10] !== t6 || $[11] !== t7 || $[12] !== t8) {
|
|
t9 = {
|
|
flex: 1,
|
|
minHeight: `${PORT_ROW_HEIGHT}px`,
|
|
minWidth: t5,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
cursor: t6,
|
|
backgroundColor: t7,
|
|
transition: `background-color ${TRANSITION_DURATION}`,
|
|
opacity: t8
|
|
};
|
|
$[9] = t5;
|
|
$[10] = t6;
|
|
$[11] = t7;
|
|
$[12] = t8;
|
|
$[13] = t9;
|
|
} else {
|
|
t9 = $[13];
|
|
}
|
|
const t10 = port.color || canvasStyles.nodes.selectedBorderColor;
|
|
const t11 = isHovered ? "scale(1.5)" : isDragTarget && isCompatible ? "scale(1.3)" : "scale(1)";
|
|
const t12 = isHovered && isDragTarget && isCompatible ? `0 0 8px ${port.color || canvasStyles.nodes.selectedBorderColor}` : "none";
|
|
let t13;
|
|
if ($[14] !== t10 || $[15] !== t11 || $[16] !== t12) {
|
|
t13 = /* @__PURE__ */ _jsx38("div", {
|
|
style: {
|
|
width: "6px",
|
|
height: "6px",
|
|
borderRadius: "50%",
|
|
backgroundColor: t10,
|
|
transition: `transform ${TRANSITION_DURATION}, box-shadow ${TRANSITION_DURATION}`,
|
|
transform: t11,
|
|
boxShadow: t12
|
|
}
|
|
});
|
|
$[14] = t10;
|
|
$[15] = t11;
|
|
$[16] = t12;
|
|
$[17] = t13;
|
|
} else {
|
|
t13 = $[17];
|
|
}
|
|
let t14;
|
|
if ($[18] !== canvasStyles || $[19] !== isCompatible || $[20] !== isDragTarget || $[21] !== isHovered || $[22] !== port.id || $[23] !== port.label || $[24] !== showExpanded || $[25] !== side) {
|
|
t14 = showExpanded && /* @__PURE__ */ _jsx38("span", {
|
|
style: {
|
|
marginLeft: side === "left" || side === "right" ? "4px" : 0,
|
|
marginTop: side === "top" || side === "bottom" ? "4px" : 0,
|
|
fontSize: "10px",
|
|
color: isHovered ? "#fff" : isCompatible ? canvasStyles.nodes.defaultBorderColor : "rgba(128, 128, 128, 0.5)",
|
|
whiteSpace: "nowrap",
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
textDecoration: isDragTarget && !isCompatible ? "line-through" : "none",
|
|
transition: `opacity ${TRANSITION_DURATION}`,
|
|
opacity: showExpanded ? 1 : 0
|
|
},
|
|
children: port.label || port.id
|
|
});
|
|
$[18] = canvasStyles;
|
|
$[19] = isCompatible;
|
|
$[20] = isDragTarget;
|
|
$[21] = isHovered;
|
|
$[22] = port.id;
|
|
$[23] = port.label;
|
|
$[24] = showExpanded;
|
|
$[25] = side;
|
|
$[26] = t14;
|
|
} else {
|
|
t14 = $[26];
|
|
}
|
|
let t15;
|
|
if ($[27] !== onMouseLeave || $[28] !== port.id || $[29] !== port.type || $[30] !== t1 || $[31] !== t13 || $[32] !== t14 || $[33] !== t2 || $[34] !== t3 || $[35] !== t4 || $[36] !== t9) {
|
|
t15 = /* @__PURE__ */ _jsxs23("div", {
|
|
"data-port-id": port.id,
|
|
"data-port-type": port.type,
|
|
"data-drag-port-id": t1,
|
|
onClick: t2,
|
|
onPointerDown: t3,
|
|
onMouseEnter: t4,
|
|
onMouseLeave,
|
|
style: t9,
|
|
children: [t13, t14]
|
|
});
|
|
$[27] = onMouseLeave;
|
|
$[28] = port.id;
|
|
$[29] = port.type;
|
|
$[30] = t1;
|
|
$[31] = t13;
|
|
$[32] = t14;
|
|
$[33] = t2;
|
|
$[34] = t3;
|
|
$[35] = t4;
|
|
$[36] = t9;
|
|
$[37] = t15;
|
|
} else {
|
|
t15 = $[37];
|
|
}
|
|
return t15;
|
|
}
|
|
|
|
// src/components/PortBar.tsx
|
|
import { jsx as _jsx39 } from "react/jsx-runtime";
|
|
var COLLAPSED_WIDTH = 8;
|
|
var EXPANDED_WIDTH = 80;
|
|
var TRANSITION_DURATION2 = "150ms";
|
|
function PortBar(t0) {
|
|
const $ = _c68(66);
|
|
const {
|
|
nodeId,
|
|
side,
|
|
ports,
|
|
nodeWidth,
|
|
nodeHeight,
|
|
forceExpanded: t1,
|
|
onPortClick,
|
|
onPortHover,
|
|
isDragTarget: t2,
|
|
dragSourcePort
|
|
} = t0;
|
|
const forceExpanded = t1 === void 0 ? false : t1;
|
|
const isDragTarget = t2 === void 0 ? false : t2;
|
|
const [isExpanded, setIsExpanded] = useState9(false);
|
|
const [hoveredPortId, setHoveredPortId] = useState9(null);
|
|
const canvasStyles = useCanvasStyle();
|
|
const setEdgeCreation = useSetAtom32(edgeCreationAtom);
|
|
const showExpanded = isExpanded || forceExpanded || isDragTarget;
|
|
let t3;
|
|
bb0: {
|
|
const t42 = side === "top" || side === "bottom" ? "row" : "column";
|
|
let t52;
|
|
if ($[0] !== canvasStyles.nodes.defaultBackground || $[1] !== t42) {
|
|
t52 = {
|
|
position: "absolute",
|
|
display: "flex",
|
|
flexDirection: t42,
|
|
alignItems: "stretch",
|
|
transition: `all ${TRANSITION_DURATION2} ease-out`,
|
|
backgroundColor: canvasStyles.nodes.defaultBackground,
|
|
borderRadius: "4px",
|
|
overflow: "hidden",
|
|
zIndex: 10
|
|
};
|
|
$[0] = canvasStyles.nodes.defaultBackground;
|
|
$[1] = t42;
|
|
$[2] = t52;
|
|
} else {
|
|
t52 = $[2];
|
|
}
|
|
const base = t52;
|
|
const barSize = showExpanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH;
|
|
const barLength = side === "left" || side === "right" ? Math.max(ports.length * PORT_ROW_HEIGHT, nodeHeight * 0.6) : Math.max(ports.length * PORT_ROW_HEIGHT, nodeWidth * 0.6);
|
|
switch (side) {
|
|
case "left": {
|
|
const t62 = -barSize;
|
|
const t72 = `${barSize}px`;
|
|
const t82 = `${barLength}px`;
|
|
const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`;
|
|
let t10;
|
|
if ($[3] !== base || $[4] !== t62 || $[5] !== t72 || $[6] !== t82 || $[7] !== t92) {
|
|
t10 = {
|
|
...base,
|
|
left: t62,
|
|
top: "50%",
|
|
transform: "translateY(-50%)",
|
|
width: t72,
|
|
height: t82,
|
|
borderRight: t92,
|
|
borderTopRightRadius: 0,
|
|
borderBottomRightRadius: 0
|
|
};
|
|
$[3] = base;
|
|
$[4] = t62;
|
|
$[5] = t72;
|
|
$[6] = t82;
|
|
$[7] = t92;
|
|
$[8] = t10;
|
|
} else {
|
|
t10 = $[8];
|
|
}
|
|
t3 = t10;
|
|
break bb0;
|
|
}
|
|
case "right": {
|
|
const t62 = -barSize;
|
|
const t72 = `${barSize}px`;
|
|
const t82 = `${barLength}px`;
|
|
const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`;
|
|
let t10;
|
|
if ($[9] !== base || $[10] !== t62 || $[11] !== t72 || $[12] !== t82 || $[13] !== t92) {
|
|
t10 = {
|
|
...base,
|
|
right: t62,
|
|
top: "50%",
|
|
transform: "translateY(-50%)",
|
|
width: t72,
|
|
height: t82,
|
|
borderLeft: t92,
|
|
borderTopLeftRadius: 0,
|
|
borderBottomLeftRadius: 0
|
|
};
|
|
$[9] = base;
|
|
$[10] = t62;
|
|
$[11] = t72;
|
|
$[12] = t82;
|
|
$[13] = t92;
|
|
$[14] = t10;
|
|
} else {
|
|
t10 = $[14];
|
|
}
|
|
t3 = t10;
|
|
break bb0;
|
|
}
|
|
case "top": {
|
|
const t62 = -barSize;
|
|
const t72 = `${barSize}px`;
|
|
const t82 = `${barLength}px`;
|
|
const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`;
|
|
let t10;
|
|
if ($[15] !== base || $[16] !== t62 || $[17] !== t72 || $[18] !== t82 || $[19] !== t92) {
|
|
t10 = {
|
|
...base,
|
|
top: t62,
|
|
left: "50%",
|
|
transform: "translateX(-50%)",
|
|
height: t72,
|
|
width: t82,
|
|
borderBottom: t92,
|
|
borderBottomLeftRadius: 0,
|
|
borderBottomRightRadius: 0
|
|
};
|
|
$[15] = base;
|
|
$[16] = t62;
|
|
$[17] = t72;
|
|
$[18] = t82;
|
|
$[19] = t92;
|
|
$[20] = t10;
|
|
} else {
|
|
t10 = $[20];
|
|
}
|
|
t3 = t10;
|
|
break bb0;
|
|
}
|
|
case "bottom": {
|
|
const t62 = -barSize;
|
|
const t72 = `${barSize}px`;
|
|
const t82 = `${barLength}px`;
|
|
const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`;
|
|
let t10;
|
|
if ($[21] !== base || $[22] !== t62 || $[23] !== t72 || $[24] !== t82 || $[25] !== t92) {
|
|
t10 = {
|
|
...base,
|
|
bottom: t62,
|
|
left: "50%",
|
|
transform: "translateX(-50%)",
|
|
height: t72,
|
|
width: t82,
|
|
borderTop: t92,
|
|
borderTopLeftRadius: 0,
|
|
borderTopRightRadius: 0
|
|
};
|
|
$[21] = base;
|
|
$[22] = t62;
|
|
$[23] = t72;
|
|
$[24] = t82;
|
|
$[25] = t92;
|
|
$[26] = t10;
|
|
} else {
|
|
t10 = $[26];
|
|
}
|
|
t3 = t10;
|
|
break bb0;
|
|
}
|
|
}
|
|
t3 = void 0;
|
|
}
|
|
const barStyle = t3;
|
|
let t4;
|
|
if ($[27] !== dragSourcePort || $[28] !== isDragTarget) {
|
|
t4 = (port) => {
|
|
if (!isDragTarget || !dragSourcePort) {
|
|
return true;
|
|
}
|
|
return arePortsCompatible(dragSourcePort, port);
|
|
};
|
|
$[27] = dragSourcePort;
|
|
$[28] = isDragTarget;
|
|
$[29] = t4;
|
|
} else {
|
|
t4 = $[29];
|
|
}
|
|
const isPortCompatible = t4;
|
|
let t5;
|
|
if ($[30] !== isDragTarget) {
|
|
t5 = () => {
|
|
if (!isDragTarget) {
|
|
setIsExpanded(true);
|
|
}
|
|
};
|
|
$[30] = isDragTarget;
|
|
$[31] = t5;
|
|
} else {
|
|
t5 = $[31];
|
|
}
|
|
const handleMouseEnter = t5;
|
|
let t6;
|
|
if ($[32] !== forceExpanded || $[33] !== onPortHover) {
|
|
t6 = () => {
|
|
if (!forceExpanded) {
|
|
setIsExpanded(false);
|
|
setHoveredPortId(null);
|
|
onPortHover?.(null, null);
|
|
}
|
|
};
|
|
$[32] = forceExpanded;
|
|
$[33] = onPortHover;
|
|
$[34] = t6;
|
|
} else {
|
|
t6 = $[34];
|
|
}
|
|
const handleMouseLeave = t6;
|
|
let t7;
|
|
if ($[35] !== nodeHeight || $[36] !== nodeId || $[37] !== nodeWidth || $[38] !== setEdgeCreation) {
|
|
t7 = (portId, port_0, e) => {
|
|
if (e.button !== 0) {
|
|
return;
|
|
}
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
const portPos = calculatePortPosition(0, 0, nodeWidth, nodeHeight, port_0);
|
|
setEdgeCreation({
|
|
isCreating: true,
|
|
sourceNodeId: nodeId,
|
|
sourceNodePosition: {
|
|
x: portPos.x,
|
|
y: portPos.y
|
|
},
|
|
targetPosition: {
|
|
x: portPos.x,
|
|
y: portPos.y
|
|
},
|
|
hoveredTargetNodeId: null,
|
|
sourceHandle: `widget:${nodeId}:${port_0.type === "input" ? "input" : "output"}:${port_0.id}`,
|
|
targetHandle: null,
|
|
sourcePort: portId,
|
|
targetPort: null,
|
|
snappedTargetPosition: null
|
|
});
|
|
};
|
|
$[35] = nodeHeight;
|
|
$[36] = nodeId;
|
|
$[37] = nodeWidth;
|
|
$[38] = setEdgeCreation;
|
|
$[39] = t7;
|
|
} else {
|
|
t7 = $[39];
|
|
}
|
|
const handlePortPointerDown = t7;
|
|
let t8;
|
|
if ($[40] !== handlePortPointerDown || $[41] !== hoveredPortId || $[42] !== isDragTarget || $[43] !== isPortCompatible || $[44] !== onPortClick || $[45] !== onPortHover || $[46] !== ports || $[47] !== showExpanded || $[48] !== side) {
|
|
let t92;
|
|
if ($[50] !== handlePortPointerDown || $[51] !== hoveredPortId || $[52] !== isDragTarget || $[53] !== isPortCompatible || $[54] !== onPortClick || $[55] !== onPortHover || $[56] !== showExpanded || $[57] !== side) {
|
|
t92 = (port_1) => /* @__PURE__ */ _jsx39(PortHandle, {
|
|
port: port_1,
|
|
side,
|
|
isHovered: hoveredPortId === port_1.id,
|
|
showExpanded,
|
|
isCompatible: isPortCompatible(port_1),
|
|
isDragTarget,
|
|
onClick: (id, p) => onPortClick?.(id, p),
|
|
onPointerDown: handlePortPointerDown,
|
|
onMouseEnter: (id_0, p_0) => {
|
|
setHoveredPortId(id_0);
|
|
onPortHover?.(id_0, p_0);
|
|
},
|
|
onMouseLeave: () => {
|
|
setHoveredPortId(null);
|
|
onPortHover?.(null, null);
|
|
}
|
|
}, port_1.id);
|
|
$[50] = handlePortPointerDown;
|
|
$[51] = hoveredPortId;
|
|
$[52] = isDragTarget;
|
|
$[53] = isPortCompatible;
|
|
$[54] = onPortClick;
|
|
$[55] = onPortHover;
|
|
$[56] = showExpanded;
|
|
$[57] = side;
|
|
$[58] = t92;
|
|
} else {
|
|
t92 = $[58];
|
|
}
|
|
t8 = ports.map(t92);
|
|
$[40] = handlePortPointerDown;
|
|
$[41] = hoveredPortId;
|
|
$[42] = isDragTarget;
|
|
$[43] = isPortCompatible;
|
|
$[44] = onPortClick;
|
|
$[45] = onPortHover;
|
|
$[46] = ports;
|
|
$[47] = showExpanded;
|
|
$[48] = side;
|
|
$[49] = t8;
|
|
} else {
|
|
t8 = $[49];
|
|
}
|
|
let t9;
|
|
if ($[59] !== barStyle || $[60] !== handleMouseEnter || $[61] !== handleMouseLeave || $[62] !== nodeId || $[63] !== side || $[64] !== t8) {
|
|
t9 = /* @__PURE__ */ _jsx39("div", {
|
|
style: barStyle,
|
|
onMouseEnter: handleMouseEnter,
|
|
onMouseLeave: handleMouseLeave,
|
|
"data-port-bar": side,
|
|
"data-node-id": nodeId,
|
|
children: t8
|
|
});
|
|
$[59] = barStyle;
|
|
$[60] = handleMouseEnter;
|
|
$[61] = handleMouseLeave;
|
|
$[62] = nodeId;
|
|
$[63] = side;
|
|
$[64] = t8;
|
|
$[65] = t9;
|
|
} else {
|
|
t9 = $[65];
|
|
}
|
|
return t9;
|
|
}
|
|
|
|
// src/components/NodePorts.tsx
|
|
import { jsx as _jsx40, Fragment as _Fragment8 } from "react/jsx-runtime";
|
|
function NodePorts(t0) {
|
|
const $ = _c69(25);
|
|
const {
|
|
nodeId,
|
|
nodeWidth,
|
|
nodeHeight,
|
|
ports: portsProp,
|
|
sides,
|
|
onStartConnection,
|
|
onPortHover,
|
|
isDragTarget: t1,
|
|
dragSourcePort
|
|
} = t0;
|
|
const isDragTarget = t1 === void 0 ? false : t1;
|
|
let portsBySide;
|
|
if ($[0] !== portsProp) {
|
|
const ports = getNodePorts(portsProp);
|
|
portsBySide = {
|
|
top: [],
|
|
right: [],
|
|
bottom: [],
|
|
left: []
|
|
};
|
|
for (const port of ports) {
|
|
portsBySide[port.side].push(port);
|
|
}
|
|
$[0] = portsProp;
|
|
$[1] = portsBySide;
|
|
} else {
|
|
portsBySide = $[1];
|
|
}
|
|
let t2;
|
|
if ($[2] !== sides) {
|
|
t2 = sides || ["left", "right", "top", "bottom"];
|
|
$[2] = sides;
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
const sidesToRender = t2;
|
|
let t3;
|
|
if ($[4] !== dragSourcePort || $[5] !== isDragTarget || $[6] !== nodeHeight || $[7] !== nodeId || $[8] !== nodeWidth || $[9] !== onPortHover || $[10] !== onStartConnection || $[11] !== portsBySide || $[12] !== sidesToRender) {
|
|
let t42;
|
|
if ($[14] !== dragSourcePort || $[15] !== isDragTarget || $[16] !== nodeHeight || $[17] !== nodeId || $[18] !== nodeWidth || $[19] !== onPortHover || $[20] !== onStartConnection || $[21] !== portsBySide) {
|
|
t42 = (side) => {
|
|
const sidePorts = portsBySide[side];
|
|
if (sidePorts.length === 0) {
|
|
return null;
|
|
}
|
|
return /* @__PURE__ */ _jsx40(PortBar, {
|
|
nodeId,
|
|
side,
|
|
ports: sidePorts,
|
|
nodeWidth,
|
|
nodeHeight,
|
|
onPortClick: (portId, port_0) => onStartConnection?.(nodeId, portId, port_0),
|
|
onPortHover: (portId_0, port_1) => onPortHover?.(nodeId, portId_0, port_1),
|
|
isDragTarget,
|
|
dragSourcePort
|
|
}, side);
|
|
};
|
|
$[14] = dragSourcePort;
|
|
$[15] = isDragTarget;
|
|
$[16] = nodeHeight;
|
|
$[17] = nodeId;
|
|
$[18] = nodeWidth;
|
|
$[19] = onPortHover;
|
|
$[20] = onStartConnection;
|
|
$[21] = portsBySide;
|
|
$[22] = t42;
|
|
} else {
|
|
t42 = $[22];
|
|
}
|
|
t3 = sidesToRender.map(t42);
|
|
$[4] = dragSourcePort;
|
|
$[5] = isDragTarget;
|
|
$[6] = nodeHeight;
|
|
$[7] = nodeId;
|
|
$[8] = nodeWidth;
|
|
$[9] = onPortHover;
|
|
$[10] = onStartConnection;
|
|
$[11] = portsBySide;
|
|
$[12] = sidesToRender;
|
|
$[13] = t3;
|
|
} else {
|
|
t3 = $[13];
|
|
}
|
|
let t4;
|
|
if ($[23] !== t3) {
|
|
t4 = /* @__PURE__ */ _jsx40(_Fragment8, {
|
|
children: t3
|
|
});
|
|
$[23] = t3;
|
|
$[24] = t4;
|
|
} else {
|
|
t4 = $[24];
|
|
}
|
|
return t4;
|
|
}
|
|
function getPortWorldPosition(nodeX, nodeY, nodeWidth, nodeHeight, port) {
|
|
return calculatePortPosition(nodeX, nodeY, nodeWidth, nodeHeight, port);
|
|
}
|
|
|
|
// src/components/ViewportControls.tsx
|
|
init_viewport_store();
|
|
init_input_store();
|
|
import { c as _c70 } from "react/compiler-runtime";
|
|
import React40, { useState as useState10 } from "react";
|
|
import { useAtom as useAtom8, useAtomValue as useAtomValue44, useSetAtom as useSetAtom33 } from "jotai";
|
|
import { jsx as _jsx41, jsxs as _jsxs24 } from "react/jsx-runtime";
|
|
function ViewportControls(t0) {
|
|
const $ = _c70(39);
|
|
const {
|
|
minZoom: t1,
|
|
maxZoom: t2,
|
|
zoomStep: t3,
|
|
position: t4,
|
|
alwaysVisible: t5,
|
|
className,
|
|
style
|
|
} = t0;
|
|
const minZoom = t1 === void 0 ? 0.1 : t1;
|
|
const maxZoom = t2 === void 0 ? 5 : t2;
|
|
const zoomStep = t3 === void 0 ? 0.25 : t3;
|
|
const position = t4 === void 0 ? "bottom-right" : t4;
|
|
const alwaysVisible = t5 === void 0 ? false : t5;
|
|
const [zoom, setZoom] = useAtom8(zoomAtom);
|
|
useAtom8(panAtom);
|
|
const isTouchDevice = useAtomValue44(isTouchDeviceAtom);
|
|
const resetViewport = useSetAtom33(resetViewportAtom);
|
|
let t6;
|
|
if ($[0] !== maxZoom || $[1] !== setZoom || $[2] !== zoom || $[3] !== zoomStep) {
|
|
t6 = () => {
|
|
setZoom(Math.min(maxZoom, zoom + zoomStep));
|
|
};
|
|
$[0] = maxZoom;
|
|
$[1] = setZoom;
|
|
$[2] = zoom;
|
|
$[3] = zoomStep;
|
|
$[4] = t6;
|
|
} else {
|
|
t6 = $[4];
|
|
}
|
|
const handleZoomIn = t6;
|
|
let t7;
|
|
if ($[5] !== minZoom || $[6] !== setZoom || $[7] !== zoom || $[8] !== zoomStep) {
|
|
t7 = () => {
|
|
setZoom(Math.max(minZoom, zoom - zoomStep));
|
|
};
|
|
$[5] = minZoom;
|
|
$[6] = setZoom;
|
|
$[7] = zoom;
|
|
$[8] = zoomStep;
|
|
$[9] = t7;
|
|
} else {
|
|
t7 = $[9];
|
|
}
|
|
const handleZoomOut = t7;
|
|
let t8;
|
|
if ($[10] !== resetViewport) {
|
|
t8 = () => {
|
|
resetViewport();
|
|
};
|
|
$[10] = resetViewport;
|
|
$[11] = t8;
|
|
} else {
|
|
t8 = $[11];
|
|
}
|
|
const handleReset = t8;
|
|
if (!alwaysVisible && !isTouchDevice) {
|
|
return null;
|
|
}
|
|
let t9;
|
|
if ($[12] !== position) {
|
|
t9 = getPositionStyles(position);
|
|
$[12] = position;
|
|
$[13] = t9;
|
|
} else {
|
|
t9 = $[13];
|
|
}
|
|
const positionStyles = t9;
|
|
let t10;
|
|
if ($[14] !== zoom) {
|
|
t10 = Math.round(zoom * 100);
|
|
$[14] = zoom;
|
|
$[15] = t10;
|
|
} else {
|
|
t10 = $[15];
|
|
}
|
|
const zoomPercent = t10;
|
|
let t11;
|
|
if ($[16] !== positionStyles || $[17] !== style) {
|
|
t11 = {
|
|
position: "absolute",
|
|
...positionStyles,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: "4px",
|
|
zIndex: 100,
|
|
pointerEvents: "auto",
|
|
...style
|
|
};
|
|
$[16] = positionStyles;
|
|
$[17] = style;
|
|
$[18] = t11;
|
|
} else {
|
|
t11 = $[18];
|
|
}
|
|
const t12 = zoom >= maxZoom;
|
|
let t13;
|
|
if ($[19] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t13 = /* @__PURE__ */ _jsx41(PlusIcon, {});
|
|
$[19] = t13;
|
|
} else {
|
|
t13 = $[19];
|
|
}
|
|
let t14;
|
|
if ($[20] !== handleZoomIn || $[21] !== t12) {
|
|
t14 = /* @__PURE__ */ _jsx41(ControlButton, {
|
|
onClick: handleZoomIn,
|
|
disabled: t12,
|
|
"aria-label": "Zoom in",
|
|
children: t13
|
|
});
|
|
$[20] = handleZoomIn;
|
|
$[21] = t12;
|
|
$[22] = t14;
|
|
} else {
|
|
t14 = $[22];
|
|
}
|
|
const t15 = `Reset zoom (${zoomPercent}%)`;
|
|
const t16 = `${zoomPercent}%`;
|
|
let t17;
|
|
if ($[23] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t17 = {
|
|
fontSize: "10px",
|
|
fontWeight: 600
|
|
};
|
|
$[23] = t17;
|
|
} else {
|
|
t17 = $[23];
|
|
}
|
|
let t18;
|
|
if ($[24] !== handleReset || $[25] !== t15 || $[26] !== t16 || $[27] !== zoomPercent) {
|
|
t18 = /* @__PURE__ */ _jsxs24(ControlButton, {
|
|
onClick: handleReset,
|
|
"aria-label": t15,
|
|
title: t16,
|
|
style: t17,
|
|
children: [zoomPercent, "%"]
|
|
});
|
|
$[24] = handleReset;
|
|
$[25] = t15;
|
|
$[26] = t16;
|
|
$[27] = zoomPercent;
|
|
$[28] = t18;
|
|
} else {
|
|
t18 = $[28];
|
|
}
|
|
const t19 = zoom <= minZoom;
|
|
let t20;
|
|
if ($[29] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t20 = /* @__PURE__ */ _jsx41(MinusIcon, {});
|
|
$[29] = t20;
|
|
} else {
|
|
t20 = $[29];
|
|
}
|
|
let t21;
|
|
if ($[30] !== handleZoomOut || $[31] !== t19) {
|
|
t21 = /* @__PURE__ */ _jsx41(ControlButton, {
|
|
onClick: handleZoomOut,
|
|
disabled: t19,
|
|
"aria-label": "Zoom out",
|
|
children: t20
|
|
});
|
|
$[30] = handleZoomOut;
|
|
$[31] = t19;
|
|
$[32] = t21;
|
|
} else {
|
|
t21 = $[32];
|
|
}
|
|
let t22;
|
|
if ($[33] !== className || $[34] !== t11 || $[35] !== t14 || $[36] !== t18 || $[37] !== t21) {
|
|
t22 = /* @__PURE__ */ _jsxs24("div", {
|
|
className,
|
|
style: t11,
|
|
children: [t14, t18, t21]
|
|
});
|
|
$[33] = className;
|
|
$[34] = t11;
|
|
$[35] = t14;
|
|
$[36] = t18;
|
|
$[37] = t21;
|
|
$[38] = t22;
|
|
} else {
|
|
t22 = $[38];
|
|
}
|
|
return t22;
|
|
}
|
|
function ControlButton(t0) {
|
|
const $ = _c70(21);
|
|
let children;
|
|
let disabled;
|
|
let onClick;
|
|
let props;
|
|
let style;
|
|
if ($[0] !== t0) {
|
|
({
|
|
children,
|
|
style,
|
|
disabled,
|
|
onClick,
|
|
...props
|
|
} = t0);
|
|
$[0] = t0;
|
|
$[1] = children;
|
|
$[2] = disabled;
|
|
$[3] = onClick;
|
|
$[4] = props;
|
|
$[5] = style;
|
|
} else {
|
|
children = $[1];
|
|
disabled = $[2];
|
|
onClick = $[3];
|
|
props = $[4];
|
|
style = $[5];
|
|
}
|
|
const [pulsing, setPulsing] = useState10(false);
|
|
let t1;
|
|
if ($[6] !== disabled || $[7] !== onClick) {
|
|
t1 = (e) => {
|
|
if (!disabled) {
|
|
setPulsing(true);
|
|
}
|
|
onClick?.(e);
|
|
};
|
|
$[6] = disabled;
|
|
$[7] = onClick;
|
|
$[8] = t1;
|
|
} else {
|
|
t1 = $[8];
|
|
}
|
|
const handleClick = t1;
|
|
const t2 = pulsing ? "canvas-tap-pulse" : void 0;
|
|
let t3;
|
|
if ($[9] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t3 = () => setPulsing(false);
|
|
$[9] = t3;
|
|
} else {
|
|
t3 = $[9];
|
|
}
|
|
const t4 = disabled ? "default" : "pointer";
|
|
const t5 = disabled ? 0.4 : 1;
|
|
let t6;
|
|
if ($[10] !== style || $[11] !== t4 || $[12] !== t5) {
|
|
t6 = {
|
|
width: "44px",
|
|
height: "44px",
|
|
borderRadius: "12px",
|
|
border: "1px solid var(--canvas-control-border, rgba(255,255,255,0.1))",
|
|
backgroundColor: "var(--canvas-control-bg, rgba(30,30,30,0.85))",
|
|
color: "var(--canvas-control-fg, rgba(255,255,255,0.9))",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
cursor: t4,
|
|
opacity: t5,
|
|
touchAction: "manipulation",
|
|
WebkitTapHighlightColor: "transparent",
|
|
userSelect: "none",
|
|
transition: "background-color 150ms, opacity 150ms",
|
|
backdropFilter: "blur(12px)",
|
|
WebkitBackdropFilter: "blur(12px)",
|
|
padding: 0,
|
|
...style
|
|
};
|
|
$[10] = style;
|
|
$[11] = t4;
|
|
$[12] = t5;
|
|
$[13] = t6;
|
|
} else {
|
|
t6 = $[13];
|
|
}
|
|
let t7;
|
|
if ($[14] !== children || $[15] !== disabled || $[16] !== handleClick || $[17] !== props || $[18] !== t2 || $[19] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsx41("button", {
|
|
...props,
|
|
disabled,
|
|
className: t2,
|
|
onAnimationEnd: t3,
|
|
onClick: handleClick,
|
|
style: t6,
|
|
"data-no-drag": "true",
|
|
children
|
|
});
|
|
$[14] = children;
|
|
$[15] = disabled;
|
|
$[16] = handleClick;
|
|
$[17] = props;
|
|
$[18] = t2;
|
|
$[19] = t6;
|
|
$[20] = t7;
|
|
} else {
|
|
t7 = $[20];
|
|
}
|
|
return t7;
|
|
}
|
|
function PlusIcon() {
|
|
const $ = _c70(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs24("svg", {
|
|
width: "18",
|
|
height: "18",
|
|
viewBox: "0 0 18 18",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
strokeLinecap: "round",
|
|
children: [/* @__PURE__ */ _jsx41("line", {
|
|
x1: "9",
|
|
y1: "4",
|
|
x2: "9",
|
|
y2: "14"
|
|
}), /* @__PURE__ */ _jsx41("line", {
|
|
x1: "4",
|
|
y1: "9",
|
|
x2: "14",
|
|
y2: "9"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function MinusIcon() {
|
|
const $ = _c70(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsx41("svg", {
|
|
width: "18",
|
|
height: "18",
|
|
viewBox: "0 0 18 18",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2",
|
|
strokeLinecap: "round",
|
|
children: /* @__PURE__ */ _jsx41("line", {
|
|
x1: "4",
|
|
y1: "9",
|
|
x2: "14",
|
|
y2: "9"
|
|
})
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function getPositionStyles(position) {
|
|
switch (position) {
|
|
case "bottom-right":
|
|
return {
|
|
bottom: "max(16px, env(safe-area-inset-bottom, 16px))",
|
|
right: "max(16px, env(safe-area-inset-right, 16px))"
|
|
};
|
|
case "bottom-left":
|
|
return {
|
|
bottom: "max(16px, env(safe-area-inset-bottom, 16px))",
|
|
left: "max(16px, env(safe-area-inset-left, 16px))"
|
|
};
|
|
case "top-right":
|
|
return {
|
|
top: "max(16px, env(safe-area-inset-top, 16px))",
|
|
right: "max(16px, env(safe-area-inset-right, 16px))"
|
|
};
|
|
case "top-left":
|
|
return {
|
|
top: "max(16px, env(safe-area-inset-top, 16px))",
|
|
left: "max(16px, env(safe-area-inset-left, 16px))"
|
|
};
|
|
default:
|
|
return {
|
|
bottom: "max(16px, env(safe-area-inset-bottom, 16px))",
|
|
right: "max(16px, env(safe-area-inset-right, 16px))"
|
|
};
|
|
}
|
|
}
|
|
|
|
// src/components/SelectionOverlay.tsx
|
|
init_selection_path_store();
|
|
init_viewport_store();
|
|
import { c as _c71 } from "react/compiler-runtime";
|
|
import React41 from "react";
|
|
import { useAtomValue as useAtomValue45 } from "jotai";
|
|
import { jsx as _jsx42, jsxs as _jsxs25 } from "react/jsx-runtime";
|
|
function SelectionOverlay(t0) {
|
|
const $ = _c71(18);
|
|
const {
|
|
fillColor: t1,
|
|
strokeColor: t2,
|
|
strokeWidth: t3
|
|
} = t0;
|
|
const fillColor = t1 === void 0 ? "rgba(99, 102, 241, 0.15)" : t1;
|
|
const strokeColor = t2 === void 0 ? "rgba(99, 102, 241, 0.6)" : t2;
|
|
const strokeWidth = t3 === void 0 ? 1.5 : t3;
|
|
const path = useAtomValue45(selectionPathAtom);
|
|
const selectionRect = useAtomValue45(selectionRectAtom);
|
|
useAtomValue45(panAtom);
|
|
const zoom = useAtomValue45(zoomAtom);
|
|
if (!path) {
|
|
return null;
|
|
}
|
|
let t4;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t4 = {
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
pointerEvents: "none",
|
|
overflow: "visible",
|
|
zIndex: 9998
|
|
};
|
|
$[0] = t4;
|
|
} else {
|
|
t4 = $[0];
|
|
}
|
|
let t5;
|
|
if ($[1] !== fillColor || $[2] !== path.type || $[3] !== selectionRect || $[4] !== strokeColor || $[5] !== strokeWidth || $[6] !== zoom) {
|
|
t5 = path.type === "rect" && selectionRect && /* @__PURE__ */ _jsx42("rect", {
|
|
x: selectionRect.x,
|
|
y: selectionRect.y,
|
|
width: selectionRect.width,
|
|
height: selectionRect.height,
|
|
fill: fillColor,
|
|
stroke: strokeColor,
|
|
strokeWidth: strokeWidth / zoom,
|
|
strokeDasharray: `${4 / zoom} ${4 / zoom}`
|
|
});
|
|
$[1] = fillColor;
|
|
$[2] = path.type;
|
|
$[3] = selectionRect;
|
|
$[4] = strokeColor;
|
|
$[5] = strokeWidth;
|
|
$[6] = zoom;
|
|
$[7] = t5;
|
|
} else {
|
|
t5 = $[7];
|
|
}
|
|
let t6;
|
|
if ($[8] !== fillColor || $[9] !== path.points || $[10] !== path.type || $[11] !== strokeColor || $[12] !== strokeWidth || $[13] !== zoom) {
|
|
t6 = path.type === "lasso" && path.points.length >= 2 && /* @__PURE__ */ _jsx42("polygon", {
|
|
points: path.points.map(_temp19).join(" "),
|
|
fill: fillColor,
|
|
stroke: strokeColor,
|
|
strokeWidth: strokeWidth / zoom,
|
|
strokeLinejoin: "round"
|
|
});
|
|
$[8] = fillColor;
|
|
$[9] = path.points;
|
|
$[10] = path.type;
|
|
$[11] = strokeColor;
|
|
$[12] = strokeWidth;
|
|
$[13] = zoom;
|
|
$[14] = t6;
|
|
} else {
|
|
t6 = $[14];
|
|
}
|
|
let t7;
|
|
if ($[15] !== t5 || $[16] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsxs25("svg", {
|
|
style: t4,
|
|
children: [t5, t6]
|
|
});
|
|
$[15] = t5;
|
|
$[16] = t6;
|
|
$[17] = t7;
|
|
} else {
|
|
t7 = $[17];
|
|
}
|
|
return t7;
|
|
}
|
|
function _temp19(p) {
|
|
return `${p.x},${p.y}`;
|
|
}
|
|
|
|
// src/components/Minimap.tsx
|
|
init_graph_derived();
|
|
init_graph_store();
|
|
init_viewport_store();
|
|
init_selection_store();
|
|
import { c as _c72 } from "react/compiler-runtime";
|
|
import React42, { useRef as useRef29, useEffect as useEffect22 } from "react";
|
|
import { useAtomValue as useAtomValue46, useSetAtom as useSetAtom34 } from "jotai";
|
|
|
|
// src/components/minimap-utils.ts
|
|
function computeGraphBounds(nodes) {
|
|
if (nodes.length === 0) return null;
|
|
let minX = Infinity;
|
|
let minY = Infinity;
|
|
let maxX = -Infinity;
|
|
let maxY = -Infinity;
|
|
for (const node of nodes) {
|
|
const x = node.position.x;
|
|
const y = node.position.y;
|
|
const w = node.width ?? 200;
|
|
const h = node.height ?? 100;
|
|
if (x < minX) minX = x;
|
|
if (y < minY) minY = y;
|
|
if (x + w > maxX) maxX = x + w;
|
|
if (y + h > maxY) maxY = y + h;
|
|
}
|
|
return {
|
|
minX,
|
|
minY,
|
|
maxX,
|
|
maxY
|
|
};
|
|
}
|
|
function getMinimapTransform(bounds, minimapWidth, minimapHeight, padding = 20) {
|
|
const graphW = bounds.maxX - bounds.minX;
|
|
const graphH = bounds.maxY - bounds.minY;
|
|
if (graphW <= 0 || graphH <= 0) return null;
|
|
const scaleX = (minimapWidth - padding * 2) / graphW;
|
|
const scaleY = (minimapHeight - padding * 2) / graphH;
|
|
const scale = Math.min(scaleX, scaleY);
|
|
const offsetX = padding + (minimapWidth - padding * 2 - graphW * scale) / 2;
|
|
const offsetY = padding + (minimapHeight - padding * 2 - graphH * scale) / 2;
|
|
return {
|
|
scale,
|
|
offsetX,
|
|
offsetY,
|
|
minX: bounds.minX,
|
|
minY: bounds.minY
|
|
};
|
|
}
|
|
function minimapToWorld(mx, my, transform) {
|
|
return {
|
|
x: (mx - transform.offsetX) / transform.scale + transform.minX,
|
|
y: (my - transform.offsetY) / transform.scale + transform.minY
|
|
};
|
|
}
|
|
|
|
// src/components/Minimap.tsx
|
|
import { jsx as _jsx43 } from "react/jsx-runtime";
|
|
function Minimap(t0) {
|
|
const $ = _c72(60);
|
|
const {
|
|
width: t1,
|
|
height: t2,
|
|
backgroundColor: t3,
|
|
nodeColor: t4,
|
|
selectedNodeColor: t5,
|
|
viewportColor: t6,
|
|
borderColor: t7,
|
|
edgeColor: t8,
|
|
position: t9,
|
|
offset: t10,
|
|
className
|
|
} = t0;
|
|
const width = t1 === void 0 ? 200 : t1;
|
|
const height = t2 === void 0 ? 150 : t2;
|
|
const backgroundColor = t3 === void 0 ? "rgba(0, 0, 0, 0.7)" : t3;
|
|
const nodeColor = t4 === void 0 ? "rgba(148, 163, 184, 0.6)" : t4;
|
|
const selectedNodeColor = t5 === void 0 ? "rgba(99, 102, 241, 0.8)" : t5;
|
|
const viewportColor = t6 === void 0 ? "rgba(255, 255, 255, 0.3)" : t6;
|
|
const borderColor = t7 === void 0 ? "rgba(255, 255, 255, 0.2)" : t7;
|
|
const edgeColor = t8 === void 0 ? "rgba(148, 163, 184, 0.3)" : t8;
|
|
const position = t9 === void 0 ? "bottom-right" : t9;
|
|
const offset = t10 === void 0 ? 12 : t10;
|
|
const canvasRef = useRef29(null);
|
|
const nodes = useAtomValue46(uiNodesAtom);
|
|
const graph = useAtomValue46(graphAtom);
|
|
const pan = useAtomValue46(panAtom);
|
|
const zoom = useAtomValue46(zoomAtom);
|
|
const viewportRect = useAtomValue46(viewportRectAtom);
|
|
const selectedNodeIds = useAtomValue46(selectedNodeIdsAtom);
|
|
const setPan = useSetAtom34(panAtom);
|
|
const isDragging = useRef29(false);
|
|
let t11;
|
|
if ($[0] !== nodes) {
|
|
t11 = computeGraphBounds(nodes);
|
|
$[0] = nodes;
|
|
$[1] = t11;
|
|
} else {
|
|
t11 = $[1];
|
|
}
|
|
const graphBounds = t11;
|
|
let t12;
|
|
if ($[2] !== graphBounds || $[3] !== height || $[4] !== width) {
|
|
t12 = (mx, my) => {
|
|
if (!graphBounds) {
|
|
return null;
|
|
}
|
|
const t = getMinimapTransform(graphBounds, width, height);
|
|
if (!t) {
|
|
return null;
|
|
}
|
|
return minimapToWorld(mx, my, t);
|
|
};
|
|
$[2] = graphBounds;
|
|
$[3] = height;
|
|
$[4] = width;
|
|
$[5] = t12;
|
|
} else {
|
|
t12 = $[5];
|
|
}
|
|
const minimapToWorldCoords = t12;
|
|
let t13;
|
|
if ($[6] !== setPan || $[7] !== viewportRect || $[8] !== zoom) {
|
|
t13 = (worldX, worldY) => {
|
|
if (!viewportRect) {
|
|
return;
|
|
}
|
|
const newPanX = -worldX * zoom + viewportRect.width / 2;
|
|
const newPanY = -worldY * zoom + viewportRect.height / 2;
|
|
setPan({
|
|
x: newPanX,
|
|
y: newPanY
|
|
});
|
|
};
|
|
$[6] = setPan;
|
|
$[7] = viewportRect;
|
|
$[8] = zoom;
|
|
$[9] = t13;
|
|
} else {
|
|
t13 = $[9];
|
|
}
|
|
const panToWorld = t13;
|
|
let t14;
|
|
if ($[10] !== backgroundColor || $[11] !== borderColor || $[12] !== edgeColor || $[13] !== graph || $[14] !== graphBounds || $[15] !== height || $[16] !== nodeColor || $[17] !== nodes || $[18] !== pan || $[19] !== selectedNodeColor || $[20] !== selectedNodeIds || $[21] !== viewportColor || $[22] !== viewportRect || $[23] !== width || $[24] !== zoom) {
|
|
t14 = () => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) {
|
|
return;
|
|
}
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
const dpr = window.devicePixelRatio || 1;
|
|
canvas.width = width * dpr;
|
|
canvas.height = height * dpr;
|
|
ctx.scale(dpr, dpr);
|
|
ctx.fillStyle = backgroundColor;
|
|
ctx.fillRect(0, 0, width, height);
|
|
const t_0 = graphBounds ? getMinimapTransform(graphBounds, width, height) : null;
|
|
if (!t_0) {
|
|
return;
|
|
}
|
|
const nodePositions = /* @__PURE__ */ new Map();
|
|
for (const node of nodes) {
|
|
const x = (node.position.x - t_0.minX) * t_0.scale + t_0.offsetX;
|
|
const y = (node.position.y - t_0.minY) * t_0.scale + t_0.offsetY;
|
|
const w = Math.max((node.width ?? 200) * t_0.scale, 2);
|
|
const h = Math.max((node.height ?? 100) * t_0.scale, 2);
|
|
nodePositions.set(node.id, {
|
|
cx: x + w / 2,
|
|
cy: y + h / 2
|
|
});
|
|
}
|
|
ctx.strokeStyle = edgeColor;
|
|
ctx.lineWidth = 1;
|
|
graph.forEachEdge((_key, _attrs, source, target) => {
|
|
const from = nodePositions.get(source);
|
|
const to = nodePositions.get(target);
|
|
if (!from || !to) {
|
|
return;
|
|
}
|
|
ctx.beginPath();
|
|
ctx.moveTo(from.cx, from.cy);
|
|
ctx.lineTo(to.cx, to.cy);
|
|
ctx.stroke();
|
|
});
|
|
for (const node_0 of nodes) {
|
|
const pos = nodePositions.get(node_0.id);
|
|
if (!pos) {
|
|
continue;
|
|
}
|
|
const w_0 = Math.max((node_0.width ?? 200) * t_0.scale, 2);
|
|
const h_0 = Math.max((node_0.height ?? 100) * t_0.scale, 2);
|
|
ctx.fillStyle = selectedNodeIds.has(node_0.id) ? selectedNodeColor : nodeColor;
|
|
ctx.fillRect(pos.cx - w_0 / 2, pos.cy - h_0 / 2, w_0, h_0);
|
|
}
|
|
if (viewportRect && viewportRect.width > 0) {
|
|
const vpWorldMinX = -pan.x / zoom;
|
|
const vpWorldMinY = -pan.y / zoom;
|
|
const vpWorldW = viewportRect.width / zoom;
|
|
const vpWorldH = viewportRect.height / zoom;
|
|
const vpX = (vpWorldMinX - t_0.minX) * t_0.scale + t_0.offsetX;
|
|
const vpY = (vpWorldMinY - t_0.minY) * t_0.scale + t_0.offsetY;
|
|
const vpW = vpWorldW * t_0.scale;
|
|
const vpH = vpWorldH * t_0.scale;
|
|
ctx.strokeStyle = viewportColor;
|
|
ctx.lineWidth = 1.5;
|
|
ctx.strokeRect(vpX, vpY, vpW, vpH);
|
|
ctx.fillStyle = viewportColor.replace(/[\d.]+\)$/, "0.1)");
|
|
ctx.fillRect(vpX, vpY, vpW, vpH);
|
|
}
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(0, 0, width, height);
|
|
};
|
|
$[10] = backgroundColor;
|
|
$[11] = borderColor;
|
|
$[12] = edgeColor;
|
|
$[13] = graph;
|
|
$[14] = graphBounds;
|
|
$[15] = height;
|
|
$[16] = nodeColor;
|
|
$[17] = nodes;
|
|
$[18] = pan;
|
|
$[19] = selectedNodeColor;
|
|
$[20] = selectedNodeIds;
|
|
$[21] = viewportColor;
|
|
$[22] = viewportRect;
|
|
$[23] = width;
|
|
$[24] = zoom;
|
|
$[25] = t14;
|
|
} else {
|
|
t14 = $[25];
|
|
}
|
|
let t15;
|
|
if ($[26] !== backgroundColor || $[27] !== borderColor || $[28] !== edgeColor || $[29] !== graph || $[30] !== height || $[31] !== nodeColor || $[32] !== nodes || $[33] !== pan || $[34] !== selectedNodeColor || $[35] !== selectedNodeIds || $[36] !== viewportColor || $[37] !== viewportRect || $[38] !== width || $[39] !== zoom) {
|
|
t15 = [nodes, graph, pan, zoom, viewportRect, selectedNodeIds, width, height, backgroundColor, nodeColor, selectedNodeColor, viewportColor, borderColor, edgeColor, getMinimapTransform];
|
|
$[26] = backgroundColor;
|
|
$[27] = borderColor;
|
|
$[28] = edgeColor;
|
|
$[29] = graph;
|
|
$[30] = height;
|
|
$[31] = nodeColor;
|
|
$[32] = nodes;
|
|
$[33] = pan;
|
|
$[34] = selectedNodeColor;
|
|
$[35] = selectedNodeIds;
|
|
$[36] = viewportColor;
|
|
$[37] = viewportRect;
|
|
$[38] = width;
|
|
$[39] = zoom;
|
|
$[40] = t15;
|
|
} else {
|
|
t15 = $[40];
|
|
}
|
|
useEffect22(t14, t15);
|
|
let t16;
|
|
if ($[41] !== minimapToWorldCoords || $[42] !== panToWorld) {
|
|
t16 = (e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
isDragging.current = true;
|
|
e.target.setPointerCapture(e.pointerId);
|
|
const rect = canvasRef.current?.getBoundingClientRect();
|
|
if (!rect) {
|
|
return;
|
|
}
|
|
const mx_0 = e.clientX - rect.left;
|
|
const my_0 = e.clientY - rect.top;
|
|
const worldPos = minimapToWorldCoords(mx_0, my_0);
|
|
if (worldPos) {
|
|
panToWorld(worldPos.x, worldPos.y);
|
|
}
|
|
};
|
|
$[41] = minimapToWorldCoords;
|
|
$[42] = panToWorld;
|
|
$[43] = t16;
|
|
} else {
|
|
t16 = $[43];
|
|
}
|
|
const handlePointerDown = t16;
|
|
let t17;
|
|
if ($[44] !== minimapToWorldCoords || $[45] !== panToWorld) {
|
|
t17 = (e_0) => {
|
|
if (!isDragging.current) {
|
|
return;
|
|
}
|
|
e_0.stopPropagation();
|
|
const rect_0 = canvasRef.current?.getBoundingClientRect();
|
|
if (!rect_0) {
|
|
return;
|
|
}
|
|
const mx_1 = e_0.clientX - rect_0.left;
|
|
const my_1 = e_0.clientY - rect_0.top;
|
|
const worldPos_0 = minimapToWorldCoords(mx_1, my_1);
|
|
if (worldPos_0) {
|
|
panToWorld(worldPos_0.x, worldPos_0.y);
|
|
}
|
|
};
|
|
$[44] = minimapToWorldCoords;
|
|
$[45] = panToWorld;
|
|
$[46] = t17;
|
|
} else {
|
|
t17 = $[46];
|
|
}
|
|
const handlePointerMove = t17;
|
|
let t18;
|
|
if ($[47] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t18 = (e_1) => {
|
|
isDragging.current = false;
|
|
e_1.target.releasePointerCapture(e_1.pointerId);
|
|
};
|
|
$[47] = t18;
|
|
} else {
|
|
t18 = $[47];
|
|
}
|
|
const handlePointerUp = t18;
|
|
let t19;
|
|
if ($[48] !== offset || $[49] !== position) {
|
|
t19 = {
|
|
position: "absolute",
|
|
zIndex: 9999,
|
|
...position.includes("top") ? {
|
|
top: offset
|
|
} : {
|
|
bottom: offset
|
|
},
|
|
...position.includes("left") ? {
|
|
left: offset
|
|
} : {
|
|
right: offset
|
|
}
|
|
};
|
|
$[48] = offset;
|
|
$[49] = position;
|
|
$[50] = t19;
|
|
} else {
|
|
t19 = $[50];
|
|
}
|
|
const positionStyles = t19;
|
|
let t20;
|
|
if ($[51] !== height || $[52] !== positionStyles || $[53] !== width) {
|
|
t20 = {
|
|
...positionStyles,
|
|
width,
|
|
height,
|
|
borderRadius: 6,
|
|
cursor: "pointer",
|
|
touchAction: "none"
|
|
};
|
|
$[51] = height;
|
|
$[52] = positionStyles;
|
|
$[53] = width;
|
|
$[54] = t20;
|
|
} else {
|
|
t20 = $[54];
|
|
}
|
|
let t21;
|
|
if ($[55] !== className || $[56] !== handlePointerDown || $[57] !== handlePointerMove || $[58] !== t20) {
|
|
t21 = /* @__PURE__ */ _jsx43("canvas", {
|
|
ref: canvasRef,
|
|
className,
|
|
style: t20,
|
|
onPointerDown: handlePointerDown,
|
|
onPointerMove: handlePointerMove,
|
|
onPointerUp: handlePointerUp
|
|
});
|
|
$[55] = className;
|
|
$[56] = handlePointerDown;
|
|
$[57] = handlePointerMove;
|
|
$[58] = t20;
|
|
$[59] = t21;
|
|
} else {
|
|
t21 = $[59];
|
|
}
|
|
return t21;
|
|
}
|
|
|
|
// src/components/GroupNode.tsx
|
|
init_group_store();
|
|
import { c as _c73 } from "react/compiler-runtime";
|
|
import React43 from "react";
|
|
import { useAtomValue as useAtomValue47, useSetAtom as useSetAtom35 } from "jotai";
|
|
import { jsx as _jsx44, jsxs as _jsxs26, Fragment as _Fragment9 } from "react/jsx-runtime";
|
|
function GroupNode(t0) {
|
|
const $ = _c73(42);
|
|
const {
|
|
node,
|
|
isSelected,
|
|
renderHeader,
|
|
className,
|
|
style
|
|
} = t0;
|
|
const collapsed = useAtomValue47(collapsedGroupsAtom);
|
|
const toggleCollapse = useSetAtom35(toggleGroupCollapseAtom);
|
|
const getChildCount = useAtomValue47(groupChildCountAtom);
|
|
let childCount;
|
|
let isCollapsed;
|
|
let t1;
|
|
let t2;
|
|
let t3;
|
|
let t4;
|
|
let t5;
|
|
let t6;
|
|
let t7;
|
|
if ($[0] !== className || $[1] !== collapsed || $[2] !== getChildCount || $[3] !== isSelected || $[4] !== node || $[5] !== renderHeader || $[6] !== style || $[7] !== toggleCollapse) {
|
|
isCollapsed = collapsed.has(node.id);
|
|
childCount = getChildCount(node.id);
|
|
let t82;
|
|
if ($[17] !== node.id || $[18] !== toggleCollapse) {
|
|
t82 = (e) => {
|
|
e.stopPropagation();
|
|
toggleCollapse(node.id);
|
|
};
|
|
$[17] = node.id;
|
|
$[18] = toggleCollapse;
|
|
$[19] = t82;
|
|
} else {
|
|
t82 = $[19];
|
|
}
|
|
const handleToggle = t82;
|
|
t6 = className;
|
|
const t92 = `2px ${isSelected ? "solid" : "dashed"} ${isSelected ? "#4A90D9" : "#666"}`;
|
|
const t102 = isCollapsed ? "rgba(100, 100, 100, 0.15)" : "rgba(100, 100, 100, 0.05)";
|
|
if ($[20] !== style || $[21] !== t102 || $[22] !== t92) {
|
|
t7 = {
|
|
width: "100%",
|
|
height: "100%",
|
|
border: t92,
|
|
borderRadius: "8px",
|
|
background: t102,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
overflow: "hidden",
|
|
...style
|
|
};
|
|
$[20] = style;
|
|
$[21] = t102;
|
|
$[22] = t92;
|
|
$[23] = t7;
|
|
} else {
|
|
t7 = $[23];
|
|
}
|
|
if ($[24] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t1 = {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "6px",
|
|
padding: "6px 10px",
|
|
background: "rgba(100, 100, 100, 0.2)",
|
|
borderBottom: "1px solid rgba(100, 100, 100, 0.2)",
|
|
cursor: "pointer",
|
|
userSelect: "none",
|
|
fontSize: "13px",
|
|
color: "#ccc"
|
|
};
|
|
$[24] = t1;
|
|
} else {
|
|
t1 = $[24];
|
|
}
|
|
t2 = handleToggle;
|
|
t3 = "true";
|
|
let t11;
|
|
if ($[25] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t11 = {
|
|
fontSize: "10px",
|
|
width: "14px",
|
|
textAlign: "center"
|
|
};
|
|
$[25] = t11;
|
|
} else {
|
|
t11 = $[25];
|
|
}
|
|
const t12 = isCollapsed ? "\u25B6" : "\u25BC";
|
|
if ($[26] !== t12) {
|
|
t4 = /* @__PURE__ */ _jsx44("span", {
|
|
style: t11,
|
|
children: t12
|
|
});
|
|
$[26] = t12;
|
|
$[27] = t4;
|
|
} else {
|
|
t4 = $[27];
|
|
}
|
|
t5 = renderHeader ? renderHeader({
|
|
node,
|
|
isCollapsed,
|
|
childCount
|
|
}) : /* @__PURE__ */ _jsxs26(_Fragment9, {
|
|
children: [/* @__PURE__ */ _jsx44("span", {
|
|
style: {
|
|
fontWeight: 500
|
|
},
|
|
children: node.label || "Group"
|
|
}), /* @__PURE__ */ _jsxs26("span", {
|
|
style: {
|
|
opacity: 0.6,
|
|
marginLeft: "auto"
|
|
},
|
|
children: [childCount, " node", childCount !== 1 ? "s" : ""]
|
|
})]
|
|
});
|
|
$[0] = className;
|
|
$[1] = collapsed;
|
|
$[2] = getChildCount;
|
|
$[3] = isSelected;
|
|
$[4] = node;
|
|
$[5] = renderHeader;
|
|
$[6] = style;
|
|
$[7] = toggleCollapse;
|
|
$[8] = childCount;
|
|
$[9] = isCollapsed;
|
|
$[10] = t1;
|
|
$[11] = t2;
|
|
$[12] = t3;
|
|
$[13] = t4;
|
|
$[14] = t5;
|
|
$[15] = t6;
|
|
$[16] = t7;
|
|
} else {
|
|
childCount = $[8];
|
|
isCollapsed = $[9];
|
|
t1 = $[10];
|
|
t2 = $[11];
|
|
t3 = $[12];
|
|
t4 = $[13];
|
|
t5 = $[14];
|
|
t6 = $[15];
|
|
t7 = $[16];
|
|
}
|
|
let t8;
|
|
if ($[28] !== t1 || $[29] !== t2 || $[30] !== t3 || $[31] !== t4 || $[32] !== t5) {
|
|
t8 = /* @__PURE__ */ _jsxs26("div", {
|
|
style: t1,
|
|
onClick: t2,
|
|
"data-no-drag": t3,
|
|
children: [t4, t5]
|
|
});
|
|
$[28] = t1;
|
|
$[29] = t2;
|
|
$[30] = t3;
|
|
$[31] = t4;
|
|
$[32] = t5;
|
|
$[33] = t8;
|
|
} else {
|
|
t8 = $[33];
|
|
}
|
|
let t9;
|
|
if ($[34] !== childCount || $[35] !== isCollapsed) {
|
|
t9 = isCollapsed && /* @__PURE__ */ _jsxs26("div", {
|
|
style: {
|
|
flex: 1,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
color: "#888",
|
|
fontSize: "12px",
|
|
fontStyle: "italic"
|
|
},
|
|
children: [childCount, " collapsed node", childCount !== 1 ? "s" : ""]
|
|
});
|
|
$[34] = childCount;
|
|
$[35] = isCollapsed;
|
|
$[36] = t9;
|
|
} else {
|
|
t9 = $[36];
|
|
}
|
|
let t10;
|
|
if ($[37] !== t6 || $[38] !== t7 || $[39] !== t8 || $[40] !== t9) {
|
|
t10 = /* @__PURE__ */ _jsxs26("div", {
|
|
className: t6,
|
|
style: t7,
|
|
children: [t8, t9]
|
|
});
|
|
$[37] = t6;
|
|
$[38] = t7;
|
|
$[39] = t8;
|
|
$[40] = t9;
|
|
$[41] = t10;
|
|
} else {
|
|
t10 = $[41];
|
|
}
|
|
return t10;
|
|
}
|
|
|
|
// src/components/CanvasAnimations.tsx
|
|
import { c as _c74 } from "react/compiler-runtime";
|
|
import React44 from "react";
|
|
import { jsx as _jsx45 } from "react/jsx-runtime";
|
|
var CANVAS_ANIMATION_STYLES = `
|
|
@keyframes canvas-search-pulse {
|
|
0%, 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.5); }
|
|
50% { box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.3); }
|
|
}
|
|
.canvas-search-highlight {
|
|
animation: canvas-search-pulse 1.5s ease-in-out infinite;
|
|
}
|
|
@keyframes canvas-edge-fade-in {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
@keyframes canvas-edge-fade-out {
|
|
from { opacity: 1; }
|
|
to { opacity: 0; }
|
|
}
|
|
.canvas-edge-enter {
|
|
animation: canvas-edge-fade-in 300ms ease-out forwards;
|
|
}
|
|
.canvas-edge-exit {
|
|
animation: canvas-edge-fade-out 300ms ease-out forwards;
|
|
pointer-events: none;
|
|
}
|
|
@keyframes canvas-drop-target-pulse {
|
|
0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5); }
|
|
50% { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0.3); }
|
|
}
|
|
.canvas-drop-target {
|
|
animation: canvas-drop-target-pulse 1s ease-in-out infinite;
|
|
outline: 2px dashed #3b82f6;
|
|
outline-offset: 2px;
|
|
}
|
|
@keyframes canvas-tap-pulse {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.08); }
|
|
}
|
|
.canvas-tap-pulse {
|
|
animation: canvas-tap-pulse 250ms ease-out;
|
|
}
|
|
/* iPad / touch: hide cursor indicators that aren't useful on coarse pointers */
|
|
@media (pointer: coarse) {
|
|
[data-canvas-root],
|
|
[data-canvas-root] * {
|
|
cursor: default !important;
|
|
}
|
|
}
|
|
/* Respect prefers-reduced-motion: disable canvas CSS animations */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.canvas-search-highlight,
|
|
.canvas-drop-target,
|
|
.canvas-tap-pulse {
|
|
animation: none !important;
|
|
}
|
|
.canvas-edge-enter {
|
|
animation: none !important;
|
|
opacity: 1;
|
|
}
|
|
.canvas-edge-exit {
|
|
animation: none !important;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
`;
|
|
function CanvasAnimations() {
|
|
const $ = _c74(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsx45("style", {
|
|
dangerouslySetInnerHTML: {
|
|
__html: CANVAS_ANIMATION_STYLES
|
|
}
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
|
|
// src/components/TouchActionButton/index.tsx
|
|
init_input_store();
|
|
init_external_keyboard_store();
|
|
init_selection_store();
|
|
init_graph_derived();
|
|
init_graph_mutations();
|
|
init_clipboard_store();
|
|
init_history_store();
|
|
import { c as _c77 } from "react/compiler-runtime";
|
|
import React47, { useState as useState11, useRef as useRef30 } from "react";
|
|
import { useAtomValue as useAtomValue48, useSetAtom as useSetAtom36 } from "jotai";
|
|
|
|
// src/components/TouchActionButton/icons.tsx
|
|
import { c as _c75 } from "react/compiler-runtime";
|
|
import React45 from "react";
|
|
import { jsx as _jsx46, jsxs as _jsxs27 } from "react/jsx-runtime";
|
|
function PlusIcon2() {
|
|
const $ = _c75(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs27("svg", {
|
|
width: "24",
|
|
height: "24",
|
|
viewBox: "0 0 24 24",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "2.5",
|
|
strokeLinecap: "round",
|
|
children: [/* @__PURE__ */ _jsx46("line", {
|
|
x1: "12",
|
|
y1: "6",
|
|
x2: "12",
|
|
y2: "18"
|
|
}), /* @__PURE__ */ _jsx46("line", {
|
|
x1: "6",
|
|
y1: "12",
|
|
x2: "18",
|
|
y2: "12"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function MergeIcon() {
|
|
const $ = _c75(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs27("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 20 20",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.5",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
children: [/* @__PURE__ */ _jsx46("path", {
|
|
d: "M6 4 L10 10 L6 16"
|
|
}), /* @__PURE__ */ _jsx46("path", {
|
|
d: "M14 4 L10 10 L14 16"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function DuplicateIcon() {
|
|
const $ = _c75(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs27("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 20 20",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.5",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
children: [/* @__PURE__ */ _jsx46("rect", {
|
|
x: "3",
|
|
y: "5",
|
|
width: "10",
|
|
height: "10",
|
|
rx: "2"
|
|
}), /* @__PURE__ */ _jsx46("rect", {
|
|
x: "7",
|
|
y: "5",
|
|
width: "10",
|
|
height: "10",
|
|
rx: "2"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function SelectAllIcon() {
|
|
const $ = _c75(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs27("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 20 20",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.5",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
children: [/* @__PURE__ */ _jsx46("rect", {
|
|
x: "3",
|
|
y: "3",
|
|
width: "14",
|
|
height: "14",
|
|
rx: "2",
|
|
strokeDasharray: "3 2"
|
|
}), /* @__PURE__ */ _jsx46("polyline", {
|
|
points: "7 10 9 12 13 8"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function SearchIcon() {
|
|
const $ = _c75(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs27("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 20 20",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.5",
|
|
strokeLinecap: "round",
|
|
children: [/* @__PURE__ */ _jsx46("circle", {
|
|
cx: "9",
|
|
cy: "9",
|
|
r: "5"
|
|
}), /* @__PURE__ */ _jsx46("line", {
|
|
x1: "13",
|
|
y1: "13",
|
|
x2: "17",
|
|
y2: "17"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function UndoIcon() {
|
|
const $ = _c75(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs27("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 20 20",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.5",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
children: [/* @__PURE__ */ _jsx46("polyline", {
|
|
points: "5 9 2 6 5 3"
|
|
}), /* @__PURE__ */ _jsx46("path", {
|
|
d: "M2 6h10a5 5 0 0 1 0 10H8"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function RedoIcon() {
|
|
const $ = _c75(1);
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = /* @__PURE__ */ _jsxs27("svg", {
|
|
width: "20",
|
|
height: "20",
|
|
viewBox: "0 0 20 20",
|
|
fill: "none",
|
|
stroke: "currentColor",
|
|
strokeWidth: "1.5",
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
children: [/* @__PURE__ */ _jsx46("polyline", {
|
|
points: "15 9 18 6 15 3"
|
|
}), /* @__PURE__ */ _jsx46("path", {
|
|
d: "M18 6H8a5 5 0 0 0 0 10h4"
|
|
})]
|
|
});
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
|
|
// src/components/TouchActionButton/RadialMenu.tsx
|
|
import { c as _c76 } from "react/compiler-runtime";
|
|
import React46 from "react";
|
|
import { jsx as _jsx47, jsxs as _jsxs28 } from "react/jsx-runtime";
|
|
var FAB_SIZE = 56;
|
|
var ITEM_SIZE = 48;
|
|
var STAGGER_MS = 35;
|
|
function getRadialPositions(count, radius, position) {
|
|
let startAngle;
|
|
let endAngle;
|
|
switch (position) {
|
|
case "bottom-left":
|
|
startAngle = -20;
|
|
endAngle = -160;
|
|
break;
|
|
case "bottom-right":
|
|
startAngle = -160;
|
|
endAngle = -20;
|
|
break;
|
|
case "top-left":
|
|
startAngle = 20;
|
|
endAngle = 160;
|
|
break;
|
|
case "top-right":
|
|
startAngle = 160;
|
|
endAngle = 20;
|
|
break;
|
|
default:
|
|
startAngle = -20;
|
|
endAngle = -160;
|
|
}
|
|
const positions = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const t = count === 1 ? 0.5 : i / (count - 1);
|
|
const angleDeg = startAngle + (endAngle - startAngle) * t;
|
|
const angleRad = angleDeg * Math.PI / 180;
|
|
positions.push({
|
|
dx: Math.cos(angleRad) * radius,
|
|
dy: Math.sin(angleRad) * radius,
|
|
angle: angleDeg
|
|
});
|
|
}
|
|
return positions;
|
|
}
|
|
function getPositionStyles2(position) {
|
|
switch (position) {
|
|
case "bottom-left":
|
|
return {
|
|
bottom: "max(16px, env(safe-area-inset-bottom, 16px))",
|
|
left: "max(16px, env(safe-area-inset-left, 16px))"
|
|
};
|
|
case "bottom-right":
|
|
return {
|
|
bottom: "max(16px, env(safe-area-inset-bottom, 16px))",
|
|
right: "max(16px, env(safe-area-inset-right, 16px))"
|
|
};
|
|
case "top-left":
|
|
return {
|
|
top: "max(16px, env(safe-area-inset-top, 16px))",
|
|
left: "max(16px, env(safe-area-inset-left, 16px))"
|
|
};
|
|
case "top-right":
|
|
return {
|
|
top: "max(16px, env(safe-area-inset-top, 16px))",
|
|
right: "max(16px, env(safe-area-inset-right, 16px))"
|
|
};
|
|
default:
|
|
return {
|
|
bottom: "max(16px, env(safe-area-inset-bottom, 16px))",
|
|
left: "max(16px, env(safe-area-inset-left, 16px))"
|
|
};
|
|
}
|
|
}
|
|
function RadialMenuItem(t0) {
|
|
const $ = _c76(31);
|
|
const {
|
|
item,
|
|
position: pos,
|
|
index: i,
|
|
totalItems,
|
|
isOpen,
|
|
isHighlighted,
|
|
onTap
|
|
} = t0;
|
|
const deployed = isOpen;
|
|
const t1 = deployed ? `translate(${pos.dx}px, ${pos.dy}px) scale(1)` : "translate(0, 0) scale(0)";
|
|
const t2 = deployed ? item.disabled ? 0.35 : 1 : 0;
|
|
const t3 = deployed ? `transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1) ${i * STAGGER_MS}ms, opacity 200ms ease ${i * STAGGER_MS}ms` : `transform 200ms cubic-bezier(0.55, 0, 1, 0.45) ${(totalItems - 1 - i) * 20}ms, opacity 150ms ease ${(totalItems - 1 - i) * 20}ms`;
|
|
const t4 = deployed ? "auto" : "none";
|
|
let t5;
|
|
if ($[0] !== t1 || $[1] !== t2 || $[2] !== t3 || $[3] !== t4) {
|
|
t5 = {
|
|
position: "absolute",
|
|
left: FAB_SIZE / 2 - ITEM_SIZE / 2,
|
|
top: FAB_SIZE / 2 - ITEM_SIZE / 2,
|
|
transform: t1,
|
|
opacity: t2,
|
|
transition: t3,
|
|
pointerEvents: t4,
|
|
willChange: "transform, opacity"
|
|
};
|
|
$[0] = t1;
|
|
$[1] = t2;
|
|
$[2] = t3;
|
|
$[3] = t4;
|
|
$[4] = t5;
|
|
} else {
|
|
t5 = $[4];
|
|
}
|
|
let t6;
|
|
if ($[5] !== item || $[6] !== onTap) {
|
|
t6 = () => onTap(item);
|
|
$[5] = item;
|
|
$[6] = onTap;
|
|
$[7] = t6;
|
|
} else {
|
|
t6 = $[7];
|
|
}
|
|
const t7 = `1.5px solid ${isHighlighted ? "rgba(99,160,255,0.5)" : "var(--canvas-control-border, rgba(255,255,255,0.12))"}`;
|
|
const t8 = isHighlighted ? "var(--canvas-control-active-bg, rgba(59,130,246,0.9))" : "var(--canvas-control-bg, rgba(30,30,30,0.88))";
|
|
const t9 = item.disabled ? "default" : "pointer";
|
|
const t10 = isHighlighted ? "scale(1.18)" : "scale(1)";
|
|
const t11 = isHighlighted ? "0 0 0 3px rgba(59,130,246,0.25), 0 4px 16px rgba(59,130,246,0.35)" : "0 2px 8px rgba(0,0,0,0.35)";
|
|
let t12;
|
|
if ($[8] !== t10 || $[9] !== t11 || $[10] !== t7 || $[11] !== t8 || $[12] !== t9) {
|
|
t12 = {
|
|
width: ITEM_SIZE,
|
|
height: ITEM_SIZE,
|
|
borderRadius: "50%",
|
|
border: t7,
|
|
backgroundColor: t8,
|
|
color: "var(--canvas-control-fg, rgba(255,255,255,0.9))",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
cursor: t9,
|
|
touchAction: "manipulation",
|
|
WebkitTapHighlightColor: "transparent",
|
|
userSelect: "none",
|
|
backdropFilter: "blur(16px) saturate(1.5)",
|
|
WebkitBackdropFilter: "blur(16px) saturate(1.5)",
|
|
padding: 0,
|
|
transition: "background-color 180ms ease, border-color 180ms ease, transform 180ms cubic-bezier(0.34, 1.2, 0.64, 1), box-shadow 250ms ease",
|
|
transform: t10,
|
|
boxShadow: t11
|
|
};
|
|
$[8] = t10;
|
|
$[9] = t11;
|
|
$[10] = t7;
|
|
$[11] = t8;
|
|
$[12] = t9;
|
|
$[13] = t12;
|
|
} else {
|
|
t12 = $[13];
|
|
}
|
|
let t13;
|
|
if ($[14] !== item.disabled || $[15] !== item.icon || $[16] !== item.label || $[17] !== t12 || $[18] !== t6) {
|
|
t13 = /* @__PURE__ */ _jsx47("button", {
|
|
onPointerUp: t6,
|
|
disabled: item.disabled,
|
|
"aria-label": item.label,
|
|
"data-no-drag": "true",
|
|
style: t12,
|
|
children: item.icon
|
|
});
|
|
$[14] = item.disabled;
|
|
$[15] = item.icon;
|
|
$[16] = item.label;
|
|
$[17] = t12;
|
|
$[18] = t6;
|
|
$[19] = t13;
|
|
} else {
|
|
t13 = $[19];
|
|
}
|
|
const t14 = isHighlighted ? 1 : 0;
|
|
const t15 = isHighlighted ? "translateX(-50%) translateY(0)" : "translateX(-50%) translateY(4px)";
|
|
let t16;
|
|
if ($[20] !== t14 || $[21] !== t15) {
|
|
t16 = {
|
|
position: "absolute",
|
|
top: -24,
|
|
left: "50%",
|
|
fontSize: "11px",
|
|
fontWeight: 600,
|
|
letterSpacing: "0.02em",
|
|
color: "var(--canvas-control-fg, rgba(255,255,255,0.95))",
|
|
backgroundColor: "var(--canvas-control-bg, rgba(20,20,20,0.92))",
|
|
backdropFilter: "blur(12px)",
|
|
WebkitBackdropFilter: "blur(12px)",
|
|
padding: "3px 9px",
|
|
borderRadius: "7px",
|
|
whiteSpace: "nowrap",
|
|
pointerEvents: "none",
|
|
opacity: t14,
|
|
transform: t15,
|
|
transition: "opacity 150ms ease, transform 150ms ease"
|
|
};
|
|
$[20] = t14;
|
|
$[21] = t15;
|
|
$[22] = t16;
|
|
} else {
|
|
t16 = $[22];
|
|
}
|
|
let t17;
|
|
if ($[23] !== item.label || $[24] !== t16) {
|
|
t17 = /* @__PURE__ */ _jsx47("span", {
|
|
style: t16,
|
|
children: item.label
|
|
});
|
|
$[23] = item.label;
|
|
$[24] = t16;
|
|
$[25] = t17;
|
|
} else {
|
|
t17 = $[25];
|
|
}
|
|
let t18;
|
|
if ($[26] !== item.id || $[27] !== t13 || $[28] !== t17 || $[29] !== t5) {
|
|
t18 = /* @__PURE__ */ _jsxs28("div", {
|
|
style: t5,
|
|
children: [t13, t17]
|
|
}, item.id);
|
|
$[26] = item.id;
|
|
$[27] = t13;
|
|
$[28] = t17;
|
|
$[29] = t5;
|
|
$[30] = t18;
|
|
} else {
|
|
t18 = $[30];
|
|
}
|
|
return t18;
|
|
}
|
|
|
|
// src/components/TouchActionButton/index.tsx
|
|
import { jsx as _jsx48, jsxs as _jsxs29 } from "react/jsx-runtime";
|
|
function TouchActionButton(t0) {
|
|
const $ = _c77(95);
|
|
const {
|
|
position: t1,
|
|
alwaysVisible: t2,
|
|
className,
|
|
style,
|
|
holdDelay: t3,
|
|
radius: t4
|
|
} = t0;
|
|
const position = t1 === void 0 ? "bottom-left" : t1;
|
|
const alwaysVisible = t2 === void 0 ? false : t2;
|
|
const holdDelay = t3 === void 0 ? 250 : t3;
|
|
const radius = t4 === void 0 ? 80 : t4;
|
|
const isTouchDevice = useAtomValue48(isTouchDeviceAtom);
|
|
const hasExternalKeyboard = useAtomValue48(hasExternalKeyboardAtom);
|
|
const [isOpen, setIsOpen] = useState11(false);
|
|
const [highlightedId, setHighlightedId] = useState11(null);
|
|
const selectedNodeIds = useAtomValue48(selectedNodeIdsAtom);
|
|
const nodeKeys = useAtomValue48(nodeKeysAtom);
|
|
const mergeNodes = useSetAtom36(mergeNodesAtom);
|
|
const duplicateSelection = useSetAtom36(duplicateSelectionAtom);
|
|
const clearSelection = useSetAtom36(clearSelectionAtom);
|
|
const addNodesToSelection = useSetAtom36(addNodesToSelectionAtom);
|
|
const openCommandLine = useSetAtom36(openCommandLineAtom);
|
|
const undo = useSetAtom36(undoAtom);
|
|
const redo = useSetAtom36(redoAtom);
|
|
const canUndo = useAtomValue48(canUndoAtom);
|
|
const canRedo = useAtomValue48(canRedoAtom);
|
|
const holdTimerRef = useRef30(null);
|
|
const containerRef = useRef30(null);
|
|
const fabCenterRef = useRef30(null);
|
|
const didOpenRef = useRef30(false);
|
|
if (!alwaysVisible && (!isTouchDevice || hasExternalKeyboard)) {
|
|
return null;
|
|
}
|
|
const hasSelection = selectedNodeIds.size > 0;
|
|
const canMerge = selectedNodeIds.size >= 2;
|
|
let t5;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t5 = /* @__PURE__ */ _jsx48(UndoIcon, {});
|
|
$[0] = t5;
|
|
} else {
|
|
t5 = $[0];
|
|
}
|
|
const t6 = !canUndo;
|
|
let t7;
|
|
if ($[1] !== undo) {
|
|
t7 = () => undo();
|
|
$[1] = undo;
|
|
$[2] = t7;
|
|
} else {
|
|
t7 = $[2];
|
|
}
|
|
let t8;
|
|
if ($[3] !== t6 || $[4] !== t7) {
|
|
t8 = {
|
|
id: "undo",
|
|
label: "Undo",
|
|
icon: t5,
|
|
disabled: t6,
|
|
action: t7
|
|
};
|
|
$[3] = t6;
|
|
$[4] = t7;
|
|
$[5] = t8;
|
|
} else {
|
|
t8 = $[5];
|
|
}
|
|
let t9;
|
|
if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t9 = /* @__PURE__ */ _jsx48(RedoIcon, {});
|
|
$[6] = t9;
|
|
} else {
|
|
t9 = $[6];
|
|
}
|
|
const t10 = !canRedo;
|
|
let t11;
|
|
if ($[7] !== redo) {
|
|
t11 = () => redo();
|
|
$[7] = redo;
|
|
$[8] = t11;
|
|
} else {
|
|
t11 = $[8];
|
|
}
|
|
let t12;
|
|
if ($[9] !== t10 || $[10] !== t11) {
|
|
t12 = {
|
|
id: "redo",
|
|
label: "Redo",
|
|
icon: t9,
|
|
disabled: t10,
|
|
action: t11
|
|
};
|
|
$[9] = t10;
|
|
$[10] = t11;
|
|
$[11] = t12;
|
|
} else {
|
|
t12 = $[11];
|
|
}
|
|
let t13;
|
|
if ($[12] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t13 = /* @__PURE__ */ _jsx48(MergeIcon, {});
|
|
$[12] = t13;
|
|
} else {
|
|
t13 = $[12];
|
|
}
|
|
const t14 = !canMerge;
|
|
let t15;
|
|
if ($[13] !== canMerge || $[14] !== mergeNodes || $[15] !== selectedNodeIds) {
|
|
t15 = () => {
|
|
if (canMerge) {
|
|
mergeNodes({
|
|
nodeIds: Array.from(selectedNodeIds)
|
|
});
|
|
}
|
|
};
|
|
$[13] = canMerge;
|
|
$[14] = mergeNodes;
|
|
$[15] = selectedNodeIds;
|
|
$[16] = t15;
|
|
} else {
|
|
t15 = $[16];
|
|
}
|
|
let t16;
|
|
if ($[17] !== t14 || $[18] !== t15) {
|
|
t16 = {
|
|
id: "merge",
|
|
label: "Merge",
|
|
icon: t13,
|
|
disabled: t14,
|
|
action: t15
|
|
};
|
|
$[17] = t14;
|
|
$[18] = t15;
|
|
$[19] = t16;
|
|
} else {
|
|
t16 = $[19];
|
|
}
|
|
let t17;
|
|
if ($[20] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t17 = /* @__PURE__ */ _jsx48(DuplicateIcon, {});
|
|
$[20] = t17;
|
|
} else {
|
|
t17 = $[20];
|
|
}
|
|
const t18 = !hasSelection;
|
|
let t19;
|
|
if ($[21] !== duplicateSelection || $[22] !== hasSelection) {
|
|
t19 = () => {
|
|
if (hasSelection) {
|
|
duplicateSelection();
|
|
}
|
|
};
|
|
$[21] = duplicateSelection;
|
|
$[22] = hasSelection;
|
|
$[23] = t19;
|
|
} else {
|
|
t19 = $[23];
|
|
}
|
|
let t20;
|
|
if ($[24] !== t18 || $[25] !== t19) {
|
|
t20 = {
|
|
id: "duplicate",
|
|
label: "Duplicate",
|
|
icon: t17,
|
|
disabled: t18,
|
|
action: t19
|
|
};
|
|
$[24] = t18;
|
|
$[25] = t19;
|
|
$[26] = t20;
|
|
} else {
|
|
t20 = $[26];
|
|
}
|
|
let t21;
|
|
if ($[27] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t21 = /* @__PURE__ */ _jsx48(SelectAllIcon, {});
|
|
$[27] = t21;
|
|
} else {
|
|
t21 = $[27];
|
|
}
|
|
let t22;
|
|
if ($[28] !== addNodesToSelection || $[29] !== clearSelection || $[30] !== nodeKeys) {
|
|
t22 = {
|
|
id: "select-all",
|
|
label: "Select All",
|
|
icon: t21,
|
|
action: () => {
|
|
clearSelection();
|
|
addNodesToSelection(nodeKeys);
|
|
}
|
|
};
|
|
$[28] = addNodesToSelection;
|
|
$[29] = clearSelection;
|
|
$[30] = nodeKeys;
|
|
$[31] = t22;
|
|
} else {
|
|
t22 = $[31];
|
|
}
|
|
let t23;
|
|
if ($[32] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t23 = /* @__PURE__ */ _jsx48(SearchIcon, {});
|
|
$[32] = t23;
|
|
} else {
|
|
t23 = $[32];
|
|
}
|
|
let t24;
|
|
if ($[33] !== openCommandLine) {
|
|
t24 = {
|
|
id: "search",
|
|
label: "Search",
|
|
icon: t23,
|
|
action: () => openCommandLine()
|
|
};
|
|
$[33] = openCommandLine;
|
|
$[34] = t24;
|
|
} else {
|
|
t24 = $[34];
|
|
}
|
|
let t25;
|
|
if ($[35] !== t12 || $[36] !== t16 || $[37] !== t20 || $[38] !== t22 || $[39] !== t24 || $[40] !== t8) {
|
|
t25 = [t8, t12, t16, t20, t22, t24];
|
|
$[35] = t12;
|
|
$[36] = t16;
|
|
$[37] = t20;
|
|
$[38] = t22;
|
|
$[39] = t24;
|
|
$[40] = t8;
|
|
$[41] = t25;
|
|
} else {
|
|
t25 = $[41];
|
|
}
|
|
const actions = t25;
|
|
let t26;
|
|
if ($[42] !== actions.length || $[43] !== position || $[44] !== radius) {
|
|
t26 = getRadialPositions(actions.length, radius, position);
|
|
$[42] = actions.length;
|
|
$[43] = position;
|
|
$[44] = radius;
|
|
$[45] = t26;
|
|
} else {
|
|
t26 = $[45];
|
|
}
|
|
const itemPositions = t26;
|
|
let t27;
|
|
if ($[46] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t27 = () => {
|
|
if (holdTimerRef.current) {
|
|
clearTimeout(holdTimerRef.current);
|
|
holdTimerRef.current = null;
|
|
}
|
|
};
|
|
$[46] = t27;
|
|
} else {
|
|
t27 = $[46];
|
|
}
|
|
const clearHoldTimer = t27;
|
|
let t28;
|
|
if ($[47] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t28 = () => {
|
|
const container = containerRef.current;
|
|
if (!container) {
|
|
return null;
|
|
}
|
|
const fab = container.querySelector("[data-fab-button]");
|
|
if (!fab) {
|
|
return null;
|
|
}
|
|
const rect = fab.getBoundingClientRect();
|
|
return {
|
|
x: rect.left + rect.width / 2,
|
|
y: rect.top + rect.height / 2
|
|
};
|
|
};
|
|
$[47] = t28;
|
|
} else {
|
|
t28 = $[47];
|
|
}
|
|
const getFabCenter = t28;
|
|
let t29;
|
|
if ($[48] !== actions || $[49] !== itemPositions) {
|
|
t29 = (clientX, clientY) => {
|
|
const center = fabCenterRef.current;
|
|
if (!center) {
|
|
return null;
|
|
}
|
|
let closest = null;
|
|
let closestDist = Infinity;
|
|
const hitRadius = ITEM_SIZE * 0.75;
|
|
for (let i = 0; i < actions.length; i++) {
|
|
const pos = itemPositions[i];
|
|
const itemX = center.x + pos.dx;
|
|
const itemY = center.y + pos.dy;
|
|
const dist = Math.hypot(clientX - itemX, clientY - itemY);
|
|
if (dist < hitRadius && dist < closestDist && !actions[i].disabled) {
|
|
closest = actions[i].id;
|
|
closestDist = dist;
|
|
}
|
|
}
|
|
return closest;
|
|
};
|
|
$[48] = actions;
|
|
$[49] = itemPositions;
|
|
$[50] = t29;
|
|
} else {
|
|
t29 = $[50];
|
|
}
|
|
const findClosestItem = t29;
|
|
let t30;
|
|
if ($[51] !== holdDelay) {
|
|
t30 = (e) => {
|
|
e.target.setPointerCapture(e.pointerId);
|
|
fabCenterRef.current = getFabCenter();
|
|
didOpenRef.current = false;
|
|
holdTimerRef.current = setTimeout(() => {
|
|
setIsOpen(true);
|
|
didOpenRef.current = true;
|
|
holdTimerRef.current = null;
|
|
}, holdDelay);
|
|
};
|
|
$[51] = holdDelay;
|
|
$[52] = t30;
|
|
} else {
|
|
t30 = $[52];
|
|
}
|
|
const handlePointerDown = t30;
|
|
let t31;
|
|
if ($[53] !== findClosestItem) {
|
|
t31 = (e_0) => {
|
|
if (!didOpenRef.current) {
|
|
return;
|
|
}
|
|
setHighlightedId(findClosestItem(e_0.clientX, e_0.clientY));
|
|
};
|
|
$[53] = findClosestItem;
|
|
$[54] = t31;
|
|
} else {
|
|
t31 = $[54];
|
|
}
|
|
const handlePointerMove = t31;
|
|
let t32;
|
|
if ($[55] !== actions || $[56] !== findClosestItem) {
|
|
t32 = (e_1) => {
|
|
clearHoldTimer();
|
|
if (didOpenRef.current) {
|
|
const id = findClosestItem(e_1.clientX, e_1.clientY);
|
|
if (id) {
|
|
const item = actions.find((a) => a.id === id);
|
|
if (item && !item.disabled) {
|
|
item.action();
|
|
}
|
|
}
|
|
setIsOpen(false);
|
|
setHighlightedId(null);
|
|
didOpenRef.current = false;
|
|
} else {
|
|
clearHoldTimer();
|
|
setIsOpen((o) => {
|
|
if (o) {
|
|
setHighlightedId(null);
|
|
return false;
|
|
}
|
|
didOpenRef.current = true;
|
|
fabCenterRef.current = getFabCenter();
|
|
return true;
|
|
});
|
|
}
|
|
};
|
|
$[55] = actions;
|
|
$[56] = findClosestItem;
|
|
$[57] = t32;
|
|
} else {
|
|
t32 = $[57];
|
|
}
|
|
const handlePointerUp = t32;
|
|
let t33;
|
|
if ($[58] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t33 = () => {
|
|
clearHoldTimer();
|
|
setIsOpen(false);
|
|
setHighlightedId(null);
|
|
didOpenRef.current = false;
|
|
};
|
|
$[58] = t33;
|
|
} else {
|
|
t33 = $[58];
|
|
}
|
|
const handlePointerCancel = t33;
|
|
let t34;
|
|
if ($[59] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t34 = (item_0) => {
|
|
if (item_0.disabled) {
|
|
return;
|
|
}
|
|
item_0.action();
|
|
setIsOpen(false);
|
|
setHighlightedId(null);
|
|
didOpenRef.current = false;
|
|
};
|
|
$[59] = t34;
|
|
} else {
|
|
t34 = $[59];
|
|
}
|
|
const handleItemTap = t34;
|
|
let t35;
|
|
if ($[60] !== position) {
|
|
t35 = getPositionStyles2(position);
|
|
$[60] = position;
|
|
$[61] = t35;
|
|
} else {
|
|
t35 = $[61];
|
|
}
|
|
const positionStyles = t35;
|
|
let t36;
|
|
if ($[62] !== positionStyles || $[63] !== style) {
|
|
t36 = {
|
|
position: "absolute",
|
|
...positionStyles,
|
|
zIndex: 100,
|
|
pointerEvents: "auto",
|
|
...style
|
|
};
|
|
$[62] = positionStyles;
|
|
$[63] = style;
|
|
$[64] = t36;
|
|
} else {
|
|
t36 = $[64];
|
|
}
|
|
let t37;
|
|
if ($[65] !== actions || $[66] !== highlightedId || $[67] !== isOpen || $[68] !== itemPositions) {
|
|
t37 = actions.map((item_1, i_0) => /* @__PURE__ */ _jsx48(RadialMenuItem, {
|
|
item: item_1,
|
|
position: itemPositions[i_0],
|
|
index: i_0,
|
|
totalItems: actions.length,
|
|
isOpen,
|
|
isHighlighted: highlightedId === item_1.id,
|
|
onTap: handleItemTap
|
|
}, item_1.id));
|
|
$[65] = actions;
|
|
$[66] = highlightedId;
|
|
$[67] = isOpen;
|
|
$[68] = itemPositions;
|
|
$[69] = t37;
|
|
} else {
|
|
t37 = $[69];
|
|
}
|
|
const t38 = radius * 2 + ITEM_SIZE;
|
|
const t39 = radius * 2 + ITEM_SIZE;
|
|
const t40 = isOpen ? "translate(-50%, -50%) scale(1)" : "translate(-50%, -50%) scale(0)";
|
|
const t41 = isOpen ? "transform 400ms cubic-bezier(0.34, 1.3, 0.64, 1), opacity 300ms ease" : "transform 250ms ease, opacity 150ms ease";
|
|
const t42 = isOpen ? 1 : 0;
|
|
let t43;
|
|
if ($[70] !== t38 || $[71] !== t39 || $[72] !== t40 || $[73] !== t41 || $[74] !== t42) {
|
|
t43 = /* @__PURE__ */ _jsx48("div", {
|
|
style: {
|
|
position: "absolute",
|
|
left: FAB_SIZE / 2,
|
|
top: FAB_SIZE / 2,
|
|
width: t38,
|
|
height: t39,
|
|
borderRadius: "50%",
|
|
transform: t40,
|
|
background: "radial-gradient(circle, rgba(255,255,255,0.03) 0%, transparent 70%)",
|
|
transition: t41,
|
|
opacity: t42,
|
|
pointerEvents: "none"
|
|
}
|
|
});
|
|
$[70] = t38;
|
|
$[71] = t39;
|
|
$[72] = t40;
|
|
$[73] = t41;
|
|
$[74] = t42;
|
|
$[75] = t43;
|
|
} else {
|
|
t43 = $[75];
|
|
}
|
|
const t44 = isOpen ? "Close actions" : "Hold for actions";
|
|
const t45 = `1.5px solid ${isOpen ? "rgba(99,160,255,0.4)" : "var(--canvas-control-border, rgba(255,255,255,0.15))"}`;
|
|
const t46 = isOpen ? "var(--canvas-control-active-bg, rgba(59,130,246,0.9))" : "var(--canvas-control-bg, rgba(30,30,30,0.88))";
|
|
const t47 = isOpen ? "rotate(45deg) scale(1.05)" : "rotate(0deg) scale(1)";
|
|
const t48 = isOpen ? "0 0 0 4px rgba(59,130,246,0.15), 0 6px 20px rgba(0,0,0,0.35)" : "0 4px 12px rgba(0,0,0,0.3)";
|
|
let t49;
|
|
if ($[76] !== t45 || $[77] !== t46 || $[78] !== t47 || $[79] !== t48) {
|
|
t49 = {
|
|
position: "relative",
|
|
width: FAB_SIZE,
|
|
height: FAB_SIZE,
|
|
borderRadius: "50%",
|
|
border: t45,
|
|
backgroundColor: t46,
|
|
color: "var(--canvas-control-fg, rgba(255,255,255,0.95))",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
cursor: "pointer",
|
|
touchAction: "none",
|
|
WebkitTapHighlightColor: "transparent",
|
|
userSelect: "none",
|
|
backdropFilter: "blur(16px) saturate(1.5)",
|
|
WebkitBackdropFilter: "blur(16px) saturate(1.5)",
|
|
padding: 0,
|
|
transition: "background-color 250ms ease, border-color 250ms ease, transform 350ms cubic-bezier(0.34, 1.3, 0.64, 1), box-shadow 300ms ease",
|
|
transform: t47,
|
|
boxShadow: t48
|
|
};
|
|
$[76] = t45;
|
|
$[77] = t46;
|
|
$[78] = t47;
|
|
$[79] = t48;
|
|
$[80] = t49;
|
|
} else {
|
|
t49 = $[80];
|
|
}
|
|
let t50;
|
|
if ($[81] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t50 = /* @__PURE__ */ _jsx48(PlusIcon2, {});
|
|
$[81] = t50;
|
|
} else {
|
|
t50 = $[81];
|
|
}
|
|
let t51;
|
|
if ($[82] !== handlePointerDown || $[83] !== handlePointerMove || $[84] !== handlePointerUp || $[85] !== isOpen || $[86] !== t44 || $[87] !== t49) {
|
|
t51 = /* @__PURE__ */ _jsx48("button", {
|
|
"data-fab-button": "true",
|
|
onPointerDown: handlePointerDown,
|
|
onPointerMove: handlePointerMove,
|
|
onPointerUp: handlePointerUp,
|
|
onPointerCancel: handlePointerCancel,
|
|
"aria-label": t44,
|
|
"aria-expanded": isOpen,
|
|
"data-no-drag": "true",
|
|
style: t49,
|
|
children: t50
|
|
});
|
|
$[82] = handlePointerDown;
|
|
$[83] = handlePointerMove;
|
|
$[84] = handlePointerUp;
|
|
$[85] = isOpen;
|
|
$[86] = t44;
|
|
$[87] = t49;
|
|
$[88] = t51;
|
|
} else {
|
|
t51 = $[88];
|
|
}
|
|
let t52;
|
|
if ($[89] !== className || $[90] !== t36 || $[91] !== t37 || $[92] !== t43 || $[93] !== t51) {
|
|
t52 = /* @__PURE__ */ _jsxs29("div", {
|
|
ref: containerRef,
|
|
className,
|
|
"data-no-drag": "true",
|
|
style: t36,
|
|
children: [t37, t43, t51]
|
|
});
|
|
$[89] = className;
|
|
$[90] = t36;
|
|
$[91] = t37;
|
|
$[92] = t43;
|
|
$[93] = t51;
|
|
$[94] = t52;
|
|
} else {
|
|
t52 = $[94];
|
|
}
|
|
return t52;
|
|
}
|
|
|
|
// src/components/CanvasToast.tsx
|
|
init_toast_store();
|
|
import { c as _c78 } from "react/compiler-runtime";
|
|
import React48 from "react";
|
|
import { useAtomValue as useAtomValue49 } from "jotai";
|
|
import { jsx as _jsx49 } from "react/jsx-runtime";
|
|
function CanvasToast(t0) {
|
|
const $ = _c78(12);
|
|
const {
|
|
className,
|
|
style
|
|
} = t0;
|
|
const toast = useAtomValue49(canvasToastAtom);
|
|
let t1;
|
|
if ($[0] !== style) {
|
|
t1 = {
|
|
position: "absolute",
|
|
bottom: "24px",
|
|
left: "50%",
|
|
transform: "translateX(-50%)",
|
|
zIndex: 200,
|
|
pointerEvents: "none",
|
|
...style
|
|
};
|
|
$[0] = style;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const t2 = toast ? 1 : 0;
|
|
const t3 = toast ? "translateY(0)" : "translateY(8px)";
|
|
let t4;
|
|
if ($[2] !== t2 || $[3] !== t3) {
|
|
t4 = {
|
|
fontSize: "13px",
|
|
fontWeight: 500,
|
|
color: "var(--canvas-control-fg, rgba(255,255,255,0.95))",
|
|
backgroundColor: "var(--canvas-control-bg, rgba(30,30,30,0.92))",
|
|
backdropFilter: "blur(12px)",
|
|
WebkitBackdropFilter: "blur(12px)",
|
|
border: "1px solid var(--canvas-control-border, rgba(255,255,255,0.1))",
|
|
padding: "6px 16px",
|
|
borderRadius: "8px",
|
|
whiteSpace: "nowrap",
|
|
opacity: t2,
|
|
transform: t3,
|
|
transition: "opacity 200ms ease, transform 200ms ease"
|
|
};
|
|
$[2] = t2;
|
|
$[3] = t3;
|
|
$[4] = t4;
|
|
} else {
|
|
t4 = $[4];
|
|
}
|
|
const t5 = toast?.message ?? "";
|
|
let t6;
|
|
if ($[5] !== t4 || $[6] !== t5) {
|
|
t6 = /* @__PURE__ */ _jsx49("div", {
|
|
style: t4,
|
|
children: t5
|
|
});
|
|
$[5] = t4;
|
|
$[6] = t5;
|
|
$[7] = t6;
|
|
} else {
|
|
t6 = $[7];
|
|
}
|
|
let t7;
|
|
if ($[8] !== className || $[9] !== t1 || $[10] !== t6) {
|
|
t7 = /* @__PURE__ */ _jsx49("div", {
|
|
className,
|
|
style: t1,
|
|
children: t6
|
|
});
|
|
$[8] = className;
|
|
$[9] = t1;
|
|
$[10] = t6;
|
|
$[11] = t7;
|
|
} else {
|
|
t7 = $[11];
|
|
}
|
|
return t7;
|
|
}
|
|
|
|
// src/components/AlignmentGuides.tsx
|
|
init_snap_store();
|
|
init_viewport_store();
|
|
import { c as _c79 } from "react/compiler-runtime";
|
|
import React49 from "react";
|
|
import { useAtomValue as useAtomValue50 } from "jotai";
|
|
import { jsx as _jsx50, jsxs as _jsxs30 } from "react/jsx-runtime";
|
|
var GUIDE_COLOR = "rgba(99, 160, 255, 0.6)";
|
|
function AlignmentGuides() {
|
|
const $ = _c79(22);
|
|
const guides = useAtomValue50(alignmentGuidesAtom);
|
|
const pan = useAtomValue50(panAtom);
|
|
const zoom = useAtomValue50(zoomAtom);
|
|
const viewportRect = useAtomValue50(viewportRectAtom);
|
|
const hasGuides = guides.verticalGuides.length > 0 || guides.horizontalGuides.length > 0;
|
|
if (!hasGuides || !viewportRect) {
|
|
return null;
|
|
}
|
|
const worldMinX = -pan.x / zoom;
|
|
const worldMinY = -pan.y / zoom;
|
|
const worldMaxX = worldMinX + viewportRect.width / zoom;
|
|
const worldMaxY = worldMinY + viewportRect.height / zoom;
|
|
let t0;
|
|
if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = {
|
|
position: "absolute",
|
|
left: 0,
|
|
top: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
pointerEvents: "none",
|
|
overflow: "visible",
|
|
zIndex: 999
|
|
};
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
let t1;
|
|
if ($[1] !== guides.verticalGuides || $[2] !== worldMaxY || $[3] !== worldMinY || $[4] !== zoom) {
|
|
let t22;
|
|
if ($[6] !== worldMaxY || $[7] !== worldMinY || $[8] !== zoom) {
|
|
t22 = (x, i) => /* @__PURE__ */ _jsx50("line", {
|
|
x1: x,
|
|
y1: worldMinY,
|
|
x2: x,
|
|
y2: worldMaxY,
|
|
stroke: GUIDE_COLOR,
|
|
strokeWidth: 1 / zoom,
|
|
strokeDasharray: `${4 / zoom} ${4 / zoom}`
|
|
}, `v-${i}`);
|
|
$[6] = worldMaxY;
|
|
$[7] = worldMinY;
|
|
$[8] = zoom;
|
|
$[9] = t22;
|
|
} else {
|
|
t22 = $[9];
|
|
}
|
|
t1 = guides.verticalGuides.map(t22);
|
|
$[1] = guides.verticalGuides;
|
|
$[2] = worldMaxY;
|
|
$[3] = worldMinY;
|
|
$[4] = zoom;
|
|
$[5] = t1;
|
|
} else {
|
|
t1 = $[5];
|
|
}
|
|
let t2;
|
|
if ($[10] !== guides.horizontalGuides || $[11] !== worldMaxX || $[12] !== worldMinX || $[13] !== zoom) {
|
|
let t32;
|
|
if ($[15] !== worldMaxX || $[16] !== worldMinX || $[17] !== zoom) {
|
|
t32 = (y, i_0) => /* @__PURE__ */ _jsx50("line", {
|
|
x1: worldMinX,
|
|
y1: y,
|
|
x2: worldMaxX,
|
|
y2: y,
|
|
stroke: GUIDE_COLOR,
|
|
strokeWidth: 1 / zoom,
|
|
strokeDasharray: `${4 / zoom} ${4 / zoom}`
|
|
}, `h-${i_0}`);
|
|
$[15] = worldMaxX;
|
|
$[16] = worldMinX;
|
|
$[17] = zoom;
|
|
$[18] = t32;
|
|
} else {
|
|
t32 = $[18];
|
|
}
|
|
t2 = guides.horizontalGuides.map(t32);
|
|
$[10] = guides.horizontalGuides;
|
|
$[11] = worldMaxX;
|
|
$[12] = worldMinX;
|
|
$[13] = zoom;
|
|
$[14] = t2;
|
|
} else {
|
|
t2 = $[14];
|
|
}
|
|
let t3;
|
|
if ($[19] !== t1 || $[20] !== t2) {
|
|
t3 = /* @__PURE__ */ _jsxs30("svg", {
|
|
style: t0,
|
|
children: [t1, t2]
|
|
});
|
|
$[19] = t1;
|
|
$[20] = t2;
|
|
$[21] = t3;
|
|
} else {
|
|
t3 = $[21];
|
|
}
|
|
return t3;
|
|
}
|
|
|
|
// src/nodes/NoteNode/NoteNode.tsx
|
|
init_debug();
|
|
import React50, { useEffect as useEffect23, useRef as useRef31 } from "react";
|
|
import { useCreateBlockNote, useEditorChange } from "@blocknote/react";
|
|
import { BlockNoteView } from "@blocknote/shadcn";
|
|
import { en } from "@blocknote/core/locales";
|
|
import "@blocknote/core/fonts/inter.css";
|
|
import "@blocknote/shadcn/style.css";
|
|
import { jsx as _jsx51 } from "react/jsx-runtime";
|
|
var debug22 = createDebug("note-node");
|
|
function NoteNode({
|
|
nodeData,
|
|
storage,
|
|
theme = "light",
|
|
placeholder = "Start typing...",
|
|
isResizing
|
|
}) {
|
|
const isSyncingFromStorage = useRef31(false);
|
|
const isSyncingToStorage = useRef31(false);
|
|
const lastStorageContent = useRef31("");
|
|
const editor = useCreateBlockNote({
|
|
initialContent: [{
|
|
id: crypto.randomUUID(),
|
|
type: "paragraph",
|
|
content: ""
|
|
}],
|
|
dictionary: {
|
|
...en,
|
|
placeholders: {
|
|
...en.placeholders,
|
|
emptyDocument: placeholder,
|
|
default: placeholder,
|
|
heading: "Enter a heading"
|
|
}
|
|
}
|
|
}, []);
|
|
useEditorChange(() => {
|
|
if (isSyncingFromStorage.current) {
|
|
return;
|
|
}
|
|
try {
|
|
isSyncingToStorage.current = true;
|
|
const html = editor.blocksToFullHTML(editor.document);
|
|
if (html !== lastStorageContent.current) {
|
|
lastStorageContent.current = html;
|
|
storage.onChange(html);
|
|
}
|
|
} catch (err) {
|
|
debug22.error("Failed to sync to storage: %O", err);
|
|
} finally {
|
|
isSyncingToStorage.current = false;
|
|
}
|
|
}, editor);
|
|
useEffect23(() => {
|
|
if (!editor) return;
|
|
const handleStorageChange = (newContent) => {
|
|
if (isSyncingToStorage.current) {
|
|
return;
|
|
}
|
|
queueMicrotask(() => {
|
|
if (newContent === lastStorageContent.current) return;
|
|
lastStorageContent.current = newContent;
|
|
if (newContent && newContent.trim()) {
|
|
try {
|
|
isSyncingFromStorage.current = true;
|
|
const blocks = editor.tryParseHTMLToBlocks(newContent);
|
|
editor.replaceBlocks(editor.document, blocks);
|
|
} catch (err_0) {
|
|
debug22.error("Failed to parse HTML from storage: %O", err_0);
|
|
} finally {
|
|
isSyncingFromStorage.current = false;
|
|
}
|
|
}
|
|
});
|
|
};
|
|
const unsubscribe = storage.subscribe?.(handleStorageChange);
|
|
if (storage.content && storage.content !== lastStorageContent.current) {
|
|
handleStorageChange(storage.content);
|
|
}
|
|
return () => {
|
|
unsubscribe?.();
|
|
};
|
|
}, [editor, storage]);
|
|
if (storage.isLoading) {
|
|
return /* @__PURE__ */ _jsx51("div", {
|
|
className: "note-node note-node--loading",
|
|
style: styles3.loading,
|
|
children: "Loading..."
|
|
});
|
|
}
|
|
return /* @__PURE__ */ _jsx51("div", {
|
|
className: "note-node",
|
|
style: styles3.container,
|
|
"data-no-drag": "true",
|
|
onClick: (e) => {
|
|
e.stopPropagation();
|
|
},
|
|
children: /* @__PURE__ */ _jsx51(BlockNoteView, {
|
|
editor,
|
|
theme
|
|
})
|
|
});
|
|
}
|
|
var styles3 = {
|
|
container: {
|
|
height: "100%",
|
|
width: "100%",
|
|
overflow: "auto",
|
|
touchAction: "auto"
|
|
},
|
|
loading: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100%",
|
|
color: "#666"
|
|
}
|
|
};
|
|
|
|
// src/commands/index.ts
|
|
init_registry();
|
|
|
|
// src/commands/keyboard.ts
|
|
var DEFAULT_SHORTCUTS = {
|
|
openCommandLine: "/",
|
|
closeCommandLine: "Escape",
|
|
clearSelection: "Escape",
|
|
copy: "ctrl+c",
|
|
cut: "ctrl+x",
|
|
paste: "ctrl+v",
|
|
duplicate: "ctrl+d",
|
|
selectAll: "ctrl+a",
|
|
delete: "Delete",
|
|
search: "ctrl+f",
|
|
nextSearchResult: "Enter",
|
|
prevSearchResult: "shift+Enter",
|
|
nextSearchResultAlt: "ctrl+g",
|
|
prevSearchResultAlt: "ctrl+shift+g",
|
|
mergeNodes: "ctrl+m"
|
|
};
|
|
function useGlobalKeyboard(_options) {
|
|
}
|
|
function useKeyState(_key) {
|
|
return false;
|
|
}
|
|
|
|
// src/commands/executor.ts
|
|
function collectInput(_get, _set, _inputDef, _collected) {
|
|
return Promise.reject(new Error("Interactive input collection is not yet implemented. Pre-fill all inputs via initialInputs."));
|
|
}
|
|
async function executeCommandInteractive(get, set, command, initialInputs) {
|
|
const collected = {
|
|
...initialInputs
|
|
};
|
|
for (let i = 0; i < command.inputs.length; i++) {
|
|
const inputDef = command.inputs[i];
|
|
if (collected[inputDef.name] !== void 0) {
|
|
continue;
|
|
}
|
|
if (inputDef.required === false && inputDef.default !== void 0) {
|
|
collected[inputDef.name] = inputDef.default;
|
|
continue;
|
|
}
|
|
set(commandLineStateAtom, {
|
|
phase: "collecting",
|
|
command,
|
|
inputIndex: i,
|
|
collected
|
|
});
|
|
const value = await collectInput(get, set, inputDef, collected);
|
|
collected[inputDef.name] = value;
|
|
}
|
|
set(commandLineStateAtom, {
|
|
phase: "executing",
|
|
command
|
|
});
|
|
}
|
|
function handlePickedPoint(set, point) {
|
|
set(provideInputAtom2, point);
|
|
}
|
|
function handlePickedNode(set, node) {
|
|
set(provideInputAtom2, node);
|
|
}
|
|
function cancelCommand(set) {
|
|
set(closeCommandLineAtom);
|
|
}
|
|
|
|
// src/commands/CommandProvider.tsx
|
|
init_graph_store();
|
|
init_selection_store();
|
|
init_viewport_store();
|
|
init_history_store();
|
|
import { c as _c80 } from "react/compiler-runtime";
|
|
import React51, { createContext as createContext4, useContext as useContext4, useEffect as useEffect24, useRef as useRef32 } from "react";
|
|
import { useAtomValue as useAtomValue51, useSetAtom as useSetAtom37, useAtom as useAtom9 } from "jotai";
|
|
import { useStore as useStore6 } from "jotai";
|
|
init_registry();
|
|
import { jsx as _jsx52 } from "react/jsx-runtime";
|
|
var CommandContextContext = /* @__PURE__ */ createContext4(null);
|
|
function CommandProvider(t0) {
|
|
const $ = _c80(52);
|
|
const {
|
|
children,
|
|
onCreateNode,
|
|
onUpdateNode,
|
|
onDeleteNode,
|
|
onCreateEdge,
|
|
onDeleteEdge,
|
|
onForceLayoutPersist
|
|
} = t0;
|
|
const store = useStore6();
|
|
const currentGraphId = useAtomValue51(currentGraphIdAtom);
|
|
const selectedNodeIds = useAtomValue51(selectedNodeIdsAtom);
|
|
const zoom = useAtomValue51(zoomAtom);
|
|
const pan = useAtomValue51(panAtom);
|
|
const undo = useSetAtom37(undoAtom);
|
|
const redo = useSetAtom37(redoAtom);
|
|
const {
|
|
fitToBounds
|
|
} = useFitToBounds();
|
|
let t1;
|
|
if ($[0] !== onForceLayoutPersist) {
|
|
t1 = onForceLayoutPersist ? async (updates) => {
|
|
await onForceLayoutPersist(updates.map(_temp20));
|
|
} : void 0;
|
|
$[0] = onForceLayoutPersist;
|
|
$[1] = t1;
|
|
} else {
|
|
t1 = $[1];
|
|
}
|
|
const persistCallback = t1;
|
|
let t2;
|
|
if ($[2] !== persistCallback) {
|
|
t2 = {
|
|
onPositionsChanged: persistCallback
|
|
};
|
|
$[2] = persistCallback;
|
|
$[3] = t2;
|
|
} else {
|
|
t2 = $[3];
|
|
}
|
|
const {
|
|
applyForceLayout
|
|
} = useForceLayout(t2);
|
|
let t3;
|
|
if ($[4] !== persistCallback) {
|
|
t3 = {
|
|
onPositionsChanged: persistCallback
|
|
};
|
|
$[4] = persistCallback;
|
|
$[5] = t3;
|
|
} else {
|
|
t3 = $[5];
|
|
}
|
|
const {
|
|
applyLayout: applyTreeLayoutTopDown
|
|
} = useTreeLayout(t3);
|
|
let t4;
|
|
if ($[6] !== persistCallback) {
|
|
t4 = {
|
|
direction: "left-right",
|
|
onPositionsChanged: persistCallback
|
|
};
|
|
$[6] = persistCallback;
|
|
$[7] = t4;
|
|
} else {
|
|
t4 = $[7];
|
|
}
|
|
const {
|
|
applyLayout: applyTreeLayoutLeftRight
|
|
} = useTreeLayout(t4);
|
|
let t5;
|
|
if ($[8] !== persistCallback) {
|
|
t5 = {
|
|
onPositionsChanged: persistCallback
|
|
};
|
|
$[8] = persistCallback;
|
|
$[9] = t5;
|
|
} else {
|
|
t5 = $[9];
|
|
}
|
|
const {
|
|
applyLayout: applyGridLayoutDefault
|
|
} = useGridLayout(t5);
|
|
const closeCommandLine = useSetAtom37(closeCommandLineAtom);
|
|
const setCommandError = useSetAtom37(setCommandErrorAtom);
|
|
let t6;
|
|
if ($[10] !== applyForceLayout || $[11] !== applyGridLayoutDefault || $[12] !== applyTreeLayoutLeftRight || $[13] !== applyTreeLayoutTopDown || $[14] !== currentGraphId || $[15] !== fitToBounds || $[16] !== onCreateEdge || $[17] !== onCreateNode || $[18] !== onDeleteEdge || $[19] !== onDeleteNode || $[20] !== onUpdateNode || $[21] !== pan || $[22] !== redo || $[23] !== selectedNodeIds || $[24] !== store.get || $[25] !== store.set || $[26] !== undo || $[27] !== zoom) {
|
|
t6 = () => ({
|
|
get: store.get,
|
|
set: store.set,
|
|
currentGraphId,
|
|
selectedNodeIds,
|
|
viewport: {
|
|
zoom,
|
|
pan
|
|
},
|
|
mutations: {
|
|
createNode: async (payload) => {
|
|
if (!onCreateNode) {
|
|
throw new Error("onCreateNode callback not provided to CommandProvider");
|
|
}
|
|
return onCreateNode(payload);
|
|
},
|
|
updateNode: async (nodeId, updates_0) => {
|
|
if (!onUpdateNode) {
|
|
throw new Error("onUpdateNode callback not provided to CommandProvider");
|
|
}
|
|
return onUpdateNode(nodeId, updates_0);
|
|
},
|
|
deleteNode: async (nodeId_0) => {
|
|
if (!onDeleteNode) {
|
|
throw new Error("onDeleteNode callback not provided to CommandProvider");
|
|
}
|
|
return onDeleteNode(nodeId_0);
|
|
},
|
|
createEdge: async (payload_0) => {
|
|
if (!onCreateEdge) {
|
|
throw new Error("onCreateEdge callback not provided to CommandProvider");
|
|
}
|
|
return onCreateEdge(payload_0);
|
|
},
|
|
deleteEdge: async (edgeId) => {
|
|
if (!onDeleteEdge) {
|
|
throw new Error("onDeleteEdge callback not provided to CommandProvider");
|
|
}
|
|
return onDeleteEdge(edgeId);
|
|
}
|
|
},
|
|
layout: {
|
|
fitToBounds: (mode, padding) => {
|
|
const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection;
|
|
fitToBounds(fitMode, padding);
|
|
},
|
|
applyForceLayout,
|
|
applyTreeLayout: async (opts) => {
|
|
if (opts?.direction === "left-right") {
|
|
await applyTreeLayoutLeftRight();
|
|
} else {
|
|
await applyTreeLayoutTopDown();
|
|
}
|
|
},
|
|
applyGridLayout: async () => {
|
|
await applyGridLayoutDefault();
|
|
}
|
|
},
|
|
history: {
|
|
undo,
|
|
redo
|
|
}
|
|
});
|
|
$[10] = applyForceLayout;
|
|
$[11] = applyGridLayoutDefault;
|
|
$[12] = applyTreeLayoutLeftRight;
|
|
$[13] = applyTreeLayoutTopDown;
|
|
$[14] = currentGraphId;
|
|
$[15] = fitToBounds;
|
|
$[16] = onCreateEdge;
|
|
$[17] = onCreateNode;
|
|
$[18] = onDeleteEdge;
|
|
$[19] = onDeleteNode;
|
|
$[20] = onUpdateNode;
|
|
$[21] = pan;
|
|
$[22] = redo;
|
|
$[23] = selectedNodeIds;
|
|
$[24] = store.get;
|
|
$[25] = store.set;
|
|
$[26] = undo;
|
|
$[27] = zoom;
|
|
$[28] = t6;
|
|
} else {
|
|
t6 = $[28];
|
|
}
|
|
const getContext = t6;
|
|
let t7;
|
|
if ($[29] !== closeCommandLine || $[30] !== getContext || $[31] !== setCommandError) {
|
|
t7 = async (commandName, inputs) => {
|
|
const command = commandRegistry.get(commandName);
|
|
if (!command) {
|
|
throw new Error(`Unknown command: ${commandName}`);
|
|
}
|
|
for (const inputDef of command.inputs) {
|
|
if (inputDef.required !== false && inputs[inputDef.name] === void 0) {
|
|
throw new Error(`Missing required input: ${inputDef.name}`);
|
|
}
|
|
}
|
|
const ctx = getContext();
|
|
;
|
|
try {
|
|
await command.execute(inputs, ctx);
|
|
closeCommandLine();
|
|
} catch (t82) {
|
|
const error = t82;
|
|
const message = error instanceof Error ? error.message : "Command execution failed";
|
|
setCommandError(message);
|
|
throw error;
|
|
}
|
|
};
|
|
$[29] = closeCommandLine;
|
|
$[30] = getContext;
|
|
$[31] = setCommandError;
|
|
$[32] = t7;
|
|
} else {
|
|
t7 = $[32];
|
|
}
|
|
const executeCommand = t7;
|
|
const hasCommand = _temp210;
|
|
const [commandState, setCommandState] = useAtom9(commandLineStateAtom);
|
|
const isExecutingRef = useRef32(false);
|
|
let t8;
|
|
if ($[33] !== commandState || $[34] !== getContext || $[35] !== setCommandError || $[36] !== setCommandState || $[37] !== store) {
|
|
t8 = () => {
|
|
if (commandState.phase === "executing" && !isExecutingRef.current) {
|
|
const {
|
|
command: command_0
|
|
} = commandState;
|
|
if (command_0.inputs.length === 0) {
|
|
isExecutingRef.current = true;
|
|
const ctx_0 = getContext();
|
|
command_0.execute({}, ctx_0).then(() => {
|
|
setCommandState({
|
|
phase: "searching",
|
|
query: "",
|
|
suggestions: commandRegistry.all()
|
|
});
|
|
}).catch((error_0) => {
|
|
const message_0 = error_0 instanceof Error ? error_0.message : "Command execution failed";
|
|
setCommandError(message_0);
|
|
}).finally(() => {
|
|
isExecutingRef.current = false;
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (commandState.phase !== "collecting") {
|
|
isExecutingRef.current = false;
|
|
return;
|
|
}
|
|
const {
|
|
command: command_1,
|
|
inputIndex,
|
|
collected
|
|
} = commandState;
|
|
const lastInputIndex = command_1.inputs.length - 1;
|
|
const lastInput = command_1.inputs[lastInputIndex];
|
|
if (inputIndex === lastInputIndex && collected[lastInput.name] !== void 0 && !isExecutingRef.current) {
|
|
isExecutingRef.current = true;
|
|
setCommandState({
|
|
phase: "executing",
|
|
command: command_1
|
|
});
|
|
const ctx_1 = getContext();
|
|
command_1.execute(collected, ctx_1).then(() => {
|
|
setCommandState({
|
|
phase: "searching",
|
|
query: "",
|
|
suggestions: commandRegistry.all()
|
|
});
|
|
store.set(inputModeAtom2, {
|
|
type: "normal"
|
|
});
|
|
store.set(commandFeedbackAtom, null);
|
|
}).catch((error_1) => {
|
|
const message_1 = error_1 instanceof Error ? error_1.message : "Command execution failed";
|
|
setCommandError(message_1);
|
|
}).finally(() => {
|
|
isExecutingRef.current = false;
|
|
});
|
|
}
|
|
};
|
|
$[33] = commandState;
|
|
$[34] = getContext;
|
|
$[35] = setCommandError;
|
|
$[36] = setCommandState;
|
|
$[37] = store;
|
|
$[38] = t8;
|
|
} else {
|
|
t8 = $[38];
|
|
}
|
|
let t9;
|
|
if ($[39] !== closeCommandLine || $[40] !== commandState || $[41] !== getContext || $[42] !== setCommandError || $[43] !== setCommandState || $[44] !== store) {
|
|
t9 = [commandState, getContext, closeCommandLine, setCommandError, setCommandState, store];
|
|
$[39] = closeCommandLine;
|
|
$[40] = commandState;
|
|
$[41] = getContext;
|
|
$[42] = setCommandError;
|
|
$[43] = setCommandState;
|
|
$[44] = store;
|
|
$[45] = t9;
|
|
} else {
|
|
t9 = $[45];
|
|
}
|
|
useEffect24(t8, t9);
|
|
let t10;
|
|
if ($[46] !== executeCommand || $[47] !== getContext) {
|
|
t10 = {
|
|
executeCommand,
|
|
getContext,
|
|
hasCommand
|
|
};
|
|
$[46] = executeCommand;
|
|
$[47] = getContext;
|
|
$[48] = t10;
|
|
} else {
|
|
t10 = $[48];
|
|
}
|
|
const value = t10;
|
|
let t11;
|
|
if ($[49] !== children || $[50] !== value) {
|
|
t11 = /* @__PURE__ */ _jsx52(CommandContextContext, {
|
|
value,
|
|
children
|
|
});
|
|
$[49] = children;
|
|
$[50] = value;
|
|
$[51] = t11;
|
|
} else {
|
|
t11 = $[51];
|
|
}
|
|
return t11;
|
|
}
|
|
function _temp210(name) {
|
|
return commandRegistry.has(name);
|
|
}
|
|
function _temp20(u) {
|
|
return {
|
|
nodeId: u.nodeId,
|
|
x: u.position.x,
|
|
y: u.position.y
|
|
};
|
|
}
|
|
function useCommandContext() {
|
|
const context = useContext4(CommandContextContext);
|
|
if (!context) {
|
|
throw new Error("useCommandContext must be used within a CommandProvider");
|
|
}
|
|
return context;
|
|
}
|
|
function useExecuteCommand() {
|
|
const {
|
|
executeCommand
|
|
} = useCommandContext();
|
|
return executeCommand;
|
|
}
|
|
|
|
// src/commands/builtins/viewport-commands.ts
|
|
init_registry();
|
|
var fitToViewCommand = {
|
|
name: "fitToView",
|
|
description: "Fit all nodes in the viewport",
|
|
aliases: ["fit", "fitAll"],
|
|
category: "viewport",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
ctx.layout.fitToBounds("graph");
|
|
}
|
|
};
|
|
var fitSelectionCommand = {
|
|
name: "fitSelection",
|
|
description: "Fit selected nodes in the viewport",
|
|
aliases: ["fitSel"],
|
|
category: "viewport",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
if (ctx.selectedNodeIds.size === 0) {
|
|
throw new Error("No nodes selected");
|
|
}
|
|
ctx.layout.fitToBounds("selection");
|
|
}
|
|
};
|
|
var resetViewportCommand = {
|
|
name: "resetViewport",
|
|
description: "Reset zoom and pan to default",
|
|
aliases: ["reset", "home"],
|
|
category: "viewport",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
resetViewportAtom: resetViewportAtom2
|
|
} = await Promise.resolve().then(() => (init_viewport_store(), viewport_store_exports));
|
|
ctx.set(resetViewportAtom2);
|
|
}
|
|
};
|
|
var zoomInCommand = {
|
|
name: "zoomIn",
|
|
description: "Zoom in on the canvas",
|
|
aliases: ["+", "in"],
|
|
category: "viewport",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
zoomAtom: zoomAtom2,
|
|
setZoomAtom: setZoomAtom2
|
|
} = await Promise.resolve().then(() => (init_viewport_store(), viewport_store_exports));
|
|
const currentZoom = ctx.get(zoomAtom2);
|
|
ctx.set(setZoomAtom2, {
|
|
zoom: Math.min(5, currentZoom * 1.25)
|
|
});
|
|
}
|
|
};
|
|
var zoomOutCommand = {
|
|
name: "zoomOut",
|
|
description: "Zoom out on the canvas",
|
|
aliases: ["-", "out"],
|
|
category: "viewport",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
zoomAtom: zoomAtom2,
|
|
setZoomAtom: setZoomAtom2
|
|
} = await Promise.resolve().then(() => (init_viewport_store(), viewport_store_exports));
|
|
const currentZoom = ctx.get(zoomAtom2);
|
|
ctx.set(setZoomAtom2, {
|
|
zoom: Math.max(0.1, currentZoom / 1.25)
|
|
});
|
|
}
|
|
};
|
|
function registerViewportCommands() {
|
|
registerCommand(fitToViewCommand);
|
|
registerCommand(fitSelectionCommand);
|
|
registerCommand(resetViewportCommand);
|
|
registerCommand(zoomInCommand);
|
|
registerCommand(zoomOutCommand);
|
|
}
|
|
|
|
// src/commands/builtins/selection-commands.ts
|
|
init_registry();
|
|
var selectAllCommand = {
|
|
name: "selectAll",
|
|
description: "Select all nodes in the graph",
|
|
aliases: ["all"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
nodeKeysAtom: nodeKeysAtom2,
|
|
selectedNodeIdsAtom: selectedNodeIdsAtom2
|
|
} = await Promise.resolve().then(() => (init_core(), core_exports));
|
|
const nodeKeys = ctx.get(nodeKeysAtom2);
|
|
ctx.set(selectedNodeIdsAtom2, new Set(nodeKeys));
|
|
}
|
|
};
|
|
var clearSelectionCommand = {
|
|
name: "clearSelection",
|
|
description: "Clear all selection",
|
|
aliases: ["deselect", "clear"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
clearSelectionAtom: clearSelectionAtom2
|
|
} = await Promise.resolve().then(() => (init_core(), core_exports));
|
|
ctx.set(clearSelectionAtom2);
|
|
}
|
|
};
|
|
var invertSelectionCommand = {
|
|
name: "invertSelection",
|
|
description: "Invert the current selection",
|
|
aliases: ["invert"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
nodeKeysAtom: nodeKeysAtom2,
|
|
selectedNodeIdsAtom: selectedNodeIdsAtom2
|
|
} = await Promise.resolve().then(() => (init_core(), core_exports));
|
|
const allNodeKeys = ctx.get(nodeKeysAtom2);
|
|
const currentSelection = ctx.selectedNodeIds;
|
|
const invertedSelection = allNodeKeys.filter((id) => !currentSelection.has(id));
|
|
ctx.set(selectedNodeIdsAtom2, new Set(invertedSelection));
|
|
}
|
|
};
|
|
function registerSelectionCommands() {
|
|
registerCommand(selectAllCommand);
|
|
registerCommand(clearSelectionCommand);
|
|
registerCommand(invertSelectionCommand);
|
|
}
|
|
|
|
// src/commands/builtins/history-commands.ts
|
|
init_registry();
|
|
var undoCommand = {
|
|
name: "undo",
|
|
description: "Undo the last action",
|
|
aliases: ["z"],
|
|
category: "history",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
ctx.history.undo();
|
|
}
|
|
};
|
|
var redoCommand = {
|
|
name: "redo",
|
|
description: "Redo the last undone action",
|
|
aliases: ["y"],
|
|
category: "history",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
ctx.history.redo();
|
|
}
|
|
};
|
|
function registerHistoryCommands() {
|
|
registerCommand(undoCommand);
|
|
registerCommand(redoCommand);
|
|
}
|
|
|
|
// src/commands/builtins/layout-commands.ts
|
|
init_registry();
|
|
var forceLayoutCommand = {
|
|
name: "forceLayout",
|
|
description: "Apply force-directed layout to all nodes",
|
|
aliases: ["force", "autoLayout"],
|
|
category: "layout",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
await ctx.layout.applyForceLayout();
|
|
}
|
|
};
|
|
var treeLayoutCommand = {
|
|
name: "treeLayout",
|
|
description: "Arrange nodes in a hierarchical tree (top-down)",
|
|
aliases: ["tree"],
|
|
category: "layout",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
await ctx.layout.applyTreeLayout();
|
|
}
|
|
};
|
|
var gridLayoutCommand = {
|
|
name: "gridLayout",
|
|
description: "Arrange nodes in a uniform grid",
|
|
aliases: ["grid"],
|
|
category: "layout",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
await ctx.layout.applyGridLayout();
|
|
}
|
|
};
|
|
var horizontalLayoutCommand = {
|
|
name: "horizontalLayout",
|
|
description: "Arrange nodes in a horizontal tree (left-to-right)",
|
|
aliases: ["horizontal", "hLayout"],
|
|
category: "layout",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
await ctx.layout.applyTreeLayout({
|
|
direction: "left-right"
|
|
});
|
|
}
|
|
};
|
|
function registerLayoutCommands() {
|
|
registerCommand(forceLayoutCommand);
|
|
registerCommand(treeLayoutCommand);
|
|
registerCommand(gridLayoutCommand);
|
|
registerCommand(horizontalLayoutCommand);
|
|
}
|
|
|
|
// src/commands/builtins/clipboard-commands.ts
|
|
init_registry();
|
|
var copyCommand = {
|
|
name: "copy",
|
|
description: "Copy selected nodes to clipboard",
|
|
aliases: ["cp"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
copyToClipboardAtom: copyToClipboardAtom2,
|
|
hasClipboardContentAtom: hasClipboardContentAtom2
|
|
} = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports));
|
|
if (ctx.selectedNodeIds.size === 0) {
|
|
throw new Error("No nodes selected to copy");
|
|
}
|
|
ctx.set(copyToClipboardAtom2, Array.from(ctx.selectedNodeIds));
|
|
const hasContent = ctx.get(hasClipboardContentAtom2);
|
|
if (!hasContent) {
|
|
throw new Error("Failed to copy nodes");
|
|
}
|
|
}
|
|
};
|
|
var cutCommand = {
|
|
name: "cut",
|
|
description: "Cut selected nodes (copy to clipboard)",
|
|
aliases: ["x"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
copyToClipboardAtom: copyToClipboardAtom2,
|
|
hasClipboardContentAtom: hasClipboardContentAtom2
|
|
} = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports));
|
|
if (ctx.selectedNodeIds.size === 0) {
|
|
throw new Error("No nodes selected to cut");
|
|
}
|
|
ctx.set(copyToClipboardAtom2, Array.from(ctx.selectedNodeIds));
|
|
const hasContent = ctx.get(hasClipboardContentAtom2);
|
|
if (!hasContent) {
|
|
throw new Error("Failed to cut nodes");
|
|
}
|
|
for (const nodeId of ctx.selectedNodeIds) {
|
|
await ctx.mutations.deleteNode(nodeId);
|
|
}
|
|
}
|
|
};
|
|
var pasteCommand = {
|
|
name: "paste",
|
|
description: "Paste nodes from clipboard",
|
|
aliases: ["v"],
|
|
category: "selection",
|
|
inputs: [{
|
|
name: "position",
|
|
type: "point",
|
|
prompt: "Click to paste at position (or Enter for default offset)",
|
|
required: false
|
|
}],
|
|
execute: async (inputs, ctx) => {
|
|
const {
|
|
pasteFromClipboardAtom: pasteFromClipboardAtom2,
|
|
clipboardAtom: clipboardAtom2,
|
|
PASTE_OFFSET: PASTE_OFFSET2
|
|
} = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports));
|
|
const clipboard = ctx.get(clipboardAtom2);
|
|
if (!clipboard || clipboard.nodes.length === 0) {
|
|
throw new Error("Clipboard is empty");
|
|
}
|
|
let offset = PASTE_OFFSET2;
|
|
if (inputs.position) {
|
|
const pos = inputs.position;
|
|
offset = {
|
|
x: pos.x - clipboard.bounds.minX,
|
|
y: pos.y - clipboard.bounds.minY
|
|
};
|
|
}
|
|
const newNodeIds = ctx.set(pasteFromClipboardAtom2, offset);
|
|
if (!newNodeIds || newNodeIds.length === 0) {
|
|
throw new Error("Failed to paste nodes");
|
|
}
|
|
},
|
|
feedback: (collected, currentInput) => {
|
|
if (currentInput.name === "position") {
|
|
return {
|
|
crosshair: true
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
var duplicateCommand = {
|
|
name: "duplicate",
|
|
description: "Duplicate selected nodes",
|
|
aliases: ["d", "dup"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
duplicateSelectionAtom: duplicateSelectionAtom2
|
|
} = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports));
|
|
if (ctx.selectedNodeIds.size === 0) {
|
|
throw new Error("No nodes selected to duplicate");
|
|
}
|
|
const newNodeIds = ctx.set(duplicateSelectionAtom2);
|
|
if (!newNodeIds || newNodeIds.length === 0) {
|
|
throw new Error("Failed to duplicate nodes");
|
|
}
|
|
}
|
|
};
|
|
var deleteSelectedCommand = {
|
|
name: "deleteSelected",
|
|
description: "Delete selected nodes",
|
|
aliases: ["del", "delete", "remove"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
if (ctx.selectedNodeIds.size === 0) {
|
|
throw new Error("No nodes selected to delete");
|
|
}
|
|
const {
|
|
pushHistoryAtom: pushHistoryAtom2
|
|
} = await Promise.resolve().then(() => (init_core(), core_exports));
|
|
ctx.set(pushHistoryAtom2, "Delete nodes");
|
|
for (const nodeId of ctx.selectedNodeIds) {
|
|
await ctx.mutations.deleteNode(nodeId);
|
|
}
|
|
}
|
|
};
|
|
function registerClipboardCommands() {
|
|
registerCommand(copyCommand);
|
|
registerCommand(cutCommand);
|
|
registerCommand(pasteCommand);
|
|
registerCommand(duplicateCommand);
|
|
registerCommand(deleteSelectedCommand);
|
|
}
|
|
|
|
// src/commands/builtins/group-commands.ts
|
|
init_registry();
|
|
var groupNodesCommand = {
|
|
name: "groupNodes",
|
|
description: "Group selected nodes into a container",
|
|
aliases: ["group"],
|
|
category: "nodes",
|
|
inputs: [{
|
|
name: "label",
|
|
type: "text",
|
|
prompt: "Group label:",
|
|
required: false,
|
|
default: "Group"
|
|
}],
|
|
execute: async (inputs, ctx) => {
|
|
if (ctx.selectedNodeIds.size < 2) return;
|
|
const {
|
|
addNodeToLocalGraphAtom: addNodeToLocalGraphAtom2
|
|
} = await Promise.resolve().then(() => (init_graph_mutations(), graph_mutations_exports));
|
|
const {
|
|
groupSelectedNodesAtom: groupSelectedNodesAtom2
|
|
} = await Promise.resolve().then(() => (init_group_store(), group_store_exports));
|
|
const label = inputs.label || "Group";
|
|
const groupNodeId = crypto.randomUUID();
|
|
ctx.set(addNodeToLocalGraphAtom2, {
|
|
id: groupNodeId,
|
|
graph_id: ctx.currentGraphId || "",
|
|
label,
|
|
node_type: "group",
|
|
configuration: null,
|
|
ui_properties: {
|
|
x: 0,
|
|
y: 0,
|
|
width: 500,
|
|
height: 500,
|
|
size: 15,
|
|
zIndex: 0
|
|
},
|
|
data: null,
|
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
});
|
|
ctx.set(groupSelectedNodesAtom2, {
|
|
nodeIds: Array.from(ctx.selectedNodeIds),
|
|
groupNodeId
|
|
});
|
|
}
|
|
};
|
|
var ungroupNodesCommand = {
|
|
name: "ungroupNodes",
|
|
description: "Remove grouping from a group node",
|
|
aliases: ["ungroup"],
|
|
category: "nodes",
|
|
inputs: [{
|
|
name: "groupNode",
|
|
type: "node",
|
|
prompt: "Pick a group node to ungroup:"
|
|
}],
|
|
execute: async (inputs, ctx) => {
|
|
const groupId = inputs.groupNode;
|
|
if (!groupId) return;
|
|
const {
|
|
ungroupNodesAtom: ungroupNodesAtom2
|
|
} = await Promise.resolve().then(() => (init_group_store(), group_store_exports));
|
|
ctx.set(ungroupNodesAtom2, groupId);
|
|
}
|
|
};
|
|
var collapseGroupCommand = {
|
|
name: "collapseGroup",
|
|
description: "Collapse a group node to hide its children",
|
|
aliases: ["collapse"],
|
|
category: "nodes",
|
|
inputs: [{
|
|
name: "groupNode",
|
|
type: "node",
|
|
prompt: "Pick a group node to collapse:"
|
|
}],
|
|
execute: async (inputs, ctx) => {
|
|
const groupId = inputs.groupNode;
|
|
if (!groupId) return;
|
|
const {
|
|
collapseGroupAtom: collapseGroupAtom2
|
|
} = await Promise.resolve().then(() => (init_group_store(), group_store_exports));
|
|
ctx.set(collapseGroupAtom2, groupId);
|
|
}
|
|
};
|
|
var expandGroupCommand = {
|
|
name: "expandGroup",
|
|
description: "Expand a collapsed group node",
|
|
aliases: ["expand"],
|
|
category: "nodes",
|
|
inputs: [{
|
|
name: "groupNode",
|
|
type: "node",
|
|
prompt: "Pick a group node to expand:"
|
|
}],
|
|
execute: async (inputs, ctx) => {
|
|
const groupId = inputs.groupNode;
|
|
if (!groupId) return;
|
|
const {
|
|
expandGroupAtom: expandGroupAtom2
|
|
} = await Promise.resolve().then(() => (init_group_store(), group_store_exports));
|
|
ctx.set(expandGroupAtom2, groupId);
|
|
}
|
|
};
|
|
function registerGroupCommands() {
|
|
registerCommand(groupNodesCommand);
|
|
registerCommand(ungroupNodesCommand);
|
|
registerCommand(collapseGroupCommand);
|
|
registerCommand(expandGroupCommand);
|
|
}
|
|
|
|
// src/commands/builtins/search-commands.ts
|
|
init_registry();
|
|
var searchNodesCommand = {
|
|
name: "searchNodes",
|
|
description: "Search nodes by label, type, or ID",
|
|
aliases: ["find", "search"],
|
|
category: "selection",
|
|
inputs: [{
|
|
name: "query",
|
|
type: "text",
|
|
prompt: "Search:",
|
|
required: true
|
|
}],
|
|
execute: async (inputs, ctx) => {
|
|
const query = inputs.query;
|
|
if (!query) return;
|
|
const {
|
|
setSearchQueryAtom: setSearchQueryAtom2
|
|
} = await Promise.resolve().then(() => (init_search_store(), search_store_exports));
|
|
ctx.set(setSearchQueryAtom2, query);
|
|
}
|
|
};
|
|
var clearSearchCommand = {
|
|
name: "clearSearch",
|
|
description: "Clear the active search filter",
|
|
aliases: ["clearsearch"],
|
|
category: "selection",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
clearSearchAtom: clearSearchAtom2
|
|
} = await Promise.resolve().then(() => (init_search_store(), search_store_exports));
|
|
ctx.set(clearSearchAtom2);
|
|
}
|
|
};
|
|
function registerSearchCommands() {
|
|
registerCommand(searchNodesCommand);
|
|
registerCommand(clearSearchCommand);
|
|
}
|
|
|
|
// src/commands/builtins/merge-commands.ts
|
|
init_registry();
|
|
var mergeNodesCommand = {
|
|
name: "mergeNodes",
|
|
description: "Merge selected nodes into one (first selected survives)",
|
|
aliases: ["merge"],
|
|
category: "nodes",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
if (ctx.selectedNodeIds.size < 2) return;
|
|
const {
|
|
mergeNodesAtom: mergeNodesAtom2
|
|
} = await Promise.resolve().then(() => (init_graph_mutations(), graph_mutations_exports));
|
|
const {
|
|
clearSelectionAtom: clearSelectionAtom2,
|
|
addNodesToSelectionAtom: addNodesToSelectionAtom2
|
|
} = await Promise.resolve().then(() => (init_selection_store(), selection_store_exports));
|
|
const nodeIds = Array.from(ctx.selectedNodeIds);
|
|
ctx.set(mergeNodesAtom2, {
|
|
nodeIds
|
|
});
|
|
ctx.set(clearSelectionAtom2);
|
|
ctx.set(addNodesToSelectionAtom2, [nodeIds[0]]);
|
|
}
|
|
};
|
|
function registerMergeCommands() {
|
|
registerCommand(mergeNodesCommand);
|
|
}
|
|
|
|
// src/commands/builtins/serialization-commands.ts
|
|
init_registry();
|
|
var exportCanvasCommand = {
|
|
name: "exportCanvas",
|
|
description: "Export the current canvas to a JSON snapshot (copies to clipboard)",
|
|
aliases: ["export"],
|
|
category: "custom",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
exportGraph: exportGraph2
|
|
} = await Promise.resolve().then(() => (init_canvas_serializer(), canvas_serializer_exports));
|
|
const {
|
|
showToastAtom: showToastAtom2
|
|
} = await Promise.resolve().then(() => (init_toast_store(), toast_store_exports));
|
|
const {
|
|
graphAtom: graphAtom2
|
|
} = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
|
|
const graph = ctx.get(graphAtom2);
|
|
const nodeCount = graph.order;
|
|
const edgeCount = graph.size;
|
|
if (nodeCount === 0) {
|
|
throw new Error("Canvas is empty \u2014 nothing to export");
|
|
}
|
|
const store = {
|
|
get: ctx.get,
|
|
set: ctx.set
|
|
};
|
|
const snapshot = exportGraph2(store);
|
|
const json = JSON.stringify(snapshot, null, 2);
|
|
await navigator.clipboard.writeText(json);
|
|
ctx.set(showToastAtom2, `Exported ${nodeCount} nodes, ${edgeCount} edges to clipboard`);
|
|
}
|
|
};
|
|
var importCanvasCommand = {
|
|
name: "importCanvas",
|
|
description: "Import a canvas from a JSON snapshot (reads from clipboard)",
|
|
aliases: ["import"],
|
|
category: "custom",
|
|
inputs: [],
|
|
execute: async (_inputs, ctx) => {
|
|
const {
|
|
importGraph: importGraph2,
|
|
validateSnapshot: validateSnapshot2
|
|
} = await Promise.resolve().then(() => (init_canvas_serializer(), canvas_serializer_exports));
|
|
const {
|
|
showToastAtom: showToastAtom2
|
|
} = await Promise.resolve().then(() => (init_toast_store(), toast_store_exports));
|
|
const {
|
|
pushHistoryAtom: pushHistoryAtom2
|
|
} = await Promise.resolve().then(() => (init_history_store(), history_store_exports));
|
|
let json;
|
|
try {
|
|
json = await navigator.clipboard.readText();
|
|
} catch {
|
|
throw new Error("Could not read clipboard \u2014 paste the snapshot JSON manually");
|
|
}
|
|
let data;
|
|
try {
|
|
data = JSON.parse(json);
|
|
} catch {
|
|
throw new Error("Clipboard does not contain valid JSON");
|
|
}
|
|
const result = validateSnapshot2(data);
|
|
if (!result.valid) {
|
|
throw new Error(`Invalid snapshot: ${result.errors[0]}`);
|
|
}
|
|
ctx.set(pushHistoryAtom2, "Import canvas");
|
|
const store = {
|
|
get: ctx.get,
|
|
set: ctx.set
|
|
};
|
|
const snapshot = data;
|
|
importGraph2(store, snapshot);
|
|
ctx.set(showToastAtom2, `Imported ${snapshot.nodes.length} nodes, ${snapshot.edges.length} edges`);
|
|
}
|
|
};
|
|
function registerSerializationCommands() {
|
|
registerCommand(exportCanvasCommand);
|
|
registerCommand(importCanvasCommand);
|
|
}
|
|
|
|
// src/commands/builtins/index.ts
|
|
function registerBuiltinCommands() {
|
|
registerViewportCommands();
|
|
registerSelectionCommands();
|
|
registerHistoryCommands();
|
|
registerLayoutCommands();
|
|
registerClipboardCommands();
|
|
registerGroupCommands();
|
|
registerSearchCommands();
|
|
registerMergeCommands();
|
|
registerSerializationCommands();
|
|
}
|
|
|
|
// src/gestures/index.ts
|
|
var gestures_exports = {};
|
|
__export(gestures_exports, {
|
|
ACTIVE_INTERACTION_CONTEXT: () => ACTIVE_INTERACTION_CONTEXT,
|
|
DEFAULT_CONTEXT: () => DEFAULT_CONTEXT,
|
|
GestureProvider: () => GestureProvider,
|
|
IDLE: () => IDLE,
|
|
INPUT_MODE_CONTEXTS: () => INPUT_MODE_CONTEXTS,
|
|
InputProvider: () => InputProvider,
|
|
KEYBOARD_MANIPULATE_CONTEXT: () => KEYBOARD_MANIPULATE_CONTEXT,
|
|
KEYBOARD_NAVIGATE_CONTEXT: () => KEYBOARD_NAVIGATE_CONTEXT,
|
|
LONG_PRESS_TIMER: () => LONG_PRESS_TIMER,
|
|
NO_HELD_KEYS: () => NO_HELD_KEYS,
|
|
NO_MODIFIERS: () => NO_MODIFIERS,
|
|
PALM_REJECTION_CONTEXT: () => PALM_REJECTION_CONTEXT,
|
|
PICK_NODES_CONTEXT: () => PICK_NODES_CONTEXT,
|
|
PICK_NODE_CONTEXT: () => PICK_NODE_CONTEXT,
|
|
PICK_POINT_CONTEXT: () => PICK_POINT_CONTEXT,
|
|
PanInertia: () => PanInertia,
|
|
SEARCH_CONTEXT: () => SEARCH_CONTEXT,
|
|
SETTLE_TIMER: () => SETTLE_TIMER,
|
|
TimedStateRunner: () => TimedStateRunner,
|
|
VelocitySampler: () => VelocitySampler,
|
|
ZoomInertia: () => ZoomInertia,
|
|
activateFocusedNode: () => activateFocusedNode,
|
|
buildMappingIndex: () => buildMappingIndex,
|
|
cancelActiveInteraction: () => cancelActiveInteraction,
|
|
clearHandlers: () => clearHandlers,
|
|
createPinchHandlers: () => createPinchHandlers,
|
|
createWheelHandler: () => createWheelHandler,
|
|
cutSelection: () => cutSelection,
|
|
cycleFocus: () => cycleFocus,
|
|
deleteSelection: () => deleteSelection,
|
|
dispatch: () => dispatch,
|
|
escapeInput: () => escapeInput,
|
|
extractModifiers: () => extractModifiers,
|
|
findNearestNode: () => findNearestNode,
|
|
getCurrentSubject: () => getCurrentSubject,
|
|
getHandler: () => getHandler,
|
|
indexContext: () => indexContext,
|
|
isKeyInputEvent: () => isKeyInputEvent,
|
|
isPointerGestureEvent: () => isPointerGestureEvent,
|
|
navigateFocus: () => navigateFocus,
|
|
normalizePointer: () => normalizePointer,
|
|
nudgeSelection: () => nudgeSelection,
|
|
registerAction: () => registerAction2,
|
|
resolve: () => resolve,
|
|
snapZoom: () => snapZoom,
|
|
specificity: () => specificity,
|
|
transition: () => transition,
|
|
unregisterAction: () => unregisterAction2,
|
|
useCanvasGestures: () => useCanvasGestures,
|
|
useGestureContext: () => useGestureContext,
|
|
useGestureSystem: () => useGestureSystem,
|
|
useGuardContext: () => useGuardContext,
|
|
useInertia: () => useInertia,
|
|
useInputContext: () => useInputContext,
|
|
useInputModeGestureContext: () => useInputModeGestureContext,
|
|
useInputSystem: () => useInputSystem,
|
|
useNodeGestures: () => useNodeGestures,
|
|
useRegisterInputActions: () => useRegisterInputActions
|
|
});
|
|
init_types2();
|
|
init_dispatcher();
|
|
|
|
// src/index.ts
|
|
var CANVAS_VERSION = "2.0.0";
|
|
var canvasVersion = {
|
|
version: CANVAS_VERSION,
|
|
major: 2,
|
|
minor: 0,
|
|
patch: 0,
|
|
/** Check if current version is at least the specified version */
|
|
isAtLeast: (major, minor = 0, patch = 0) => {
|
|
if (canvasVersion.major > major) return true;
|
|
if (canvasVersion.major < major) return false;
|
|
if (canvasVersion.minor > minor) return true;
|
|
if (canvasVersion.minor < minor) return false;
|
|
return canvasVersion.patch >= patch;
|
|
}
|
|
};
|
|
export {
|
|
ActionCategory,
|
|
AlignmentGuides,
|
|
BUILT_IN_PRESETS,
|
|
BuiltInActionId,
|
|
CANVAS_VERSION,
|
|
Canvas,
|
|
CanvasAnimations,
|
|
CanvasDbProvider,
|
|
CanvasEventType,
|
|
CanvasProvider,
|
|
CanvasStyleProvider,
|
|
CanvasToast,
|
|
ComboboxOption,
|
|
ComboboxSearch,
|
|
CommandFeedbackOverlay,
|
|
CommandInputCollector,
|
|
CommandLine,
|
|
CommandProvider,
|
|
CommandSearch,
|
|
ConnectedNode,
|
|
ConnectedNodeRenderer,
|
|
ContextMenuAction,
|
|
ContextMenuDivider,
|
|
Crosshairs,
|
|
DEFAULT_GESTURE_RULES,
|
|
DEFAULT_MAPPINGS,
|
|
DEFAULT_PORT,
|
|
DEFAULT_SHORTCUTS,
|
|
DeleteIcon,
|
|
EDGE_ANIMATION_DURATION,
|
|
EVENT_TYPE_INFO,
|
|
EdgeLabel,
|
|
EdgeLabelEditor,
|
|
EdgeOverlay,
|
|
EdgePath,
|
|
EdgePreviewLine,
|
|
EdgeRenderer,
|
|
EventMappingRow,
|
|
FallbackNodeTypeComponent,
|
|
FitToBoundsMode,
|
|
FitToViewIcon,
|
|
Grid,
|
|
GroupNode,
|
|
HIT_TARGET_SIZES,
|
|
InMemoryStorageAdapter,
|
|
LockedNodeOverlay,
|
|
Minimap,
|
|
Node,
|
|
NodeContextMenu,
|
|
NodeErrorBoundary,
|
|
NodePorts,
|
|
NodeRenderer,
|
|
NodeTypeCombobox,
|
|
NoteNode,
|
|
PASTE_OFFSET,
|
|
PluginError,
|
|
PortBar,
|
|
PortHandle,
|
|
ResizeHandle,
|
|
SNAPSHOT_VERSION,
|
|
SelectionOverlay,
|
|
SettingsEventMap,
|
|
SettingsPanel,
|
|
SettingsPresets,
|
|
SpatialGrid,
|
|
SupabaseStorageAdapter,
|
|
TouchActionButton,
|
|
VIRTUALIZATION_BUFFER,
|
|
Viewport,
|
|
ViewportControls,
|
|
ZOOM_EXIT_THRESHOLD,
|
|
ZOOM_TRANSITION_THRESHOLD,
|
|
activePointersAtom,
|
|
activePresetAtom,
|
|
activePresetIdAtom,
|
|
addGestureRuleAtom,
|
|
addNodeToLocalGraphAtom,
|
|
addNodesToSelectionAtom,
|
|
alignmentGuidesAtom,
|
|
allPresetsAtom,
|
|
animateFitToBoundsAtom,
|
|
animateZoomToNodeAtom,
|
|
applyDelta,
|
|
applyPresetAtom,
|
|
areNodesClose,
|
|
arePortsCompatible,
|
|
autoResizeGroupAtom,
|
|
bezierHorizontal,
|
|
bezierSmart,
|
|
bezierVertical,
|
|
buildActionHelpers,
|
|
buildRuleIndex,
|
|
calculateBounds,
|
|
calculatePortPosition,
|
|
canPortAcceptConnection,
|
|
canRedoAtom,
|
|
canUndoAtom,
|
|
cancelCommand,
|
|
cancelSelectionAtom,
|
|
canvasIsDarkAtom,
|
|
canvasKeys,
|
|
canvasMark,
|
|
canvasSettingsAtom,
|
|
canvasStyleOverridesAtom,
|
|
canvasStylesAtom,
|
|
canvasToastAtom,
|
|
canvasVersion,
|
|
canvasWrap,
|
|
centerOnNodeAtom,
|
|
checkNodesOverlap,
|
|
classifyPointer,
|
|
cleanupAllNodePositionsAtom,
|
|
cleanupNodePositionAtom,
|
|
clearActions,
|
|
clearAlignmentGuidesAtom,
|
|
clearClipboardAtom,
|
|
clearCommandErrorAtom,
|
|
clearCustomEdgePathCalculators,
|
|
clearEdgeSelectionAtom,
|
|
clearGraphOnSwitchAtom,
|
|
clearHistoryAtom,
|
|
clearMutationQueueAtom,
|
|
clearNodeTypeRegistry,
|
|
clearPendingState,
|
|
clearPlugins,
|
|
clearPointersAtom,
|
|
clearSearchAtom,
|
|
clearSelectionAtom,
|
|
clearSelectionCommand,
|
|
clipboardAtom,
|
|
clipboardNodeCountAtom,
|
|
closeCommandLineAtom,
|
|
collapseGroupAtom,
|
|
collapsedEdgeRemapAtom,
|
|
collapsedGroupsAtom,
|
|
collectInput,
|
|
commandFeedbackAtom,
|
|
commandHistoryAtom,
|
|
commandLineStateAtom,
|
|
commandLineVisibleAtom,
|
|
commandProgressAtom,
|
|
commandRegistry,
|
|
completeMutationAtom,
|
|
conditionalSnap,
|
|
consumerGestureRulesAtom,
|
|
copyCommand,
|
|
copyToClipboardAtom,
|
|
createActionContext,
|
|
createActionContextFromReactEvent,
|
|
createActionContextFromTouchEvent,
|
|
createCanvasAPI,
|
|
createComponentRegistry,
|
|
createDebug,
|
|
createFallbackComponent,
|
|
createGraphEdge,
|
|
createGraphNode,
|
|
createSnapshot,
|
|
currentGraphIdAtom,
|
|
currentInputAtom,
|
|
cutCommand,
|
|
cutToClipboardAtom,
|
|
debug,
|
|
defaultDarkStyles,
|
|
defaultEdgePathCalculator,
|
|
defaultLightStyles,
|
|
deleteGraphEdge,
|
|
deleteGraphNode,
|
|
deletePresetAtom,
|
|
deleteSelectedCommand,
|
|
departingEdgesAtom,
|
|
dequeueMutationAtom,
|
|
detectInputCapabilities,
|
|
draggingNodeIdAtom,
|
|
dropTargetNodeIdAtom,
|
|
duplicateCommand,
|
|
duplicateSelectionAtom,
|
|
edgeCreationAtom,
|
|
edgeFamilyAtom,
|
|
edgeKeysAtom,
|
|
edgeKeysWithTempEdgeAtom,
|
|
editingEdgeLabelAtom,
|
|
endNodeDragAtom,
|
|
endSelectionAtom,
|
|
eventMappingsAtom,
|
|
executeAction,
|
|
executeCommandInteractive,
|
|
expandGroupAtom,
|
|
exportGraph,
|
|
fetchGraphEdges,
|
|
fetchGraphNode,
|
|
fetchGraphNodes,
|
|
findAlignmentGuides,
|
|
fingerCountAtom,
|
|
fitSelectionCommand,
|
|
fitToBoundsAtom,
|
|
fitToViewCommand,
|
|
focusedNodeIdAtom,
|
|
forceLayoutCommand,
|
|
formatRuleLabel,
|
|
fuzzyMatch,
|
|
gestureRuleIndexAtom,
|
|
gestureRuleSettingsAtom,
|
|
gestureRulesAtom,
|
|
gestures_exports as gestures,
|
|
getAction,
|
|
getActionForEvent,
|
|
getActionsByCategories,
|
|
getActionsByCategory,
|
|
getAllActions,
|
|
getAllPlugins,
|
|
getCustomEdgePathCalculatorNames,
|
|
getEdgePathCalculator,
|
|
getGestureThresholds,
|
|
getHitTargetSize,
|
|
getNextQueuedMutationAtom,
|
|
getNodeBounds,
|
|
getNodeCenter,
|
|
getNodeDescendants,
|
|
getNodeGestureConfig,
|
|
getNodePorts,
|
|
getNodeTypeComponent,
|
|
getPendingState,
|
|
getPlugin,
|
|
getPluginGestureContexts,
|
|
getPluginIds,
|
|
getPortWorldPosition,
|
|
getRegisteredNodeTypes,
|
|
getSnapGuides,
|
|
getViewportGestureConfig,
|
|
goBackInputAtom,
|
|
goToLockedPageAtom,
|
|
graphAtom,
|
|
graphOptions,
|
|
graphUpdateVersionAtom,
|
|
groupChildCountAtom,
|
|
groupSelectedNodesAtom,
|
|
handleNodePointerDownSelectionAtom,
|
|
handlePickedNode,
|
|
handlePickedPoint,
|
|
hasAction,
|
|
hasClipboardContentAtom,
|
|
hasCustomEdgePathCalculator,
|
|
hasExternalKeyboardAtom,
|
|
hasFocusedNodeAtom,
|
|
hasLockedNodeAtom,
|
|
hasNodeTypeComponent,
|
|
hasPendingMutations,
|
|
hasPlugin,
|
|
hasSelectionAtom,
|
|
hasUnsavedChangesAtom,
|
|
highestZIndexAtom,
|
|
highlightedSearchIndexAtom,
|
|
highlightedSearchNodeIdAtom,
|
|
historyLabelsAtom,
|
|
historyStateAtom,
|
|
hitTestNode,
|
|
hitTestPort,
|
|
importGraph,
|
|
incrementRetryCountAtom,
|
|
inputCapabilitiesAtom,
|
|
inputModeAtom,
|
|
interactionFeedbackAtom,
|
|
invertDelta,
|
|
invertSelectionCommand,
|
|
isCommandActiveAtom,
|
|
isFilterActiveAtom,
|
|
isGroupNodeAtom,
|
|
isMultiTouchAtom,
|
|
isNodeCollapsed,
|
|
isOnlineAtom,
|
|
isPanelOpenAtom,
|
|
isPickNodeModeAtom,
|
|
isPickingModeAtom,
|
|
isSelectingAtom,
|
|
isSnappingActiveAtom,
|
|
isStylusActiveAtom,
|
|
isTouchDeviceAtom,
|
|
isZoomTransitioningAtom,
|
|
keyboardInteractionModeAtom,
|
|
lastSyncErrorAtom,
|
|
lastSyncTimeAtom,
|
|
loadGraphFromDbAtom,
|
|
lockNodeAtom,
|
|
lockedNodeDataAtom,
|
|
lockedNodeIdAtom,
|
|
lockedNodePageCountAtom,
|
|
lockedNodePageIndexAtom,
|
|
matchSpecificity,
|
|
mergeNodesAtom,
|
|
mergeRules,
|
|
mergeWithDefaults,
|
|
moveNodesToGroupAtom,
|
|
mutationQueueAtom,
|
|
nestNodesOnDropAtom,
|
|
nextLockedPageAtom,
|
|
nextSearchResultAtom,
|
|
nodeChildrenAtom,
|
|
nodeFamilyAtom,
|
|
nodeKeysAtom,
|
|
nodeParentAtom,
|
|
nodePositionAtomFamily,
|
|
nodePositionUpdateCounterAtom,
|
|
openCommandLineAtom,
|
|
optimisticDeleteEdgeAtom,
|
|
optimisticDeleteNodeAtom,
|
|
palmRejectionEnabledAtom,
|
|
panAtom,
|
|
pasteCommand,
|
|
pasteFromClipboardAtom,
|
|
pendingInputResolverAtom,
|
|
pendingMutationsCountAtom,
|
|
pendingNodeMutations,
|
|
perfEnabledAtom,
|
|
pointInPolygon,
|
|
pointerDownAtom,
|
|
pointerUpAtom,
|
|
preDragNodeAttributesAtom,
|
|
prefersReducedMotionAtom,
|
|
prevLockedPageAtom,
|
|
prevSearchResultAtom,
|
|
primaryInputSourceAtom,
|
|
provideInputAtom,
|
|
pushDeltaAtom,
|
|
pushHistoryAtom,
|
|
queueMutationAtom,
|
|
redoAtom,
|
|
redoCommand,
|
|
redoCountAtom,
|
|
registerAction,
|
|
registerBuiltinCommands,
|
|
registerClipboardCommands,
|
|
registerCommand,
|
|
registerEdgePathCalculator,
|
|
registerHistoryCommands,
|
|
registerLayoutCommands,
|
|
registerNodeType,
|
|
registerNodeTypes,
|
|
registerPlugin,
|
|
registerSelectionCommands,
|
|
registerViewportCommands,
|
|
removeEdgeWithAnimationAtom,
|
|
removeFromGroupAtom,
|
|
removeGestureRuleAtom,
|
|
removeNodesFromSelectionAtom,
|
|
resetGestureRulesAtom,
|
|
resetInputModeAtom,
|
|
resetKeyboardInteractionModeAtom,
|
|
resetSettingsAtom,
|
|
resetViewportAtom,
|
|
resetViewportCommand,
|
|
resolveEdgePathCalculator,
|
|
resolveGesture,
|
|
resolveGestureIndexed,
|
|
saveAsPresetAtom,
|
|
screenToWorldAtom,
|
|
searchEdgeResultCountAtom,
|
|
searchEdgeResultsAtom,
|
|
searchQueryAtom,
|
|
searchResultCountAtom,
|
|
searchResultsArrayAtom,
|
|
searchResultsAtom,
|
|
searchTotalResultCountAtom,
|
|
selectAllCommand,
|
|
selectCommandAtom,
|
|
selectEdgeAtom,
|
|
selectSingleNodeAtom,
|
|
selectedEdgeIdAtom,
|
|
selectedNodeIdsAtom,
|
|
selectedNodesCountAtom,
|
|
selectedSuggestionIndexAtom,
|
|
selectionPathAtom,
|
|
selectionRectAtom,
|
|
setCommandErrorAtom,
|
|
setEventMappingAtom,
|
|
setFocusedNodeAtom,
|
|
setGridSizeAtom,
|
|
setHitTestProvider,
|
|
setKeyboardInteractionModeAtom,
|
|
setNodeCenter,
|
|
setNodeParentAtom,
|
|
setOnlineStatusAtom,
|
|
setPanelOpenAtom,
|
|
setPerfEnabled,
|
|
setSearchQueryAtom,
|
|
setVirtualizationEnabledAtom,
|
|
setZoomAtom,
|
|
showToastAtom,
|
|
skipInputAtom,
|
|
smoothStep,
|
|
snapAlignmentEnabledAtom,
|
|
snapEnabledAtom,
|
|
snapGridSizeAtom,
|
|
snapTemporaryDisableAtom,
|
|
snapToGrid,
|
|
spatialIndexAtom,
|
|
splitNodeAtom,
|
|
startMutationAtom,
|
|
startNodeDragAtom,
|
|
startPickNodeAtom,
|
|
startPickNodesAtom,
|
|
startPickPointAtom,
|
|
startSelectionAtom,
|
|
stepHorizontal,
|
|
stepSmart,
|
|
stepVertical,
|
|
storageAdapterAtom,
|
|
straight,
|
|
swapEdgeAtomicAtom,
|
|
syncStateAtom,
|
|
syncStatusAtom,
|
|
toggleAlignmentGuidesAtom,
|
|
toggleGroupCollapseAtom,
|
|
toggleNodeInSelectionAtom,
|
|
togglePanelAtom,
|
|
toggleSnapAtom,
|
|
toggleVirtualizationAtom,
|
|
trackMutationErrorAtom,
|
|
uiNodesAtom,
|
|
undoAtom,
|
|
undoCommand,
|
|
undoCountAtom,
|
|
ungroupNodesAtom,
|
|
unlockNodeAtom,
|
|
unregisterAction,
|
|
unregisterEdgePathCalculator,
|
|
unregisterNodeType,
|
|
unregisterPlugin,
|
|
updateEdgeLabelAtom,
|
|
updateGestureRuleAtom,
|
|
updateGraphEdge,
|
|
updateGraphNode,
|
|
updateInteractionFeedbackAtom,
|
|
updateNodePositionAtom,
|
|
updatePresetAtom,
|
|
updateSearchQueryAtom,
|
|
updateSelectionAtom,
|
|
useActionExecutor,
|
|
useAnimatedLayout,
|
|
useArrowKeyNavigation,
|
|
useCanvasDrag,
|
|
useCanvasGraph,
|
|
useCanvasHistory,
|
|
useCanvasSelection,
|
|
useCanvasSettings,
|
|
useCanvasStyle,
|
|
useCanvasTheme,
|
|
useCanvasViewport,
|
|
useCommandContext,
|
|
useCommandLine,
|
|
useCreateEdge,
|
|
useCreateNode,
|
|
useDeleteEdge,
|
|
useDeleteNode,
|
|
useExecuteCommand,
|
|
useFitToBounds,
|
|
useForceLayout,
|
|
useGestureResolver,
|
|
useGetGraphBounds,
|
|
useGlobalKeyboard,
|
|
useGraphEdges,
|
|
useGraphNodes,
|
|
useGridLayout,
|
|
useKeyState,
|
|
useLayout,
|
|
useNodeDrag,
|
|
useNodeResize,
|
|
useNodeSelection,
|
|
usePlugin,
|
|
usePlugins,
|
|
useSelectionBounds,
|
|
useSplitGesture,
|
|
useStorageAdapter,
|
|
useTapGesture,
|
|
useTreeLayout,
|
|
useUpdateEdge,
|
|
useUpdateNode,
|
|
useVirtualization,
|
|
useZoomTransition,
|
|
validateSnapshot,
|
|
viewportRectAtom,
|
|
virtualizationEnabledAtom,
|
|
virtualizationMetricsAtom,
|
|
visibleBoundsAtom,
|
|
visibleEdgeKeysAtom,
|
|
visibleNodeKeysAtom,
|
|
watchExternalKeyboardAtom,
|
|
watchReducedMotionAtom,
|
|
worldToScreenAtom,
|
|
zoomAnimationTargetAtom,
|
|
zoomAtom,
|
|
zoomFocusNodeIdAtom,
|
|
zoomInCommand,
|
|
zoomOutCommand,
|
|
zoomTransitionProgressAtom
|
|
};
|
|
//# sourceMappingURL=index.mjs.map
|