fix: pasting nodes
This commit is contained in:
@@ -742,25 +742,26 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
createGraph(nodes: NodeInstance[], edges: [number, number, number, string][]) {
|
createGraph(nodes: SerializedNode[], edges: [number, number, number, string][]) {
|
||||||
// map old ids to new ids
|
// map old ids to new ids
|
||||||
const idMap = new SvelteMap<number, number>();
|
const idMap = new SvelteMap<number, number>();
|
||||||
|
|
||||||
let startId = this.createNodeId();
|
let startId = this.createNodeId();
|
||||||
|
|
||||||
nodes = nodes.map((node) => {
|
const instances: NodeInstance[] = nodes.map((node) => {
|
||||||
const id = startId++;
|
const id = startId++;
|
||||||
idMap.set(node.id, id);
|
idMap.set(node.id, id);
|
||||||
const type = this.registry.getNode(node.type);
|
const type = this.registry.getNode(node.type);
|
||||||
if (!type && !node.type.startsWith('__internal/')) {
|
if (!type && !node.type.startsWith('__internal/')) {
|
||||||
throw new Error(`Node type not found: ${node.type}`);
|
throw new Error(`Node type not found: ${node.type}`);
|
||||||
}
|
}
|
||||||
return { ...node, id, tmp: { type } };
|
const registryType = this.registry.getNode(node.type);
|
||||||
|
return { ...node, id, state: { type: registryType } };
|
||||||
});
|
});
|
||||||
|
|
||||||
const _edges = edges.map((edge) => {
|
const _edges = edges.map((edge) => {
|
||||||
const from = nodes.find((n) => n.id === idMap.get(edge[0]));
|
const from = instances.find((n) => n.id === idMap.get(edge[0]));
|
||||||
const to = nodes.find((n) => n.id === idMap.get(edge[2]));
|
const to = instances.find((n) => n.id === idMap.get(edge[2]));
|
||||||
|
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
throw new Error('Edge references non-existing node');
|
throw new Error('Edge references non-existing node');
|
||||||
@@ -775,14 +776,15 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return [from, edge[1], to, edge[3]] as Edge;
|
return [from, edge[1], to, edge[3]] as Edge;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of instances) {
|
||||||
this.nodes.set(node.id, node);
|
const n = $state(node);
|
||||||
|
this.nodes.set(node.id, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.edges.push(..._edges);
|
this.edges.push(..._edges);
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
return nodes;
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUnusedGroups() {
|
getUnusedGroups() {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { animate, lerp } from '$lib/helpers';
|
import { animate, lerp } from '$lib/helpers';
|
||||||
import type { NodeInstance, Socket } from '@nodarium/types';
|
import type { NodeInstance, SerializedEdge, SerializedNode, Socket } from '@nodarium/types';
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||||
import type { OrthographicCamera, Vector3 } from 'three';
|
import type { OrthographicCamera, Vector3 } from 'three';
|
||||||
import type { GraphManager } from './graph-manager.svelte';
|
import type { GraphManager } from './graph-manager.svelte';
|
||||||
import { ColorGenerator } from './graph/colors';
|
import { ColorGenerator } from './graph/colors';
|
||||||
import { getNodeHeight, getParameterHeight } from './helpers/nodeHelpers';
|
import {
|
||||||
|
getNodeHeight,
|
||||||
|
getParameterHeight,
|
||||||
|
serializeEdge,
|
||||||
|
serializeNode
|
||||||
|
} from './helpers/nodeHelpers';
|
||||||
|
|
||||||
const graphStateKey = Symbol('graph-state');
|
const graphStateKey = Symbol('graph-state');
|
||||||
export function getGraphState() {
|
export function getGraphState() {
|
||||||
@@ -95,8 +100,8 @@ export class GraphState {
|
|||||||
cameraPosition: [number, number, number] = $state([140, 100, 3.5]);
|
cameraPosition: [number, number, number] = $state([140, 100, 3.5]);
|
||||||
|
|
||||||
clipboard: null | {
|
clipboard: null | {
|
||||||
nodes: NodeInstance[];
|
nodes: SerializedNode[];
|
||||||
edges: [number, number, number, string][];
|
edges: SerializedEdge[];
|
||||||
} = null;
|
} = null;
|
||||||
|
|
||||||
cameraBounds = $derived([
|
cameraBounds = $derived([
|
||||||
@@ -190,12 +195,10 @@ export class GraphState {
|
|||||||
if (this.activeNodeId === -1 && !this.selectedNodes?.size) {
|
if (this.activeNodeId === -1 && !this.selectedNodes?.size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let nodes = [
|
const ids = new SvelteSet([this.activeNodeId, ...(this.selectedNodes?.values() || [])]);
|
||||||
this.activeNodeId,
|
let nodes = [...ids]
|
||||||
...(this.selectedNodes?.values() || [])
|
|
||||||
]
|
|
||||||
.map((id) => this.graph.getNode(id))
|
.map((id) => this.graph.getNode(id))
|
||||||
.filter(b => !!b);
|
.filter((b): b is NodeInstance => !!b);
|
||||||
|
|
||||||
const edges = this.graph.getEdgesBetweenNodes(nodes);
|
const edges = this.graph.getEdgesBetweenNodes(nodes);
|
||||||
nodes = nodes.map((node) => ({
|
nodes = nodes.map((node) => ({
|
||||||
@@ -203,13 +206,12 @@ export class GraphState {
|
|||||||
position: [
|
position: [
|
||||||
this.mousePosition[0] - node.position[0],
|
this.mousePosition[0] - node.position[0],
|
||||||
this.mousePosition[1] - node.position[1]
|
this.mousePosition[1] - node.position[1]
|
||||||
],
|
]
|
||||||
tmp: undefined
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.clipboard = {
|
this.clipboard = {
|
||||||
nodes: nodes,
|
nodes: nodes.map(n => serializeNode(n)),
|
||||||
edges: edges
|
edges: edges.map(e => serializeEdge(e))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,13 +257,16 @@ export class GraphState {
|
|||||||
pasteNodes() {
|
pasteNodes() {
|
||||||
if (!this.clipboard) return;
|
if (!this.clipboard) return;
|
||||||
|
|
||||||
const nodes = this.clipboard.nodes
|
// Create fresh node objects — never mutate clipboard so repeat pastes work correctly.
|
||||||
.map((node) => {
|
// State is also spread (with cleared parents/children) so createGraph's mutations
|
||||||
node.position[0] = this.mousePosition[0] - node.position[0];
|
// don't corrupt the clipboard's stored state references.
|
||||||
node.position[1] = this.mousePosition[1] - node.position[1];
|
const nodes = this.clipboard.nodes.map((node) => ({
|
||||||
return node;
|
...node,
|
||||||
})
|
position: [
|
||||||
.filter(Boolean) as NodeInstance[];
|
this.mousePosition[0] - node.position[0],
|
||||||
|
this.mousePosition[1] - node.position[1]
|
||||||
|
] as [number, number]
|
||||||
|
}));
|
||||||
|
|
||||||
const newNodes = this.graph.createGraph(nodes, this.clipboard.edges);
|
const newNodes = this.graph.createGraph(nodes, this.clipboard.edges);
|
||||||
this.selectedNodes.clear();
|
this.selectedNodes.clear();
|
||||||
|
|||||||
@@ -228,7 +228,7 @@
|
|||||||
style:transform={`scale(${graphState.cameraPosition[2] * 0.1})`}
|
style:transform={`scale(${graphState.cameraPosition[2] * 0.1})`}
|
||||||
class:hovering-sockets={graphState.activeSocket}
|
class:hovering-sockets={graphState.activeSocket}
|
||||||
>
|
>
|
||||||
{#each graph.nodeArray as node, index (node.id)}
|
{#each graph.nodeArray as node, index (node)}
|
||||||
<NodeEl
|
<NodeEl
|
||||||
bind:node={graph.nodeArray[index]}
|
bind:node={graph.nodeArray[index]}
|
||||||
inView={node ? graphState.isNodeInView(node) : false}
|
inView={node ? graphState.isNodeInView(node) : false}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
graph
|
graph
|
||||||
? {
|
? {
|
||||||
...graph,
|
...graph,
|
||||||
nodes: graph.nodes.map((n: object) => ({ ...n, tmp: undefined, state: undefined }))
|
nodes: graph.nodes.map((n: object) => ({ ...n, state: undefined }))
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user