15 Commits

Author SHA1 Message Date
9b94159f8e register works 2026-01-23 11:23:07 +01:00
aa4d7f73a8 setup zig node 2026-01-23 10:24:21 +01:00
1efb94b09c Add zig to flake packages 2026-01-23 09:07:05 +01:00
Felix Hungenberg
5570d975f5 feat: unmigrate number into universal float, inherit step if unset
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m1s
2026-01-22 23:57:56 +01:00
Felix Hungenberg
8c1ba2ee65 feat: move add context menu within view if outside
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m59s
2026-01-22 23:26:56 +01:00
Felix Hungenberg
3e019e4e21 feat: don't move graph on right click drag 2026-01-22 23:26:26 +01:00
Felix Hungenberg
a58b19e935 Merge branch 'main' of github.com:jim-fx/nodarium
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m57s
2026-01-22 14:06:44 +01:00
Max Richter
6f5c5bb46e feat: change initial camera position so that nodes are visible
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m3s
2026-01-22 12:07:37 +01:00
7f2214f15c fix(utils): make sure we do not build a .wasm file for utils
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m54s
2026-01-21 17:24:54 +01:00
43ef563ae7 feat: show all nodes in add menu
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m57s
2026-01-21 17:08:47 +01:00
Felix Hungenberg
714d01da94 chore: move pnpm links to workspace (auto link) 2026-01-21 16:39:54 +01:00
Felix Hungenberg
92308fc43a chore: run ui debug server from root
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m57s
2026-01-21 16:36:20 +01:00
Felix Hungenberg
5adf67ed52 Merge branch 'main' of git.max-richter.dev:max/nodarium
Some checks failed
Deploy to GitHub Pages / build_site (push) Has been cancelled
2026-01-21 16:35:34 +01:00
Felix Hungenberg
f54cde734e fix: integer width 2026-01-21 16:35:13 +01:00
Felix Hungenberg
2a90d5de3f chore: dev scripts & linting 2026-01-21 16:31:26 +01:00
34 changed files with 706 additions and 526 deletions

View File

@@ -1,12 +1,14 @@
{ {
"$schema": "https://dprint.dev/schemas/v0.json", "$schema": "https://dprint.dev/schemas/v0.json",
"indentWidth": 2, "indentWidth": 2,
"lineWidth": 100,
"typescript": { "typescript": {
// https://dprint.dev/plugins/typescript/config/ // https://dprint.dev/plugins/typescript/config/
"quoteStyle": "preferSingle", "quoteStyle": "preferSingle",
"trailingCommas": "never" "trailingCommas": "never",
}, },
"json": { "json": {
// https://dprint.dev/plugins/json/config/
}, },
"markdown": { "markdown": {
}, },
@@ -21,6 +23,9 @@
"malva": { "malva": {
}, },
"markup": { "markup": {
// https://dprint.dev/plugins/markup_fmt/config/
"scriptIndent": true,
"styleIndent": true,
}, },
"yaml": { "yaml": {
}, },
@@ -28,19 +33,24 @@
}, },
"exec": { "exec": {
"cwd": "${configDir}", "cwd": "${configDir}",
"commands": [{ "commands": [{
"command": "rustfmt --edition 2024", "command": "rustfmt",
"exts": ["rs"], "exts": ["rs"],
// add the config files for automatic cache invalidation when the rust version or rustfmt config changes
"cacheKeyFiles": [ "cacheKeyFiles": [
"rustfmt.toml", "rustfmt.toml",
"rust-toolchain.toml" "rust-toolchain.toml",
] ],
}] }],
}, },
"excludes": [ "excludes": [
"**/node_modules", "**/node_modules",
"**/*-lock.yaml" "**/build",
"**/.svelte-kit",
"**/package",
"**/*-lock.yaml",
"**/yaml.lock",
"**/.DS_Store",
], ],
"plugins": [ "plugins": [
"https://plugins.dprint.dev/typescript-0.95.13.wasm", "https://plugins.dprint.dev/typescript-0.95.13.wasm",
@@ -54,6 +64,7 @@
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm", "https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm",
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm", "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm",
"https://plugins.dprint.dev/g-plane/pretty_graphql-v0.2.3.wasm", "https://plugins.dprint.dev/g-plane/pretty_graphql-v0.2.3.wasm",
"https://plugins.dprint.dev/exec-0.6.0.json@a054130d458f124f9b5c91484833828950723a5af3f8ff2bd1523bd47b83b364" "https://plugins.dprint.dev/exec-0.6.0.json@a054130d458f124f9b5c91484833828950723a5af3f8ff2bd1523bd47b83b364",
] "https://plugins.dprint.dev/biome-0.11.10.wasm",
],
} }

View File

@@ -6,7 +6,10 @@ members = [
"packages/types", "packages/types",
"packages/utils", "packages/utils",
] ]
exclude = ["nodes/max/plantarium/.template"] exclude = [
"nodes/max/plantarium/.template",
"nodes/max/plantarium/zig"
]
[profile.release] [profile.release]
lto = true lto = true

View File

