feat: some shit
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { NodeDefinition, NodeRegistry } from "@nodes/types";
|
||||
import { onMount } from "svelte";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
|
||||
let mx = $state(0);
|
||||
let my = $state(0);
|
||||
@ -39,9 +39,10 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
wrapper?.parentElement?.setAttribute("style", "cursor:help !important");
|
||||
const style = wrapper.parentElement?.style;
|
||||
style?.setProperty("cursor", "help");
|
||||
return () => {
|
||||
wrapper?.parentElement?.style.removeProperty("cursor");
|
||||
style?.removeProperty("cursor");
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@ -91,8 +92,9 @@
|
||||
border-radius: 5px;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
max-width: 250px;
|
||||
border: 1px solid var(--outline);
|
||||
z-index: 1000;
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { T } from "@threlte/core";
|
||||
|
||||
import BackgroundVert from "./Background.vert";
|
||||
import BackgroundFrag from "./Background.frag";
|
||||
import { colors } from "../graph/state.svelte";
|
||||
import { colors } from "../graph/colors.svelte";
|
||||
import { Color } from "three";
|
||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||
|
||||
type Props = {
|
||||
minZoom: number;
|
||||
@ -42,10 +42,10 @@
|
||||
value: [0, 1, 0],
|
||||
},
|
||||
backgroundColor: {
|
||||
value: new Color(0x171717),
|
||||
value: colors["layer-0"].clone(),
|
||||
},
|
||||
lineColor: {
|
||||
value: new Color(0x111111),
|
||||
value: colors["outline"].clone(),
|
||||
},
|
||||
zoomLimits: {
|
||||
value: [2, 50],
|
||||
@ -55,8 +55,9 @@
|
||||
},
|
||||
}}
|
||||
uniforms.camPos.value={cameraPosition}
|
||||
uniforms.backgroundColor.value={$colors["layer-0"]}
|
||||
uniforms.lineColor.value={$colors["outline"]}
|
||||
uniforms.backgroundColor.value={appSettings.theme &&
|
||||
colors["layer-0"].clone()}
|
||||
uniforms.lineColor.value={appSettings.theme && colors["outline"].clone()}
|
||||
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||
uniforms.dimensions.value={[width, height]}
|
||||
/>
|
||||
|
@ -1,13 +1,15 @@
|
||||
<script module lang="ts">
|
||||
import { colors } from "../graph/state.svelte";
|
||||
import { colors } from "../graph/colors.svelte";
|
||||
|
||||
const circleMaterial = new MeshBasicMaterial({
|
||||
color: get(colors).edge,
|
||||
color: colors.edge.clone(),
|
||||
toneMapped: false,
|
||||
});
|
||||
|
||||
colors.subscribe((c) => {
|
||||
circleMaterial.color.copy(c.edge.clone().convertSRGBToLinear());
|
||||
$effect.root(() => {
|
||||
$effect(() => {
|
||||
appSettings.theme;
|
||||
circleMaterial.color = colors.edge.clone().convertSRGBToLinear();
|
||||
})
|
||||
});
|
||||
|
||||
const lineCache = new Map<number, BufferGeometry>();
|
||||
@ -23,23 +25,24 @@
|
||||
<script lang="ts">
|
||||
import { T } from "@threlte/core";
|
||||
import { MeshLineMaterial } from "@threlte/extras";
|
||||
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
|
||||
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
|
||||
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
||||
import { Vector2 } from "three/src/math/Vector2.js";
|
||||
import { createEdgeGeometry } from "./createEdgeGeometry.js";
|
||||
import { get } from "svelte/store";
|
||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||
|
||||
type Props = {
|
||||
from: { x: number; y: number };
|
||||
to: { x: number; y: number };
|
||||
z:number;
|
||||
};
|
||||
|
||||
const { from, to }: Props = $props();
|
||||
|
||||
let samples = 5;
|
||||
const { from, to, z }: Props = $props();
|
||||
|
||||
let geometry: BufferGeometry|null = $state(null);
|
||||
|
||||
const lineColor = $derived(appSettings.theme && colors.edge.clone().convertSRGBToLinear());
|
||||
|
||||
let lastId: number | null = null;
|
||||
|
||||
const primeA = 31;
|
||||
@ -63,7 +66,8 @@
|
||||
const length = Math.floor(
|
||||
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
|
||||
);
|
||||
samples = Math.min(Math.max(10, length), 60) * 2;
|
||||
|
||||
const samples = Math.max(length * 16, 10);
|
||||
|
||||
curve.v0.set(0, 0);
|
||||
curve.v1.set(mid.x, 0);
|
||||
@ -79,13 +83,12 @@
|
||||
lineCache.set(curveId, geometry);
|
||||
};
|
||||
|
||||
|
||||
$effect(() => {
|
||||
if (from || to) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
const lineColor = $derived($colors.edge.clone().convertSRGBToLinear());
|
||||
</script>
|
||||
|
||||
<T.Mesh
|
||||
@ -110,6 +113,6 @@
|
||||
|
||||
{#if geometry}
|
||||
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
||||
<MeshLineMaterial width={3} attenuate={false} color={lineColor} />
|
||||
<MeshLineMaterial width={Math.max(z*0.0001,0.00001)} color={lineColor} />
|
||||
</T.Mesh>
|
||||
{/if}
|
||||
|
@ -1,8 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Edge from "./Edge.svelte";
|
||||
|
||||
type Props = { from: { x: number; y: number }; to: { x: number; y: number } };
|
||||
const { from, to }: Props = $props();
|
||||
type Props = {
|
||||
from: { x: number; y: number };
|
||||
to: { x: number; y: number };
|
||||
z: number;
|
||||
};
|
||||
const { from, to, z }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Edge {from} {to} />
|
||||
<Edge {from} {to} {z} />
|
||||
|
@ -1,11 +1,29 @@
|
||||
import { BufferGeometry, Vector3, BufferAttribute } from 'three'
|
||||
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils.js'
|
||||
import { BufferAttribute, BufferGeometry, Vector3 } from 'three';
|
||||
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils.js';
|
||||
|
||||
|
||||
export function createEdgeGeometry(points: Vector3[]) {
|
||||
|
||||
let shape = 'none'
|
||||
let shapeFunction = (p: number) => 1
|
||||
const length = points[0].distanceTo(points[points.length - 1]);
|
||||
|
||||
const startRadius = 10.5;
|
||||
const constantWidth = 2;
|
||||
const taperFraction = 0.8 / length;
|
||||
|
||||
function ease(t: number) {
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
let shapeFunction = (alpha: number) => {
|
||||
if (alpha < taperFraction) {
|
||||
const easedAlpha = ease(alpha / taperFraction);
|
||||
return startRadius + (constantWidth - startRadius) * easedAlpha;
|
||||
} else if (alpha > 1 - taperFraction) {
|
||||
const easedAlpha = ease((alpha - (1 - taperFraction)) / taperFraction);
|
||||
return constantWidth + (startRadius - constantWidth) * easedAlpha;
|
||||
} else {
|
||||
return constantWidth;
|
||||
}
|
||||
};
|
||||
|
||||
// When the component first runs we create the buffer geometry and allocate the buffer attributes
|
||||
let pointCount = points.length
|
||||
@ -19,9 +37,7 @@ export function createEdgeGeometry(points: Vector3[]) {
|
||||
let indices: number[] = []
|
||||
let indicesIndex = 0
|
||||
|
||||
if (shape === 'taper') {
|
||||
shapeFunction = (p: number) => 1 * Math.pow(4 * p * (1 - p), 1)
|
||||
}
|
||||
|
||||
|
||||
for (let j = 0; j < pointCount; j++) {
|
||||
const c = j / points.length
|
||||
@ -30,7 +46,7 @@ export function createEdgeGeometry(points: Vector3[]) {
|
||||
counterIndex += 2
|
||||
|
||||
setXY(side, doubleIndex, 1, -1)
|
||||
let width = shape === 'none' ? 1 : shapeFunction(j / (pointCount - 1))
|
||||
let width = shapeFunction((j / (pointCount - 1)))
|
||||
setXY(widthArray, doubleIndex, width, width)
|
||||
doubleIndex += 2
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Edge, Graph, Node, NodeInput, NodeRegistry, Socket, } from "@nodes/types";
|
||||
import { fastHashString } from "@nodes/utils";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import EventEmitter from "./helpers/EventEmitter.js";
|
||||
import { createLogger } from "./helpers/index.js";
|
||||
import throttle from "./helpers/throttle.js";
|
||||
@ -42,6 +42,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
|
||||
history: HistoryManager = new HistoryManager();
|
||||
execute = throttle(() => {
|
||||
console.log("Props", get(this.nodes).values().find(n => n.type === "max/plantarium/gravity")?.props);
|
||||
if (this.loaded === false) return;
|
||||
this.emit("result", this.serialize());
|
||||
}, 10);
|
||||
|
@ -6,7 +6,6 @@
|
||||
} from "../helpers/index.js";
|
||||
import type { OrthographicCamera } from "three";
|
||||
import Background from "../background/Background.svelte";
|
||||
import type { GraphManager } from "../graph-manager.js";
|
||||
import { getContext, onMount, setContext } from "svelte";
|
||||
import Camera from "../Camera.svelte";
|
||||
import GraphView from "./GraphView.svelte";
|
||||
@ -23,14 +22,13 @@
|
||||
import { Canvas } from "@threlte/core";
|
||||
import { getGraphManager } from "./context.js";
|
||||
|
||||
const state = getGraphState();
|
||||
const graphState = getGraphState();
|
||||
|
||||
export let snapToGrid = true;
|
||||
export let showGrid = true;
|
||||
export let showHelp = false;
|
||||
|
||||
let keymap =
|
||||
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
|
||||
const keymap = getContext<ReturnType<typeof createKeyMap>>("keymap");
|
||||
|
||||
const manager = getGraphManager();
|
||||
|
||||
@ -179,7 +177,7 @@
|
||||
}
|
||||
|
||||
setContext("setDownSocket", (socket: Socket) => {
|
||||
state.activeSocket = socket;
|
||||
graphState.activeSocket = socket;
|
||||
|
||||
let { node, index, position } = socket;
|
||||
|
||||
@ -198,14 +196,14 @@
|
||||
}
|
||||
|
||||
mouseDown = position;
|
||||
state.activeSocket = {
|
||||
graphState.activeSocket = {
|
||||
node,
|
||||
index,
|
||||
position,
|
||||
};
|
||||
|
||||
state.possibleSockets = manager
|
||||
.getPossibleSockets(state.activeSocket)
|
||||
graphState.possibleSockets = manager
|
||||
.getPossibleSockets(graphState.activeSocket)
|
||||
.map(([node, index]) => {
|
||||
return {
|
||||
node,
|
||||
@ -259,14 +257,15 @@
|
||||
let my = event.clientY - rect.y;
|
||||
|
||||
mousePosition = projectScreenToWorld(mx, my);
|
||||
hoveredNodeId = getNodeIdFromEvent(event);
|
||||
|
||||
if (!mouseDown) return;
|
||||
|
||||
// we are creating a new edge here
|
||||
if (state.activeSocket || state.possibleSockets?.length) {
|
||||
if (graphState.activeSocket || graphState.possibleSockets?.length) {
|
||||
let smallestDist = 1000;
|
||||
let _socket;
|
||||
for (const socket of state.possibleSockets) {
|
||||
for (const socket of graphState.possibleSockets) {
|
||||
const dist = Math.sqrt(
|
||||
(socket.position[0] - mousePosition[0]) ** 2 +
|
||||
(socket.position[1] - mousePosition[1]) ** 2,
|
||||
@ -279,9 +278,9 @@
|
||||
|
||||
if (_socket && smallestDist < 0.9) {
|
||||
mousePosition = _socket.position;
|
||||
state.hoveredSocket = _socket;
|
||||
graphState.hoveredSocket = _socket;
|
||||
} else {
|
||||
state.hoveredSocket = null;
|
||||
graphState.hoveredSocket = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -301,17 +300,17 @@
|
||||
const y = node.position[1];
|
||||
const height = getNodeHeight(node.type);
|
||||
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
|
||||
state.selectedNodes?.add(node.id);
|
||||
graphState.selectedNodes?.add(node.id);
|
||||
} else {
|
||||
state.selectedNodes?.delete(node.id);
|
||||
graphState.selectedNodes?.delete(node.id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// here we are handling dragging of nodes
|
||||
if (state.activeNodeId !== -1 && mouseDownId !== -1) {
|
||||
const node = manager.getNode(state.activeNodeId);
|
||||
if (graphState.activeNodeId !== -1 && mouseDownId !== -1) {
|
||||
const node = manager.getNode(graphState.activeNodeId);
|
||||
if (!node || event.buttons !== 1) return;
|
||||
|
||||
node.tmp = node.tmp || {};
|
||||
@ -340,8 +339,8 @@
|
||||
const vecX = oldX - newX;
|
||||
const vecY = oldY - newY;
|
||||
|
||||
if (state.selectedNodes?.size) {
|
||||
for (const nodeId of state.selectedNodes) {
|
||||
if (graphState.selectedNodes?.size) {
|
||||
for (const nodeId of graphState.selectedNodes) {
|
||||
const n = manager.getNode(nodeId);
|
||||
if (!n?.tmp) continue;
|
||||
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||
@ -360,6 +359,7 @@
|
||||
}
|
||||
|
||||
// here we are handling panning of camera
|
||||
isPanning = true;
|
||||
let newX = cameraDown[0] - (mx - mouseDown[0]) / cameraPosition[2];
|
||||
let newY = cameraDown[1] - (my - mouseDown[1]) / cameraPosition[2];
|
||||
|
||||
@ -424,43 +424,46 @@
|
||||
|
||||
// if we clicked on a node
|
||||
if (clickedNodeId !== -1) {
|
||||
if (state.activeNodeId === -1) {
|
||||
state.activeNodeId = clickedNodeId;
|
||||
if (graphState.activeNodeId === -1) {
|
||||
graphState.activeNodeId = clickedNodeId;
|
||||
// if the selected node is the same as the clicked node
|
||||
} else if (state.activeNodeId === clickedNodeId) {
|
||||
} else if (graphState.activeNodeId === clickedNodeId) {
|
||||
//$activeNodeId = -1;
|
||||
// if the clicked node is different from the selected node and secondary
|
||||
} else if (event.ctrlKey) {
|
||||
state.selectedNodes = state.selectedNodes || new Set();
|
||||
state.selectedNodes.add(state.activeNodeId);
|
||||
state.selectedNodes.delete(clickedNodeId);
|
||||
state.activeNodeId = clickedNodeId;
|
||||
graphState.selectedNodes.add(graphState.activeNodeId);
|
||||
graphState.selectedNodes.delete(clickedNodeId);
|
||||
graphState.activeNodeId = clickedNodeId;
|
||||
// select the node
|
||||
} else if (event.shiftKey) {
|
||||
const activeNode = manager.getNode(state.activeNodeId);
|
||||
const activeNode = manager.getNode(graphState.activeNodeId);
|
||||
const newNode = manager.getNode(clickedNodeId);
|
||||
if (activeNode && newNode) {
|
||||
const edge = manager.getNodesBetween(activeNode, newNode);
|
||||
if (edge) {
|
||||
const selected = new Set(edge.map((n) => n.id));
|
||||
selected.add(clickedNodeId);
|
||||
state.selectedNodes = selected;
|
||||
graphState.selectedNodes.clear();
|
||||
for (const node of edge) {
|
||||
graphState.selectedNodes.add(node.id);
|
||||
}
|
||||
graphState.selectedNodes.add(clickedNodeId);
|
||||
}
|
||||
}
|
||||
} else if (!state.selectedNodes?.has(clickedNodeId)) {
|
||||
state.activeNodeId = clickedNodeId;
|
||||
state.clearSelection();
|
||||
} else if (!graphState.selectedNodes.has(clickedNodeId)) {
|
||||
graphState.activeNodeId = clickedNodeId;
|
||||
graphState.clearSelection();
|
||||
}
|
||||
} else if (event.ctrlKey) {
|
||||
boxSelection = true;
|
||||
}
|
||||
const node = manager.getNode(state.activeNodeId);
|
||||
|
||||
const node = manager.getNode(graphState.activeNodeId);
|
||||
if (!node) return;
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.downX = node.position[0];
|
||||
node.tmp.downY = node.position[1];
|
||||
if (state.selectedNodes) {
|
||||
for (const nodeId of state.selectedNodes) {
|
||||
|
||||
if (graphState.selectedNodes) {
|
||||
for (const nodeId of graphState.selectedNodes) {
|
||||
const n = manager.getNode(nodeId);
|
||||
if (!n) continue;
|
||||
n.tmp = n.tmp || {};
|
||||
@ -471,8 +474,12 @@
|
||||
}
|
||||
|
||||
function copyNodes() {
|
||||
if (state.activeNodeId === -1 && !state.selectedNodes?.size) return;
|
||||
let _nodes = [state.activeNodeId, ...(state.selectedNodes?.values() || [])]
|
||||
if (graphState.activeNodeId === -1 && !graphState.selectedNodes?.size)
|
||||
return;
|
||||
let _nodes = [
|
||||
graphState.activeNodeId,
|
||||
...(graphState.selectedNodes?.values() || []),
|
||||
]
|
||||
.map((id) => manager.getNode(id))
|
||||
.filter(Boolean) as Node[];
|
||||
|
||||
@ -508,7 +515,10 @@
|
||||
.filter(Boolean) as Node[];
|
||||
|
||||
const newNodes = manager.createGraph(_nodes, clipboard.edges);
|
||||
state.selectedNodes = new Set(newNodes.map((n) => n.id));
|
||||
graphState.selectedNodes.clear();
|
||||
for (const node of newNodes) {
|
||||
graphState.selectedNodes.add(node.id);
|
||||
}
|
||||
}
|
||||
|
||||
const isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
|
||||
@ -517,12 +527,14 @@
|
||||
key: "l",
|
||||
description: "Select linked nodes",
|
||||
callback: () => {
|
||||
const activeNode = manager.getNode(state.activeNodeId);
|
||||
const activeNode = manager.getNode(graphState.activeNodeId);
|
||||
if (activeNode) {
|
||||
const nodes = manager.getLinkedNodes(activeNode);
|
||||
state.selectedNodes = new Set(nodes.map((n) => n.id));
|
||||
graphState.selectedNodes.clear();
|
||||
for (const node of nodes) {
|
||||
graphState.selectedNodes.add(node.id);
|
||||
}
|
||||
}
|
||||
console.log(activeNode);
|
||||
},
|
||||
});
|
||||
|
||||
@ -552,8 +564,8 @@
|
||||
key: "Escape",
|
||||
description: "Deselect nodes",
|
||||
callback: () => {
|
||||
state.activeNodeId = -1;
|
||||
state.clearSelection();
|
||||
graphState.activeNodeId = -1;
|
||||
graphState.clearSelection();
|
||||
(document.activeElement as HTMLElement)?.blur();
|
||||
},
|
||||
});
|
||||
@ -605,7 +617,9 @@
|
||||
description: "Select all nodes",
|
||||
callback: () => {
|
||||
if (!isBodyFocused()) return;
|
||||
state.selectedNodes = new Set($nodes.keys());
|
||||
for (const node of $nodes.keys()) {
|
||||
graphState.selectedNodes.add(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -654,38 +668,39 @@
|
||||
callback: (event) => {
|
||||
if (!isBodyFocused()) return;
|
||||
manager.startUndoGroup();
|
||||
if (state.activeNodeId !== -1) {
|
||||
const node = manager.getNode(state.activeNodeId);
|
||||
if (graphState.activeNodeId !== -1) {
|
||||
const node = manager.getNode(graphState.activeNodeId);
|
||||
if (node) {
|
||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
state.activeNodeId = -1;
|
||||
graphState.activeNodeId = -1;
|
||||
}
|
||||
}
|
||||
if (state.selectedNodes) {
|
||||
for (const nodeId of state.selectedNodes) {
|
||||
if (graphState.selectedNodes) {
|
||||
for (const nodeId of graphState.selectedNodes) {
|
||||
const node = manager.getNode(nodeId);
|
||||
if (node) {
|
||||
manager.removeNode(node, { restoreEdges: event.ctrlKey });
|
||||
}
|
||||
}
|
||||
state.clearSelection();
|
||||
graphState.clearSelection();
|
||||
}
|
||||
manager.saveUndoGroup();
|
||||
},
|
||||
});
|
||||
|
||||
function handleMouseUp(event: MouseEvent) {
|
||||
isPanning = false;
|
||||
if (!mouseDown) return;
|
||||
|
||||
const activeNode = manager.getNode(state.activeNodeId);
|
||||
const activeNode = manager.getNode(graphState.activeNodeId);
|
||||
|
||||
const clickedNodeId = getNodeIdFromEvent(event);
|
||||
|
||||
if (clickedNodeId !== -1) {
|
||||
if (activeNode) {
|
||||
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) {
|
||||
state.clearSelection();
|
||||
state.activeNodeId = clickedNodeId;
|
||||
graphState.activeNodeId = clickedNodeId;
|
||||
graphState.clearSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -708,7 +723,7 @@
|
||||
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
|
||||
}
|
||||
const nodes = [
|
||||
...[...(state.selectedNodes?.values() || [])].map((id) =>
|
||||
...[...(graphState.selectedNodes?.values() || [])].map((id) =>
|
||||
manager.getNode(id),
|
||||
),
|
||||
] as NodeType[];
|
||||
@ -747,26 +762,26 @@
|
||||
$edges = $edges;
|
||||
});
|
||||
manager.save();
|
||||
} else if (state.hoveredSocket && state.activeSocket) {
|
||||
} else if (graphState.hoveredSocket && graphState.activeSocket) {
|
||||
if (
|
||||
typeof state.hoveredSocket.index === "number" &&
|
||||
typeof state.activeSocket.index === "string"
|
||||
typeof graphState.hoveredSocket.index === "number" &&
|
||||
typeof graphState.activeSocket.index === "string"
|
||||
) {
|
||||
manager.createEdge(
|
||||
state.hoveredSocket.node,
|
||||
state.hoveredSocket.index || 0,
|
||||
state.activeSocket.node,
|
||||
state.activeSocket.index,
|
||||
graphState.hoveredSocket.node,
|
||||
graphState.hoveredSocket.index || 0,
|
||||
graphState.activeSocket.node,
|
||||
graphState.activeSocket.index,
|
||||
);
|
||||
} else if (
|
||||
typeof state.activeSocket.index == "number" &&
|
||||
typeof state.hoveredSocket.index === "string"
|
||||
typeof graphState.activeSocket.index == "number" &&
|
||||
typeof graphState.hoveredSocket.index === "string"
|
||||
) {
|
||||
manager.createEdge(
|
||||
state.activeSocket.node,
|
||||
state.activeSocket.index || 0,
|
||||
state.hoveredSocket.node,
|
||||
state.hoveredSocket.index,
|
||||
graphState.activeSocket.node,
|
||||
graphState.activeSocket.index || 0,
|
||||
graphState.hoveredSocket.node,
|
||||
graphState.hoveredSocket.index,
|
||||
);
|
||||
}
|
||||
manager.save();
|
||||
@ -780,22 +795,25 @@
|
||||
cameraDown[1] === cameraPosition[1] &&
|
||||
isBodyFocused()
|
||||
) {
|
||||
state.activeNodeId = -1;
|
||||
state.clearSelection();
|
||||
graphState.activeNodeId = -1;
|
||||
graphState.clearSelection();
|
||||
}
|
||||
|
||||
mouseDown = null;
|
||||
boxSelection = false;
|
||||
state.activeSocket = null;
|
||||
state.possibleSockets = [];
|
||||
state.hoveredSocket = null;
|
||||
graphState.activeSocket = null;
|
||||
graphState.possibleSockets = [];
|
||||
graphState.hoveredSocket = null;
|
||||
addMenuPosition = null;
|
||||
}
|
||||
|
||||
let isPanning = false;
|
||||
let isDragging = false;
|
||||
let hoveredNodeId = -1;
|
||||
|
||||
function handleMouseLeave() {
|
||||
isDragging = false;
|
||||
isPanning = false;
|
||||
}
|
||||
|
||||
function handleDrop(event: DragEvent) {
|
||||
@ -865,16 +883,19 @@
|
||||
function handleDragEnter(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
isDragging = true;
|
||||
isPanning = false;
|
||||
}
|
||||
|
||||
function handlerDragOver(e: DragEvent) {
|
||||
isDragging = true;
|
||||
e.preventDefault();
|
||||
isDragging = true;
|
||||
isPanning = false;
|
||||
}
|
||||
|
||||
function handleDragEnd(e: DragEvent) {
|
||||
isDragging = false;
|
||||
e.preventDefault();
|
||||
isDragging = true;
|
||||
isPanning = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@ -893,6 +914,8 @@
|
||||
on:wheel={handleMouseScroll}
|
||||
bind:this={wrapper}
|
||||
class="graph-wrapper"
|
||||
class:is-panning={isPanning}
|
||||
class:is-hovering={hoveredNodeId !== -1}
|
||||
aria-label="Graph"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@ -916,9 +939,6 @@
|
||||
/>
|
||||
<label for="drop-zone"></label>
|
||||
|
||||
{#if showHelp}
|
||||
<HelpView registry={manager.registry} />
|
||||
{/if}
|
||||
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||
<Camera bind:camera position={cameraPosition} />
|
||||
|
||||
@ -943,11 +963,12 @@
|
||||
<AddMenu bind:position={addMenuPosition} graph={manager} />
|
||||
{/if}
|
||||
|
||||
{#if state.activeSocket}
|
||||
{#if graphState.activeSocket}
|
||||
<FloatingEdge
|
||||
z={cameraPosition[2]}
|
||||
from={{
|
||||
x: state.activeSocket.position[0],
|
||||
y: state.activeSocket.position[1],
|
||||
x: graphState.activeSocket.position[0],
|
||||
y: graphState.activeSocket.position[1],
|
||||
}}
|
||||
to={{ x: mousePosition[0], y: mousePosition[1] }}
|
||||
/>
|
||||
@ -962,6 +983,10 @@
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
{#if showHelp}
|
||||
<HelpView registry={manager.registry} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.graph-wrapper {
|
||||
position: relative;
|
||||
@ -969,6 +994,15 @@
|
||||
transition: opacity 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.is-hovering {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.is-panning {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
@ -6,10 +6,23 @@
|
||||
import { getContext, onMount } from "svelte";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { getGraphState } from "./state.svelte";
|
||||
import { useThrelte } from "@threlte/core";
|
||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||
|
||||
export let nodes: Writable<Map<number, NodeType>>;
|
||||
export let edges: Writable<EdgeType[]>;
|
||||
export let cameraPosition = [0, 0, 4];
|
||||
type Props = {
|
||||
nodes: Writable<Map<number, NodeType>>;
|
||||
edges: Writable<EdgeType[]>;
|
||||
cameraPosition: [number, number, number];
|
||||
};
|
||||
|
||||
const { nodes, edges, cameraPosition = [0, 0, 4] }: Props = $props();
|
||||
|
||||
const { invalidate } = useThrelte();
|
||||
|
||||
$effect(() => {
|
||||
appSettings.theme;
|
||||
invalidate();
|
||||
});
|
||||
|
||||
const graphState = getGraphState();
|
||||
|
||||
@ -23,7 +36,6 @@
|
||||
function getEdgePosition(edge: EdgeType) {
|
||||
const pos1 = getSocketPosition(edge[0], edge[1]);
|
||||
const pos2 = getSocketPosition(edge[2], edge[3]);
|
||||
|
||||
return [pos1[0], pos1[1], pos2[0], pos2[1]];
|
||||
}
|
||||
|
||||
@ -41,6 +53,7 @@
|
||||
{@const pos = getEdgePosition(edge)}
|
||||
{@const [x1, y1, x2, y2] = pos}
|
||||
<Edge
|
||||
z={cameraPosition[2]}
|
||||
from={{
|
||||
x: x1,
|
||||
y: y1,
|
||||
|
@ -3,19 +3,18 @@
|
||||
import GraphEl from "./Graph.svelte";
|
||||
import { GraphManager } from "../graph-manager.js";
|
||||
import { setContext } from "svelte";
|
||||
import { type Writable } from "svelte/store";
|
||||
import { debounce } from "$lib/helpers";
|
||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||
import { GraphState } from "./state.svelte";
|
||||
|
||||
const state = new GraphState();
|
||||
setContext("graphState", state);
|
||||
const graphState = new GraphState();
|
||||
setContext("graphState", graphState);
|
||||
|
||||
type Props = {
|
||||
graph: Graph;
|
||||
registry: NodeRegistry;
|
||||
|
||||
settings?: Writable<Record<string, any>>;
|
||||
settings?: Record<string, any>;
|
||||
|
||||
activeNode?: Node;
|
||||
showGrid?: boolean;
|
||||
@ -41,33 +40,32 @@
|
||||
}: Props = $props();
|
||||
|
||||
export const keymap = createKeyMap([]);
|
||||
setContext("keymap", keymap);
|
||||
|
||||
export const manager = new GraphManager(registry);
|
||||
setContext("graphManager", manager);
|
||||
|
||||
$effect(() => {
|
||||
if (state.activeNodeId !== -1) {
|
||||
activeNode = manager.getNode(state.activeNodeId);
|
||||
if (graphState.activeNodeId !== -1) {
|
||||
activeNode = manager.getNode(graphState.activeNodeId);
|
||||
} else {
|
||||
activeNode = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
setContext("keymap", keymap);
|
||||
|
||||
const updateSettings = debounce((s) => {
|
||||
manager.setSettings(s);
|
||||
}, 200);
|
||||
|
||||
$effect(() => {
|
||||
if (settingTypes && settings) {
|
||||
updateSettings($settings);
|
||||
updateSettings($state.snapshot(settings));
|
||||
}
|
||||
});
|
||||
|
||||
manager.on("settings", (_settings) => {
|
||||
settingTypes = _settings.types;
|
||||
settings?.set(_settings.values);
|
||||
settings = _settings.values;
|
||||
});
|
||||
|
||||
manager.on("result", (result) => onresult?.(result));
|
||||
|
31
app/src/lib/graph-interface/graph/colors.svelte.ts
Normal file
31
app/src/lib/graph-interface/graph/colors.svelte.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||
import { Color } from "three";
|
||||
|
||||
const variables = [
|
||||
"layer-0",
|
||||
"layer-1",
|
||||
"layer-2",
|
||||
"layer-3",
|
||||
"outline",
|
||||
"active",
|
||||
"selected",
|
||||
"edge",
|
||||
] as const;
|
||||
|
||||
function getColor(variable: typeof variables[number]) {
|
||||
const style = getComputedStyle(document.body.parentElement!);
|
||||
let color = style.getPropertyValue(`--${variable}`);
|
||||
return new Color().setStyle(color);
|
||||
}
|
||||
|
||||
export const colors = Object.fromEntries(variables.map(v => [v, getColor(v)])) as Record<typeof variables[number], Color>;
|
||||
|
||||
$effect.root(() => {
|
||||
$effect(() => {
|
||||
if (!appSettings.theme || !("getComputedStyle" in globalThis)) return;
|
||||
const style = getComputedStyle(document.body.parentElement!);
|
||||
for (const v of variables) {
|
||||
colors[v].setStyle(style.getPropertyValue(`--${v}`));
|
||||
}
|
||||
});
|
||||
})
|
@ -1,48 +0,0 @@
|
||||
import { readable } from "svelte/store";
|
||||
import { Color } from "three";
|
||||
|
||||
const variables = [
|
||||
"layer-0",
|
||||
"layer-1",
|
||||
"layer-2",
|
||||
"layer-3",
|
||||
"outline",
|
||||
"active",
|
||||
"selected",
|
||||
"edge",
|
||||
] as const;
|
||||
|
||||
const store = Object.fromEntries(variables.map(v => [v, new Color()])) as Record<typeof variables[number], Color>;
|
||||
|
||||
let lastStyle = "";
|
||||
|
||||
function updateColors() {
|
||||
if (!("getComputedStyle" in globalThis)) return;
|
||||
const style = getComputedStyle(document.body.parentElement!);
|
||||
let hash = "";
|
||||
for (const v of variables) {
|
||||
let color = style.getPropertyValue(`--${v}`);
|
||||
hash += color;
|
||||
store[v].setStyle(color);
|
||||
}
|
||||
if (hash === lastStyle) return;
|
||||
lastStyle = hash;
|
||||
}
|
||||
|
||||
export const colors = readable(store, set => {
|
||||
|
||||
updateColors();
|
||||
set(store);
|
||||
|
||||
setTimeout(() => {
|
||||
updateColors();
|
||||
set(store);
|
||||
}, 1000);
|
||||
|
||||
window.onload = function () { updateColors(); set(store) };
|
||||
|
||||
document.body.addEventListener("transitionstart", () => {
|
||||
updateColors();
|
||||
set(store);
|
||||
})
|
||||
});
|
@ -1,26 +1,22 @@
|
||||
import type { Socket } from "@nodes/types";
|
||||
import { getContext } from "svelte";
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
export function getGraphState() {
|
||||
return getContext<GraphState>("graphState");
|
||||
}
|
||||
|
||||
export class GraphState {
|
||||
|
||||
activeNodeId = $state(-1);
|
||||
selectedNodes = $state(new Set<number>());
|
||||
selectedNodes = new SvelteSet<number>();
|
||||
activeSocket = $state<Socket | null>(null);
|
||||
hoveredSocket = $state<Socket | null>(null);
|
||||
possibleSockets = $state<Socket[]>([]);
|
||||
possibleSocketIds = $derived(new Set(
|
||||
this.possibleSockets.map((s) => `${s.node.id}-${s.index}`),
|
||||
));
|
||||
|
||||
clearSelection() {
|
||||
this.selectedNodes = new Set();
|
||||
this.selectedNodes.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { colors } from "./colors";
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Node } from "@nodes/types";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import { colors, getGraphState } from "../graph/state.svelte";
|
||||
import { getGraphState } from "../graph/state.svelte";
|
||||
import { T } from "@threlte/core";
|
||||
import { Color, type Mesh } from "three";
|
||||
import { type Mesh } from "three";
|
||||
import NodeFrag from "./Node.frag";
|
||||
import NodeVert from "./Node.vert";
|
||||
import NodeHtml from "./NodeHTML.svelte";
|
||||
import { colors } from "../graph/colors.svelte";
|
||||
import { appSettings } from "$lib/settings/app-settings.svelte";
|
||||
|
||||
const graphState = getGraphState();
|
||||
|
||||
@ -18,7 +20,16 @@
|
||||
const { node, inView, z }: Props = $props();
|
||||
|
||||
const isActive = $derived(graphState.activeNodeId === node.id);
|
||||
const isSelected = $derived(!!graphState.selectedNodes?.has(node.id));
|
||||
const isSelected = $derived(graphState.selectedNodes.has(node.id));
|
||||
let strokeColor = $state(colors.selected);
|
||||
$effect(() => {
|
||||
appSettings.theme;
|
||||
strokeColor = isSelected
|
||||
? colors.selected.clone()
|
||||
: isActive
|
||||
? colors.active.clone()
|
||||
: colors.outline.clone();
|
||||
});
|
||||
|
||||
const updateNodePosition =
|
||||
getContext<(n: Node) => void>("updateNodePosition");
|
||||
@ -30,16 +41,11 @@
|
||||
const height = getNodeHeight?.(node.type);
|
||||
|
||||
$effect(() => {
|
||||
if (node && meshRef) {
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.mesh = meshRef;
|
||||
updateNodePosition?.(node);
|
||||
}
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.mesh = meshRef;
|
||||
updateNodePosition?.(node);
|
||||
});
|
||||
|
||||
const colorBright = $colors["layer-2"];
|
||||
const colorDark = $colors["layer-1"];
|
||||
|
||||
onMount(() => {
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.mesh = meshRef;
|
||||
@ -61,20 +67,14 @@
|
||||
fragmentShader={NodeFrag}
|
||||
transparent
|
||||
uniforms={{
|
||||
uColorBright: { value: new Color("#171717") },
|
||||
uColorDark: { value: new Color("#151515") },
|
||||
uStrokeColor: { value: new Color("#9d5f28") },
|
||||
uColorBright: { value: colors["layer-2"] },
|
||||
uColorDark: { value: colors["layer-1"] },
|
||||
uStrokeColor: { value: colors.outline.clone() },
|
||||
uStrokeWidth: { value: 1.0 },
|
||||
uWidth: { value: 20 },
|
||||
uHeight: { value: height },
|
||||
}}
|
||||
uniforms.uColorBright.value={colorBright}
|
||||
uniforms.uColorDark.value={colorDark}
|
||||
uniforms.uStrokeColor.value={isSelected
|
||||
? $colors.selected
|
||||
: isActive
|
||||
? $colors.active
|
||||
: $colors.outline}
|
||||
uniforms.uStrokeColor.value={strokeColor.clone()}
|
||||
uniforms.uStrokeWidth.value={(7 - z) / 3}
|
||||
/>
|
||||
</T.Mesh>
|
||||
|
@ -3,14 +3,27 @@
|
||||
import NodeHeader from "./NodeHeader.svelte";
|
||||
import NodeParameter from "./NodeParameter.svelte";
|
||||
import { getContext, onMount } from "svelte";
|
||||
export let isActive = false;
|
||||
export let isSelected = false;
|
||||
export let inView = true;
|
||||
export let z = 2;
|
||||
|
||||
let ref: HTMLDivElement;
|
||||
export let node: Node;
|
||||
export let position = "absolute";
|
||||
|
||||
type Props = {
|
||||
node: Node;
|
||||
|
||||
position?: "absolute" | "fixed";
|
||||
isActive?: boolean;
|
||||
isSelected?: boolean;
|
||||
inView?: boolean;
|
||||
z?: number;
|
||||
};
|
||||
|
||||
let {
|
||||
node,
|
||||
position = "absolute",
|
||||
isActive = false,
|
||||
isSelected = false,
|
||||
inView = true,
|
||||
z = 2,
|
||||
}: Props = $props();
|
||||
|
||||
const zOffset = (node.tmp?.random || 0) * 0.5;
|
||||
const zLimit = 2 - zOffset;
|
||||
@ -25,12 +38,6 @@
|
||||
const updateNodePosition =
|
||||
getContext<(n: Node) => void>("updateNodePosition");
|
||||
|
||||
$: if (node && ref) {
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.ref = ref;
|
||||
updateNodePosition?.(node);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.ref = ref;
|
||||
|
@ -26,9 +26,9 @@
|
||||
const aspectRatio = 0.25;
|
||||
|
||||
const path = createNodePath({
|
||||
depth: 5,
|
||||
height: 29,
|
||||
y: 50,
|
||||
depth: 5.5,
|
||||
height: 34,
|
||||
y: 49,
|
||||
cornerTop,
|
||||
rightBump,
|
||||
aspectRatio,
|
||||
@ -42,9 +42,9 @@
|
||||
aspectRatio,
|
||||
});
|
||||
const pathHover = createNodePath({
|
||||
depth: 9,
|
||||
depth: 8.5,
|
||||
height: 50,
|
||||
y: 50,
|
||||
y: 49,
|
||||
cornerTop,
|
||||
rightBump,
|
||||
aspectRatio,
|
||||
@ -103,12 +103,12 @@
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
z-index: -1;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: calc(100% - 2px);
|
||||
height: calc(100% - 1px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
@ -53,14 +53,14 @@
|
||||
const path = createNodePath({
|
||||
depth: 7,
|
||||
height: 20,
|
||||
y: 51,
|
||||
y: 50.5,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio,
|
||||
});
|
||||
const pathDisabled = createNodePath({
|
||||
depth: 4.5,
|
||||
height: 14,
|
||||
depth: 6,
|
||||
height: 18,
|
||||
y: 50.5,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
@ -172,11 +172,11 @@
|
||||
svg {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
width: calc(100% - 2px);
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
top: 0;
|
||||
left: 0;
|
||||
left: 1px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user