feat: add shortcut viewer
This commit is contained in:
parent
f1fcf78f6c
commit
e575974872
@ -20,7 +20,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { T } from "@threlte/core";
|
import { T } from "@threlte/core";
|
||||||
import { MeshLineMaterial } from "@threlte/extras";
|
import { MeshLineMaterial } from "@threlte/extras";
|
||||||
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
|
import { BufferGeometry, Color, MeshBasicMaterial, Vector3 } from "three";
|
||||||
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
||||||
import { Vector2 } from "three/src/math/Vector2.js";
|
import { Vector2 } from "three/src/math/Vector2.js";
|
||||||
import { createEdgeGeometry } from "./createEdgeGeometry.js";
|
import { createEdgeGeometry } from "./createEdgeGeometry.js";
|
||||||
@ -74,6 +74,13 @@
|
|||||||
$: if (from || to) {
|
$: if (from || to) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lineColor = new Color($colors.outline);
|
||||||
|
|
||||||
|
$: if ($colors.outline) {
|
||||||
|
lineColor.copyLinearToSRGB($colors.outline);
|
||||||
|
console.log("lineColor", lineColor);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<T.Mesh
|
<T.Mesh
|
||||||
@ -83,7 +90,7 @@
|
|||||||
rotation.x={-Math.PI / 2}
|
rotation.x={-Math.PI / 2}
|
||||||
material={circleMaterial}
|
material={circleMaterial}
|
||||||
>
|
>
|
||||||
<T.CircleGeometry args={[0.3, 16]} />
|
<T.CircleGeometry args={[0.5, 16]} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
<T.Mesh
|
<T.Mesh
|
||||||
@ -93,11 +100,16 @@
|
|||||||
rotation.x={-Math.PI / 2}
|
rotation.x={-Math.PI / 2}
|
||||||
material={circleMaterial}
|
material={circleMaterial}
|
||||||
>
|
>
|
||||||
<T.CircleGeometry args={[0.3, 16]} />
|
<T.CircleGeometry args={[0.5, 16]} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
{#if geometry}
|
{#if geometry}
|
||||||
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
||||||
<MeshLineMaterial width={3} attenuate={false} color={$colors.outline} />
|
<MeshLineMaterial
|
||||||
|
width={3}
|
||||||
|
attenuate={false}
|
||||||
|
color={lineColor}
|
||||||
|
toneMapped={false}
|
||||||
|
/>
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { animate, lerp, snapToGrid } from "../helpers/index.js";
|
import { animate, lerp, snapToGrid } from "../helpers/index.js";
|
||||||
import {
|
|
||||||
LinearSRGBColorSpace,
|
|
||||||
LinearToneMapping,
|
|
||||||
NoToneMapping,
|
|
||||||
SRGBColorSpace,
|
|
||||||
} from "three";
|
|
||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import type { OrthographicCamera } from "three";
|
import type { OrthographicCamera } from "three";
|
||||||
import Background from "../background/Background.svelte";
|
import Background from "../background/Background.svelte";
|
||||||
import type { GraphManager } from "../graph-manager.js";
|
import type { GraphManager } from "../graph-manager.js";
|
||||||
import { onMount, setContext } from "svelte";
|
import { getContext, onMount, setContext } from "svelte";
|
||||||
import Camera from "../Camera.svelte";
|
import Camera from "../Camera.svelte";
|
||||||
import GraphView from "./GraphView.svelte";
|
import GraphView from "./GraphView.svelte";
|
||||||
import type { Node, Node as NodeType, Socket } from "@nodes/types";
|
import type { Node, Node as NodeType, Socket } from "@nodes/types";
|
||||||
@ -23,10 +17,15 @@
|
|||||||
possibleSocketIds,
|
possibleSocketIds,
|
||||||
selectedNodes,
|
selectedNodes,
|
||||||
} from "./stores.js";
|
} from "./stores.js";
|
||||||
|
import { createKeyMap } from "../../helpers/createKeyMap";
|
||||||
import BoxSelection from "../BoxSelection.svelte";
|
import BoxSelection from "../BoxSelection.svelte";
|
||||||
import AddMenu from "../AddMenu.svelte";
|
import AddMenu from "../AddMenu.svelte";
|
||||||
|
|
||||||
export let graph: GraphManager;
|
export let graph: GraphManager;
|
||||||
|
|
||||||
|
let keymap =
|
||||||
|
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
|
||||||
|
|
||||||
setContext("graphManager", graph);
|
setContext("graphManager", graph);
|
||||||
const status = graph.status;
|
const status = graph.status;
|
||||||
const nodes = graph.nodes;
|
const nodes = graph.nodes;
|
||||||
@ -78,19 +77,6 @@
|
|||||||
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
|
localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
export let debug = {};
|
|
||||||
$: debug = {
|
|
||||||
activeNodeId: $activeNodeId,
|
|
||||||
activeSocket: $activeSocket
|
|
||||||
? `${$activeSocket?.node.id}-${$activeSocket?.index}`
|
|
||||||
: null,
|
|
||||||
hoveredSocket: $hoveredSocket
|
|
||||||
? `${$hoveredSocket?.node.id}-${$hoveredSocket?.index}`
|
|
||||||
: null,
|
|
||||||
selectedNodes: [...($selectedNodes?.values() || [])],
|
|
||||||
cameraPosition,
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateNodePosition(node: NodeType) {
|
function updateNodePosition(node: NodeType) {
|
||||||
if (node?.tmp?.ref) {
|
if (node?.tmp?.ref) {
|
||||||
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
||||||
@ -518,30 +504,61 @@
|
|||||||
$selectedNodes = new Set(newNodes.map((n) => n.id));
|
$selectedNodes = new Set(newNodes.map((n) => n.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
const isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
|
||||||
const bodyIsFocused = document?.activeElement?.nodeName !== "INPUT";
|
|
||||||
|
|
||||||
if (event.key === "l") {
|
keymap.addShortcut({
|
||||||
|
key: "l",
|
||||||
|
description: "Select linked nodes",
|
||||||
|
callback: () => {
|
||||||
const activeNode = graph.getNode($activeNodeId);
|
const activeNode = graph.getNode($activeNodeId);
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
const nodes = graph.getLinkedNodes(activeNode);
|
const nodes = graph.getLinkedNodes(activeNode);
|
||||||
$selectedNodes = new Set(nodes.map((n) => n.id));
|
$selectedNodes = new Set(nodes.map((n) => n.id));
|
||||||
}
|
}
|
||||||
console.log(activeNode);
|
console.log(activeNode);
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (event.key === "Escape") {
|
keymap.addShortcut({
|
||||||
|
key: "c",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Copy active nodes",
|
||||||
|
callback: copyNodes,
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "v",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Paste nodes",
|
||||||
|
callback: pasteNodes,
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "Escape",
|
||||||
|
description: "Deselect nodes",
|
||||||
|
callback: () => {
|
||||||
$activeNodeId = -1;
|
$activeNodeId = -1;
|
||||||
$selectedNodes?.clear();
|
$selectedNodes?.clear();
|
||||||
$selectedNodes = $selectedNodes;
|
$selectedNodes = $selectedNodes;
|
||||||
(document.activeElement as HTMLElement)?.blur();
|
(document.activeElement as HTMLElement)?.blur();
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (event.key === "A" && event.shiftKey) {
|
keymap.addShortcut({
|
||||||
|
key: "A",
|
||||||
|
shift: true,
|
||||||
|
description: "Add new Node",
|
||||||
|
callback: () => {
|
||||||
addMenuPosition = [mousePosition[0], mousePosition[1]];
|
addMenuPosition = [mousePosition[0], mousePosition[1]];
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: ".",
|
||||||
|
description: "Center camera",
|
||||||
|
callback: () => {
|
||||||
|
if (!isBodyFocused()) return;
|
||||||
|
|
||||||
if (event.key === "." && bodyIsFocused) {
|
|
||||||
const average = [0, 0];
|
const average = [0, 0];
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
average[0] += node.position[0];
|
average[0] += node.position[0];
|
||||||
@ -564,40 +581,50 @@
|
|||||||
);
|
);
|
||||||
if (mouseDown) return false;
|
if (mouseDown) return false;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (event.key === "a" && event.ctrlKey && bodyIsFocused) {
|
keymap.addShortcut({
|
||||||
|
key: "a",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Select all nodes",
|
||||||
|
callback: () => {
|
||||||
|
if (!isBodyFocused()) return;
|
||||||
$selectedNodes = new Set($nodes.keys());
|
$selectedNodes = new Set($nodes.keys());
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (event.key === "c" && event.ctrlKey) {
|
keymap.addShortcut({
|
||||||
copyNodes();
|
key: "z",
|
||||||
}
|
ctrl: true,
|
||||||
|
description: "Undo",
|
||||||
if (event.key === "v" && event.ctrlKey) {
|
callback: () => {
|
||||||
pasteNodes();
|
if (!isBodyFocused()) return;
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === "z" && event.ctrlKey) {
|
|
||||||
graph.undo();
|
graph.undo();
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (event.key === "y" && event.ctrlKey) {
|
keymap.addShortcut({
|
||||||
|
key: "y",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Redo",
|
||||||
|
callback: () => {
|
||||||
|
if (!isBodyFocused()) return;
|
||||||
graph.redo();
|
graph.redo();
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
keymap.addShortcut({
|
||||||
(event.key === "Delete" ||
|
key: ["Delete", "Backspace", "x"],
|
||||||
event.key === "Backspace" ||
|
description: "Delete selected nodes",
|
||||||
event.key === "x") &&
|
callback: (event) => {
|
||||||
bodyIsFocused
|
if (!isBodyFocused()) return;
|
||||||
) {
|
|
||||||
graph.startUndoGroup();
|
graph.startUndoGroup();
|
||||||
if ($activeNodeId !== -1) {
|
if ($activeNodeId !== -1) {
|
||||||
const node = graph.getNode($activeNodeId);
|
const node = graph.getNode($activeNodeId);
|
||||||
@ -617,8 +644,8 @@
|
|||||||
$selectedNodes = $selectedNodes;
|
$selectedNodes = $selectedNodes;
|
||||||
}
|
}
|
||||||
graph.saveUndoGroup();
|
graph.saveUndoGroup();
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
|
|
||||||
function handleMouseUp(event: MouseEvent) {
|
function handleMouseUp(event: MouseEvent) {
|
||||||
const activeNode = graph.getNode($activeNodeId);
|
const activeNode = graph.getNode($activeNodeId);
|
||||||
@ -752,7 +779,7 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
bind:clientWidth={width}
|
bind:clientWidth={width}
|
||||||
bind:clientHeight={height}
|
bind:clientHeight={height}
|
||||||
on:keydown={handleKeyDown}
|
on:keydown={keymap.handleKeyboardEvent}
|
||||||
on:mousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
>
|
>
|
||||||
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||||
|
@ -24,6 +24,8 @@ if ("getComputedStyle" in globalThis) {
|
|||||||
|
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
|
|
||||||
|
let lastStyle = "";
|
||||||
|
|
||||||
function updateColors() {
|
function updateColors() {
|
||||||
|
|
||||||
const style = getComputedStyle(body);
|
const style = getComputedStyle(body);
|
||||||
@ -35,6 +37,11 @@ if ("getComputedStyle" in globalThis) {
|
|||||||
const active = style.getPropertyValue("--active");
|
const active = style.getPropertyValue("--active");
|
||||||
const selected = style.getPropertyValue("--selected");
|
const selected = style.getPropertyValue("--selected");
|
||||||
|
|
||||||
|
const newStyle = `${layer0}${layer1}${layer2}${layer3}${outline}${active}${selected}`;
|
||||||
|
|
||||||
|
if (newStyle === lastStyle) return;
|
||||||
|
lastStyle = newStyle;
|
||||||
|
|
||||||
colors.update(col => {
|
colors.update(col => {
|
||||||
col.layer0.setStyle(layer0);
|
col.layer0.setStyle(layer0);
|
||||||
col.layer0.convertLinearToSRGB();
|
col.layer0.convertLinearToSRGB();
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
const aspectRatio = 0.25;
|
const aspectRatio = 0.25;
|
||||||
|
|
||||||
const path = createNodePath({
|
const path = createNodePath({
|
||||||
depth: 4,
|
depth: 7,
|
||||||
height: 24,
|
height: 40,
|
||||||
y: 50,
|
y: 50,
|
||||||
cornerTop,
|
cornerTop,
|
||||||
rightBump,
|
rightBump,
|
||||||
@ -42,8 +42,8 @@
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
const pathHover = createNodePath({
|
const pathHover = createNodePath({
|
||||||
depth: 5,
|
depth: 9,
|
||||||
height: 30,
|
height: 50,
|
||||||
y: 50,
|
y: 50,
|
||||||
cornerTop,
|
cornerTop,
|
||||||
rightBump,
|
rightBump,
|
||||||
|
@ -46,8 +46,8 @@
|
|||||||
const aspectRatio = 0.5;
|
const aspectRatio = 0.5;
|
||||||
|
|
||||||
const path = createNodePath({
|
const path = createNodePath({
|
||||||
depth: 4,
|
depth: 7,
|
||||||
height: 12,
|
height: 20,
|
||||||
y: 51,
|
y: 51,
|
||||||
cornerBottom,
|
cornerBottom,
|
||||||
leftBump,
|
leftBump,
|
||||||
@ -62,8 +62,8 @@
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
const pathHover = createNodePath({
|
const pathHover = createNodePath({
|
||||||
depth: 6,
|
depth: 8,
|
||||||
height: 18,
|
height: 25,
|
||||||
y: 50.5,
|
y: 50.5,
|
||||||
cornerBottom,
|
cornerBottom,
|
||||||
leftBump,
|
leftBump,
|
||||||
|
43
app/src/lib/helpers/createKeyMap.ts
Normal file
43
app/src/lib/helpers/createKeyMap.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { get, writable } from "svelte/store";
|
||||||
|
|
||||||
|
type Shortcut = {
|
||||||
|
key: string | string[],
|
||||||
|
shift?: boolean,
|
||||||
|
ctrl?: boolean,
|
||||||
|
alt?: boolean,
|
||||||
|
description?: string,
|
||||||
|
callback: (event: KeyboardEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createKeyMap(keys: Shortcut[]) {
|
||||||
|
|
||||||
|
const store = writable(keys);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleKeyboardEvent: (event: KeyboardEvent) => {
|
||||||
|
const key = get(store).find(k => {
|
||||||
|
if (Array.isArray(k.key) ? !k.key.includes(event.key) : k.key !== event.key) return false;
|
||||||
|
if ("shift" in k && k.shift !== event.shiftKey) return false;
|
||||||
|
if ("ctrl" in k && k.ctrl !== event.ctrlKey) return false;
|
||||||
|
if ("alt" in k && k.alt !== event.altKey) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
console.log({ keys: get(store), out: key, key: event.key });
|
||||||
|
key?.callback(event);
|
||||||
|
},
|
||||||
|
addShortcut: (shortcut: Shortcut) => {
|
||||||
|
if (Array.isArray(shortcut.key)) {
|
||||||
|
for (const k of shortcut.key) {
|
||||||
|
store.update(keys => {
|
||||||
|
if (keys.find(kk => kk.key === k)) return keys;
|
||||||
|
return [...keys, { ...shortcut, key: k }];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
store.update(keys => [...keys, shortcut]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keys: store
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,75 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
const { keys } = getContext<ReturnType<typeof createKeyMap>>("keymap");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
duuude
|
<div class="wrapper">
|
||||||
|
<h3>Editor</h3>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
{#each $keys as key}
|
||||||
|
{#if key.description}
|
||||||
|
<div class="command-wrapper">
|
||||||
|
<div class="command">
|
||||||
|
{#if key.ctrl}
|
||||||
|
<b>Ctrl</b>
|
||||||
|
{/if}
|
||||||
|
{#if key.shift}
|
||||||
|
<b>Shift</b>
|
||||||
|
{/if}
|
||||||
|
{#if key.alt}
|
||||||
|
<b>Alt</b>
|
||||||
|
{/if}
|
||||||
|
<b>{key.key}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>{key.description}</p>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: min-content 1fr;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command {
|
||||||
|
background: var(--layer-3);
|
||||||
|
padding: 0.4em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command > * {
|
||||||
|
color: var(--layer-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
}
|
}
|
||||||
details {
|
details {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
min-height: 69px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-bottom: solid thin var(--outline);
|
border-bottom: solid thin var(--outline);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings";
|
import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings";
|
||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import Keymap from "$lib/settings/Keymap.svelte";
|
import Keymap from "$lib/settings/Keymap.svelte";
|
||||||
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
|
import { setContext } from "svelte";
|
||||||
|
|
||||||
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
||||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||||
@ -30,6 +32,10 @@
|
|||||||
localStorage.setItem("graph", JSON.stringify(event.detail));
|
localStorage.setItem("graph", JSON.stringify(event.detail));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keyMap = createKeyMap([]);
|
||||||
|
|
||||||
|
setContext("keymap", keyMap);
|
||||||
|
|
||||||
let settings: Record<string, any> = {
|
let settings: Record<string, any> = {
|
||||||
general: {
|
general: {
|
||||||
id: "general",
|
id: "general",
|
||||||
|
Loading…
Reference in New Issue
Block a user