@@ -28,7 +28,7 @@
"devDependencies": { "devDependencies": {
"@iconify-json/tabler": "^1.2.26", "@iconify-json/tabler": "^1.2.26",
"@iconify/tailwind4": "^1.2.1", "@iconify/tailwind4": "^1.2.1",
"@nodarium/types": "link:../packages/types", "@nodarium/types": "workspace:",
"@sveltejs/adapter-static": "^3.0.10", "@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/vite-plugin-svelte": "^6.2.4", "@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tsconfig/svelte": "^5.0.6", "@tsconfig/svelte": "^5.0.6",

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { HTML } from "@threlte/extras"; import type { NodeId, NodeInstance } from '@nodarium/types';
import { onMount } from "svelte"; import { HTML } from '@threlte/extras';
import type { NodeInstance, NodeId } from "@nodarium/types"; import { onMount } from 'svelte';
import { getGraphManager, getGraphState } from "../graph-state.svelte"; import { getGraphManager, getGraphState } from '../graph-state.svelte';
type Props = { type Props = {
onnode: (n: NodeInstance) => void; onnode: (n: NodeInstance) => void;
@@ -14,6 +14,7 @@
const graphState = getGraphState(); const graphState = getGraphState();
let input: HTMLInputElement; let input: HTMLInputElement;
let wrapper: HTMLDivElement;
let value = $state<string>(); let value = $state<string>();
let activeNodeId = $state<NodeId>(); let activeNodeId = $state<NodeId>();
@@ -22,10 +23,10 @@
: graph.getNodeDefinitions(); : graph.getNodeDefinitions();
function filterNodes() { function filterNodes() {
return allNodes.filter((node) => node.id.includes(value ?? "")); return allNodes.filter((node) => node.id.includes(value ?? ''));
} }
const nodes = $derived(value === "" ? allNodes : filterNodes()); const nodes = $derived(value === '' ? allNodes : filterNodes());
$effect(() => { $effect(() => {
if (nodes) { if (nodes) {
if (activeNodeId === undefined) { if (activeNodeId === undefined) {
@@ -39,38 +40,38 @@
} }
}); });
function handleNodeCreation(nodeType: NodeInstance["type"]) { function handleNodeCreation(nodeType: NodeInstance['type']) {
if (!graphState.addMenuPosition) return; if (!graphState.addMenuPosition) return;
onnode?.({ onnode?.({
id: -1, id: -1,
type: nodeType, type: nodeType,
position: [...graphState.addMenuPosition], position: [...graphState.addMenuPosition],
props: {}, props: {},
state: {}, state: {}
}); });
} }
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
if (event.key === "Escape") { if (event.key === 'Escape') {
graphState.addMenuPosition = null; graphState.addMenuPosition = null;
return; return;
} }
if (event.key === "ArrowDown") { if (event.key === 'ArrowDown') {
const index = nodes.findIndex((node) => node.id === activeNodeId); const index = nodes.findIndex((node) => node.id === activeNodeId);
activeNodeId = nodes[(index + 1) % nodes.length].id; activeNodeId = nodes[(index + 1) % nodes.length].id;
return; return;
} }
if (event.key === "ArrowUp") { if (event.key === 'ArrowUp') {
const index = nodes.findIndex((node) => node.id === activeNodeId); const index = nodes.findIndex((node) => node.id === activeNodeId);
activeNodeId = nodes[(index - 1 + nodes.length) % nodes.length].id; activeNodeId = nodes[(index - 1 + nodes.length) % nodes.length].id;
return; return;
} }
if (event.key === "Enter") { if (event.key === 'Enter') {
if (activeNodeId && graphState.addMenuPosition) { if (activeNodeId && graphState.addMenuPosition) {
handleNodeCreation(activeNodeId); handleNodeCreation(activeNodeId);
} }
@@ -81,6 +82,16 @@
onMount(() => { onMount(() => {
input.disabled = false; input.disabled = false;
setTimeout(() => input.focus(), 50); setTimeout(() => input.focus(), 50);
const rect = wrapper.getBoundingClientRect();
const deltaY = rect.bottom - window.innerHeight;
const deltaX = rect.right - window.innerWidth;
if (deltaY > 0) {
wrapper.style.marginTop = `-${deltaY + 30}px`;
}
if (deltaX > 0) {
wrapper.style.marginLeft = `-${deltaX + 30}px`;
}
}); });
</script> </script>
@@ -89,7 +100,7 @@
position.z={graphState.addMenuPosition?.[1]} position.z={graphState.addMenuPosition?.[1]}
transform={false} transform={false}
> >
<div class="add-menu-wrapper"> <div class="add-menu-wrapper" bind:this={wrapper}>
<div class="header"> <div class="header">
<input <input
id="add-menu" id="add-menu"
@@ -112,7 +123,7 @@
tabindex="0" tabindex="0"
aria-selected={node.id === activeNodeId} aria-selected={node.id === activeNodeId}
onkeydown={(event) => { onkeydown={(event) => {
if (event.key === "Enter") { if (event.key === 'Enter') {
handleNodeCreation(node.id); handleNodeCreation(node.id);
} }
}} }}
@@ -125,7 +136,7 @@
activeNodeId = node.id; activeNodeId = node.id;
}} }}
> >
{node.id.split("/").at(-1)} {node.id.split('/').at(-1)}
</div> </div>
{/each} {/each}
</div> </div>
@@ -167,6 +178,8 @@
min-height: none; min-height: none;
width: 100%; width: 100%;
color: var(--text-color); color: var(--text-color);
max-height: 300px;
overflow-y: auto;
} }
.result { .result {

View File

@@ -1,4 +1,5 @@
import throttle from '$lib/helpers/throttle'; import throttle from '$lib/helpers/throttle';
import { RemoteNodeRegistry } from '@nodarium/registry';
import type { import type {
Edge, Edge,
Graph, Graph,
@@ -18,6 +19,8 @@ import { HistoryManager } from './history-manager';
const logger = createLogger('graph-manager'); const logger = createLogger('graph-manager');
logger.mute(); logger.mute();
const remoteRegistry = new RemoteNodeRegistry('');
const clone = 'structuredClone' in self const clone = 'structuredClone' in self
? self.structuredClone ? self.structuredClone
: (args: any) => JSON.parse(JSON.stringify(args)); : (args: any) => JSON.parse(JSON.stringify(args));
@@ -173,7 +176,9 @@ export class GraphManager extends EventEmitter<{
return areSocketsCompatible(edgeOutputSocketType, accepted); return areSocketsCompatible(edgeOutputSocketType, accepted);
}); });
const bestOutputIdx = draggedOutputs.findIndex(outputType => areSocketsCompatible(outputType, targetAcceptedTypes)); const bestOutputIdx = draggedOutputs.findIndex(outputType =>
areSocketsCompatible(outputType, targetAcceptedTypes)
);
if (!bestInputEntry || bestOutputIdx === -1) { if (!bestInputEntry || bestOutputIdx === -1) {
logger.error('Could not find compatible sockets for drop'); logger.error('Could not find compatible sockets for drop');
@@ -308,6 +313,21 @@ export class GraphManager extends EventEmitter<{
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)])); const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
await this.registry.load(nodeIds); await this.registry.load(nodeIds);
// Fetch all nodes from all collections of the loaded nodes
const allCollections = new Set<`${string}/${string}`>();
for (const id of nodeIds) {
const [user, collection] = id.split('/');
allCollections.add(`${user}/${collection}`);
}
for (const collection of allCollections) {
remoteRegistry
.fetchCollection(collection)
.then((collection: { nodes: { id: NodeId }[] }) => {
const ids = collection.nodes.map((n) => n.id);
return this.registry.load(ids);
});
}
logger.info('loaded node types', this.registry.getAllNodes()); logger.info('loaded node types', this.registry.getAllNodes());
for (const node of this.graph.nodes) { for (const node of this.graph.nodes) {

View File

@@ -58,11 +58,13 @@ export class GraphState {
wrapper = $state<HTMLDivElement>(null!); wrapper = $state<HTMLDivElement>(null!);
rect: DOMRect = $derived( rect: DOMRect = $derived(
(this.wrapper && this.width && this.height) ? this.wrapper.getBoundingClientRect() : new DOMRect(0, 0, 0, 0) (this.wrapper && this.width && this.height)
? this.wrapper.getBoundingClientRect()
: new DOMRect(0, 0, 0, 0)
); );
camera = $state<OrthographicCamera>(null!); camera = $state<OrthographicCamera>(null!);
cameraPosition: [number, number, number] = $state([0, 0, 100]); cameraPosition: [number, number, number] = $state([140, 100, 3.5]);
clipboard: null | { clipboard: null | {
nodes: NodeInstance[]; nodes: NodeInstance[];
@@ -333,4 +335,8 @@ export class GraphState {
&& node.position[1] < this.cameraBounds[3] && node.position[1] < this.cameraBounds[3]
); );
} }
openNodePalette() {
this.addMenuPosition = [this.mousePosition[0], this.mousePosition[1]];
}
} }

View File

@@ -1,23 +1,23 @@
<script lang="ts"> <script lang="ts">
import type { Edge, NodeInstance } from "@nodarium/types"; import type { Edge, NodeInstance } from '@nodarium/types';
import { createKeyMap } from "../../helpers/createKeyMap"; import { Canvas } from '@threlte/core';
import AddMenu from "../components/AddMenu.svelte"; import { HTML } from '@threlte/extras';
import Background from "../background/Background.svelte"; import { createKeyMap } from '../../helpers/createKeyMap';
import BoxSelection from "../components/BoxSelection.svelte"; import Background from '../background/Background.svelte';
import EdgeEl from "../edges/Edge.svelte"; import AddMenu from '../components/AddMenu.svelte';
import NodeEl from "../node/Node.svelte"; import BoxSelection from '../components/BoxSelection.svelte';
import Camera from "../components/Camera.svelte"; import Camera from '../components/Camera.svelte';
import { Canvas } from "@threlte/core"; import HelpView from '../components/HelpView.svelte';
import HelpView from "../components/HelpView.svelte"; import Debug from '../debug/Debug.svelte';
import { getGraphManager, getGraphState } from "../graph-state.svelte"; import EdgeEl from '../edges/Edge.svelte';
import { HTML } from "@threlte/extras"; import { getGraphManager, getGraphState } from '../graph-state.svelte';
import { maxZoom, minZoom } from "./constants"; import NodeEl from '../node/Node.svelte';
import Debug from "../debug/Debug.svelte"; import { maxZoom, minZoom } from './constants';
import { FileDropEventManager } from "./drop.events"; import { FileDropEventManager } from './drop.events';
import { MouseEventManager } from "./mouse.events"; import { MouseEventManager } from './mouse.events';
const { const {
keymap, keymap
}: { }: {
keymap: ReturnType<typeof createKeyMap>; keymap: ReturnType<typeof createKeyMap>;
} = $props(); } = $props();
@@ -45,19 +45,18 @@
const newNode = graph.createNode({ const newNode = graph.createNode({
type: node.type, type: node.type,
position: node.position, position: node.position,
props: node.props, props: node.props
}); });
if (!newNode) return; if (!newNode) return;
if (graphState.activeSocket) { if (graphState.activeSocket) {
if (typeof graphState.activeSocket.index === "number") { if (typeof graphState.activeSocket.index === 'number') {
const socketType = const socketType = graphState.activeSocket.node.state?.type?.outputs?.[
graphState.activeSocket.node.state?.type?.outputs?.[
graphState.activeSocket.index graphState.activeSocket.index
]; ];
const input = Object.entries(newNode?.state?.type?.inputs || {}).find( const input = Object.entries(newNode?.state?.type?.inputs || {}).find(
(inp) => inp[1].type === socketType, (inp) => inp[1].type === socketType
); );
if (input) { if (input) {
@@ -65,12 +64,11 @@
graphState.activeSocket.node, graphState.activeSocket.node,
graphState.activeSocket.index, graphState.activeSocket.index,
newNode, newNode,
input[0], input[0]
); );
} }
} else { } else {
const socketType = const socketType = graphState.activeSocket.node.state?.type?.inputs?.[
graphState.activeSocket.node.state?.type?.inputs?.[
graphState.activeSocket.index graphState.activeSocket.index
]; ];
@@ -85,7 +83,7 @@
newNode, newNode,
output.indexOf(output), output.indexOf(output),
graphState.activeSocket.node, graphState.activeSocket.node,
graphState.activeSocket.index, graphState.activeSocket.index
); );
} }
} }
@@ -97,15 +95,15 @@
</script> </script>
<svelte:window <svelte:window
onmousemove={(ev) => mouseEvents.handleMouseMove(ev)} onmousemove={(ev) => mouseEvents.handleWindowMouseMove(ev)}
onmouseup={(ev) => mouseEvents.handleMouseUp(ev)} onmouseup={(ev) => mouseEvents.handleWindowMouseUp(ev)}
/> />
<div <div
onwheel={(ev) => mouseEvents.handleMouseScroll(ev)} onwheel={(ev) => mouseEvents.handleMouseScroll(ev)}
bind:this={graphState.wrapper} bind:this={graphState.wrapper}
class="graph-wrapper" class="graph-wrapper"
style="height: 100%;" style="height: 100%"
class:is-panning={graphState.isPanning} class:is-panning={graphState.isPanning}
class:is-hovering={graphState.hoveredNodeId !== -1} class:is-hovering={graphState.hoveredNodeId !== -1}
aria-label="Graph" aria-label="Graph"
@@ -115,6 +113,7 @@
bind:clientHeight={graphState.height} bind:clientHeight={graphState.height}
onkeydown={(ev) => keymap.handleKeyboardEvent(ev)} onkeydown={(ev) => keymap.handleKeyboardEvent(ev)}
onmousedown={(ev) => mouseEvents.handleMouseDown(ev)} onmousedown={(ev) => mouseEvents.handleMouseDown(ev)}
oncontextmenu={(ev) => mouseEvents.handleContextMenu(ev)}
{...fileDropEvents.getEventListenerProps()} {...fileDropEvents.getEventListenerProps()}
> >
<input <input
@@ -147,20 +146,18 @@
<BoxSelection <BoxSelection
cameraPosition={graphState.cameraPosition} cameraPosition={graphState.cameraPosition}
p1={{ p1={{
x: x: graphState.cameraPosition[0]
graphState.cameraPosition[0] + + (graphState.mouseDown[0] - graphState.width / 2)
(graphState.mouseDown[0] - graphState.width / 2) / / graphState.cameraPosition[2],
graphState.cameraPosition[2], y: graphState.cameraPosition[1]
y: + (graphState.mouseDown[1] - graphState.height / 2)
graphState.cameraPosition[1] + / graphState.cameraPosition[2]
(graphState.mouseDown[1] - graphState.height / 2) /
graphState.cameraPosition[2],
}} }}
p2={{ x: graphState.mousePosition[0], y: graphState.mousePosition[1] }} p2={{ x: graphState.mousePosition[0], y: graphState.mousePosition[1] }}
/> />
{/if} {/if}
{#if graph.status === "idle"} {#if graph.status === 'idle'}
{#if graphState.addMenuPosition} {#if graphState.addMenuPosition}
<AddMenu onnode={handleNodeCreation} /> <AddMenu onnode={handleNodeCreation} />
{/if} {/if}
@@ -207,9 +204,9 @@
{/each} {/each}
</div> </div>
</HTML> </HTML>
{:else if graph.status === "loading"} {:else if graph.status === 'loading'}
<span>Loading</span> <span>Loading</span>
{:else if graph.status === "error"} {:else if graph.status === 'error'}
<span>Error</span> <span>Error</span>
{/if} {/if}
</Canvas> </Canvas>

View File

@@ -1,7 +1,7 @@
import { animate, lerp } from '$lib/helpers'; import { animate, lerp } from '$lib/helpers';
import { type NodeInstance } from '@nodarium/types'; import { type NodeInstance } from '@nodarium/types';
import type { GraphManager } from '../graph-manager.svelte'; import type { GraphManager } from '../graph-manager.svelte';
import type { GraphState } from '../graph-state.svelte'; import { type GraphState } from '../graph-state.svelte';
import { snapToGrid as snapPointToGrid } from '../helpers'; import { snapToGrid as snapPointToGrid } from '../helpers';
import { maxZoom, minZoom, zoomSpeed } from './constants'; import { maxZoom, minZoom, zoomSpeed } from './constants';
import { EdgeInteractionManager } from './edge.events'; import { EdgeInteractionManager } from './edge.events';
@@ -16,7 +16,7 @@ export class MouseEventManager {
this.edgeInteractionManager = new EdgeInteractionManager(graph, state); this.edgeInteractionManager = new EdgeInteractionManager(graph, state);
} }
handleMouseUp(event: MouseEvent) { handleWindowMouseUp(event: MouseEvent) {
this.edgeInteractionManager.handleMouseUp(); this.edgeInteractionManager.handleMouseUp();
this.state.isPanning = false; this.state.isPanning = false;
if (!this.state.mouseDown) return; if (!this.state.mouseDown) return;
@@ -151,7 +151,19 @@ export class MouseEventManager {
this.state.addMenuPosition = null; this.state.addMenuPosition = null;
} }
handleContextMenu(event: MouseEvent) {
if (!this.state.addMenuPosition) {
event.preventDefault();
this.state.openNodePalette();
}
}
handleMouseDown(event: MouseEvent) { handleMouseDown(event: MouseEvent) {
// Right click
if (event.button === 2) {
return;
}
if (this.state.mouseDown) return; if (this.state.mouseDown) return;
this.state.edgeEndPosition = null; this.state.edgeEndPosition = null;
@@ -229,7 +241,7 @@ export class MouseEventManager {
this.state.edgeEndPosition = null; this.state.edgeEndPosition = null;
} }
handleMouseMove(event: MouseEvent) { handleWindowMouseMove(event: MouseEvent) {
let mx = event.clientX - this.state.rect.x; let mx = event.clientX - this.state.rect.x;
let my = event.clientY - this.state.rect.y; let my = event.clientY - this.state.rect.y;

View File

@@ -59,9 +59,7 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
key: 'A', key: 'A',
shift: true, shift: true,
description: 'Add new Node', description: 'Add new Node',
callback: () => { callback: () => graphState.openNodePalette()
graphState.addMenuPosition = [graphState.mousePosition[0], graphState.mousePosition[1]];
}
}); });
keymap.addShortcut({ keymap.addShortcut({

View File

@@ -5,17 +5,17 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { humanizeDuration } from "$lib/helpers"; import { humanizeDuration } from '$lib/helpers';
import { localState } from "$lib/helpers/localState.svelte"; import { localState } from '$lib/helpers/localState.svelte';
import Monitor from "$lib/performance/Monitor.svelte"; import Monitor from '$lib/performance/Monitor.svelte';
import { Number } from "@nodarium/ui"; import { Number } from '@nodarium/ui';
import { writable } from "svelte/store"; import { writable } from 'svelte/store';
function calculateStandardDeviation(array: number[]) { function calculateStandardDeviation(array: number[]) {
const n = array.length; const n = array.length;
const mean = array.reduce((a, b) => a + b) / n; const mean = array.reduce((a, b) => a + b) / n;
return Math.sqrt( return Math.sqrt(
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n, array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
); );
} }
type Props = { type Props = {
@@ -25,21 +25,21 @@
const { run }: Props = $props(); const { run }: Props = $props();
let isRunning = $state(false); let isRunning = $state(false);
let amount = localState<number>("nodes.benchmark.samples", 500); let amount = localState<number>('nodes.benchmark.samples', 500);
let samples = $state(0); let samples = $state(0);
let warmUp = writable(0); let warmUp = writable(0);
let warmUpAmount = 10; let warmUpAmount = 10;
let status = ""; let status = '';
const copyContent = async (text?: string | number) => { const copyContent = async (text?: string | number) => {
if (!text) return; if (!text) return;
if (typeof text !== "string") { if (typeof text !== 'string') {
text = (Math.floor(text * 100) / 100).toString(); text = (Math.floor(text * 100) / 100).toString();
} }
try { try {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
} catch (err) { } catch (err) {
console.error("Failed to copy: ", err); console.error('Failed to copy: ', err);
} }
}; };
@@ -74,7 +74,7 @@
stdev: calculateStandardDeviation(results), stdev: calculateStandardDeviation(results),
samples: results, samples: results,
duration: performance.now() - a, duration: performance.now() - a,
avg: results.reduce((a, b) => a + b) / results.length, avg: results.reduce((a, b) => a + b) / results.length
}; };
} }
</script> </script>
@@ -90,38 +90,39 @@
<label for="bench-avg">Average </label> <label for="bench-avg">Average </label>
<button <button
id="bench-avg" id="bench-avg"
onkeydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)} onkeydown={(ev) => ev.key === 'Enter' && copyContent(result?.avg)}
onclick={() => copyContent(result?.avg)} onclick={() => copyContent(result?.avg)}
>{Math.floor(result.avg * 100) / 100}</button
> >
{Math.floor(result.avg * 100) / 100}
</button>
<i <i
role="button" role="button"
tabindex="0" tabindex="0"
onkeydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)} onkeydown={(ev) => ev.key === 'Enter' && copyContent(result?.avg)}
onclick={() => copyContent(result?.avg)}>(click to copy)</i onclick={() => copyContent(result?.avg)}
> >(click to copy)</i>
<label for="bench-stdev">Standard Deviation σ</label> <label for="bench-stdev">Standard Deviation σ</label>
<button id="bench-stdev" onclick={() => copyContent(result?.stdev)} <button id="bench-stdev" onclick={() => copyContent(result?.stdev)}>
>{Math.floor(result.stdev * 100) / 100}</button {Math.floor(result.stdev * 100) / 100}
> </button>
<i <i
role="button" role="button"
tabindex="0" tabindex="0"
onkeydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)} onkeydown={(ev) => ev.key === 'Enter' && copyContent(result?.avg)}
onclick={() => copyContent(result?.stdev + "")}>(click to copy)</i onclick={() => copyContent(result?.stdev + '')}
> >(click to copy)</i>
<div> <div>
<button onclick={() => (isRunning = false)}>reset</button> <button onclick={() => (isRunning = false)}>reset</button>
</div> </div>
{:else if isRunning} {:else if isRunning}
<p>WarmUp ({$warmUp}/{warmUpAmount})</p> <p>WarmUp ({$warmUp}/{warmUpAmount})</p>
<progress value={$warmUp} max={warmUpAmount} <progress value={$warmUp} max={warmUpAmount}>
>{Math.floor(($warmUp / warmUpAmount) * 100)}%</progress {Math.floor(($warmUp / warmUpAmount) * 100)}%
> </progress>
<p>Progress ({samples}/{amount.value})</p> <p>Progress ({samples}/{amount.value})</p>
<progress value={samples} max={amount.value} <progress value={samples} max={amount.value}>
>{Math.floor((samples / amount.value) * 100)}%</progress {Math.floor((samples / amount.value) * 100)}%
> </progress>
{:else} {:else}
<label for="bench-samples">Samples</label> <label for="bench-samples">Samples</label>
<Number id="bench-sample" bind:value={amount.value} max={1000} /> <Number id="bench-sample" bind:value={amount.value} max={1000} />

