feat: node store interface
This commit is contained in:
parent
1d203c687c
commit
78c88e4d66
@ -36,7 +36,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
|
||||
history: HistoryManager = new HistoryManager();
|
||||
|
||||
constructor(private nodeRegistry: NodeRegistry) {
|
||||
constructor(public registry: NodeRegistry) {
|
||||
super();
|
||||
this.nodes.subscribe((nodes) => {
|
||||
this._nodes = nodes;
|
||||
@ -82,7 +82,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
getNodeTypes() {
|
||||
return this.nodeRegistry.getAllNodes();
|
||||
return this.registry.getAllNodes();
|
||||
}
|
||||
|
||||
getLinkedNodes(node: Node) {
|
||||
@ -122,7 +122,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
|
||||
private _init(graph: Graph) {
|
||||
const nodes = new Map(graph.nodes.map(node => {
|
||||
const nodeType = this.nodeRegistry.getNode(node.type);
|
||||
const nodeType = this.registry.getNode(node.type);
|
||||
if (nodeType) {
|
||||
node.tmp = {
|
||||
random: (Math.random() - 0.5) * 2,
|
||||
@ -164,10 +164,10 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
this.id.set(graph.id);
|
||||
|
||||
const nodeIds = Array.from(new Set([...graph.nodes.map(n => n.type)]));
|
||||
await this.nodeRegistry.load(nodeIds);
|
||||
await this.registry.load(nodeIds);
|
||||
|
||||
for (const node of this.graph.nodes) {
|
||||
const nodeType = this.nodeRegistry.getNode(node.type);
|
||||
const nodeType = this.registry.getNode(node.type);
|
||||
if (!nodeType) {
|
||||
logger.error(`Node type not found: ${node.type}`);
|
||||
this.status.set("error");
|
||||
@ -222,7 +222,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
}
|
||||
|
||||
getNodeType(id: string) {
|
||||
return this.nodeRegistry.getNode(id);
|
||||
return this.registry.getNode(id);
|
||||
}
|
||||
|
||||
getChildrenOfNode(node: Node) {
|
||||
@ -303,7 +303,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
nodes = nodes.map((node, i) => {
|
||||
const id = startId + i;
|
||||
idMap.set(node.id, id);
|
||||
const type = this.nodeRegistry.getNode(node.type);
|
||||
const type = this.registry.getNode(node.type);
|
||||
if (!type) {
|
||||
throw new Error(`Node type not found: ${node.type}`);
|
||||
}
|
||||
@ -343,7 +343,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
||||
|
||||
createNode({ type, position, props = {} }: { type: Node["type"], position: Node["position"], props: Node["props"] }) {
|
||||
|
||||
const nodeType = this.nodeRegistry.getNode(type);
|
||||
const nodeType = this.registry.getNode(type);
|
||||
if (!nodeType) {
|
||||
logger.error(`Node type not found: ${type}`);
|
||||
return;
|
||||
|
@ -20,6 +20,7 @@
|
||||
import { createKeyMap } from "../../helpers/createKeyMap";
|
||||
import BoxSelection from "../BoxSelection.svelte";
|
||||
import AddMenu from "../AddMenu.svelte";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
export let graph: GraphManager;
|
||||
|
||||
@ -78,7 +79,7 @@
|
||||
}
|
||||
|
||||
function updateNodePosition(node: NodeType) {
|
||||
if (node?.tmp?.ref) {
|
||||
if (node?.tmp?.ref && node?.tmp?.mesh) {
|
||||
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
|
||||
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
|
||||
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
|
||||
@ -758,6 +759,34 @@
|
||||
addMenuPosition = null;
|
||||
}
|
||||
|
||||
function handleDrop(event: DragEvent) {
|
||||
if (!event.dataTransfer) return;
|
||||
const nodeId = event.dataTransfer.getData("data/node-id");
|
||||
let mx = event.clientX - rect.x;
|
||||
let my = event.clientY - rect.y;
|
||||
|
||||
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
|
||||
let nodeOffsetY = event.dataTransfer.getData("data/node-offset-y");
|
||||
if (nodeOffsetX && nodeOffsetY) {
|
||||
mx += parseInt(nodeOffsetX);
|
||||
my += parseInt(nodeOffsetY);
|
||||
}
|
||||
|
||||
const pos = projectScreenToWorld(mx, my);
|
||||
graph.registry.load([nodeId]).then(() => {
|
||||
graph.createNode({
|
||||
type: nodeId,
|
||||
props: {},
|
||||
position: pos,
|
||||
});
|
||||
});
|
||||
console.log({ nodeId });
|
||||
}
|
||||
|
||||
function handlerDragOver(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (localStorage.getItem("cameraPosition")) {
|
||||
const cPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
|
||||
@ -779,6 +808,8 @@
|
||||
tabindex="0"
|
||||
bind:clientWidth={width}
|
||||
bind:clientHeight={height}
|
||||
on:dragover={handlerDragOver}
|
||||
on:drop={handleDrop}
|
||||
on:keydown={keymap.handleKeyboardEvent}
|
||||
on:mousedown={handleMouseDown}
|
||||
>
|
||||
|
@ -11,7 +11,7 @@
|
||||
export let graph: Graph;
|
||||
export let settings: Writable<Record<string, any>> | undefined;
|
||||
|
||||
const manager = new GraphManager(registry);
|
||||
export const manager = new GraphManager(registry);
|
||||
|
||||
export const status = manager.status;
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
import { Color, type Mesh } from "three";
|
||||
import NodeFrag from "./Node.frag";
|
||||
import NodeVert from "./Node.vert";
|
||||
import NodeHtml from "./NodeHTML.svelte";
|
||||
|
||||
export let node: Node;
|
||||
export let inView = true;
|
||||
@ -22,32 +23,20 @@
|
||||
|
||||
const getNodeHeight = getContext<(n: string) => number>("getNodeHeight");
|
||||
|
||||
const type = node?.tmp?.type;
|
||||
|
||||
const zOffset = (node.tmp?.random || 0) * 0.5;
|
||||
const zLimit = 2 - zOffset;
|
||||
|
||||
const parameters = Object.entries(type?.inputs || {})
|
||||
.filter((p) => p[1].type !== "seed")
|
||||
.filter((p) => !("setting" in p[1]));
|
||||
|
||||
let ref: HTMLDivElement;
|
||||
let meshRef: Mesh;
|
||||
|
||||
const height = getNodeHeight(node.type);
|
||||
const height = getNodeHeight?.(node.type);
|
||||
|
||||
$: if (node && ref && meshRef) {
|
||||
$: if (node && meshRef) {
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.ref = ref;
|
||||
node.tmp.mesh = meshRef;
|
||||
updateNodePosition(node);
|
||||
updateNodePosition?.(node);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
node.tmp = node.tmp || {};
|
||||
node.tmp.ref = ref;
|
||||
node.tmp.mesh = meshRef;
|
||||
updateNodePosition(node);
|
||||
updateNodePosition?.(node);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -83,51 +72,4 @@
|
||||
/>
|
||||
</T.Mesh>
|
||||
|
||||
<div
|
||||
class="node"
|
||||
class:active={isActive}
|
||||
style:--cz={z + zOffset}
|
||||
style:display={inView && z > zLimit ? "block" : "none"}
|
||||
class:selected={isSelected}
|
||||
class:out-of-view={!inView}
|
||||
data-node-id={node.id}
|
||||
bind:this={ref}
|
||||
>
|
||||
<NodeHeader {node} />
|
||||
|
||||
{#each parameters as [key, value], i}
|
||||
<NodeParameter
|
||||
bind:node
|
||||
id={key}
|
||||
input={value}
|
||||
isLast={i == parameters.length - 1}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.node {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
user-select: none !important;
|
||||
cursor: pointer;
|
||||
width: 200px;
|
||||
color: var(--text-color);
|
||||
transform: translate3d(var(--nx), var(--ny), 0);
|
||||
z-index: 1;
|
||||
opacity: calc((var(--cz) - 2.5) / 3.5);
|
||||
font-weight: 300;
|
||||
--stroke: var(--outline);
|
||||
--stroke-width: 2px;
|
||||
}
|
||||
|
||||
.node.active {
|
||||
--stroke: var(--active);
|
||||
--stroke-width: 2px;
|
||||
}
|
||||
|
||||
.node.selected {
|
||||
--stroke: var(--selected);
|
||||
--stroke-width: 2px;
|
||||
}
|
||||
</style>
|
||||
<NodeHtml {node} {inView} {isActive} {isSelected} {z} />
|
||||
|
86
app/src/lib/graph-interface/node/NodeHTML.svelte
Normal file
86
app/src/lib/graph-interface/node/NodeHTML.svelte
Normal file
@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import type { Node } from "@nodes/types";
|
||||
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";
|
||||
|
||||
const zOffset = (node.tmp?.random || 0) * 0.5;
|
||||
const zLimit = 2 - zOffset;
|
||||
|
||||
const type = node?.tmp?.type;
|
||||
|
||||
const parameters = Object.entries(type?.inputs || {})
|
||||
.filter((p) => p[1].type !== "seed")
|
||||
.filter((p) => !("setting" in p[1]));
|
||||
|
||||
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;
|
||||
updateNodePosition?.(node);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="node {position}"
|
||||
class:active={isActive}
|
||||
style:--cz={z + zOffset}
|
||||
style:display={inView && z > zLimit ? "block" : "none"}
|
||||
class:selected={isSelected}
|
||||
class:out-of-view={!inView}
|
||||
data-node-id={node.id}
|
||||
bind:this={ref}
|
||||
>
|
||||
<NodeHeader {node} />
|
||||
|
||||
{#each parameters as [key, value], i}
|
||||
<NodeParameter
|
||||
bind:node
|
||||
id={key}
|
||||
input={value}
|
||||
isLast={i == parameters.length - 1}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.node {
|
||||
box-sizing: border-box;
|
||||
user-select: none !important;
|
||||
cursor: pointer;
|
||||
width: 200px;
|
||||
color: var(--text-color);
|
||||
transform: translate3d(var(--nx), var(--ny), 0);
|
||||
z-index: 1;
|
||||
opacity: calc((var(--cz) - 2.5) / 3.5);
|
||||
font-weight: 300;
|
||||
--stroke: var(--outline);
|
||||
--stroke-width: 2px;
|
||||
}
|
||||
|
||||
.node.active {
|
||||
--stroke: var(--active);
|
||||
--stroke-width: 2px;
|
||||
}
|
||||
|
||||
.node.selected {
|
||||
--stroke: var(--selected);
|
||||
--stroke-width: 2px;
|
||||
}
|
||||
</style>
|
@ -14,10 +14,10 @@
|
||||
function handleMouseDown(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
setDownSocket({
|
||||
setDownSocket?.({
|
||||
node,
|
||||
index: 0,
|
||||
position: getSocketPosition(node, 0),
|
||||
position: getSocketPosition?.(node, 0),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,10 @@
|
||||
|
||||
$: if (node?.props?.[id] !== value) {
|
||||
node.props = { ...node.props, [id]: value };
|
||||
graph.save();
|
||||
graph.execute();
|
||||
if (graph) {
|
||||
graph.save();
|
||||
graph.execute();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -20,8 +20,8 @@
|
||||
const socketId = `${node.id}-${id}`;
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphId = graph.id;
|
||||
const inputSockets = graph.inputSockets;
|
||||
const graphId = graph?.id;
|
||||
const inputSockets = graph?.inputSockets;
|
||||
|
||||
const elementId = `input-${Math.random().toString(36).substring(7)}`;
|
||||
|
||||
@ -34,10 +34,10 @@
|
||||
function handleMouseDown(ev: MouseEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setDownSocket({
|
||||
setDownSocket?.({
|
||||
node,
|
||||
index: id,
|
||||
position: getSocketPosition(node, id),
|
||||
position: getSocketPosition?.(node, id),
|
||||
});
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@
|
||||
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
||||
>
|
||||
{#key id && graphId}
|
||||
<div class="content" class:disabled={$inputSockets.has(socketId)}>
|
||||
<div class="content" class:disabled={$inputSockets?.has(socketId)}>
|
||||
{#if inputType.label !== false}
|
||||
<label for={elementId}>{input.label || id}</label>
|
||||
{/if}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { get, writable } from "svelte/store";
|
||||
import { derived, get, writable } from "svelte/store";
|
||||
|
||||
type Shortcut = {
|
||||
key: string | string[],
|
||||
@ -9,35 +9,45 @@ type Shortcut = {
|
||||
callback: (event: KeyboardEvent) => void
|
||||
}
|
||||
|
||||
function getShortcutId(shortcut: Shortcut) {
|
||||
return `${shortcut.key}${shortcut.shift ? "+shift" : ""}${shortcut.ctrl ? "+ctrl" : ""}${shortcut.alt ? "+alt" : ""}`;
|
||||
}
|
||||
|
||||
export function createKeyMap(keys: Shortcut[]) {
|
||||
|
||||
const store = writable(keys);
|
||||
const store = writable(new Map(keys.map(k => [getShortcutId(k), k])));
|
||||
|
||||
return {
|
||||
handleKeyboardEvent: (event: KeyboardEvent) => {
|
||||
const key = get(store).find(k => {
|
||||
const key = [...get(store).values()].find(k => {
|
||||
if (Array.isArray(k.key) ? !k.key.includes(event.key) : k.key !== event.key) return false;
|
||||
if ("shift" in k && k.shift !== event.shiftKey) return false;
|
||||
if ("ctrl" in k && k.ctrl !== event.ctrlKey) return false;
|
||||
if ("alt" in k && k.alt !== event.altKey) return false;
|
||||
return true;
|
||||
});
|
||||
console.log({ keys: get(store), out: key, key: event.key });
|
||||
key?.callback(event);
|
||||
},
|
||||
addShortcut: (shortcut: Shortcut) => {
|
||||
if (Array.isArray(shortcut.key)) {
|
||||
for (const k of shortcut.key) {
|
||||
store.update(keys => {
|
||||
if (keys.find(kk => kk.key === k)) return keys;
|
||||
return [...keys, { ...shortcut, key: k }];
|
||||
store.update(shortcuts => {
|
||||
const id = getShortcutId({ ...shortcut, key: k });
|
||||
shortcuts.delete(id);
|
||||
shortcuts.set(id, { ...shortcut, key: k });
|
||||
return shortcuts;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
store.update(keys => [...keys, shortcut]);
|
||||
store.update(shortcuts => {
|
||||
const id = getShortcutId(shortcut);
|
||||
shortcuts.delete(id);
|
||||
shortcuts.set(id, shortcut);
|
||||
return shortcuts;
|
||||
});
|
||||
}
|
||||
},
|
||||
keys: store
|
||||
keys: derived(store, $store => Array.from($store.values()))
|
||||
}
|
||||
|
||||
}
|
||||
|
93
app/src/lib/node-registry-client.ts
Normal file
93
app/src/lib/node-registry-client.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import type { NodeRegistry, NodeType } from "@nodes/types";
|
||||
import { createWasmWrapper } from "@nodes/utils";
|
||||
import { createLogger } from "./helpers";
|
||||
|
||||
const log = createLogger("node-registry");
|
||||
export class RemoteNodeRegistry implements NodeRegistry {
|
||||
|
||||
status: "loading" | "ready" | "error" = "loading";
|
||||
private nodes: Map<string, NodeType> = new Map();
|
||||
|
||||
constructor(private url: string) { }
|
||||
|
||||
private async loadNode(id: `${string}/${string}/${string}`) {
|
||||
const wasmResponse = await this.fetchNode(id);
|
||||
|
||||
// Setup Wasm wrapper
|
||||
const wrapper = createWasmWrapper();
|
||||
const module = new WebAssembly.Module(wasmResponse);
|
||||
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
|
||||
wrapper.setInstance(instance);
|
||||
|
||||
const definition = wrapper.get_definition();
|
||||
|
||||
return {
|
||||
...definition,
|
||||
id,
|
||||
execute: wrapper.execute
|
||||
};
|
||||
}
|
||||
|
||||
async fetchUsers() {
|
||||
const response = await fetch(`${this.url}/nodes/users.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load users`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchUser(userId: `${string}`) {
|
||||
const response = await fetch(`${this.url}/nodes/${userId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load user ${userId}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
||||
const response = await fetch(`${this.url}/nodes/${userCollectionId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load collection ${userCollectionId}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchNode(nodeId: `${string}/${string}/${string}`) {
|
||||
const response = await fetch(`${this.url}/nodes/${nodeId}.wasm`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||
}
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
|
||||
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
||||
const response = await fetch(`${this.url}/nodes/${nodeId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load node definition ${nodeId}`);
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||
const a = performance.now();
|
||||
|
||||
const nodes = await Promise.all(nodeIds.map(id => this.loadNode(id)));
|
||||
|
||||
for (const node of nodes) {
|
||||
this.nodes.set(node.id, node);
|
||||
}
|
||||
|
||||
const duration = performance.now() - a;
|
||||
|
||||
log.log("loaded nodes in", duration, "ms");
|
||||
this.status = "ready";
|
||||
}
|
||||
|
||||
getNode(id: string) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
|
||||
getAllNodes() {
|
||||
return [...this.nodes.values()];
|
||||
}
|
||||
}
|
@ -1,120 +1,101 @@
|
||||
import type { NodeRegistry, NodeType } from "@nodes/types";
|
||||
import { createWasmWrapper } from "@nodes/utils";
|
||||
import { createWasmWrapper } from "@nodes/utils"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
import { createLogger } from "./helpers";
|
||||
export async function getWasm(id: `${string}/${string}/${string}`) {
|
||||
const filePath = path.resolve(`../nodes/${id}/pkg/index_bg.wasm`);
|
||||
|
||||
const nodeTypes: NodeType[] = [
|
||||
{
|
||||
id: "max/plantarium/float",
|
||||
inputs: {
|
||||
"value": { type: "float", value: 0.1, internal: true },
|
||||
},
|
||||
outputs: ["float"],
|
||||
execute: (value) => { return value; }
|
||||
},
|
||||
{
|
||||
id: "max/plantarium/math",
|
||||
inputs: {
|
||||
"op_type": { label: "type", type: "select", options: ["add", "subtract", "multiply", "divide"], value: 0 },
|
||||
"a": { type: "float" },
|
||||
"b": { type: "float" },
|
||||
},
|
||||
outputs: ["float"],
|
||||
execute: ([op_type, a, b]: number[]) => {
|
||||
switch (op_type) {
|
||||
case 0: return a + b;
|
||||
case 1: return a - b;
|
||||
case 2: return a * b;
|
||||
case 3: return a / b;
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
|
||||
const file = await fs.readFile(filePath);
|
||||
|
||||
const bytes = new Uint8Array(file);
|
||||
|
||||
return bytes;
|
||||
|
||||
}
|
||||
|
||||
export async function getNodeWasm(id: `${string}/${string}/${string}`) {
|
||||
|
||||
const wasmBytes = await getWasm(id);
|
||||
if (!wasmBytes) return null;
|
||||
|
||||
const wrapper = createWasmWrapper();
|
||||
const module = new WebAssembly.Module(wasmBytes);
|
||||
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
|
||||
wrapper.setInstance(instance)
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
|
||||
export async function getNode(id: `${string}/${string}/${string}`) {
|
||||
|
||||
const wrapper = await getNodeWasm(id);
|
||||
|
||||
const definition = wrapper?.get_definition?.();
|
||||
|
||||
if (!definition) return null;
|
||||
|
||||
const { inputs, outputs } = definition;
|
||||
try {
|
||||
return { id, inputs, outputs }
|
||||
} catch (e) {
|
||||
console.log("Failed to parse input types for node", { id });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function getCollectionNodes(userId: `${string}/${string}`) {
|
||||
const nodes = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
||||
return nodes
|
||||
.filter(n => n !== "pkg" && n !== ".template")
|
||||
.map(n => {
|
||||
return {
|
||||
id: `${userId}/${n}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "max/plantarium/output",
|
||||
inputs: {
|
||||
"input": { type: "float" },
|
||||
},
|
||||
outputs: [],
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const log = createLogger("node-registry");
|
||||
export class RemoteNodeRegistry implements NodeRegistry {
|
||||
|
||||
|
||||
status: "loading" | "ready" | "error" = "loading";
|
||||
private nodes: Map<string, NodeType> = new Map();
|
||||
|
||||
constructor(private url: string) { }
|
||||
|
||||
private async loadNode(id: string) {
|
||||
const nodeUrl = `${this.url}/n/${id}`;
|
||||
const [response, wasmResponse] = await Promise.all([fetch(nodeUrl), fetch(`${nodeUrl}/wasm`)]);
|
||||
if (!wasmResponse.ok || !response.ok) {
|
||||
this.status = "error";
|
||||
throw new Error(`Failed to load node ${id}`);
|
||||
}
|
||||
|
||||
// Setup Wasm wrapper
|
||||
const wrapper = createWasmWrapper();
|
||||
const module = new WebAssembly.Module(await wasmResponse.arrayBuffer());
|
||||
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
|
||||
wrapper.setInstance(instance);
|
||||
|
||||
const node = await response.json();
|
||||
node.execute = wrapper.execute;
|
||||
return node;
|
||||
}
|
||||
|
||||
async load(nodeIds: string[]) {
|
||||
const a = performance.now();
|
||||
|
||||
nodeIds.push("max/plantarium/random");
|
||||
nodeIds.push("max/plantarium/float");
|
||||
nodeIds.push("max/plantarium/triangle");
|
||||
nodeIds.push("max/plantarium/vec3");
|
||||
nodeIds.push("max/plantarium/output");
|
||||
nodeIds.push("max/plantarium/array");
|
||||
nodeIds.push("max/plantarium/sum");
|
||||
nodeIds.push("max/plantarium/stem");
|
||||
nodeIds.push("max/plantarium/box");
|
||||
nodeIds.push("max/plantarium/math");
|
||||
|
||||
const nodes = await Promise.all(nodeIds.map(id => this.loadNode(id)));
|
||||
|
||||
for (const node of nodes) {
|
||||
this.nodes.set(node.id, node);
|
||||
}
|
||||
|
||||
const duration = performance.now() - a;
|
||||
|
||||
log.log("loaded nodes in", duration, "ms");
|
||||
this.status = "ready";
|
||||
}
|
||||
|
||||
getNode(id: string) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
|
||||
getAllNodes() {
|
||||
return [...this.nodes.values()];
|
||||
export async function getCollection(userId: `${string}/${string}`) {
|
||||
const nodes = await getCollectionNodes(userId);
|
||||
return {
|
||||
id: userId,
|
||||
nodes,
|
||||
}
|
||||
}
|
||||
|
||||
export class MemoryNodeRegistry implements NodeRegistry {
|
||||
export async function getUserCollections(userId: string) {
|
||||
const collections = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
||||
return Promise.all(collections.map(async n => {
|
||||
const nodes = await getCollectionNodes(`${userId}/${n}`);
|
||||
return {
|
||||
id: `${userId}/${n}`,
|
||||
nodes,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
status: "loading" | "ready" | "error" = "ready";
|
||||
|
||||
async load(nodeIds: string[]) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
getNode(id: string) {
|
||||
return nodeTypes.find((nodeType) => nodeType.id === id);
|
||||
}
|
||||
getAllNodes() {
|
||||
return [...nodeTypes];
|
||||
export async function getUser(userId: string) {
|
||||
const collections = await getUserCollections(userId);
|
||||
return {
|
||||
id: userId,
|
||||
collections
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUsers() {
|
||||
const nodes = await fs.readdir(path.resolve("../nodes"));
|
||||
const users = await Promise.all(nodes.map(async n => {
|
||||
const collections = await getUserCollections(n);
|
||||
return {
|
||||
id: n,
|
||||
collections
|
||||
}
|
||||
}))
|
||||
return users;
|
||||
}
|
||||
|
81
app/src/lib/node-store/BreadCrumbs.svelte
Normal file
81
app/src/lib/node-store/BreadCrumbs.svelte
Normal file
@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
export let activeId: Writable<string>;
|
||||
$: [activeUser, activeCollection, activeNode] = $activeId.split(`/`);
|
||||
</script>
|
||||
|
||||
<div class="breadcrumbs">
|
||||
{#if activeUser}
|
||||
<button
|
||||
on:click={() => {
|
||||
$activeId = "";
|
||||
}}
|
||||
>
|
||||
root
|
||||
</button>
|
||||
{#if activeCollection}
|
||||
<button
|
||||
on:click={() => {
|
||||
$activeId = activeUser;
|
||||
}}
|
||||
>
|
||||
{activeUser}
|
||||
</button>
|
||||
{#if activeNode}
|
||||
<button
|
||||
on:click={() => {
|
||||
$activeId = `${activeUser}/${activeCollection}`;
|
||||
}}
|
||||
>
|
||||
{activeCollection}
|
||||
</button>
|
||||
<span>{activeNode}</span>
|
||||
{:else}
|
||||
<span>{activeCollection}</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<span>{activeUser}</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<span>root</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.4em;
|
||||
gap: 0.8em;
|
||||
height: 1em;
|
||||
border-bottom: solid thin var(--outline);
|
||||
}
|
||||
.breadcrumbs > button {
|
||||
position: relative;
|
||||
background: none;
|
||||
font-family: var(--font-family);
|
||||
border: none;
|
||||
font-size: 1em;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.breadcrumbs > button::after {
|
||||
content: "/";
|
||||
position: absolute;
|
||||
right: -11px;
|
||||
opacity: 0.5;
|
||||
white-space: pre;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.breadcrumbs > button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.breadcrumbs > span {
|
||||
font-size: 1em;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
74
app/src/lib/node-store/DraggableNode.svelte
Normal file
74
app/src/lib/node-store/DraggableNode.svelte
Normal file
@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
|
||||
import type { NodeType } from "@nodes/types";
|
||||
|
||||
export let node: NodeType;
|
||||
|
||||
let dragging = false;
|
||||
|
||||
function handleDragStart(e: DragEvent) {
|
||||
dragging = true;
|
||||
const box = (e?.target as HTMLElement)?.getBoundingClientRect();
|
||||
if (e.dataTransfer === null) return;
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
e.dataTransfer.setData("data/node-id", node.id);
|
||||
e.dataTransfer.setData(
|
||||
"data/node-offset-x",
|
||||
Math.round(box.left - e.clientX).toString(),
|
||||
);
|
||||
e.dataTransfer.setData(
|
||||
"data/node-offset-y",
|
||||
Math.round(box.top - e.clientY).toString(),
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="node-wrapper" class:dragging>
|
||||
<div
|
||||
on:dragend={() => {
|
||||
dragging = false;
|
||||
}}
|
||||
draggable={true}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:dragstart={handleDragStart}
|
||||
>
|
||||
<NodeHtml
|
||||
inView={true}
|
||||
position={"relative"}
|
||||
z={5}
|
||||
node={{
|
||||
id: 0,
|
||||
type: node.id,
|
||||
position: [0, 0],
|
||||
tmp: {
|
||||
type: node,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.node-wrapper {
|
||||
width: fit-content;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
border: solid 2px transparent;
|
||||
padding: 5px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
border: dashed 2px var(--outline);
|
||||
}
|
||||
.node-wrapper > div {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
pointer-events: all;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.dragging > div {
|
||||
opacity: 0.2;
|
||||
}
|
||||
</style>
|
92
app/src/lib/node-store/NodeStore.svelte
Normal file
92
app/src/lib/node-store/NodeStore.svelte
Normal file
@ -0,0 +1,92 @@
|
||||
<script lang="ts">
|
||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
||||
import Node from "$lib/graph-interface/node/Node.svelte";
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import type { RemoteNodeRegistry } from "$lib/node-registry-client";
|
||||
import { Canvas } from "@threlte/core";
|
||||
import BreadCrumbs from "./BreadCrumbs.svelte";
|
||||
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
|
||||
import DraggableNode from "./DraggableNode.svelte";
|
||||
|
||||
export let nodeRegistry: RemoteNodeRegistry;
|
||||
export let manager: GraphManager;
|
||||
|
||||
function handleImport() {
|
||||
nodeRegistry.load([$activeId]);
|
||||
}
|
||||
|
||||
const activeId = localStore<
|
||||
`${string}` | `${string}/${string}` | `${string}/${string}/${string}`
|
||||
>("nodes.store.activeId", "");
|
||||
|
||||
$: [activeUser, activeCollection, activeNode] = $activeId.split(`/`);
|
||||
</script>
|
||||
|
||||
<BreadCrumbs {activeId} />
|
||||
|
||||
<div class="wrapper">
|
||||
{#if !activeUser}
|
||||
<h3>Users</h3>
|
||||
{#await nodeRegistry.fetchUsers()}
|
||||
<div>Loading...</div>
|
||||
{:then users}
|
||||
{#each users as user}
|
||||
<button
|
||||
on:click={() => {
|
||||
$activeId = user.id;
|
||||
}}>{user.id}</button
|
||||
>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<div>{error.message}</div>
|
||||
{/await}
|
||||
{:else if !activeCollection}
|
||||
{#await nodeRegistry.fetchUser(activeUser)}
|
||||
<div>Loading...</div>
|
||||
{:then user}
|
||||
<h3>Collections</h3>
|
||||
{#each user.collections as collection}
|
||||
<button
|
||||
on:click={() => {
|
||||
$activeId = collection.id;
|
||||
}}
|
||||
>
|
||||
{collection.id.split(`/`)[1]}
|
||||
</button>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<div>{error.message}</div>
|
||||
{/await}
|
||||
{:else if !activeNode}
|
||||
<h3>Nodes</h3>
|
||||
{#await nodeRegistry.fetchCollection(`${activeUser}/${activeCollection}`)}
|
||||
<div>Loading...</div>
|
||||
{:then collection}
|
||||
{#each collection.nodes as node}
|
||||
<button
|
||||
on:click={() => {
|
||||
$activeId = node.id;
|
||||
}}
|
||||
>
|
||||
{node.id.split(`/`)[2]}
|
||||
</button>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<div>{error.message}</div>
|
||||
{/await}
|
||||
{:else}
|
||||
{#await nodeRegistry.fetchNodeDefinition(`${activeUser}/${activeCollection}/${activeNode}`)}
|
||||
<div>Loading...</div>
|
||||
{:then node}
|
||||
<DraggableNode {node} />
|
||||
{:catch error}
|
||||
<div>{error.message}</div>
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
31
app/src/lib/node-store/Spinner.svelte
Normal file
31
app/src/lib/node-store/Spinner.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<span class="spinner"></span>
|
||||
|
||||
<style>
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.spinner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
animation: spin 1s linear infinite;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' class='icon icon-tabler icons-tabler-outline icon-tabler-loader-2'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 3a9 9 0 1 0 9 9' /%3E%3C/svg%3E");
|
||||
background-size: cover;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
@ -14,15 +14,15 @@
|
||||
<div class="command-wrapper">
|
||||
<div class="command">
|
||||
{#if key.ctrl}
|
||||
<b>Ctrl</b>
|
||||
<span>Ctrl + </span>
|
||||
{/if}
|
||||
{#if key.shift}
|
||||
<b>Shift</b>
|
||||
<span>Shift</span>
|
||||
{/if}
|
||||
{#if key.alt}
|
||||
<b>Alt</b>
|
||||
<span>Alt</span>
|
||||
{/if}
|
||||
<b>{key.key}</b>
|
||||
<span>{key.key}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>{key.description}</p>
|
||||
@ -51,21 +51,18 @@
|
||||
|
||||
.command-wrapper {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
justify-content: right;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.command {
|
||||
background: var(--layer-3);
|
||||
background: var(--outline);
|
||||
padding: 0.4em;
|
||||
font-size: 0.8em;
|
||||
border-radius: 0.3em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.command > * {
|
||||
color: var(--layer-0);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9em;
|
||||
margin: 0;
|
||||
|
@ -113,7 +113,7 @@
|
||||
transform 0.2s,
|
||||
background 0.2s ease;
|
||||
width: 30%;
|
||||
min-width: 300px;
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
1
app/src/routes/+layout.server.ts
Normal file
1
app/src/routes/+layout.server.ts
Normal file
@ -0,0 +1 @@
|
||||
export const prerender = true;
|
@ -5,7 +5,3 @@
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
||||
{#if false}
|
||||
<span class="absolute i-tabler-settings w-6 h-6 block"></span>
|
||||
{/if}
|
||||
|
@ -2,17 +2,20 @@
|
||||
import Grid from "$lib/grid";
|
||||
import GraphInterface from "$lib/graph-interface";
|
||||
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
||||
import { RemoteNodeRegistry } from "$lib/node-registry";
|
||||
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
||||
import * as templates from "$lib/graph-templates";
|
||||
import type { Graph } from "@nodes/types";
|
||||
import Viewer from "$lib/viewer/Viewer.svelte";
|
||||
import Viewer from "$lib/result-viewer/Viewer.svelte";
|
||||
import Settings from "$lib/settings/Settings.svelte";
|
||||
import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import Keymap from "$lib/settings/Keymap.svelte";
|
||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
||||
import { setContext } from "svelte";
|
||||
|
||||
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
||||
const nodeRegistry = new RemoteNodeRegistry("");
|
||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||
|
||||
let res: Int32Array;
|
||||
@ -21,7 +24,11 @@
|
||||
? JSON.parse(localStorage.getItem("graph")!)
|
||||
: templates.grid(3, 3);
|
||||
|
||||
let manager: GraphManager;
|
||||
let managerStatus: Writable<"loading" | "error" | "idle">;
|
||||
$: if (manager) {
|
||||
setContext("graphManager", manager);
|
||||
}
|
||||
|
||||
let keymap: ReturnType<typeof createKeyMap>;
|
||||
|
||||
@ -41,6 +48,7 @@
|
||||
definition: AppSettingTypes,
|
||||
},
|
||||
shortcuts: {},
|
||||
nodeStore: {},
|
||||
graph: {},
|
||||
};
|
||||
|
||||
@ -53,7 +61,16 @@
|
||||
};
|
||||
|
||||
settings = settings;
|
||||
console.log({ settings });
|
||||
}
|
||||
|
||||
$: if (manager) {
|
||||
settings.nodeStore = {
|
||||
id: "Node Store",
|
||||
icon: "i-tabler-database",
|
||||
props: { nodeRegistry, manager },
|
||||
component: NodeStore,
|
||||
};
|
||||
settings = settings;
|
||||
}
|
||||
|
||||
function handleSettings(
|
||||
@ -91,10 +108,10 @@
|
||||
<Grid.Cell>
|
||||
{#key graph}
|
||||
<GraphInterface
|
||||
bind:manager
|
||||
registry={nodeRegistry}
|
||||
{graph}
|
||||
bind:keymap
|
||||
bind:status={managerStatus}
|
||||
settings={settings?.graph?.settings}
|
||||
on:settings={handleSettings}
|
||||
on:result={handleResult}
|
||||
|
22
app/src/routes/nodes/[user].json/+server.ts
Normal file
22
app/src/routes/nodes/[user].json/+server.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { json } from "@sveltejs/kit";
|
||||
import type { EntryGenerator, RequestHandler } from "./$types";
|
||||
import * as registry from "$lib/node-registry";
|
||||
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const entries: EntryGenerator = async () => {
|
||||
const users = await registry.getUsers();
|
||||
return users.map(user => {
|
||||
return { user: user.id }
|
||||
}).flat(2);
|
||||
}
|
||||
|
||||
|
||||
export const GET: RequestHandler = async function GET({ params }) {
|
||||
|
||||
const namespaces = await registry.getUser(params.user)
|
||||
|
||||
return json(namespaces);
|
||||
|
||||
}
|
22
app/src/routes/nodes/[user]/[collection].json/+server.ts
Normal file
22
app/src/routes/nodes/[user]/[collection].json/+server.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { json } from "@sveltejs/kit";
|
||||
import type { EntryGenerator, RequestHandler } from "./$types";
|
||||
import * as registry from "$lib/node-registry";
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const entries: EntryGenerator = async () => {
|
||||
const users = await registry.getUsers();
|
||||
return users.map(user => {
|
||||
return user.collections.map(collection => {
|
||||
return { user: user.id, collection: collection.id }
|
||||
})
|
||||
}).flat(2);
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async function GET({ params }) {
|
||||
|
||||
const namespaces = await registry.getCollection(`${params.user}/${params.collection}`);
|
||||
|
||||
return json(namespaces);
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import { json } from "@sveltejs/kit";
|
||||
import type { EntryGenerator, RequestHandler } from "./$types";
|
||||
import { getNode } from "$lib/node-registry";
|
||||
import * as registry from "$lib/node-registry";
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
|
||||
export const entries: EntryGenerator = async () => {
|
||||
const users = await registry.getUsers();
|
||||
return users.map(user => {
|
||||
return user.collections.map(collection => {
|
||||
return collection.nodes.map(node => {
|
||||
return { user: user.id, collection: collection.id, node: node.id }
|
||||
});
|
||||
})
|
||||
}).flat(2);
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async function GET({ params }) {
|
||||
|
||||
const nodeId = `${params.user}/${params.collection}/${params.node}` as const;
|
||||
|
||||
try {
|
||||
const node = await getNode(nodeId);
|
||||
return json(node);
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import type { RequestHandler } from "./$types";
|
||||
import * as registry from "$lib/node-registry";
|
||||
import type { EntryGenerator } from "../$types";
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const entries: EntryGenerator = async () => {
|
||||
const users = await registry.getUsers();
|
||||
return users.map(user => {
|
||||
return user.collections.map(collection => {
|
||||
return collection.nodes.map(node => {
|
||||
return { user: user.id, collection: collection.id, node: node.id }
|
||||
});
|
||||
})
|
||||
}).flat(2);
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async function GET({ params }) {
|
||||
|
||||
const wasm = await registry.getWasm(`${params.user}/${params.collection}/${params.node}`);
|
||||
|
||||
if (!wasm) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
return new Response(wasm, { status: 200, headers: { "Content-Type": "application/wasm" } });
|
||||
}
|
14
app/src/routes/nodes/users.json/+server.ts
Normal file
14
app/src/routes/nodes/users.json/+server.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { json } from "@sveltejs/kit";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
import * as registry from "$lib/node-registry";
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const GET: RequestHandler = async function GET() {
|
||||
|
||||
const users = await registry.getUsers();
|
||||
|
||||
return json(users);
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
@ -1,31 +0,0 @@
|
||||
/** @type { import("eslint").Linter.Config } */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
10
packages/node-registry/.gitignore
vendored
10
packages/node-registry/.gitignore
vendored
@ -1,10 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
@ -1 +0,0 @@
|
||||
engine-strict=true
|
@ -1,4 +0,0 @@
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "node-registry",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "vitest",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.2.0",
|
||||
"@sveltejs/kit": "^2.5.6",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||
"@types/eslint": "^8.56.9",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.37.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.3",
|
||||
"svelte": "^4.2.15",
|
||||
"svelte-check": "^3.6.9",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.9",
|
||||
"vite-plugin-wasm": "^3.3.0",
|
||||
"vitest": "^1.5.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@nodes/utils": "link:../utils",
|
||||
"utils": "link:../utils"
|
||||
}
|
||||
}
|
13
packages/node-registry/src/app.d.ts
vendored
13
packages/node-registry/src/app.d.ts
vendored
@ -1,13 +0,0 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
@ -1 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
@ -1,32 +0,0 @@
|
||||
import { createWasmWrapper } from "@nodes/utils"
|
||||
|
||||
export async function getNodeWasm(id: `${string}/${string}/${string}`) {
|
||||
|
||||
const wasmResponse = await fetch(`/n/${id}/wasm`);
|
||||
|
||||
if (!wasmResponse.ok) {
|
||||
throw new Error(`Failed to load node ${id}`);
|
||||
}
|
||||
|
||||
const wrapper = createWasmWrapper();
|
||||
const module = new WebAssembly.Module(await wasmResponse.arrayBuffer());
|
||||
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
|
||||
wrapper.setInstance(instance)
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
|
||||
export async function getNode(id: `${string}/${string}/${string}`) {
|
||||
|
||||
const wrapper = await getNodeWasm(id);
|
||||
|
||||
const { inputs, outputs } = wrapper?.get_definition?.();
|
||||
try {
|
||||
return { id, inputs, outputs }
|
||||
} catch (e) {
|
||||
console.log("Failed to parse input types for node", { id });
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
@ -1,17 +0,0 @@
|
||||
import { json } from "@sveltejs/kit";
|
||||
import type { RequestHandler } from "./$types";
|
||||
import { getNode } from "$lib/registry";
|
||||
|
||||
export const GET: RequestHandler = async function GET({ fetch, params }) {
|
||||
globalThis.fetch = fetch;
|
||||
|
||||
const nodeId = `${params.user}/${params.collection}/${params.node}` as const;
|
||||
|
||||
try {
|
||||
const node = await getNode(nodeId);
|
||||
return json(node);
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
|
||||
import { getNode, getNodeWasm } from '$lib/registry';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let data: PageData;
|
||||
const nodeId = `${data.params.user}/${data.params.collection}/${data.params.node}` as const;
|
||||
|
||||
let node;
|
||||
let wasm;
|
||||
|
||||
onMount(async () => {
|
||||
wasm = await getNodeWasm(nodeId);
|
||||
window['wasm'] = wasm;
|
||||
node = await getNode(nodeId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>{data.params.user}/{data.params.collection}/{data.params.node}</h1>
|
||||
|
||||
<h3>Node Definition</h3>
|
||||
{#if !node}
|
||||
<p>Loading Node</p>
|
||||
{:else}
|
||||
<pre>{JSON.stringify(node, null, 2)}</pre>
|
||||
{/if}
|
@ -1,7 +0,0 @@
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load: PageLoad = ({ params }) => {
|
||||
return {
|
||||
params
|
||||
}
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
import type { RequestHandler } from "./$types";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
export const GET: RequestHandler = async function GET({ fetch, params }) {
|
||||
|
||||
const filePath = path.resolve(`../../nodes/${params.user}/${params.collection}/${params.node}/pkg/index_bg.wasm`);
|
||||
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch (e) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
const file = await fs.readFile(filePath);
|
||||
|
||||
const bytes = new Uint8Array(file);
|
||||
|
||||
return new Response(bytes, { status: 200, headers: { "Content-Type": "application/wasm" } });
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
@ -1,20 +0,0 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import wasm from 'vite-plugin-wasm';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit(), wasm()],
|
||||
|
||||
server: {
|
||||
port: 3001,
|
||||
},
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||
}
|
||||
});
|
@ -35,7 +35,7 @@ export type NodeType = {
|
||||
meta?: {
|
||||
title?: string;
|
||||
},
|
||||
execute?: (args: number[]) => unknown;
|
||||
execute?: (args: Int32Array) => Int32Array;
|
||||
}
|
||||
|
||||
export type Socket = {
|
||||
@ -44,7 +44,6 @@ export type Socket = {
|
||||
position: [number, number];
|
||||
};
|
||||
|
||||
|
||||
export interface NodeRegistry {
|
||||
/**
|
||||
* The status of the node registry
|
||||
|
Loading…
Reference in New Issue
Block a user