import { animate, lerp } from "$lib/helpers"; import type { createKeyMap } from "$lib/helpers/createKeyMap"; import FileSaver from "file-saver"; import type { GraphManager } from "./graph-manager.svelte"; import type { GraphState } from "./graph-state.svelte"; type Keymap = ReturnType; export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) { keymap.addShortcut({ key: "l", description: "Select linked nodes", callback: () => { const activeNode = graph.getNode(graphState.activeNodeId); if (activeNode) { const nodes = graph.getLinkedNodes(activeNode); graphState.selectedNodes.clear(); for (const node of nodes) { graphState.selectedNodes.add(node.id); } } }, }); keymap.addShortcut({ key: "?", description: "Toggle Help", callback: () => { // TODO: fix this // showHelp = !showHelp; }, }); keymap.addShortcut({ key: "c", ctrl: true, description: "Copy active nodes", callback: () => graphState.copyNodes(), }); keymap.addShortcut({ key: "v", ctrl: true, description: "Paste nodes", callback: () => graphState.pasteNodes(), }); keymap.addShortcut({ key: "Escape", description: "Deselect nodes", callback: () => { graphState.activeNodeId = -1; graphState.clearSelection(); graphState.edgeEndPosition = null; (document.activeElement as HTMLElement)?.blur(); }, }); keymap.addShortcut({ key: "A", shift: true, description: "Add new Node", callback: () => { graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]]; }, }); keymap.addShortcut({ key: ".", description: "Center camera", callback: () => { if (!graphState.isBodyFocused()) return; const average = [0, 0]; for (const node of graph.nodes.values()) { average[0] += node.position[0]; average[1] += node.position[1]; } average[0] = average[0] ? average[0] / graph.nodes.size : 0; average[1] = average[1] ? average[1] / graph.nodes.size : 0; const camX = graphState.cameraPosition[0]; const camY = graphState.cameraPosition[1]; const camZ = graphState.cameraPosition[2]; const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t); animate(500, (a: number) => { graphState.cameraPosition[0] = lerp(camX, average[0], ease(a)); graphState.cameraPosition[1] = lerp(camY, average[1], ease(a)); graphState.cameraPosition[2] = lerp(camZ, 2, ease(a)) if (graphState.mouseDown) return false; }); }, }); keymap.addShortcut({ key: "a", ctrl: true, preventDefault: true, description: "Select all nodes", callback: () => { if (!graphState.isBodyFocused()) return; for (const node of graph.nodes.keys()) { graphState.selectedNodes.add(node); } }, }); keymap.addShortcut({ key: "z", ctrl: true, description: "Undo", callback: () => { if (!graphState.isBodyFocused()) return; graph.undo(); for (const node of graph.nodes.values()) { graphState.updateNodePosition(node); } }, }); keymap.addShortcut({ key: "y", ctrl: true, description: "Redo", callback: () => { graph.redo(); for (const node of graph.nodes.values()) { graphState.updateNodePosition(node); } }, }); keymap.addShortcut({ key: "s", ctrl: true, description: "Save", preventDefault: true, callback: () => { const state = graph.serialize(); const blob = new Blob([JSON.stringify(state)], { type: "application/json;charset=utf-8", }); FileSaver.saveAs(blob, "nodarium-graph.json"); }, }); keymap.addShortcut({ key: ["Delete", "Backspace", "x"], description: "Delete selected nodes", callback: (event) => { if (!graphState.isBodyFocused()) return; graph.startUndoGroup(); if (graphState.activeNodeId !== -1) { const node = graph.getNode(graphState.activeNodeId); if (node) { graph.removeNode(node, { restoreEdges: event.ctrlKey }); graphState.activeNodeId = -1; } } if (graphState.selectedNodes) { for (const nodeId of graphState.selectedNodes) { const node = graph.getNode(nodeId); if (node) { graph.removeNode(node, { restoreEdges: event.ctrlKey }); } } graphState.clearSelection(); } graph.saveUndoGroup(); }, }); keymap.addShortcut({ key: "f", description: "Smart Connect Nodes", callback: () => { const nodes = [...graphState.selectedNodes.values()] .map((g) => graph.getNode(g)) .filter((n) => !!n); const edge = graph.smartConnect(nodes[0], nodes[1]); if (!edge) graph.smartConnect(nodes[1], nodes[0]); }, }); }