feat: make group input/output node work
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m11s
🚀 Lint & Test & Deploy / quality (pull_request) Successful in 2m7s
🚀 Lint & Test & Deploy / test-unit (pull_request) Successful in 32s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Successful in 1m50s
🚀 Lint & Test & Deploy / deploy (pull_request) Successful in 1m56s
📊 Benchmark the Runtime / benchmark (pull_request) Successful in 1m11s
🚀 Lint & Test & Deploy / quality (pull_request) Successful in 2m7s
🚀 Lint & Test & Deploy / test-unit (pull_request) Successful in 32s
🚀 Lint & Test & Deploy / test-e2e (pull_request) Successful in 1m50s
🚀 Lint & Test & Deploy / deploy (pull_request) Successful in 1m56s
This commit is contained in:
@@ -69,7 +69,7 @@ export class GraphManager extends EventEmitter<{
|
||||
status = $state<'loading' | 'idle' | 'error'>();
|
||||
loaded = false;
|
||||
|
||||
graph: Graph = { id: 0, nodes: [], edges: [], groups: [] };
|
||||
graph: Graph = $state({ id: 0, nodes: [], edges: [], groups: [] });
|
||||
id = $state(0);
|
||||
|
||||
nodes = new SvelteMap<number, NodeInstance>();
|
||||
@@ -495,9 +495,24 @@ export class GraphManager extends EventEmitter<{
|
||||
const groupId = this.graphStack.at(-1)?.groupId;
|
||||
const group = groupId !== undefined ? this.getGroup(groupId) : undefined;
|
||||
if (!group) return node.state.type;
|
||||
|
||||
const groupInputs: NodeDefinition['inputs'] = Object.fromEntries(
|
||||
Object.values(group?.inputs || {}).map((o, i) => {
|
||||
return [
|
||||
`in_${i}`,
|
||||
{
|
||||
...o,
|
||||
external: true
|
||||
}
|
||||
];
|
||||
}) || []
|
||||
);
|
||||
return {
|
||||
id: '__internal/group/input' as NodeId,
|
||||
outputs: Object.values(group.inputs ?? {}).map(i => i.type),
|
||||
meta: {
|
||||
title: 'Group Input'
|
||||
},
|
||||
inputs: groupInputs,
|
||||
execute: (x: Int32Array) => x
|
||||
} as NodeDefinition;
|
||||
}
|
||||
@@ -514,6 +529,9 @@ export class GraphManager extends EventEmitter<{
|
||||
i
|
||||
) => [`out_${i}`, { type: o.type, label: o.label, external: true }])
|
||||
),
|
||||
meta: {
|
||||
title: 'Group Output'
|
||||
},
|
||||
outputs: [],
|
||||
execute: (x: Int32Array) => x
|
||||
} as NodeDefinition;
|
||||
@@ -1007,7 +1025,9 @@ export class GraphManager extends EventEmitter<{
|
||||
const toType = this.getNodeType(to);
|
||||
|
||||
// check if socket types match
|
||||
const fromSocketType = fromType?.outputs?.[fromSocket];
|
||||
const fromSocketType = from.type === '__internal/group/input'
|
||||
? fromType?.inputs?.[Object.keys(fromType?.inputs || {})[fromSocket]].type
|
||||
: fromType?.outputs?.[fromSocket];
|
||||
const toSocketType = [toType?.inputs?.[toSocket]?.type];
|
||||
if (toType?.inputs?.[toSocket]?.accepts) {
|
||||
toSocketType.push(...(toType?.inputs?.[toSocket]?.accepts || []));
|
||||
@@ -1179,7 +1199,9 @@ export class GraphManager extends EventEmitter<{
|
||||
}
|
||||
});
|
||||
|
||||
const ownType = nodeType.outputs?.[index];
|
||||
const ownType = node.type === '__internal/group/input'
|
||||
? nodeType.inputs?.[Object.keys(nodeType?.inputs || {})[index]].type
|
||||
: nodeType.outputs?.[index];
|
||||
|
||||
for (const node of nodes) {
|
||||
const inputs = this.getNodeType(node)?.inputs;
|
||||
|
||||
@@ -384,6 +384,13 @@ export class GraphState {
|
||||
node: NodeInstance,
|
||||
index: string | number
|
||||
): [number, number] {
|
||||
if (node.type === '__internal/group/input' && typeof index === 'number') {
|
||||
return [
|
||||
(node?.state?.x ?? node.position[0]) + 20,
|
||||
(node?.state?.y ?? node.position[1]) + 2.5 + 5 * index + 5
|
||||
];
|
||||
}
|
||||
|
||||
if (typeof index === 'number') {
|
||||
return [
|
||||
(node?.state?.x ?? node.position[0]) + 20,
|
||||
|
||||
@@ -100,8 +100,22 @@
|
||||
if (typeof index === 'string') {
|
||||
return nodeType?.inputs?.[index].type || 'unknown';
|
||||
}
|
||||
|
||||
if (node.type === '__internal/group/input') {
|
||||
const key = Object.keys(nodeType?.inputs || {})[index];
|
||||
return nodeType?.inputs?.[key].type || 'unknown';
|
||||
}
|
||||
|
||||
return nodeType?.outputs?.[index] || 'unknown';
|
||||
}
|
||||
|
||||
function getGroupName() {
|
||||
const groupId = graph.graphStack.at(-1)?.groupId;
|
||||
if (groupId !== undefined) {
|
||||
const group = graph.getGroup(groupId);
|
||||
return group?.name || `Group#${groupId}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
@@ -114,6 +128,7 @@
|
||||
bind:this={graphState.wrapper}
|
||||
class="graph-wrapper"
|
||||
style="height: 100%"
|
||||
class:is-inside-group={graph.isInsideGroup}
|
||||
class:is-panning={graphState.isPanning}
|
||||
class:is-hovering={graphState.hoveredNodeId !== -1}
|
||||
aria-label="Graph"
|
||||
@@ -121,11 +136,13 @@
|
||||
tabindex="0"
|
||||
bind:clientWidth={graphState.width}
|
||||
bind:clientHeight={graphState.height}
|
||||
style:--padding-right="{safePadding?.right || 0}px"
|
||||
onkeydown={(ev) => keymap.handleKeyboardEvent(ev)}
|
||||
onmousedown={(ev) => mouseEvents.handleMouseDown(ev)}
|
||||
oncontextmenu={(ev) => mouseEvents.handleContextMenu(ev)}
|
||||
{...fileDropEvents.getEventListenerProps()}
|
||||
>
|
||||
<div class="shadow"></div>
|
||||
<input
|
||||
type="file"
|
||||
accept="application/wasm,application/json"
|
||||
@@ -140,6 +157,9 @@
|
||||
<button class="exit-group" onclick={() => graphState.exitGroupNode()}>
|
||||
↑ Exit Group
|
||||
</button>
|
||||
<p class="group-name absolute">
|
||||
Group <b>{getGroupName()}</b>
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||
@@ -250,6 +270,15 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
position: absolute;
|
||||
left: calc(50% - var(--padding-right) / 2);
|
||||
transition: left 0.3s ease;
|
||||
top: 12px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.exit-group {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
@@ -280,6 +309,22 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
right: calc(var(--padding-right) - 5px);
|
||||
bottom: -5px;
|
||||
z-index: 1;
|
||||
transition: box-shadow 0.3s ease;
|
||||
box-shadow: 0 0 0px 0px var(--color-layer-2) inset;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.is-inside-group .shadow {
|
||||
box-shadow: 0 0 0px 8px var(--color-layer-2) inset;
|
||||
}
|
||||
|
||||
.is-panning {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { NodeDefinition } from '@nodarium/types';
|
||||
|
||||
export function getParameterHeight(node: NodeDefinition, inputKey: string) {
|
||||
if (node.id === '__internal/group/input') {
|
||||
return 50;
|
||||
}
|
||||
|
||||
const input = node.inputs?.[inputKey];
|
||||
if (!input) {
|
||||
return 0;
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
|
||||
const cornerTop = 10;
|
||||
const nodeType = $derived(graph.getNodeType(node));
|
||||
const rightBump = $derived(!!nodeType?.outputs?.length);
|
||||
const rightBump = $derived(
|
||||
!!nodeType?.outputs?.length && node.type !== '__internal/group/input'
|
||||
);
|
||||
|
||||
const aspectRatio = 0.25;
|
||||
|
||||
const path = $derived(
|
||||
@@ -73,13 +76,15 @@
|
||||
{/if}
|
||||
{nodeType?.meta?.title || node.type?.split('/').pop()}
|
||||
</div>
|
||||
<div
|
||||
class="target"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onmousedown={handleMouseDown}
|
||||
>
|
||||
</div>
|
||||
{#if rightBump}
|
||||
<div
|
||||
class="target"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onmousedown={handleMouseDown}
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
|
||||
@@ -29,14 +29,27 @@
|
||||
function handleMouseDown(ev: MouseEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
graphState.setDownSocket({
|
||||
node,
|
||||
index: id,
|
||||
position: graphState.getSocketPosition(node, id)
|
||||
});
|
||||
|
||||
if (node.type === '__internal/group/input') {
|
||||
const outputIndex = Object.entries(nodeType?.inputs ?? {}).findIndex(([key]) => key === id);
|
||||
graphState.setDownSocket({
|
||||
node,
|
||||
index: outputIndex,
|
||||
position: graphState.getSocketPosition(node, outputIndex)
|
||||
});
|
||||
} else {
|
||||
graphState.setDownSocket({
|
||||
node,
|
||||
index: id,
|
||||
position: graphState.getSocketPosition(node, id)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const leftBump = $derived(nodeType.inputs?.[id].internal !== true);
|
||||
const leftBump = $derived(
|
||||
nodeType.inputs?.[id].internal !== true && node.type !== '__internal/group/input'
|
||||
);
|
||||
const rightBump = $derived(node.type === '__internal/group/input');
|
||||
const cornerBottom = $derived(isLast ? 5 : 0);
|
||||
const aspectRatio = 0.5;
|
||||
|
||||
@@ -46,6 +59,7 @@
|
||||
height: 2000 / height,
|
||||
y: 50.5,
|
||||
cornerBottom,
|
||||
rightBump,
|
||||
leftBump,
|
||||
aspectRatio
|
||||
})
|
||||
@@ -55,6 +69,7 @@
|
||||
depth: 7,
|
||||
height: 2200 / height,
|
||||
y: 50.5,
|
||||
rightBump,
|
||||
cornerBottom,
|
||||
leftBump,
|
||||
aspectRatio
|
||||
@@ -76,6 +91,7 @@
|
||||
<div
|
||||
class="wrapper"
|
||||
data-node-type={node.type}
|
||||
class:is-group-input={node.type === '__internal/group/input'}
|
||||
data-node-input={id}
|
||||
style:height="{height}px"
|
||||
style:--socket-color={hoverColor}
|
||||
@@ -130,6 +146,11 @@
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
}
|
||||
|
||||
.is-group-input .target {
|
||||
right: 0px;
|
||||
transform: translateY(-50%) translateX(50%);
|
||||
}
|
||||
|
||||
.possible-socket .target::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user