feat: open keyboard shortcuts with ?
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m4s

This commit is contained in:
Felix Hungenberg
2026-01-20 16:23:21 +01:00
parent a8c76a846e
commit 3e3d41ae98
4 changed files with 65 additions and 70 deletions

View File

@@ -1,16 +1,15 @@
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";
import { animate, lerp } from '$lib/helpers';
import type { createKeyMap } from '$lib/helpers/createKeyMap';
import { panelState } from '$lib/sidebar/PanelState.svelte';
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",
key: 'l',
description: 'Select linked nodes',
callback: () => {
const activeNode = graph.getNode(graphState.activeNodeId);
if (activeNode) {
@@ -20,56 +19,54 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
graphState.selectedNodes.add(node.id);
}
}
},
}
});
keymap.addShortcut({
key: "?",
description: "Toggle Help",
key: '?',
description: 'Toggle Help',
callback: () => {
// TODO: fix this
// showHelp = !showHelp;
},
panelState.setActivePanel('shortcuts');
}
});
keymap.addShortcut({
key: "c",
key: 'c',
ctrl: true,
description: "Copy active nodes",
callback: () => graphState.copyNodes(),
description: 'Copy active nodes',
callback: () => graphState.copyNodes()
});
keymap.addShortcut({
key: "v",
key: 'v',
ctrl: true,
description: "Paste nodes",
callback: () => graphState.pasteNodes(),
description: 'Paste nodes',
callback: () => graphState.pasteNodes()
});
keymap.addShortcut({
key: "Escape",
description: "Deselect nodes",
key: 'Escape',
description: 'Deselect nodes',
callback: () => {
graphState.activeNodeId = -1;
graphState.clearSelection();
graphState.edgeEndPosition = null;
(document.activeElement as HTMLElement)?.blur();
},
}
});
keymap.addShortcut({
key: "A",
key: 'A',
shift: true,
description: "Add new Node",
description: 'Add new Node',
callback: () => {
graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]];
},
}
});
keymap.addShortcut({
key: ".",
description: "Center camera",
key: '.',
description: 'Center camera',
callback: () => {
if (!graphState.isBodyFocused()) return;
@@ -90,67 +87,67 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
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))
graphState.cameraPosition[2] = lerp(camZ, 2, ease(a));
if (graphState.mouseDown) return false;
});
},
}
});
keymap.addShortcut({
key: "a",
key: 'a',
ctrl: true,
preventDefault: true,
description: "Select all nodes",
description: 'Select all nodes',
callback: () => {
if (!graphState.isBodyFocused()) return;
for (const node of graph.nodes.keys()) {
graphState.selectedNodes.add(node);
}
},
}
});
keymap.addShortcut({
key: "z",
key: 'z',
ctrl: true,
description: "Undo",
description: 'Undo',
callback: () => {
if (!graphState.isBodyFocused()) return;
graph.undo();
for (const node of graph.nodes.values()) {
graphState.updateNodePosition(node);
}
},
}
});
keymap.addShortcut({
key: "y",
key: 'y',
ctrl: true,
description: "Redo",
description: 'Redo',
callback: () => {
graph.redo();
for (const node of graph.nodes.values()) {
graphState.updateNodePosition(node);
}
},
}
});
keymap.addShortcut({
key: "s",
key: 's',
ctrl: true,
description: "Save",
description: 'Save',
preventDefault: true,
callback: () => {
const state = graph.serialize();
const blob = new Blob([JSON.stringify(state)], {
type: "application/json;charset=utf-8",
type: 'application/json;charset=utf-8'
});
FileSaver.saveAs(blob, "nodarium-graph.json");
},
FileSaver.saveAs(blob, 'nodarium-graph.json');
}
});
keymap.addShortcut({
key: ["Delete", "Backspace", "x"],
description: "Delete selected nodes",
key: ['Delete', 'Backspace', 'x'],
description: 'Delete selected nodes',
callback: (event) => {
if (!graphState.isBodyFocused()) return;
graph.startUndoGroup();
@@ -171,20 +168,18 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
graphState.clearSelection();
}
graph.saveUndoGroup();
},
}
});
keymap.addShortcut({
key: "f",
description: "Smart Connect Nodes",
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]);
},
}
});
}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { getContext, type Snippet } from "svelte";
import type { PanelState } from "./PanelState.svelte";
import { type Snippet } from "svelte";
import { panelState } from "./PanelState.svelte";
const {
id,
@@ -18,8 +18,6 @@
children?: Snippet;
}>();
const panelState = getContext<PanelState>("panel-state");
const panel = panelState.registerPanel(id, icon, classes, hidden);
$effect(() => {
panel.hidden = hidden;

View File

@@ -1,15 +1,14 @@
import { localState } from "$lib/helpers/localState.svelte";
import { localState } from '$lib/helpers/localState.svelte';
type Panel = {
icon: string;
classes: string;
hidden?: boolean;
}
export class PanelState {
};
class PanelState {
panels = $state<Record<string, Panel>>({});
activePanel = localState<string | boolean>("node.activePanel", "")
activePanel = localState<string | boolean>('node.activePanel', '');
get keys() {
return Object.keys(this.panels);
@@ -19,7 +18,7 @@ export class PanelState {
const state = $state({
icon: icon,
classes: classes,
hidden: hidden,
hidden: hidden
});
this.panels[id] = state;
return state;
@@ -29,7 +28,13 @@ export class PanelState {
if (this.activePanel.value) {
this.activePanel.value = false;
} else {
this.activePanel.value = this.keys[0]
this.activePanel.value = this.keys[0];
}
}
public setActivePanel(panelId: string) {
this.activePanel.value = panelId;
}
}
export const panelState = new PanelState();

View File

@@ -1,9 +1,6 @@
<script lang="ts">
import { setContext, type Snippet } from "svelte";
import { PanelState } from "./PanelState.svelte";
const state = new PanelState();
setContext("panel-state", state);
import { type Snippet } from "svelte";
import { panelState as state } from "./PanelState.svelte";
const { children } = $props<{ children?: Snippet }>();
</script>