View File

@@ -4,7 +4,7 @@
}; };
outputs = {nixpkgs, ...}: let outputs = {nixpkgs, ...}: let
systems = ["aarch64-darwin" "x86_64-linux"]; systems = ["aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux"];
eachSystem = function: eachSystem = function:
nixpkgs.lib.genAttrs systems (system: nixpkgs.lib.genAttrs systems (system:
function { function {
@@ -19,14 +19,15 @@
pkgs.nodejs_24 pkgs.nodejs_24
pkgs.pnpm_10 pkgs.pnpm_10
# wasm/rust stuff # wasm stuff
pkgs.rustc pkgs.rustc
pkgs.cargo pkgs.cargo
pkgs.rust-analyzer pkgs.rust-analyzer
pkgs.rustfmt pkgs.rustfmt
pkgs.wasm-bindgen-cli pkgs.binaryen
pkgs.wasm-pack
pkgs.lld pkgs.lld
pkgs.zig
pkgs.zls
# frontend # frontend
pkgs.vscode-langservers-extracted pkgs.vscode-langservers-extracted
@@ -35,6 +36,10 @@
pkgs.tailwindcss-language-server pkgs.tailwindcss-language-server
pkgs.svelte-language-server pkgs.svelte-language-server
]; ];
shellHook = ''
unset ZIG_GLOBAL_CACHE_DIR
'';
}; };
}); });
}; };

