feat/drop-node-on-connection #18
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"excludes": [
|
"excludes": [
|
||||||
"**/node_modules",
|
"**/node_modules",
|
||||||
"**/*-lock.json"
|
"**/*-lock.yaml"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"https://plugins.dprint.dev/typescript-0.95.13.wasm",
|
"https://plugins.dprint.dev/typescript-0.95.13.wasm",
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { animate, lerp } from "$lib/helpers";
|
import { animate, lerp } from '$lib/helpers';
|
||||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
import type { createKeyMap } from '$lib/helpers/createKeyMap';
|
||||||
import FileSaver from "file-saver";
|
import { panelState } from '$lib/sidebar/PanelState.svelte';
|
||||||
import type { GraphManager } from "./graph-manager.svelte";
|
import FileSaver from 'file-saver';
|
||||||
import type { GraphState } from "./graph-state.svelte";
|
import type { GraphManager } from './graph-manager.svelte';
|
||||||
|
import type { GraphState } from './graph-state.svelte';
|
||||||
|
|
||||||
type Keymap = ReturnType<typeof createKeyMap>;
|
type Keymap = ReturnType<typeof createKeyMap>;
|
||||||
export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) {
|
export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) {
|
||||||
|
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "l",
|
key: 'l',
|
||||||
description: "Select linked nodes",
|
description: 'Select linked nodes',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const activeNode = graph.getNode(graphState.activeNodeId);
|
const activeNode = graph.getNode(graphState.activeNodeId);
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
@@ -20,56 +19,54 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
|
|||||||
graphState.selectedNodes.add(node.id);
|
graphState.selectedNodes.add(node.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "?",
|
key: '?',
|
||||||
description: "Toggle Help",
|
description: 'Toggle Help',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
// TODO: fix this
|
panelState.setActivePanel('shortcuts');
|
||||||
// showHelp = !showHelp;
|
}
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "c",
|
key: 'c',
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
description: "Copy active nodes",
|
description: 'Copy active nodes',
|
||||||
callback: () => graphState.copyNodes(),
|
callback: () => graphState.copyNodes()
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "v",
|
key: 'v',
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
description: "Paste nodes",
|
description: 'Paste nodes',
|
||||||
callback: () => graphState.pasteNodes(),
|
callback: () => graphState.pasteNodes()
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "Escape",
|
key: 'Escape',
|
||||||
description: "Deselect nodes",
|
description: 'Deselect nodes',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
graphState.activeNodeId = -1;
|
graphState.activeNodeId = -1;
|
||||||
graphState.clearSelection();
|
graphState.clearSelection();
|
||||||
graphState.edgeEndPosition = null;
|
graphState.edgeEndPosition = null;
|
||||||
(document.activeElement as HTMLElement)?.blur();
|
(document.activeElement as HTMLElement)?.blur();
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "A",
|
key: 'A',
|
||||||
shift: true,
|
shift: true,
|
||||||
description: "Add new Node",
|
description: 'Add new Node',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]];
|
graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]];
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: ".",
|
key: '.',
|
||||||
description: "Center camera",
|
description: 'Center camera',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!graphState.isBodyFocused()) return;
|
if (!graphState.isBodyFocused()) return;
|
||||||
|
|
||||||
@@ -90,67 +87,67 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
|
|||||||
animate(500, (a: number) => {
|
animate(500, (a: number) => {
|
||||||
graphState.cameraPosition[0] = lerp(camX, average[0], ease(a));
|
graphState.cameraPosition[0] = lerp(camX, average[0], ease(a));
|
||||||
graphState.cameraPosition[1] = lerp(camY, average[1], 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;
|
if (graphState.mouseDown) return false;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "a",
|
key: 'a',
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
description: "Select all nodes",
|
description: 'Select all nodes',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!graphState.isBodyFocused()) return;
|
if (!graphState.isBodyFocused()) return;
|
||||||
for (const node of graph.nodes.keys()) {
|
for (const node of graph.nodes.keys()) {
|
||||||
graphState.selectedNodes.add(node);
|
graphState.selectedNodes.add(node);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "z",
|
key: 'z',
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
description: "Undo",
|
description: 'Undo',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!graphState.isBodyFocused()) return;
|
if (!graphState.isBodyFocused()) return;
|
||||||
graph.undo();
|
graph.undo();
|
||||||
for (const node of graph.nodes.values()) {
|
for (const node of graph.nodes.values()) {
|
||||||
graphState.updateNodePosition(node);
|
graphState.updateNodePosition(node);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "y",
|
key: 'y',
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
description: "Redo",
|
description: 'Redo',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
graph.redo();
|
graph.redo();
|
||||||
for (const node of graph.nodes.values()) {
|
for (const node of graph.nodes.values()) {
|
||||||
graphState.updateNodePosition(node);
|
graphState.updateNodePosition(node);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "s",
|
key: 's',
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
description: "Save",
|
description: 'Save',
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const state = graph.serialize();
|
const state = graph.serialize();
|
||||||
const blob = new Blob([JSON.stringify(state)], {
|
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({
|
keymap.addShortcut({
|
||||||
key: ["Delete", "Backspace", "x"],
|
key: ['Delete', 'Backspace', 'x'],
|
||||||
description: "Delete selected nodes",
|
description: 'Delete selected nodes',
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
if (!graphState.isBodyFocused()) return;
|
if (!graphState.isBodyFocused()) return;
|
||||||
graph.startUndoGroup();
|
graph.startUndoGroup();
|
||||||
@@ -171,20 +168,18 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
|
|||||||
graphState.clearSelection();
|
graphState.clearSelection();
|
||||||
}
|
}
|
||||||
graph.saveUndoGroup();
|
graph.saveUndoGroup();
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "f",
|
key: 'f',
|
||||||
description: "Smart Connect Nodes",
|
description: 'Smart Connect Nodes',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const nodes = [...graphState.selectedNodes.values()]
|
const nodes = [...graphState.selectedNodes.values()]
|
||||||
.map((g) => graph.getNode(g))
|
.map((g) => graph.getNode(g))
|
||||||
.filter((n) => !!n);
|
.filter((n) => !!n);
|
||||||
const edge = graph.smartConnect(nodes[0], nodes[1]);
|
const edge = graph.smartConnect(nodes[0], nodes[1]);
|
||||||
if (!edge) graph.smartConnect(nodes[1], nodes[0]);
|
if (!edge) graph.smartConnect(nodes[1], nodes[0]);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, type Snippet } from "svelte";
|
import { type Snippet } from "svelte";
|
||||||
import type { PanelState } from "./PanelState.svelte";
|
import { panelState } from "./PanelState.svelte";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@@ -18,8 +18,6 @@
|
|||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const panelState = getContext<PanelState>("panel-state");
|
|
||||||
|
|
||||||
const panel = panelState.registerPanel(id, icon, classes, hidden);
|
const panel = panelState.registerPanel(id, icon, classes, hidden);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
panel.hidden = hidden;
|
panel.hidden = hidden;
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { localState } from "$lib/helpers/localState.svelte";
|
import { localState } from '$lib/helpers/localState.svelte';
|
||||||
|
|
||||||
type Panel = {
|
type Panel = {
|
||||||
icon: string;
|
icon: string;
|
||||||
classes: string;
|
classes: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export class PanelState {
|
|
||||||
|
|
||||||
|
class PanelState {
|
||||||
panels = $state<Record<string, Panel>>({});
|
panels = $state<Record<string, Panel>>({});
|
||||||
activePanel = localState<string | boolean>("node.activePanel", "")
|
activePanel = localState<string | boolean>('node.activePanel', '');
|
||||||
|
|
||||||
get keys() {
|
get keys() {
|
||||||
return Object.keys(this.panels);
|
return Object.keys(this.panels);
|
||||||
@@ -19,7 +18,7 @@ export class PanelState {
|
|||||||
const state = $state({
|
const state = $state({
|
||||||
icon: icon,
|
icon: icon,
|
||||||
classes: classes,
|
classes: classes,
|
||||||
hidden: hidden,
|
hidden: hidden
|
||||||
});
|
});
|
||||||
this.panels[id] = state;
|
this.panels[id] = state;
|
||||||
return state;
|
return state;
|
||||||
@@ -29,7 +28,13 @@ export class PanelState {
|
|||||||
if (this.activePanel.value) {
|
if (this.activePanel.value) {
|
||||||
this.activePanel.value = false;
|
this.activePanel.value = false;
|
||||||
} else {
|
} 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();
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext, type Snippet } from "svelte";
|
import { type Snippet } from "svelte";
|
||||||
import { PanelState } from "./PanelState.svelte";
|
import { panelState as state } from "./PanelState.svelte";
|
||||||
|
|
||||||
const state = new PanelState();
|
|
||||||
setContext("panel-state", state);
|
|
||||||
|
|
||||||
const { children } = $props<{ children?: Snippet }>();
|
const { children } = $props<{ children?: Snippet }>();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
import type { NodeInput } from '@nodarium/types';
|
import type { NodeInput } from '@nodarium/types';
|
||||||
|
|
||||||
import Checkbox from './inputs/Checkbox.svelte';
|
import Checkbox from './inputs/Checkbox.svelte';
|
||||||
import Float from './inputs/Float.svelte';
|
import Number from './inputs/Number.svelte';
|
||||||
import Integer from './inputs/Integer.svelte';
|
|
||||||
import Select from './inputs/Select.svelte';
|
import Select from './inputs/Select.svelte';
|
||||||
import Vec3 from './inputs/Vec3.svelte';
|
import Vec3 from './inputs/Vec3.svelte';
|
||||||
// import Number from './inputs/Number.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
input: NodeInput;
|
input: NodeInput;
|
||||||
@@ -18,9 +16,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if input.type === 'float'}
|
{#if input.type === 'float'}
|
||||||
<Float bind:value min={input?.min} max={input?.max} />
|
<Number bind:value min={input?.min} max={input?.max} step={0.01} />
|
||||||
{:else if input.type === 'integer'}
|
{:else if input.type === 'integer'}
|
||||||
<Integer bind:value min={input?.min} max={input?.max} />
|
<Number bind:value min={input?.min} max={input?.max} />
|
||||||
{:else if input.type === 'boolean'}
|
{:else if input.type === 'boolean'}
|
||||||
<Checkbox bind:value {id} />
|
<Checkbox bind:value {id} />
|
||||||
{:else if input.type === 'select'}
|
{:else if input.type === 'select'}
|
||||||
|
|||||||
@@ -112,10 +112,7 @@
|
|||||||
onmousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
onmouseup={handleMouseUp}
|
onmouseup={handleMouseUp}
|
||||||
>
|
>
|
||||||
{#if typeof min !== 'undefined' && typeof max !== 'undefined'}
|
<div class="">
|
||||||
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
|
|
||||||
></span>
|
|
||||||
{/if}
|
|
||||||
<button onclick={() => handleChange(-step)}>-</button>
|
<button onclick={() => handleChange(-step)}>-</button>
|
||||||
<input
|
<input
|
||||||
bind:value
|
bind:value
|
||||||
@@ -130,6 +127,11 @@
|
|||||||
|
|
||||||
<button onclick={() => handleChange(+step)}>+</button>
|
<button onclick={() => handleChange(+step)}>+</button>
|
||||||
</div>
|
</div>
|
||||||
|
{#if typeof min !== 'undefined' && typeof max !== 'undefined'}
|
||||||
|
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
|
||||||
|
></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.component-wrapper {
|
.component-wrapper {
|
||||||
|
|||||||
@@ -12,8 +12,10 @@
|
|||||||
step = 1,
|
step = 1,
|
||||||
min = $bindable(0),
|
min = $bindable(0),
|
||||||
max = $bindable(1),
|
max = $bindable(1),
|
||||||
id
|
id: _id
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
const uid = $props.id();
|
||||||
|
const id = $derived(_id || uid);
|
||||||
|
|
||||||
if (min > max) {
|
if (min > max) {
|
||||||
[min, max] = [max, min];
|
[min, max] = [max, min];
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
return +parseFloat(input + '').toPrecision(2);
|
return +parseFloat(input + '').toPrecision(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputEl: HTMLInputElement | undefined = $state();
|
let inputEl = $state() as HTMLInputElement;
|
||||||
|
|
||||||
let prev = -1;
|
let prev = -1;
|
||||||
function update() {
|
function update() {
|
||||||
@@ -82,6 +84,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
--slider-height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.component-wrapper {
|
.component-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--layer-2, #4b4b4b);
|
background-color: var(--layer-2, #4b4b4b);
|
||||||
@@ -145,7 +151,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 3px;
|
height: var(--slider-height);
|
||||||
background: var(--layer-2, #4b4b4b);
|
background: var(--layer-2, #4b4b4b);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -154,16 +160,18 @@
|
|||||||
input[type='range']::-webkit-slider-thumb {
|
input[type='range']::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 0px;
|
width: 12px;
|
||||||
height: 0px;
|
height: var(--slider-height);
|
||||||
|
background: var(--text-color);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Thumb: for Firefox */
|
/* Thumb: for Firefox */
|
||||||
input[type='range']::-moz-range-thumb {
|
input[type='range']::-moz-range-thumb {
|
||||||
border: none;
|
border: none;
|
||||||
width: 0px;
|
width: 12px;
|
||||||
height: 0px;
|
height: var(--slider-height);
|
||||||
|
background: var(--text-color);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user