All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m59s
191 lines
4.9 KiB
TypeScript
191 lines
4.9 KiB
TypeScript
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<typeof createKeyMap>;
|
|
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]);
|
|
},
|
|
});
|
|
|
|
|
|
}
|