2
nodes/max/plantarium/zig/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.zig-cache/
zig-out/

View File

@@ -0,0 +1,19 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.resolveTargetQuery(.{ .os_tag = .freestanding, .abi = .none, .cpu_arch = .wasm32 });
const release = b.option(bool, "release", "To build a wasm release") orelse false;
const exe = b.addExecutable(.{
.name = "zig",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = if (release) .ReleaseSmall else .Debug,
}),
});
exe.rdynamic = true;
exe.entry = .disabled;
b.installArtifact(exe);
}

View File

@@ -0,0 +1,81 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = .math,
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",
// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
// package is first created, and then *never changes*. This allows
// unambiguous detection of one package being an updated version of
// another.
//
// When forking a Zig project, this id should be regenerated (delete the
// field and run `zig build`) if the upstream project is still maintained.
// Otherwise, the fork is *hostile*, attempting to take control over the
// original project's identity. Thus it is recommended to leave the comment
// on the following line intact, so that it shows up in code reviews that
// modify the field.
.fingerprint = 0xa927044d8d610b01, // Changing this has security and trust implications.
// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.15.2",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL. If the contents of a URL change this will result in a hash mismatch
// // which will prevent zig from using it.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
//
// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},
// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package. Only files listed here will remain on disk
// when using the zig package manager. As a rule of thumb, one should list
// files required for compilation plus any license(s).
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}

View File

@@ -0,0 +1,27 @@
{
"id": "max/nodarium/zig",
"outputs": [
"float"
],
"inputs": {
"op_type": {
"label": "type",
"type": "select",
"options": [
"add",
"subtract",
"multiply",
"divide"
],
"internal": true
},
"a": {
"type": "float",
"value": 2
},
"b": {
"type": "float",
"value": 2
}
}
}

View File

@@ -0,0 +1,29 @@
const std = @import("std");
const def = @embedFile("input.json");
export fn execute(ptr: *anyopaque, len: c_int) c_int {
_ = ptr; // autofix
_ = len; // autofix
return 0;
}
export fn __alloc(len: c_int) ?*anyopaque {
if (len < 0) return null;
const mem = std.heap.wasm_allocator.alloc(u8, @intCast(len)) catch return null;
return mem.ptr;
}
export fn __free(ptr: *anyopaque, len: c_int) void {
if (len < 1) return;
const mem: [*]u8 = @ptrCast(@alignCast(ptr));
std.heap.wasm_allocator.free(mem[0..@intCast(len)]);
}
export fn getDefinitionPtr() *const anyopaque {
return def.ptr;
}
export fn getDefinitionLen() usize {
return def.len;
}

View File

@@ -1,13 +1,18 @@
{ {
"scripts": { "scripts": {
"postinstall": "pnpm run -r --filter 'ui' build",
"build": "pnpm build:nodes && pnpm build:app", "build": "pnpm build:nodes && pnpm build:app",
"build:story": "pnpm -r --filter 'ui' story:build", "build:story": "pnpm -r --filter 'ui' story:build",
"build:app": "BASE_PATH=/ui pnpm -r --filter 'ui' build && pnpm -r --filter 'app' build", "build:app": "BASE_PATH=/ui pnpm -r --filter 'ui' build && pnpm -r --filter 'app' build",
"build:nodes": "cargo build --workspace --target wasm32-unknown-unknown --release && rm -rf ./app/static/nodes/max/plantarium/ && mkdir -p ./app/static/nodes/max/plantarium/ && cp -R ./target/wasm32-unknown-unknown/release/*.wasm ./app/static/nodes/max/plantarium/", "build:nodes": "cargo build --workspace --target wasm32-unknown-unknown --release && rm -rf ./app/static/nodes/max/plantarium/ && mkdir -p ./app/static/nodes/max/plantarium/ && cp -R ./target/wasm32-unknown-unknown/release/*.wasm ./app/static/nodes/max/plantarium/",
"build:deploy": "pnpm build", "build:deploy": "pnpm build",
"dev:all": "pnpm -r dev", "dev:nodes": "chokidar './nodes/**' --initial -i '/pkg/' -c 'pnpm build:nodes'",
"dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev", "dev:app_ui": "pnpm -r --parallel --filter 'app' --filter './packages/ui' dev",
"dev": "pnpm -r --filter 'app' --filter './packages/ui' dev" "dev_ui": "pnpm -r --filter 'ui' dev:ui",
"dev": "pnpm run \"/^dev:.*/\""
}, },
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316" "packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316",
"devDependencies": {
"chokidar-cli": "catalog:"
}
} }

