feat: make it work
Some checks failed
📊 Benchmark the Runtime / release (pull_request) Successful in 1m5s
🚀 Lint & Test & Deploy / release (pull_request) Failing after 59s

This commit is contained in:
2026-04-24 21:45:56 +02:00
parent 6457c9db0b
commit c42bc93174
7 changed files with 90 additions and 32 deletions

View File

@@ -94,7 +94,7 @@ export class GraphManager extends EventEmitter<{
groups = new SvelteMap<string, NodeGroupDefinition>(); groups = new SvelteMap<string, NodeGroupDefinition>();
groupNodeDefinitions: Map<string, NodeDefinition> = new Map(); groupNodeDefinitions: Map<string, NodeDefinition> = new Map();
currentGroupContext: string | null = null; currentGroupContext: string | null = null;
graphStack: { rootGraph: Graph; groupId: string; cameraPosition: [number, number, number] }[] = $state([]); graphStack: { rootGraph: Graph; groupId: string; nodeId: number; cameraPosition: [number, number, number] }[] = $state([]);
inputSockets = $derived.by(() => { inputSockets = $derived.by(() => {
const s = new SvelteSet<string>(); const s = new SvelteSet<string>();
@@ -115,7 +115,7 @@ export class GraphManager extends EventEmitter<{
const { rootGraph, groupId } = $state.snapshot(this.graphStack[i]); const { rootGraph, groupId } = $state.snapshot(this.graphStack[i]);
// Prefer the live definition (may have been updated via addGroupSocket/rename) // Prefer the live definition (may have been updated via addGroupSocket/rename)
// over the snapshot taken when we entered the group. // over the snapshot taken when we entered the group.
const currentDef = (this.graph.groups ?? rootGraph.groups)?.[groupId]; const currentDef = $state.snapshot((this.graph.groups ?? rootGraph.groups)?.[groupId]);
merged = { merged = {
...rootGraph, ...rootGraph,
groups: { groups: {
@@ -563,7 +563,7 @@ export class GraphManager extends EventEmitter<{
if (!group) return false; if (!group) return false;
const currentSerialized = this.serialize(); const currentSerialized = this.serialize();
this.graphStack.push({ rootGraph: currentSerialized, groupId, cameraPosition }); this.graphStack.push({ rootGraph: currentSerialized, groupId, nodeId, cameraPosition });
this.currentGroupContext = groupId; this.currentGroupContext = groupId;
@@ -581,10 +581,10 @@ export class GraphManager extends EventEmitter<{
return true; return true;
} }
exitGroup(): [number, number, number] | false { exitGroup(): { camera: [number, number, number]; nodeId: number } | false {
if (this.graphStack.length === 0) return false; if (this.graphStack.length === 0) return false;
const { rootGraph, groupId, cameraPosition } = this.graphStack[this.graphStack.length - 1]; const { rootGraph, groupId, nodeId, cameraPosition } = this.graphStack[this.graphStack.length - 1];
this.graphStack.pop(); this.graphStack.pop();
// Serialize current internal graph state // Serialize current internal graph state
@@ -614,7 +614,7 @@ export class GraphManager extends EventEmitter<{
this.history.reset(); this.history.reset();
this.save(); this.save();
return cameraPosition; return { camera: cameraPosition, nodeId };
} }
get isInsideGroup(): boolean { get isInsideGroup(): boolean {

View File

@@ -91,21 +91,23 @@
function navigateToBreadcrumb(index: number) { function navigateToBreadcrumb(index: number) {
const crumbs = manager.breadcrumbs; const crumbs = manager.breadcrumbs;
const depth = crumbs.length - 1 - index; const depth = crumbs.length - 1 - index;
let restoredCamera: [number, number, number] | false = false; let result: { camera: [number, number, number]; nodeId: number } | false = false;
for (let i = 0; i < depth; i++) { for (let i = 0; i < depth; i++) {
const groupId = manager.currentGroupContext; const groupId = manager.currentGroupContext;
if (groupId) { if (groupId) {
state.groupCameras.set(groupId, [...state.cameraPosition] as [number, number, number]); state.groupCameras.set(groupId, [...state.cameraPosition] as [number, number, number]);
} }
restoredCamera = manager.exitGroup(); result = manager.exitGroup();
} }
state.activeNodeId = -1; if (result !== false) {
state.clearSelection(); state.activeNodeId = result.nodeId;
if (restoredCamera !== false) { state.clearSelection();
state.cameraPosition[0] = restoredCamera[0]; state.cameraPosition[0] = result.camera[0];
state.cameraPosition[1] = restoredCamera[1]; state.cameraPosition[1] = result.camera[1];
state.cameraPosition[2] = restoredCamera[2]; state.cameraPosition[2] = result.camera[2];
} else { } else {
state.activeNodeId = -1;
state.clearSelection();
state.centerNode(); state.centerNode();
} }
} }

View File

@@ -31,6 +31,20 @@ export function getSocketPosition(
index: string | number index: string | number
): [number, number] { ): [number, number] {
if (typeof index === 'number') { if (typeof index === 'number') {
if (node.type === '__virtual/group/input') {
const nodeType = node.state.type;
const keys = Object.keys(nodeType?.inputs || {});
let height = 5;
for (let i = 0; i < keys.length; i++) {
const h = getParameterHeight(nodeType!, keys[i]) / 10;
if (i === index) { height += h / 2; break; }
height += h;
}
return [
(node?.state?.x ?? node.position[0]) + 20,
(node?.state?.y ?? node.position[1]) + height
];
}
return [ return [
(node?.state?.x ?? node.position[0]) + 20, (node?.state?.x ?? node.position[0]) + 20,
(node?.state?.y ?? node.position[1]) + 2.5 + 10 * index (node?.state?.y ?? node.position[1]) + 2.5 + 10 * index

View File

@@ -55,13 +55,13 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
[...graphState.cameraPosition] as [number, number, number] [...graphState.cameraPosition] as [number, number, number]
); );
} }
const savedCamera = graph.exitGroup(); const result = graph.exitGroup();
if (savedCamera !== false) { if (result !== false) {
graphState.activeNodeId = -1; graphState.activeNodeId = result.nodeId;
graphState.clearSelection(); graphState.clearSelection();
graphState.cameraPosition[0] = savedCamera[0]; graphState.cameraPosition[0] = result.camera[0];
graphState.cameraPosition[1] = savedCamera[1]; graphState.cameraPosition[1] = result.camera[1];
graphState.cameraPosition[2] = savedCamera[2]; graphState.cameraPosition[2] = result.camera[2];
return; return;
} }
} }

View File

@@ -128,6 +128,7 @@
id={key} id={key}
input={value} input={value}
isLast={i == parameters.length - 1} isLast={i == parameters.length - 1}
outputIndex={node.type === '__virtual/group/input' ? i : undefined}
/> />
{/each} {/each}
</div> </div>

View File

@@ -22,7 +22,7 @@
} }
const cornerTop = 10; const cornerTop = 10;
const rightBump = $derived(!!node?.state?.type?.outputs?.length); const rightBump = $derived(!!node?.state?.type?.outputs?.length && node.type !== '__virtual/group/input');
const aspectRatio = 0.25; const aspectRatio = 0.25;
const path = $derived( const path = $derived(
@@ -72,13 +72,15 @@
{/if} {/if}
{node.state?.type?.meta?.title ?? node.type.split('/').pop()} {node.state?.type?.meta?.title ?? node.type.split('/').pop()}
</div> </div>
<div {#if node.type !== '__virtual/group/input'}
class="target" <div
role="button" class="target"
tabindex="0" role="button"
onmousedown={handleMouseDown} tabindex="0"
> onmousedown={handleMouseDown}
</div> >
</div>
{/if}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100" viewBox="0 0 100 100"

View File

@@ -10,6 +10,7 @@
input: NodeInput; input: NodeInput;
id: string; id: string;
isLast?: boolean; isLast?: boolean;
outputIndex?: number;
}; };
const graph = getGraphManager(); const graph = getGraphManager();
@@ -17,13 +18,14 @@
const graphId = graph?.id; const graphId = graph?.id;
const elementId = `input-${Math.random().toString(36).substring(7)}`; const elementId = `input-${Math.random().toString(36).substring(7)}`;
let { node = $bindable(), input, id, isLast }: Props = $props(); let { node = $bindable(), input, id, isLast, outputIndex = undefined }: Props = $props();
const nodeType = $derived(node.state.type!); const nodeType = $derived(node.state.type!);
const inputType = $derived(nodeType.inputs?.[id]); const inputType = $derived(nodeType.inputs?.[id]);
const socketId = $derived(`${node.id}-${id}`); const socketId = $derived(`${node.id}-${id}`);
const outputSocketId = $derived(outputIndex !== undefined ? `${node.id}-${outputIndex}` : '');
const height = $derived(getParameterHeight(nodeType, id)); const height = $derived(getParameterHeight(nodeType, id));
function handleMouseDown(ev: MouseEvent) { function handleMouseDown(ev: MouseEvent) {
@@ -36,7 +38,19 @@
}); });
} }
const leftBump = $derived(!id.startsWith('__virtual') && nodeType.inputs?.[id].internal !== true); function handleOutputMouseDown(ev: MouseEvent) {
ev.preventDefault();
ev.stopPropagation();
if (outputIndex === undefined) return;
graphState.setDownSocket({
node,
index: outputIndex,
position: getSocketPosition(node, outputIndex)
});
}
const leftBump = $derived(!id.startsWith('__virtual') && nodeType.inputs?.[id].internal !== true && outputIndex === undefined);
const rightBump = $derived(outputIndex !== undefined);
const cornerBottom = $derived(isLast ? 5 : 0); const cornerBottom = $derived(isLast ? 5 : 0);
const aspectRatio = 0.5; const aspectRatio = 0.5;
@@ -47,6 +61,7 @@
y: 50.5, y: 50.5,
cornerBottom, cornerBottom,
leftBump, leftBump,
rightBump,
aspectRatio aspectRatio
}) })
); );
@@ -57,6 +72,7 @@
y: 50.5, y: 50.5,
cornerBottom, cornerBottom,
leftBump, leftBump,
rightBump,
aspectRatio aspectRatio
}) })
); );
@@ -79,7 +95,9 @@
data-node-input={id} data-node-input={id}
style:height="{height}px" style:height="{height}px"
style:--socket-color={hoverColor} style:--socket-color={hoverColor}
class:possible-socket={graphState?.possibleSocketIds.has(socketId)} class:possible-socket={outputIndex !== undefined
? graphState?.possibleSocketIds.has(outputSocketId)
: graphState?.possibleSocketIds.has(socketId)}
> >
{#key id && graphId} {#key id && graphId}
<div class="content" class:disabled={graph?.inputSockets?.has(socketId)}> <div class="content" class:disabled={graph?.inputSockets?.has(socketId)}>
@@ -91,7 +109,7 @@
{/if} {/if}
</div> </div>
{#if node?.state?.type?.inputs?.[id]?.internal !== true} {#if outputIndex === undefined && node?.state?.type?.inputs?.[id]?.internal !== true}
<div <div
data-node-socket data-node-socket
class="target" class="target"
@@ -103,6 +121,17 @@
{/if} {/if}
{/key} {/key}
{#if outputIndex !== undefined}
<div
data-node-socket
class="target target-right"
onmousedown={handleOutputMouseDown}
role="button"
tabindex="0"
>
</div>
{/if}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100" viewBox="0 0 100 100"
@@ -130,6 +159,16 @@
transform: translateY(-50%) translateX(-50%); transform: translateY(-50%) translateX(-50%);
} }
.target-right {
right: 0;
left: auto;
transform: translateY(-50%) translateX(50%);
}
.target-right:hover ~ svg path {
d: var(--hover-path);
}
.possible-socket .target::before { .possible-socket .target::before {
content: ""; content: "";
position: absolute; position: absolute;