feat: make it work
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
if (result !== false) {
|
||||||
|
state.activeNodeId = result.nodeId;
|
||||||
|
state.clearSelection();
|
||||||
|
state.cameraPosition[0] = result.camera[0];
|
||||||
|
state.cameraPosition[1] = result.camera[1];
|
||||||
|
state.cameraPosition[2] = result.camera[2];
|
||||||
|
} else {
|
||||||
state.activeNodeId = -1;
|
state.activeNodeId = -1;
|
||||||
state.clearSelection();
|
state.clearSelection();
|
||||||
if (restoredCamera !== false) {
|
|
||||||
state.cameraPosition[0] = restoredCamera[0];
|
|
||||||
state.cameraPosition[1] = restoredCamera[1];
|
|
||||||
state.cameraPosition[2] = restoredCamera[2];
|
|
||||||
} else {
|
|
||||||
state.centerNode();
|
state.centerNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,6 +72,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{node.state?.type?.meta?.title ?? node.type.split('/').pop()}
|
{node.state?.type?.meta?.title ?? node.type.split('/').pop()}
|
||||||
</div>
|
</div>
|
||||||
|
{#if node.type !== '__virtual/group/input'}
|
||||||
<div
|
<div
|
||||||
class="target"
|
class="target"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -79,6 +80,7 @@
|
|||||||
onmousedown={handleMouseDown}
|
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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user