View File

@@ -10,8 +10,8 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@nodarium/types": "link:../types", "@nodarium/types": "workspace:",
"@nodarium/utils": "link:../utils", "@nodarium/utils": "workspace:",
"idb": "^8.0.3" "idb": "^8.0.3"
} }
} }

View File

@@ -1,32 +1,31 @@
import { import {
NodeDefinitionSchema,
type AsyncCache, type AsyncCache,
type NodeDefinition, type NodeDefinition,
type NodeRegistry, NodeDefinitionSchema,
} from "@nodarium/types"; type NodeRegistry
import { createLogger, createWasmWrapper } from "@nodarium/utils"; } from '@nodarium/types';
import { createLogger, createWasmWrapper } from '@nodarium/utils';
const log = createLogger("node-registry"); const log = createLogger('node-registry');
log.mute(); log.mute();
export class RemoteNodeRegistry implements NodeRegistry { export class RemoteNodeRegistry implements NodeRegistry {
status: "loading" | "ready" | "error" = "loading"; status: 'loading' | 'ready' | 'error' = 'loading';
private nodes: Map<string, NodeDefinition> = new Map(); private nodes: Map<string, NodeDefinition> = new Map();
constructor( constructor(
private url: string, private url: string,
public cache?: AsyncCache<ArrayBuffer | string>, public cache?: AsyncCache<ArrayBuffer | string>
) { } ) { }
async fetchJson(url: string, skipCache = false) { async fetchJson(url: string, skipCache = false) {
const finalUrl = `${this.url}/${url}`; const finalUrl = `${this.url}/${url}`;
if (!skipCache && this.cache) { if (!skipCache && this.cache) {
const cachedValue = await this.cache?.get<string>(finalUrl); const cachedValue = await this.cache?.get<string>(finalUrl);
if (cachedValue) { if (cachedValue) {
// fetch again in the background, maybe implement that only refetch after a certain time // fetch again in the background, maybe implement that only refetch after a certain time
this.fetchJson(url, true) this.fetchJson(url, true);
return JSON.parse(cachedValue); return JSON.parse(cachedValue);
} }
} }
@@ -46,14 +45,13 @@ export class RemoteNodeRegistry implements NodeRegistry {
} }
async fetchArrayBuffer(url: string, skipCache = false) { async fetchArrayBuffer(url: string, skipCache = false) {
const finalUrl = `${this.url}/${url}`; const finalUrl = `${this.url}/${url}`;
if (!skipCache && this.cache) { if (!skipCache && this.cache) {
const cachedNode = await this.cache?.get<ArrayBuffer>(finalUrl); const cachedNode = await this.cache?.get<ArrayBuffer>(finalUrl);
if (cachedNode) { if (cachedNode) {
// fetch again in the background, maybe implement that only refetch after a certain time // fetch again in the background, maybe implement that only refetch after a certain time
this.fetchArrayBuffer(url, true) this.fetchArrayBuffer(url, true);
return cachedNode; return cachedNode;
} }
} }
@@ -79,7 +77,7 @@ export class RemoteNodeRegistry implements NodeRegistry {
async fetchCollection(userCollectionId: `${string}/${string}`) { async fetchCollection(userCollectionId: `${string}/${string}`) {
const col = await this.fetchJson(`nodes/${userCollectionId}.json`); const col = await this.fetchJson(`nodes/${userCollectionId}.json`);
return col return col;
} }
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) { async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
@@ -87,7 +85,6 @@ export class RemoteNodeRegistry implements NodeRegistry {
} }
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) { private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
const node = await this.fetchArrayBuffer(`nodes/${nodeId}.wasm`); const node = await this.fetchArrayBuffer(`nodes/${nodeId}.wasm`);
if (!node) { if (!node) {
throw new Error(`Failed to load node wasm ${nodeId}`); throw new Error(`Failed to load node wasm ${nodeId}`);
@@ -99,7 +96,7 @@ export class RemoteNodeRegistry implements NodeRegistry {
async load(nodeIds: `${string}/${string}/${string}`[]) { async load(nodeIds: `${string}/${string}/${string}`[]) {
const a = performance.now(); const a = performance.now();
const nodes = await Promise.all( const nodes = (await Promise.all(
[...new Set(nodeIds).values()].map(async (id) => { [...new Set(nodeIds).values()].map(async (id) => {
if (this.nodes.has(id)) { if (this.nodes.has(id)) {
return this.nodes.get(id)!; return this.nodes.get(id)!;
@@ -107,17 +104,23 @@ export class RemoteNodeRegistry implements NodeRegistry {
const wasmBuffer = await this.fetchNodeWasm(id); const wasmBuffer = await this.fetchNodeWasm(id);
return this.register(wasmBuffer); try {
}), return await this.register(wasmBuffer);
); } catch (e) {
console.log('Failed to register: ', id);
console.error(e);
return;
}
})
)).filter(Boolean) as NodeDefinition[];
const duration = performance.now() - a; const duration = performance.now() - a;
log.group("loaded nodes in", duration, "ms"); log.group('loaded nodes in', duration, 'ms');
log.info(nodeIds); log.info(nodeIds);
log.info(nodes); log.info(nodes);
log.groupEnd(); log.groupEnd();
this.status = "ready"; this.status = 'ready';
return nodes; return nodes;
} }
@@ -128,17 +131,16 @@ export class RemoteNodeRegistry implements NodeRegistry {
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition()); const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
if (definition.error) { if (definition.error) {
console.error(definition.error);
throw definition.error; throw definition.error;
} }
if (this.cache) { if (this.cache) {
await this.cache.set(definition.data.id, wasmBuffer); this.cache.set(definition.data.id, wasmBuffer);
} }
let node = { let node = {
...definition.data, ...definition.data,
execute: wrapper.execute, execute: wrapper.execute
}; };
this.nodes.set(definition.data.id, node); this.nodes.set(definition.data.id, node);

View File

@@ -3,6 +3,7 @@
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "chokidar './src/**' --initial -c 'pnpm build'", "dev": "chokidar './src/**' --initial -c 'pnpm build'",
"dev:ui": "vite",
"build": "vite build && npm run package", "build": "vite build && npm run package",
"preview": "vite preview", "preview": "vite preview",
"package": "svelte-kit sync && svelte-package && publint", "package": "svelte-kit sync && svelte-package && publint",
@@ -10,6 +11,7 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest", "test": "vitest",
"lint-dprint": "dprint check -c '../../.dprint.jsonc' .",
"lint": "eslint ." "lint": "eslint ."
}, },
"exports": { "exports": {
@@ -28,7 +30,7 @@
"svelte": "^4.0.0" "svelte": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nodarium/types": "link:../types", "@nodarium/types": "workspace:",
"@sveltejs/adapter-static": "^3.0.10", "@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.50.0", "@sveltejs/kit": "^2.50.0",
"@sveltejs/package": "^2.5.7", "@sveltejs/package": "^2.5.7",

View File

@@ -1,10 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { NodeInput } from '@nodarium/types'; import type { NodeInput } from '@nodarium/types';
import Checkbox from './inputs/Checkbox.svelte'; import { Checkbox, Number, Select, Vec3 } from './index.js';
import Number from './inputs/Number.svelte';
import Select from './inputs/Select.svelte';
import Vec3 from './inputs/Vec3.svelte';
interface Props { interface Props {
input: NodeInput; input: NodeInput;
@@ -16,7 +13,7 @@
</script> </script>
{#if input.type === 'float'} {#if input.type === 'float'}
<Number bind:value min={input?.min} max={input?.max} step={0.01} /> <Number bind:value min={input?.min} max={input?.max} step={input?.step} />
{:else if input.type === 'integer'} {:else if input.type === 'integer'}
<Number bind:value min={input?.min} max={input?.max} /> <Number bind:value min={input?.min} max={input?.max} />
{:else if input.type === 'boolean'} {:else if input.type === 'boolean'}

View File

@@ -1,8 +1,7 @@
export { default as Input } from './Input.svelte'; export { default as Input } from './Input.svelte';
export { default as Checkbox } from './inputs/Checkbox.svelte'; export { default as Checkbox } from './inputs/Checkbox.svelte';
export { default as Float } from './inputs/Float.svelte'; export { default as Float, default as Number } from './inputs/Float.svelte';
export { default as Integer } from './inputs/Integer.svelte'; export { default as Integer } from './inputs/Integer.svelte';
export { default as Number } from './inputs/Number.svelte';
export { default as Select } from './inputs/Select.svelte'; export { default as Select } from './inputs/Select.svelte';
export { default as Vec3 } from './inputs/Vec3.svelte'; export { default as Vec3 } from './inputs/Vec3.svelte';

View File

@@ -9,7 +9,7 @@
let { let {
value = $bindable(0.5), value = $bindable(0.5),
step = 0.01, step,
min = $bindable(0), min = $bindable(0),
max = $bindable(1), max = $bindable(1),
id id
@@ -22,8 +22,11 @@
max = value; max = value;
} }
// svelte-ignore state_referenced_locally only use initial values
const precision = ((step || value).toString().split('.')[1] || '').length;
function strip(input: number) { function strip(input: number) {
return +parseFloat(input + '').toPrecision(2); return +parseFloat(input + '').toFixed(precision);
} }
let inputEl: HTMLInputElement | undefined = $state(); let inputEl: HTMLInputElement | undefined = $state();
@@ -94,14 +97,22 @@
} else { } else {
value = Math.max(Math.min(min + (max - min) * vx, max), min); value = Math.max(Math.min(min + (max - min) * vx, max), min);
} }
(ev.target as HTMLElement)?.blur();
value = strip(value);
// With ctrl + outside of input ev.target becomes HTMLDocument
if (ev.target instanceof HTMLElement) {
ev.target?.blur();
} }
}
$effect(() => { $effect(() => {
if ((value || 0).toString().length > 5) { if (value.toString().length > 5) {
value = strip(value || 0); value = strip(value);
} }
value !== undefined && handleChange(); value !== undefined && handleChange();
}); });
let width = $derived( let width = $derived(
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px' : '20px' Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px' : '20px'
); );
@@ -137,12 +148,12 @@
border-radius: var(--border-radius, 2px); border-radius: var(--border-radius, 2px);
} }
input[type='number']::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button { input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
} }
input[type='number'] { input[type="number"] {
box-sizing: border-box; box-sizing: border-box;
-webkit-appearance: textfield; -webkit-appearance: textfield;
-moz-appearance: textfield; -moz-appearance: textfield;

View File

@@ -1,3 +1,8 @@
<!--
@component
@deprecated use Float.svelte
-->
<script lang="ts"> <script lang="ts">
interface Props { interface Props {
value?: number; value?: number;
@@ -112,7 +117,6 @@
onmousedown={handleMouseDown} onmousedown={handleMouseDown}
onmouseup={handleMouseUp} onmouseup={handleMouseUp}
> >
<div class="">
<button onclick={() => handleChange(-step)}>-</button> <button onclick={() => handleChange(-step)}>-</button>
<input <input
bind:value bind:value
@@ -126,9 +130,10 @@
/> />
<button onclick={() => handleChange(+step)}>+</button> <button onclick={() => handleChange(+step)}>+</button>
</div>
{#if typeof min !== 'undefined' && typeof max !== 'undefined'} {#if typeof min !== 'undefined' && typeof max !== 'undefined'}
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`} <span
class="overlay"
style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
></span> ></span>
{/if} {/if}
</div> </div>
@@ -146,12 +151,12 @@
border-radius: var(--border-radius, 2px); border-radius: var(--border-radius, 2px);
} }
input[type='number']::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button { input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
} }
input[type='number'] { input[type="number"] {
-webkit-appearance: textfield; -webkit-appearance: textfield;
-moz-appearance: textfield; -moz-appearance: textfield;
appearance: textfield; appearance: textfield;
@@ -198,7 +203,7 @@
margin-inline: 6px; margin-inline: 6px;
} }
div input[type='number'] { div input[type="number"] {
color: var(--text-color); color: var(--text-color);
background-color: transparent; background-color: transparent;
padding: var(--padding, 6px); padding: var(--padding, 6px);

View File

@@ -1,177 +0,0 @@
<script lang="ts">
interface Props {
value?: number;
step?: number;
min?: number;
max?: number;
id?: string;
}
let {
value = $bindable(1),
step = 1,
min = $bindable(0),
max = $bindable(1),
id: _id
}: Props = $props();
const uid = $props.id();
const id = $derived(_id || uid);
if (min > max) {
[min, max] = [max, min];
}
if (value > max) {
max = value;
}
function strip(input: number) {
return +parseFloat(input + '').toPrecision(2);
}
let inputEl = $state() as HTMLInputElement;
let prev = -1;
function update() {
if (prev === value) return;
if (value.toString().length > 5) {
value = strip(value);
}
prev = value;
}
function handleChange(change: number) {
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
}
function handleKeyDown(ev: KeyboardEvent) {
if (ev.key === 'Escape' || ev.key === 'Enter') {
inputEl?.blur();
}
}
$effect(() => {
update();
});
let ratio = $derived(((value - min) / (max - min)) * 100);
</script>
<div>
<div class="component-wrapper">
<button onclick={() => handleChange(-step)}>-</button>
<input
bind:value
bind:this={inputEl}
{id}
{step}
{max}
{min}
type="number"
onkeydown={handleKeyDown}
/>
<button onclick={() => handleChange(+step)}>+</button>
</div>
<div class="slider">
<input
type="range"
bind:value
{min}
{max}
{step}
style={`background: linear-gradient(90deg, var(--text-color) ${ratio}%, var(--layer-2, #4b4b4b) ${ratio}%)`}
/>
</div>
</div>
<style>
:root {
--slider-height: 4px;
}
.component-wrapper {
display: flex;
background-color: var(--layer-2, #4b4b4b);
user-select: none;
transition: box-shadow 0.3s ease;
border: solid 1px var(--outline);
overflow: hidden;
border-radius: 0 var(--border-radius, 2px); /* only top */
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
}
input[type='number'] {
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
cursor: pointer;
font-family: var(--font-family);
font-variant-numeric: tabular-nums;
color: var(--text-color);
background-color: transparent;
padding: var(--padding, 6px);
font-size: 1em;
padding-inline: 10px;
text-align: center;
border: none;
border-style: none;
flex: 1;
width: 72%;
}
button {
background-color: transparent;
border: none;
cursor: pointer;
line-height: 0px;
margin: 0;
color: var(--text-color);
margin-inline: 6px;
}
div input[type='number'] {
color: var(--text-color);
background-color: transparent;
padding: var(--padding, 6px);
padding-inline: 0px;
text-align: center;
border: none;
border-style: none;
}
.slider {
position: relative;
margin-top: -1px; /* hide edge */
}
input[type='range'] {
position: absolute;
appearance: none;
width: 100%;
height: var(--slider-height);
background: var(--layer-2, #4b4b4b);
cursor: pointer;
}
/* Thumb: for Chrome, Safari, Edge */
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 12px;
height: var(--slider-height);
background: var(--text-color);
box-shadow: none;
}
/* Thumb: for Firefox */
input[type='range']::-moz-range-thumb {
border: none;
width: 12px;
height: var(--slider-height);
background: var(--text-color);
box-shadow: none;
}
</style>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import Number from './Number.svelte'; import { Number } from '$lib/index.js';
interface Props { interface Props {
value?: any; value?: any;

View File

@@ -1,10 +1,12 @@
<script lang="ts"> <script lang="ts">
import '$lib/app.css'; import '$lib/app.css';
import { Checkbox, Details, Float, Integer, Number, Select, ShortCut, Vec3 } from '$lib/index.js'; import { Checkbox, Details, Number, Select, ShortCut, Vec3 } from '$lib/index.js';
import Section from './Section.svelte'; import Section from './Section.svelte';
let intValue = $state(0); let intValue = $state(0);
let floatValue = $state(0.2); let floatValue = $state(0.2);
let float2Value = $state(0.02);
let float3Value = $state(1);
let vecValue = $state([0.2, 0.3, 0.4]); let vecValue = $state([0.2, 0.3, 0.4]);
const options = ['strawberry', 'raspberry', 'chickpeas']; const options = ['strawberry', 'raspberry', 'chickpeas'];
let selectValue = $state(0); let selectValue = $state(0);
@@ -30,22 +32,22 @@
<Select bind:value={themeIndex} options={themes}></Select> <Select bind:value={themeIndex} options={themes}></Select>
</div> </div>
<Section title="Integer" value={intValue}> <Section title="Integer (step inherit)" value={intValue}>
<Integer bind:value={intValue} /> <Number bind:value={intValue} max={2} />
</Section> </Section>
<Section title="Float" value={floatValue}> <Section title="Float (step inherit)" value={floatValue}>
<Float bind:value={floatValue} />
</Section>
<Section title="Number" value={intValue}>
<Number bind:value={intValue} />
</Section>
<Section title="Number (float)" value={floatValue}>
<Number bind:value={floatValue} /> <Number bind:value={floatValue} />
</Section> </Section>
<Section title="Float 2 (step inherit)" value={intValue}>
<Number bind:value={float2Value} />
</Section>
<Section title="Float (0.01 step)" value={floatValue}>
<Number bind:value={float3Value} step={0.01} max={3} />
</Section>
<Section title="Vec3" value={JSON.stringify(vecValue)}> <Section title="Vec3" value={JSON.stringify(vecValue)}>
<Vec3 bind:value={vecValue} /> <Vec3 bind:value={vecValue} />
</Section> </Section>

View File

@@ -2,15 +2,15 @@
name = "nodarium_utils" name = "nodarium_utils"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
description = "A collection of utilities for Nodarium"
license = "MIT" license = "MIT"
repository = "https://github.com/jim-fx/nodes" repository = "https://github.com/jim-fx/nodes"
description = "A collection of utilities for Nodarium"
[lib] [lib]
crate-type = ["rlib"] crate-type = ["rlib"]
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
glam = "0.30.10" glam = "0.30.10"
noise = "0.9.0" noise = "0.9.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }

View File

@@ -10,7 +10,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@nodarium/types": "link:../types" "@nodarium/types": "workspace:"
}, },
"devDependencies": { "devDependencies": {
"vite": "^7.3.1", "vite": "^7.3.1",

View File

@@ -33,3 +33,19 @@ macro_rules! log {
}}; }};
} }
#[allow(dead_code)]
#[rustfmt::skip]
fn test_split_args(){
let inputs = vec![
vec![0, 1, 0, 4, 1056964608, 1065353216, 1056964608, 1, 4, 1080872141, 1054951342, 32, 1, 1 ],
vec![0, 4, 1056964608, 1065353216, 1056964608, 1, 4],
vec![0, 1, 0, 3, 0, 0, 0, 5, 0, 0, 1073741824, 1073741824, 1, 1, 1, 1, 1, 4, 1065353216, 1054615798, 5, 1, 1 ],
vec![ 0, 1, 0, 3, 0, 0, 0, 1, 4, 1073741824, 1073741824, 32, 1, 1 ],
vec![0, 1, 0, 1, 0, 14, 0, 1056964608, 1056964608, 1056964608, 1058810102, 1056964608, 1069547520, 1056964608, 1050421494, 1056964608, 1075838976, 1056964608, 0, 1, 1, 1, 2, 13, 1, 1],
vec![ 0, 1, 0, 2, 0, 0, 5, 0, 0, 1073741824, 1073741824, 1, 2, 0, 1, 4, 1088212173, 1083388723, 20, 1, 1 ]
];
for input in inputs {
println!("RESULT: {:?}", split_args(&input));
}
}

View File

@@ -1,36 +0,0 @@
use nodarium_utils::{
geometry::{create_multiple_paths, wrap_multiple_paths},
split_args,
};
#[allow(dead_code)]
#[rustfmt::skip]
fn test_split_args(){
let inputs = vec![
vec![0, 1, 0, 4, 1056964608, 1065353216, 1056964608, 1, 4, 1080872141, 1054951342, 32, 1, 1 ],
vec![0, 4, 1056964608, 1065353216, 1056964608, 1, 4],
vec![0, 1, 0, 3, 0, 0, 0, 5, 0, 0, 1073741824, 1073741824, 1, 1, 1, 1, 1, 4, 1065353216, 1054615798, 5, 1, 1 ],
vec![ 0, 1, 0, 3, 0, 0, 0, 1, 4, 1073741824, 1073741824, 32, 1, 1 ],
vec![0, 1, 0, 1, 0, 14, 0, 1056964608, 1056964608, 1056964608, 1058810102, 1056964608, 1069547520, 1056964608, 1050421494, 1056964608, 1075838976, 1056964608, 0, 1, 1, 1, 2, 13, 1, 1],
vec![ 0, 1, 0, 2, 0, 0, 5, 0, 0, 1073741824, 1073741824, 1, 2, 0, 1, 4, 1088212173, 1083388723, 20, 1, 1 ]
];
for input in inputs {
println!("RESULT: {:?}", split_args(&input));
}
}
fn test_path() {
// let path_data = create_path(3, 2);
// println!("{:?}", path_data);
let mut multiple_paths = create_multiple_paths(1, 4, 1);
let mut wrapped_paths = wrap_multiple_paths(&mut multiple_paths);
wrapped_paths[0].points[0] = 1.0;
println!("{:?}", wrapped_paths);
println!("{:?}", multiple_paths);
}
fn main() {
test_path()
}

View File

@@ -3,6 +3,8 @@ interface NodariumExports extends WebAssembly.Exports {
execute: (ptr: number, len: number) => number; execute: (ptr: number, len: number) => number;
__free: (ptr: number, len: number) => void; __free: (ptr: number, len: number) => void;
__alloc: (len: number) => number; __alloc: (len: number) => number;
getDefinitionPtr: () => number;
getDefinitionLen: () => number;
} }
export function createWasmWrapper(buffer: ArrayBuffer) { export function createWasmWrapper(buffer: ArrayBuffer) {
@@ -19,8 +21,8 @@ export function createWasmWrapper(buffer: ArrayBuffer) {
if (!exports) return; if (!exports) return;
const view = new Uint8Array(exports.memory.buffer, ptr, len); const view = new Uint8Array(exports.memory.buffer, ptr, len);
console.log("RUST:", new TextDecoder().decode(view)); console.log("RUST:", new TextDecoder().decode(view));
} },
} },
}; };
const module = new WebAssembly.Module(buffer); const module = new WebAssembly.Module(buffer);
@@ -43,12 +45,22 @@ export function createWasmWrapper(buffer: ArrayBuffer) {
} }
function get_definition() { function get_definition() {
const sections = WebAssembly.Module.customSections(module, "nodarium_definition");
if (sections.length > 0) {
const decoder = new TextDecoder(); const decoder = new TextDecoder();
const sections = WebAssembly.Module.customSections(
module,
"nodarium_definition",
);
if (sections.length > 0) {
const jsonString = decoder.decode(sections[0]); const jsonString = decoder.decode(sections[0]);
return JSON.parse(jsonString); return JSON.parse(jsonString);
} }
const ptr = exports.getDefinitionPtr();
const len = exports.getDefinitionLen();
const view = new Uint8Array(exports.memory.buffer, ptr, len);
const jsonString = decoder.decode(view);
return JSON.parse(jsonString);
} }
return { execute, get_definition }; return { execute, get_definition };

127
pnpm-lock.yaml generated
View File

@@ -4,9 +4,19 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
catalogs:
default:
chokidar-cli:
specifier: github:open-cli-tools/chokidar-cli#semver:v4.0.0
version: 4.0.0
importers: importers:
.: {} .:
devDependencies:
chokidar-cli:
specifier: 'catalog:'
version: https://codeload.github.com/open-cli-tools/chokidar-cli/tar.gz/8dd8a1e8631d377de600f628d819a0cda46c102f
app: app:
dependencies: dependencies:
@@ -60,7 +70,7 @@ importers:
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1(tailwindcss@4.1.18) version: 1.2.1(tailwindcss@4.1.18)
'@nodarium/types': '@nodarium/types':
specifier: link:../packages/types specifier: 'workspace:'
version: link:../packages/types version: link:../packages/types
'@sveltejs/adapter-static': '@sveltejs/adapter-static':
specifier: ^3.0.10 specifier: ^3.0.10
@@ -108,10 +118,10 @@ importers:
packages/registry: packages/registry:
dependencies: dependencies:
'@nodarium/types': '@nodarium/types':
specifier: link:../types specifier: 'workspace:'
version: link:../types version: link:../types
'@nodarium/utils': '@nodarium/utils':
specifier: link:../utils specifier: 'workspace:'
version: link:../utils version: link:../utils
idb: idb:
specifier: ^8.0.3 specifier: ^8.0.3
@@ -145,7 +155,7 @@ importers:
version: 4.1.18 version: 4.1.18
devDependencies: devDependencies:
'@nodarium/types': '@nodarium/types':
specifier: link:../types specifier: 'workspace:'
version: link:../types version: link:../types
'@sveltejs/adapter-static': '@sveltejs/adapter-static':
specifier: ^3.0.10 specifier: ^3.0.10
@@ -205,7 +215,7 @@ importers:
packages/utils: packages/utils:
dependencies: dependencies:
'@nodarium/types': '@nodarium/types':
specifier: link:../types specifier: 'workspace:'
version: link:../types version: link:../types
devDependencies: devDependencies:
vite: vite:
@@ -1229,6 +1239,10 @@ packages:
resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==}
engines: {node: '>=6'} engines: {node: '>=6'}
ansi-regex@6.2.2:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
ansi-styles@3.2.1: ansi-styles@3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -1237,6 +1251,10 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'} engines: {node: '>=8'}
ansi-styles@6.2.3:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
anymatch@3.1.3: anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -1331,6 +1349,12 @@ packages:
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
hasBin: true hasBin: true
chokidar-cli@https://codeload.github.com/open-cli-tools/chokidar-cli/tar.gz/8dd8a1e8631d377de600f628d819a0cda46c102f:
resolution: {tarball: https://codeload.github.com/open-cli-tools/chokidar-cli/tar.gz/8dd8a1e8631d377de600f628d819a0cda46c102f}
version: 4.0.0
engines: {node: '>= 20.0.0'}
hasBin: true
chokidar@3.6.0: chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
@@ -1349,6 +1373,10 @@ packages:
cliui@5.0.0: cliui@5.0.0:
resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}
cliui@9.0.1:
resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==}
engines: {node: '>=20'}
clsx@2.1.1: clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1533,6 +1561,9 @@ packages:
earcut@2.2.4: earcut@2.2.4:
resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
emoji-regex@7.0.3: emoji-regex@7.0.3:
resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==}
@@ -1581,6 +1612,10 @@ packages:
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-string-regexp@4.0.0: escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1719,6 +1754,10 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*} engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.4.0:
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
engines: {node: '>=18'}
get-intrinsic@1.3.0: get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2393,10 +2432,18 @@ packages:
resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
engines: {node: '>=6'} engines: {node: '>=6'}
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
strip-ansi@5.2.0: strip-ansi@5.2.0:
resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==}
engines: {node: '>=6'} engines: {node: '>=6'}
strip-ansi@7.1.2:
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
engines: {node: '>=12'}
strip-json-comments@3.1.1: strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2736,6 +2783,10 @@ packages:
resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==} resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
wrap-ansi@9.0.2:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
ws@8.19.0: ws@8.19.0:
resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@@ -2762,6 +2813,10 @@ packages:
y18n@4.0.3: y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yaml@1.10.2: yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -2769,9 +2824,17 @@ packages:
yargs-parser@13.1.2: yargs-parser@13.1.2:
resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==} resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==}
yargs-parser@22.0.0:
resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
yargs@13.3.2: yargs@13.3.2:
resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==} resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==}
yargs@18.0.0:
resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
yn@3.1.1: yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -3696,6 +3759,8 @@ snapshots:
ansi-regex@4.1.1: {} ansi-regex@4.1.1: {}
ansi-regex@6.2.2: {}
ansi-styles@3.2.1: ansi-styles@3.2.1:
dependencies: dependencies:
color-convert: 1.9.3 color-convert: 1.9.3
@@ -3704,6 +3769,8 @@ snapshots:
dependencies: dependencies:
color-convert: 2.0.1 color-convert: 2.0.1
ansi-styles@6.2.3: {}
anymatch@3.1.3: anymatch@3.1.3:
dependencies: dependencies:
normalize-path: 3.0.0 normalize-path: 3.0.0
@@ -3796,6 +3863,13 @@ snapshots:
lodash.throttle: 4.1.1 lodash.throttle: 4.1.1
yargs: 13.3.2 yargs: 13.3.2
chokidar-cli@https://codeload.github.com/open-cli-tools/chokidar-cli/tar.gz/8dd8a1e8631d377de600f628d819a0cda46c102f:
dependencies:
chokidar: 3.6.0
lodash.debounce: 4.0.8
lodash.throttle: 4.1.1
yargs: 18.0.0
chokidar@3.6.0: chokidar@3.6.0:
dependencies: dependencies:
anymatch: 3.1.3 anymatch: 3.1.3
@@ -3826,6 +3900,12 @@ snapshots:
strip-ansi: 5.2.0 strip-ansi: 5.2.0
wrap-ansi: 5.1.0 wrap-ansi: 5.1.0
cliui@9.0.1:
dependencies:
string-width: 7.2.0
strip-ansi: 7.1.2
wrap-ansi: 9.0.2
clsx@2.1.1: {} clsx@2.1.1: {}
color-convert@1.9.3: color-convert@1.9.3:
@@ -3987,6 +4067,8 @@ snapshots:
earcut@2.2.4: {} earcut@2.2.4: {}
emoji-regex@10.6.0: {}
emoji-regex@7.0.3: {} emoji-regex@7.0.3: {}
enhanced-resolve@5.18.4: enhanced-resolve@5.18.4:
@@ -4082,6 +4164,8 @@ snapshots:
'@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-ia32': 0.27.2
'@esbuild/win32-x64': 0.27.2 '@esbuild/win32-x64': 0.27.2
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
eslint-plugin-svelte@3.14.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.46.4)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.9.3)): eslint-plugin-svelte@3.14.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.46.4)(ts-node@10.9.2(@types/node@22.8.6)(typescript@5.9.3)):
@@ -4242,6 +4326,8 @@ snapshots:
get-caller-file@2.0.5: {} get-caller-file@2.0.5: {}
get-east-asian-width@1.4.0: {}
get-intrinsic@1.3.0: get-intrinsic@1.3.0:
dependencies: dependencies:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
@@ -4900,10 +4986,20 @@ snapshots:
is-fullwidth-code-point: 2.0.0 is-fullwidth-code-point: 2.0.0
strip-ansi: 5.2.0 strip-ansi: 5.2.0
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
get-east-asian-width: 1.4.0
strip-ansi: 7.1.2
strip-ansi@5.2.0: strip-ansi@5.2.0:
dependencies: dependencies:
ansi-regex: 4.1.1 ansi-regex: 4.1.1
strip-ansi@7.1.2:
dependencies:
ansi-regex: 6.2.2
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
supports-color@7.2.0: supports-color@7.2.0:
@@ -5232,6 +5328,12 @@ snapshots:
string-width: 3.1.0 string-width: 3.1.0
strip-ansi: 5.2.0 strip-ansi: 5.2.0
wrap-ansi@9.0.2:
dependencies:
ansi-styles: 6.2.3
string-width: 7.2.0
strip-ansi: 7.1.2
ws@8.19.0: ws@8.19.0:
optional: true optional: true
@@ -5248,6 +5350,8 @@ snapshots:
y18n@4.0.3: {} y18n@4.0.3: {}
y18n@5.0.8: {}
yaml@1.10.2: {} yaml@1.10.2: {}
yargs-parser@13.1.2: yargs-parser@13.1.2:
@@ -5255,6 +5359,8 @@ snapshots:
camelcase: 5.3.1 camelcase: 5.3.1
decamelize: 1.2.0 decamelize: 1.2.0
yargs-parser@22.0.0: {}
yargs@13.3.2: yargs@13.3.2:
dependencies: dependencies:
cliui: 5.0.0 cliui: 5.0.0
@@ -5268,6 +5374,15 @@ snapshots:
y18n: 4.0.3 y18n: 4.0.3
yargs-parser: 13.1.2 yargs-parser: 13.1.2
yargs@18.0.0:
dependencies:
cliui: 9.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
string-width: 7.2.0
y18n: 5.0.8
yargs-parser: 22.0.0
yn@3.1.1: yn@3.1.1:
optional: true optional: true

View File

@@ -1,6 +1,9 @@
packages: packages:
- app - "app"
- packages/* - "packages/*"
- nodes/**/**/**/* - "nodes/**"
- '!nodes/**/**/**/pkg' - "!**/.template/**"
- '!**/.template' - "!**/pkg/**"
catalog:
chokidar-cli: github:open-cli-tools/chokidar-cli#semver:v4.0.0