fix: gravity node
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m35s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m35s
This commit is contained in:
parent
dca4469f55
commit
26d3f6a2f1
@ -13,12 +13,13 @@
|
|||||||
"@nodes/ui": "link:../packages/ui",
|
"@nodes/ui": "link:../packages/ui",
|
||||||
"@nodes/utils": "link:../packages/utils",
|
"@nodes/utils": "link:../packages/utils",
|
||||||
"@sveltejs/kit": "^2.5.7",
|
"@sveltejs/kit": "^2.5.7",
|
||||||
"@threlte/core": "next",
|
"@threlte/core": "^7.3.0",
|
||||||
"@threlte/extras": "next",
|
"@threlte/extras": "^8.11.2",
|
||||||
"@types/three": "^0.164.0",
|
"@types/three": "^0.164.0",
|
||||||
"@unocss/reset": "^0.59.4",
|
"@unocss/reset": "^0.59.4",
|
||||||
"comlink": "^4.4.1",
|
"comlink": "^4.4.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"idb": "^8.0.0",
|
||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
"three": "^0.164.1"
|
"three": "^0.164.1"
|
||||||
},
|
},
|
||||||
@ -26,11 +27,11 @@
|
|||||||
"@iconify-json/tabler": "^1.1.110",
|
"@iconify-json/tabler": "^1.1.110",
|
||||||
"@nodes/types": "link:../packages/types",
|
"@nodes/types": "link:../packages/types",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "next",
|
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@unocss/preset-icons": "^0.59.4",
|
"@unocss/preset-icons": "^0.59.4",
|
||||||
"svelte": "5.0.0-next.118",
|
"svelte": "^4.2.15",
|
||||||
"svelte-check": "^3.7.0",
|
"svelte-check": "^3.7.0",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
|
@ -407,7 +407,10 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
|
|
||||||
// check if socket types match
|
// check if socket types match
|
||||||
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket];
|
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket];
|
||||||
const toSocketType = to.tmp?.type?.inputs?.[toSocket]?.type;
|
const toSocketType = [to.tmp?.type?.inputs?.[toSocket]?.type];
|
||||||
|
if (to.tmp?.type?.inputs?.[toSocket]?.accepts) {
|
||||||
|
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || []));
|
||||||
|
}
|
||||||
|
|
||||||
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
|
||||||
logger.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
logger.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
||||||
@ -534,7 +537,11 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
|
|||||||
const inputs = node?.tmp?.type?.inputs;
|
const inputs = node?.tmp?.type?.inputs;
|
||||||
if (!inputs) continue;
|
if (!inputs) continue;
|
||||||
for (const key in inputs) {
|
for (const key in inputs) {
|
||||||
if (areSocketsCompatible(ownType, inputs[key].type) && edges.get(node.id) !== key) {
|
|
||||||
|
const otherType = [inputs[key].type];
|
||||||
|
otherType.push(...(inputs[key].accepts || []));
|
||||||
|
|
||||||
|
if (areSocketsCompatible(ownType, otherType) && edges.get(node.id) !== key) {
|
||||||
sockets.push([node, key]);
|
sockets.push([node, key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
import Camera from "../Camera.svelte";
|
import Camera from "../Camera.svelte";
|
||||||
import GraphView from "./GraphView.svelte";
|
import GraphView from "./GraphView.svelte";
|
||||||
import type { Node, NodeId, Node as NodeType, Socket } from "@nodes/types";
|
import type { Node, NodeId, Node as NodeType, Socket } from "@nodes/types";
|
||||||
import { GraphSchema, NodeDefinitionSchema } from "@nodes/types";
|
import { GraphSchema } from "@nodes/types";
|
||||||
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
||||||
import {
|
import {
|
||||||
activeNodeId,
|
activeNodeId,
|
||||||
@ -25,7 +25,6 @@
|
|||||||
import { createKeyMap } from "../../helpers/createKeyMap";
|
import { createKeyMap } from "../../helpers/createKeyMap";
|
||||||
import BoxSelection from "../BoxSelection.svelte";
|
import BoxSelection from "../BoxSelection.svelte";
|
||||||
import AddMenu from "../AddMenu.svelte";
|
import AddMenu from "../AddMenu.svelte";
|
||||||
import { createWasmWrapper } from "@nodes/utils";
|
|
||||||
|
|
||||||
import HelpView from "../HelpView.svelte";
|
import HelpView from "../HelpView.svelte";
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
@ -819,11 +818,10 @@
|
|||||||
isDragging = false;
|
isDragging = false;
|
||||||
if (!event.dataTransfer) return;
|
if (!event.dataTransfer) return;
|
||||||
const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
|
const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
|
||||||
|
let mx = event.clientX - rect.x;
|
||||||
|
let my = event.clientY - rect.y;
|
||||||
|
|
||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
let mx = event.clientX - rect.x;
|
|
||||||
let my = event.clientY - rect.y;
|
|
||||||
|
|
||||||
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
|
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
|
||||||
let nodeOffsetY = event.dataTransfer.getData("data/node-offset-y");
|
let nodeOffsetY = event.dataTransfer.getData("data/node-offset-y");
|
||||||
if (nodeOffsetX && nodeOffsetY) {
|
if (nodeOffsetX && nodeOffsetY) {
|
||||||
@ -852,13 +850,16 @@
|
|||||||
|
|
||||||
if (file.type === "application/wasm") {
|
if (file.type === "application/wasm") {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = async (e) => {
|
||||||
const buffer = e.target?.result as Buffer;
|
const buffer = e.target?.result;
|
||||||
if (buffer) {
|
if (buffer?.constructor === ArrayBuffer) {
|
||||||
const wrapper = createWasmWrapper(buffer);
|
const nodeType = await manager.registry.register(buffer);
|
||||||
const definition = wrapper.get_definition();
|
|
||||||
const res = NodeDefinitionSchema.parse(definition);
|
manager.createNode({
|
||||||
console.log(res);
|
type: nodeType.id,
|
||||||
|
props: {},
|
||||||
|
position: projectScreenToWorld(mx, my),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
|
38
app/src/lib/node-registry-cache.ts
Normal file
38
app/src/lib/node-registry-cache.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { RuntimeCache } from '@nodes/types';
|
||||||
|
import { openDB, type IDBPDatabase } from 'idb';
|
||||||
|
|
||||||
|
export class IndexDBCache implements RuntimeCache<ArrayBuffer> {
|
||||||
|
|
||||||
|
size: number = 100;
|
||||||
|
|
||||||
|
db: Promise<IDBPDatabase<ArrayBuffer>>;
|
||||||
|
private _cache = new Map<string, ArrayBuffer>();
|
||||||
|
|
||||||
|
constructor(id: string) {
|
||||||
|
this.db = openDB<ArrayBuffer>('cache/' + id, 1, {
|
||||||
|
upgrade(db) {
|
||||||
|
db.createObjectStore('keyval');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string) {
|
||||||
|
let res = this._cache.get(key);
|
||||||
|
if (!res) {
|
||||||
|
res = await (await this.db).get('keyval', key);
|
||||||
|
}
|
||||||
|
if (res) {
|
||||||
|
this._cache.set(key, res);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
async set(key: string, value: ArrayBuffer) {
|
||||||
|
this._cache.set(key, value);
|
||||||
|
const db = await this.db;
|
||||||
|
await db.put('keyval', value, key);
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
this.db.then(db => db.clear('keyval'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import type { NodeRegistry, NodeDefinition } from "@nodes/types";
|
import { type NodeRegistry, type NodeDefinition, NodeDefinitionSchema, type RuntimeCache } from "@nodes/types";
|
||||||
import { createWasmWrapper } from "@nodes/utils";
|
import { createWasmWrapper } from "@nodes/utils";
|
||||||
import { createLogger } from "./helpers";
|
import { createLogger } from "./helpers";
|
||||||
|
|
||||||
@ -10,6 +10,8 @@ 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();
|
||||||
|
|
||||||
|
cache?: RuntimeCache<ArrayBuffer>;
|
||||||
|
|
||||||
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
|
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
|
||||||
|
|
||||||
constructor(private url: string) { }
|
constructor(private url: string) { }
|
||||||
@ -46,6 +48,22 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
|
||||||
|
|
||||||
|
const response = await this.fetch(`${this.url}/nodes/${nodeId}.wasm`);
|
||||||
|
if (!response.ok) {
|
||||||
|
if (this.cache) {
|
||||||
|
let value = await this.cache.get(nodeId);
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.arrayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||||
const a = performance.now();
|
const a = performance.now();
|
||||||
|
|
||||||
@ -55,26 +73,12 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
return this.nodes.get(id)!;
|
return this.nodes.get(id)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.fetch(`${this.url}/nodes/${id}.wasm`);
|
const wasmBuffer = await this.fetchNodeWasm(id);
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to load node wasm ${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const wasmBuffer = await response.arrayBuffer();
|
return this.register(wasmBuffer);
|
||||||
|
|
||||||
const wrapper = createWasmWrapper(wasmBuffer);
|
|
||||||
|
|
||||||
const definition = wrapper.get_definition();
|
|
||||||
|
|
||||||
return {
|
|
||||||
...definition,
|
|
||||||
execute: wrapper.execute
|
|
||||||
};
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
this.nodes.set(node.id, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = performance.now() - a;
|
const duration = performance.now() - a;
|
||||||
|
|
||||||
@ -87,6 +91,31 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async register(wasmBuffer: ArrayBuffer) {
|
||||||
|
|
||||||
|
const wrapper = createWasmWrapper(wasmBuffer);
|
||||||
|
|
||||||
|
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
|
||||||
|
|
||||||
|
if (definition.error) {
|
||||||
|
console.error(definition.error);
|
||||||
|
throw definition.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cache) {
|
||||||
|
await this.cache.set(definition.data.id, wasmBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = {
|
||||||
|
...definition.data,
|
||||||
|
execute: wrapper.execute
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nodes.set(definition.data.id, node);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
getNode(id: string) {
|
getNode(id: string) {
|
||||||
return this.nodes.get(id);
|
return this.nodes.get(id);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
camera: Vector3Tuple;
|
camera: Vector3Tuple;
|
||||||
target: Vector3Tuple;
|
target: Vector3Tuple;
|
||||||
}>("nodes.camera.transform", {
|
}>("nodes.camera.transform", {
|
||||||
camera: [0, 0, 10],
|
camera: [10, 10, 10],
|
||||||
target: [0, 0, 0],
|
target: [0, 0, 0],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,20 +11,43 @@
|
|||||||
import { AppSettings } from "../settings/app-settings";
|
import { AppSettings } from "../settings/app-settings";
|
||||||
import Camera from "./Camera.svelte";
|
import Camera from "./Camera.svelte";
|
||||||
|
|
||||||
const d = useThrelte();
|
const threlte = useThrelte();
|
||||||
|
|
||||||
export const invalidate = d.invalidate;
|
export const invalidate = function () {
|
||||||
|
if (scene) {
|
||||||
|
geometries = scene.children
|
||||||
|
.filter(
|
||||||
|
(child) => "geometry" in child && child.isObject3D && child.geometry,
|
||||||
|
)
|
||||||
|
.map((child) => {
|
||||||
|
return child.geometry;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export let geometries: BufferGeometry[];
|
if (geometries && scene && centerCamera) {
|
||||||
|
const aabb = new Box3().setFromObject(scene);
|
||||||
|
center = aabb
|
||||||
|
.getCenter(new Vector3())
|
||||||
|
.max(new Vector3(-4, -4, -4))
|
||||||
|
.min(new Vector3(4, 4, 4));
|
||||||
|
}
|
||||||
|
threlte.invalidate();
|
||||||
|
};
|
||||||
|
|
||||||
|
let geometries: BufferGeometry[] = [];
|
||||||
export let lines: Vector3[][];
|
export let lines: Vector3[][];
|
||||||
export let scene;
|
export let scene: Group;
|
||||||
let geos: Group;
|
|
||||||
$: scene = geos;
|
|
||||||
export let geoGroup: Group;
|
|
||||||
|
|
||||||
export let centerCamera: boolean = true;
|
export let centerCamera: boolean = true;
|
||||||
let center = new Vector3(0, 4, 0);
|
let center = new Vector3(0, 4, 0);
|
||||||
|
|
||||||
|
$: if ($AppSettings && scene) {
|
||||||
|
scene.children.forEach((child) => {
|
||||||
|
child.material.wireframe = $AppSettings.wireframe;
|
||||||
|
threlte.invalidate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getPosition(geo: BufferGeometry, i: number) {
|
function getPosition(geo: BufferGeometry, i: number) {
|
||||||
return [
|
return [
|
||||||
geo.attributes.position.array[i],
|
geo.attributes.position.array[i],
|
||||||
@ -32,14 +55,6 @@
|
|||||||
geo.attributes.position.array[i + 2],
|
geo.attributes.position.array[i + 2],
|
||||||
] as Vector3Tuple;
|
] as Vector3Tuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (geometries && geos && centerCamera) {
|
|
||||||
const aabb = new Box3().setFromObject(geos);
|
|
||||||
center = aabb
|
|
||||||
.getCenter(new Vector3())
|
|
||||||
.max(new Vector3(-4, -4, -4))
|
|
||||||
.min(new Vector3(4, 4, 4));
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Camera {center} {centerCamera} />
|
<Camera {center} {centerCamera} />
|
||||||
@ -48,7 +63,7 @@
|
|||||||
<T.GridHelper args={[20, 20]} />
|
<T.GridHelper args={[20, 20]} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<T.Group bind:ref={geos}>
|
<T.Group>
|
||||||
{#each geometries as geo}
|
{#each geometries as geo}
|
||||||
{#if $AppSettings.showIndices}
|
{#if $AppSettings.showIndices}
|
||||||
{#each geo.attributes.position.array as _, i}
|
{#each geo.attributes.position.array as _, i}
|
||||||
@ -66,7 +81,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<T.Group bind:ref={geoGroup}></T.Group>
|
<T.Group bind:ref={scene}></T.Group>
|
||||||
</T.Group>
|
</T.Group>
|
||||||
|
|
||||||
{#if $AppSettings.showStemLines && lines}
|
{#if $AppSettings.showStemLines && lines}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import Scene from "./Scene.svelte";
|
import Scene from "./Scene.svelte";
|
||||||
import { BufferGeometry, Group, Vector3 } from "three";
|
import { Group, Vector3 } from "three";
|
||||||
|
|
||||||
import { updateGeometries } from "./updateGeometries";
|
import { updateGeometries } from "./updateGeometries";
|
||||||
import { decodeFloat, splitNestedArray } from "@nodes/utils";
|
import { decodeFloat, splitNestedArray } from "@nodes/utils";
|
||||||
@ -12,9 +12,6 @@
|
|||||||
export let perf: PerformanceStore;
|
export let perf: PerformanceStore;
|
||||||
export let scene: Group;
|
export let scene: Group;
|
||||||
|
|
||||||
let geoGroup: Group;
|
|
||||||
|
|
||||||
let geometries: BufferGeometry[] = [];
|
|
||||||
let lines: Vector3[][] = [];
|
let lines: Vector3[][] = [];
|
||||||
|
|
||||||
let invalidate: () => void;
|
let invalidate: () => void;
|
||||||
@ -53,7 +50,7 @@
|
|||||||
|
|
||||||
perf?.addPoint("update-geometries");
|
perf?.addPoint("update-geometries");
|
||||||
|
|
||||||
const { totalVertices, totalFaces } = updateGeometries(inputs, geoGroup);
|
const { totalVertices, totalFaces } = updateGeometries(inputs, scene);
|
||||||
perf?.endPoint();
|
perf?.endPoint();
|
||||||
|
|
||||||
perf?.addPoint("total-vertices", totalVertices);
|
perf?.addPoint("total-vertices", totalVertices);
|
||||||
@ -63,12 +60,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Scene
|
<Scene bind:scene bind:invalidate {lines} {centerCamera} />
|
||||||
bind:scene
|
|
||||||
bind:geoGroup
|
|
||||||
bind:invalidate
|
|
||||||
{geometries}
|
|
||||||
{lines}
|
|
||||||
{centerCamera}
|
|
||||||
/>
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import localStore from "$lib/helpers/localStore";
|
import localStore from "$lib/helpers/localStore";
|
||||||
|
|
||||||
export const AppSettings = localStore("node-settings", {
|
export const AppSettings = localStore("node.settings", {
|
||||||
theme: 0,
|
theme: 0,
|
||||||
showGrid: true,
|
showGrid: true,
|
||||||
showNodeGrid: true,
|
showNodeGrid: true,
|
||||||
|
@ -2,9 +2,12 @@ import { MemoryRuntimeExecutor, MemoryRuntimeCache } from "./runtime-executor";
|
|||||||
import { RemoteNodeRegistry } from "./node-registry-client";
|
import { RemoteNodeRegistry } from "./node-registry-client";
|
||||||
import type { Graph } from "@nodes/types";
|
import type { Graph } from "@nodes/types";
|
||||||
import { createPerformanceStore } from "./performance/store";
|
import { createPerformanceStore } from "./performance/store";
|
||||||
|
import { IndexDBCache } from "./node-registry-cache";
|
||||||
|
|
||||||
const cache = new MemoryRuntimeCache();
|
const cache = new MemoryRuntimeCache();
|
||||||
|
const indexDbCache = new IndexDBCache("node-registry");
|
||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("");
|
||||||
|
nodeRegistry.cache = indexDbCache;
|
||||||
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
|
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
|
||||||
|
|
||||||
const performanceStore = createPerformanceStore("worker");
|
const performanceStore = createPerformanceStore("worker");
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
||||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
||||||
import { setContext } from "svelte";
|
|
||||||
import ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte";
|
import ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte";
|
||||||
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
|
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
|
||||||
import Panel from "$lib/settings/Panel.svelte";
|
import Panel from "$lib/settings/Panel.svelte";
|
||||||
@ -26,12 +25,15 @@
|
|||||||
MemoryRuntimeCache,
|
MemoryRuntimeCache,
|
||||||
MemoryRuntimeExecutor,
|
MemoryRuntimeExecutor,
|
||||||
} from "$lib/runtime-executor";
|
} from "$lib/runtime-executor";
|
||||||
|
import { IndexDBCache } from "$lib/node-registry-cache";
|
||||||
import { decodeNestedArray, fastHashString } from "@nodes/utils";
|
import { decodeNestedArray, fastHashString } from "@nodes/utils";
|
||||||
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
|
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
|
||||||
|
|
||||||
let performanceStore = createPerformanceStore("page");
|
let performanceStore = createPerformanceStore("page");
|
||||||
|
|
||||||
|
const registryCache = new IndexDBCache("node-registry");
|
||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("");
|
||||||
|
nodeRegistry.cache = registryCache;
|
||||||
const workerRuntime = new WorkerRuntimeExecutor();
|
const workerRuntime = new WorkerRuntimeExecutor();
|
||||||
const runtimeCache = new MemoryRuntimeCache();
|
const runtimeCache = new MemoryRuntimeCache();
|
||||||
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
||||||
@ -39,6 +41,15 @@
|
|||||||
|
|
||||||
globalThis.decode = decodeNestedArray;
|
globalThis.decode = decodeNestedArray;
|
||||||
|
|
||||||
|
globalThis.clearCache = () => {
|
||||||
|
registryCache.clear();
|
||||||
|
runtimeCache.clear();
|
||||||
|
localStorage.clear();
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
$: runtime = $AppSettings.useWorker ? workerRuntime : memoryRuntime;
|
$: runtime = $AppSettings.useWorker ? workerRuntime : memoryRuntime;
|
||||||
|
|
||||||
let activeNode: Node | undefined;
|
let activeNode: Node | undefined;
|
||||||
@ -51,6 +62,9 @@
|
|||||||
|
|
||||||
let manager: GraphManager;
|
let manager: GraphManager;
|
||||||
let managerStatus: Writable<"loading" | "error" | "idle">;
|
let managerStatus: Writable<"loading" | "error" | "idle">;
|
||||||
|
$: if (manager) {
|
||||||
|
managerStatus = manager.status;
|
||||||
|
}
|
||||||
|
|
||||||
async function randomGenerate() {
|
async function randomGenerate() {
|
||||||
const g = manager.serialize();
|
const g = manager.serialize();
|
||||||
@ -82,6 +96,7 @@
|
|||||||
|
|
||||||
async function handleResult(_graph: Graph, _settings: Record<string, any>) {
|
async function handleResult(_graph: Graph, _settings: Record<string, any>) {
|
||||||
if (!_settings) return;
|
if (!_settings) return;
|
||||||
|
if ($managerStatus !== "idle") return;
|
||||||
const inputHash = fastHashString(
|
const inputHash = fastHashString(
|
||||||
JSON.stringify(_graph) + JSON.stringify(_settings),
|
JSON.stringify(_graph) + JSON.stringify(_settings),
|
||||||
);
|
);
|
||||||
@ -131,6 +146,10 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if ($managerStatus === "idle") {
|
||||||
|
handleResult(manager.serialize(), $graphSettings);
|
||||||
|
}
|
||||||
|
|
||||||
$: if (AppSettings) {
|
$: if (AppSettings) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nodarium_macros::include_definition_file;
|
use nodarium_macros::include_definition_file;
|
||||||
use nodarium_utils::{
|
use nodarium_utils::{
|
||||||
encode_float, evaluate_float, geometry::calculate_normals, set_panic_hook, split_args, wrap_arg,
|
encode_float, evaluate_float, geometry::calculate_normals, log, set_panic_hook, split_args,
|
||||||
|
wrap_arg,
|
||||||
};
|
};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use web_sys::console;
|
|
||||||
|
|
||||||
include_definition_file!("src/input.json");
|
include_definition_file!("src/input.json");
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
|
|
||||||
let args = split_args(input);
|
let args = split_args(input);
|
||||||
|
|
||||||
console::log_1(&format!("WASM(cube): input: {:?} -> {:?}", input, args ).into());
|
log!("WASM(cube): input: {:?} -> {:?}", input, args);
|
||||||
|
|
||||||
let size = evaluate_float(args[0]);
|
let size = evaluate_float(args[0]);
|
||||||
|
|
||||||
@ -79,6 +79,10 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
|
|
||||||
calculate_normals(&mut cube_geometry);
|
calculate_normals(&mut cube_geometry);
|
||||||
|
|
||||||
wrap_arg(&cube_geometry)
|
let res = wrap_arg(&cube_geometry);
|
||||||
|
|
||||||
|
log!("WASM(cube): output: {:?}", res);
|
||||||
|
|
||||||
|
res
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Correct midpoint length
|
// Correct midpoint length
|
||||||
mid_point *= mid_point.length() / length;
|
mid_point *= length / mid_point.length();
|
||||||
|
|
||||||
let final_end_point = start_point + mid_point;
|
let final_end_point = start_point + mid_point;
|
||||||
let offset_end_point = end_point + offset_vec;
|
let offset_end_point = end_point + offset_vec;
|
||||||
|
@ -2,7 +2,7 @@ use nodarium_macros::include_definition_file;
|
|||||||
use nodarium_utils::{
|
use nodarium_utils::{
|
||||||
concat_args, evaluate_int,
|
concat_args, evaluate_int,
|
||||||
geometry::{extrude_path, wrap_path},
|
geometry::{extrude_path, wrap_path},
|
||||||
log, split_args,
|
log, set_panic_hook, split_args,
|
||||||
};
|
};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
@ -10,16 +10,17 @@ include_definition_file!("src/inputs.json");
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn execute(input: &[i32]) -> Vec<i32> {
|
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||||
utils::set_panic_hook();
|
set_panic_hook();
|
||||||
|
|
||||||
let args = split_args(input);
|
let args = split_args(input);
|
||||||
|
|
||||||
|
log!("WASM(output) args: {:?}", args);
|
||||||
|
|
||||||
assert_eq!(args.len(), 2, "Expected 2 arguments, got {}", args.len());
|
assert_eq!(args.len(), 2, "Expected 2 arguments, got {}", args.len());
|
||||||
let inputs = split_args(args[0]);
|
let inputs = split_args(args[0]);
|
||||||
|
|
||||||
let resolution = evaluate_int(args[1]) as usize;
|
let resolution = evaluate_int(args[1]) as usize;
|
||||||
|
|
||||||
log!("output inputs: {:?}", inputs);
|
|
||||||
log!("inputs: {}, resolution: {}", inputs.len(), resolution);
|
log!("inputs: {}, resolution: {}", inputs.len(), resolution);
|
||||||
|
|
||||||
let mut output: Vec<Vec<i32>> = Vec::new();
|
let mut output: Vec<Vec<i32>> = Vec::new();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Graph, NodeDefinition, NodeId } from "./types";
|
import { Graph, NodeDefinition, NodeId } from "./types";
|
||||||
|
|
||||||
export interface NodeRegistry {
|
export interface NodeRegistry {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The status of the node registry
|
* The status of the node registry
|
||||||
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
||||||
@ -25,6 +27,16 @@ export interface NodeRegistry {
|
|||||||
* @returns An array of all nodes
|
* @returns An array of all nodes
|
||||||
*/
|
*/
|
||||||
getAllNodes: () => NodeDefinition[];
|
getAllNodes: () => NodeDefinition[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new node
|
||||||
|
* @param wasmBuffer - The WebAssembly buffer for the node
|
||||||
|
* @returns The node definition
|
||||||
|
*/
|
||||||
|
register: (wasmBuffer: ArrayBuffer) => Promise<NodeDefinition>;
|
||||||
|
|
||||||
|
|
||||||
|
cache?: RuntimeCache<ArrayBuffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeExecutor {
|
export interface RuntimeExecutor {
|
||||||
@ -49,13 +61,13 @@ export interface RuntimeCache<T = unknown> {
|
|||||||
* @param key - The key to get the value for
|
* @param key - The key to get the value for
|
||||||
* @returns The value for the given key, or undefined if no such value exists
|
* @returns The value for the given key, or undefined if no such value exists
|
||||||
*/
|
*/
|
||||||
get: (key: string) => T | undefined;
|
get: (key: string) => T | Promise<T> | undefined;
|
||||||
/**
|
/**
|
||||||
* Set the value for the given key
|
* Set the value for the given key
|
||||||
* @param key - The key to set the value for
|
* @param key - The key to set the value for
|
||||||
* @param value - The value to set
|
* @param value - The value to set
|
||||||
*/
|
*/
|
||||||
set: (key: string, value: T) => void;
|
set: (key: string, value: T) => void | Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Clear the cache
|
* Clear the cache
|
||||||
*/
|
*/
|
||||||
|
12
packages/ui/histoire.config.ts
Normal file
12
packages/ui/histoire.config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from 'histoire'
|
||||||
|
import { HstSvelte } from '@histoire/plugin-svelte'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
setupFile: '/src/histoire.setup.ts',
|
||||||
|
storyMatch: [
|
||||||
|
'./src/lib/**/*.story.svelte',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
HstSvelte(),
|
||||||
|
],
|
||||||
|
})
|
@ -9,8 +9,11 @@
|
|||||||
"prepublishOnly": "npm run package",
|
"prepublishOnly": "npm run package",
|
||||||
"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",
|
||||||
"lint": "prettier --check . && eslint .",
|
"test": "vitest",
|
||||||
"format": "prettier --write ."
|
"lint": "eslint .",
|
||||||
|
"story:dev": "histoire dev",
|
||||||
|
"story:build": "histoire build",
|
||||||
|
"story:preview": "histoire preview"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
@ -24,35 +27,36 @@
|
|||||||
"!dist/**/*.test.*",
|
"!dist/**/*.test.*",
|
||||||
"!dist/**/*.spec.*"
|
"!dist/**/*.spec.*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
|
||||||
"@nodes/types": "link:../types"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"svelte": "^5.0.0-next.1"
|
"svelte": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@histoire/plugin-svelte": "^0.17.17",
|
||||||
|
"@sveltejs/adapter-auto": "^3.2.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.5.7",
|
||||||
"@sveltejs/package": "^2.0.0",
|
"@sveltejs/package": "^2.3.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||||
"@types/eslint": "^8.56.0",
|
"@types/eslint": "^8.56.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.7.1",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^7.7.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^9.1.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-plugin-svelte": "^2.38.0",
|
||||||
"eslint-plugin-svelte": "^2.36.0-next.4",
|
"histoire": "^0.17.17",
|
||||||
"prettier": "^3.1.1",
|
"publint": "^0.2.7",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"svelte": "^4.2.15",
|
||||||
"publint": "^0.1.9",
|
"svelte-check": "^3.7.0",
|
||||||
"svelte": "^5.0.0-next.1",
|
"tslib": "^2.6.2",
|
||||||
"svelte-check": "^3.6.0",
|
"typescript": "^5.4.5",
|
||||||
"tslib": "^2.4.1",
|
"vite": "^5.2.10",
|
||||||
"typescript": "^5.0.0",
|
"vitest": "^1.5.2"
|
||||||
"vite": "^5.0.11",
|
|
||||||
"vitest": "^1.5.3"
|
|
||||||
},
|
},
|
||||||
"svelte": "./dist/index.js",
|
"svelte": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@nodes/types": "link:../types",
|
||||||
|
"@threlte/core": "^7.3.0",
|
||||||
|
"@threlte/extras": "^8.11.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,6 @@
|
|||||||
export let input: NodeInput;
|
export let input: NodeInput;
|
||||||
export let value: any;
|
export let value: any;
|
||||||
export let id: string;
|
export let id: string;
|
||||||
|
|
||||||
$: if (value === undefined || value === null) {
|
|
||||||
switch (input.type) {
|
|
||||||
case 'float':
|
|
||||||
value = 0;
|
|
||||||
case 'integer':
|
|
||||||
value = 0;
|
|
||||||
case 'boolean':
|
|
||||||
value = false;
|
|
||||||
case 'select':
|
|
||||||
value = 0;
|
|
||||||
case 'vec3':
|
|
||||||
value = [0, 0, 0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if input.type === 'float'}
|
{#if input.type === 'float'}
|
||||||
@ -34,7 +19,8 @@
|
|||||||
{:else if input.type === 'boolean'}
|
{:else if input.type === 'boolean'}
|
||||||
<Checkbox {id} bind:value />
|
<Checkbox {id} bind:value />
|
||||||
{:else if input.type === 'select'}
|
{:else if input.type === 'select'}
|
||||||
<Select {id} bind:value options={input?.options || []} />
|
<Select {id} bind:value options={input.options} />
|
||||||
{:else if input.type === 'vec3'}
|
{:else if input.type === 'vec3'}
|
||||||
<Vec3 {id} bind:value />
|
<Vec3 {id} bind:value />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -94,3 +94,4 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
17
packages/ui/src/lib/elements/Float.story.svelte
Normal file
17
packages/ui/src/lib/elements/Float.story.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Hst } from '@histoire/plugin-svelte';
|
||||||
|
export let Hst: Hst;
|
||||||
|
import Float from './Float.svelte';
|
||||||
|
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
||||||
|
import StorySettings from '$lib/helpers/StorySettings.svelte';
|
||||||
|
let theme = 'dark';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hst.Story>
|
||||||
|
<StoryContent {theme}>
|
||||||
|
<Float value={0} min={0} max={6.9} />
|
||||||
|
</StoryContent>
|
||||||
|
<svelte:fragment slot="controls">
|
||||||
|
<StorySettings bind:theme />
|
||||||
|
</svelte:fragment>
|
||||||
|
</Hst.Story>
|
@ -1,19 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let {
|
export let value = 0.5;
|
||||||
onchange,
|
export let step = 0.01;
|
||||||
value = $bindable(0),
|
export let min = 0;
|
||||||
id,
|
export let max = 1;
|
||||||
step = 0.01,
|
export let id = '';
|
||||||
min = 0,
|
|
||||||
max = 1
|
|
||||||
}: {
|
|
||||||
onchange?: (num: number) => void;
|
|
||||||
value?: number;
|
|
||||||
id?: string;
|
|
||||||
step?: number;
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
if (min > max) {
|
if (min > max) {
|
||||||
[min, max] = [max, min];
|
[min, max] = [max, min];
|
||||||
@ -28,28 +18,21 @@
|
|||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl: HTMLInputElement;
|
||||||
|
|
||||||
$effect(() => {
|
$: if ((value || 0).toString().length > 5) {
|
||||||
if ((value || 0).toString().length > 5) {
|
value = strip(value || 0);
|
||||||
value = strip(value || 0);
|
}
|
||||||
}
|
$: value !== undefined && handleChange();
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (value !== undefined) handleChange();
|
|
||||||
});
|
|
||||||
|
|
||||||
let oldValue: number;
|
let oldValue: number;
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
if (value === oldValue) return;
|
if (value === oldValue) return;
|
||||||
oldValue = value;
|
oldValue = value;
|
||||||
onchange?.(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let width = $derived(
|
$: width = Number.isFinite(value)
|
||||||
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px' : '20px'
|
? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px'
|
||||||
);
|
: '20px';
|
||||||
|
|
||||||
let isMouseDown = $state(false);
|
let isMouseDown = false;
|
||||||
let downV = 0;
|
let downV = 0;
|
||||||
let vx = 0;
|
let vx = 0;
|
||||||
let rect: DOMRect;
|
let rect: DOMRect;
|
||||||
@ -57,6 +40,8 @@
|
|||||||
function handleMouseDown(ev: MouseEvent) {
|
function handleMouseDown(ev: MouseEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
inputEl.focus();
|
||||||
|
|
||||||
isMouseDown = true;
|
isMouseDown = true;
|
||||||
|
|
||||||
downV = value;
|
downV = value;
|
||||||
@ -97,7 +82,6 @@
|
|||||||
|
|
||||||
function handleMouseMove(ev: MouseEvent) {
|
function handleMouseMove(ev: MouseEvent) {
|
||||||
vx = (ev.clientX - rect.left) / rect.width;
|
vx = (ev.clientX - rect.left) / rect.width;
|
||||||
/* vy = ev.clientY - downY; */
|
|
||||||
|
|
||||||
if (ev.ctrlKey) {
|
if (ev.ctrlKey) {
|
||||||
let v = min + (max - min) * vx;
|
let v = min + (max - min) * vx;
|
||||||
@ -117,9 +101,9 @@
|
|||||||
{step}
|
{step}
|
||||||
{max}
|
{max}
|
||||||
{min}
|
{min}
|
||||||
onkeydown={handleKeyDown}
|
on:keydown={handleKeyDown}
|
||||||
onmousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
onmouseup={handleMouseUp}
|
on:mouseup={handleMouseUp}
|
||||||
type="number"
|
type="number"
|
||||||
style={`width:${width};`}
|
style={`width:${width};`}
|
||||||
/>
|
/>
|
||||||
|
18
packages/ui/src/lib/elements/Integer.story.svelte
Normal file
18
packages/ui/src/lib/elements/Integer.story.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Hst } from '@histoire/plugin-svelte';
|
||||||
|
export let Hst: Hst;
|
||||||
|
import Integer from './Integer.svelte';
|
||||||
|
import StorySettings from '../helpers/StorySettings.svelte';
|
||||||
|
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
||||||
|
|
||||||
|
let theme = 'dark';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hst.Story>
|
||||||
|
<StoryContent {theme}>
|
||||||
|
<Integer value={5} min={0} max={42} />
|
||||||
|
</StoryContent>
|
||||||
|
<svelte:fragment slot="controls">
|
||||||
|
<StorySettings bind:theme />
|
||||||
|
</svelte:fragment>
|
||||||
|
</Hst.Story>
|
@ -1,19 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let {
|
import { createEventDispatcher } from 'svelte';
|
||||||
min = 0,
|
const dispatch = createEventDispatcher();
|
||||||
max = 10,
|
|
||||||
step = 1,
|
// Styling
|
||||||
value = $bindable(0),
|
export let min: number | undefined = undefined;
|
||||||
id,
|
export let max: number | undefined = undefined;
|
||||||
onchange
|
export let step = 1;
|
||||||
}: {
|
export let value = 0;
|
||||||
min?: number;
|
export let id = '';
|
||||||
max?: number;
|
|
||||||
step?: number;
|
|
||||||
value?: number;
|
|
||||||
id?: string;
|
|
||||||
onchange?: (num: number) => void;
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = 0;
|
value = 0;
|
||||||
@ -21,22 +15,18 @@
|
|||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl: HTMLInputElement;
|
||||||
let wrapper: HTMLDivElement;
|
let wrapper: HTMLDivElement;
|
||||||
$effect(() => {
|
$: value !== undefined && update();
|
||||||
if (value !== undefined) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let prev = -1;
|
let prev = -1;
|
||||||
function update() {
|
function update() {
|
||||||
if (prev === value) return;
|
if (prev === value) return;
|
||||||
prev = value;
|
prev = value;
|
||||||
onchange?.(value);
|
dispatch('change', parseFloat(value + ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
let width = $derived(
|
$: width = Number.isFinite(value)
|
||||||
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px' : '20px'
|
? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px'
|
||||||
);
|
: '20px';
|
||||||
|
|
||||||
function handleChange(change: number) {
|
function handleChange(change: number) {
|
||||||
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
|
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
|
||||||
@ -88,14 +78,13 @@
|
|||||||
role="slider"
|
role="slider"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-valuenow={value}
|
aria-valuenow={value}
|
||||||
onmousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
onmouseup={handleMouseUp}
|
on:mouseup={handleMouseUp}
|
||||||
>
|
>
|
||||||
{#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>
|
|
||||||
{/if}
|
{/if}
|
||||||
<button onclick={() => handleChange(-step)}>-</button>
|
<button on:click={() => handleChange(-step)}>-</button>
|
||||||
<input
|
<input
|
||||||
bind:value
|
bind:value
|
||||||
bind:this={inputEl}
|
bind:this={inputEl}
|
||||||
@ -107,7 +96,7 @@
|
|||||||
style={`width:${width};`}
|
style={`width:${width};`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button onclick={() => handleChange(+step)}>+</button>
|
<button on:click={() => handleChange(+step)}>+</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -170,3 +159,4 @@
|
|||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
18
packages/ui/src/lib/elements/Select.story.svelte
Normal file
18
packages/ui/src/lib/elements/Select.story.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Hst } from '@histoire/plugin-svelte';
|
||||||
|
export let Hst: Hst;
|
||||||
|
import Select from './Select.svelte';
|
||||||
|
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
||||||
|
import StorySettings from '$lib/helpers/StorySettings.svelte';
|
||||||
|
|
||||||
|
let theme = 'dark';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hst.Story>
|
||||||
|
<StoryContent {theme}>
|
||||||
|
<Select id="" options={['strawberry', 'apple', 'banana']} />
|
||||||
|
</StoryContent>
|
||||||
|
<svelte:fragment slot="controls">
|
||||||
|
<StorySettings bind:theme />
|
||||||
|
</svelte:fragment>
|
||||||
|
</Hst.Story>
|
@ -1,9 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let {
|
export let options: string[] = [];
|
||||||
id,
|
export let value: number = 0;
|
||||||
value = $bindable(0),
|
export let id = '';
|
||||||
options
|
|
||||||
}: { id: string; value: number; options: string[] } = $props();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<select {id} bind:value>
|
<select {id} bind:value>
|
||||||
@ -23,3 +21,4 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
18
packages/ui/src/lib/elements/Vec3.story.svelte
Normal file
18
packages/ui/src/lib/elements/Vec3.story.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Hst } from '@histoire/plugin-svelte';
|
||||||
|
export let Hst: Hst;
|
||||||
|
import Vec3 from './Vec3.svelte';
|
||||||
|
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
||||||
|
import StorySettings from '$lib/helpers/StorySettings.svelte';
|
||||||
|
|
||||||
|
let theme = 'dark';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hst.Story>
|
||||||
|
<StoryContent {theme}>
|
||||||
|
<Vec3 value={[0.2, 0.4, 0.6]} />
|
||||||
|
</StoryContent>
|
||||||
|
<svelte:fragment slot="controls">
|
||||||
|
<StorySettings bind:theme />
|
||||||
|
</svelte:fragment>
|
||||||
|
</Hst.Story>
|
@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Float from './Float.svelte';
|
import Float from './Float.svelte';
|
||||||
|
|
||||||
let { value = $bindable([0, 0, 0]), id }: { value: number[]; id: string } = $props();
|
export let value = [0, 0, 0];
|
||||||
|
export let id = '';
|
||||||
|
$: console.log(value);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
24
packages/ui/src/lib/helpers/StoryContent.svelte
Normal file
24
packages/ui/src/lib/helpers/StoryContent.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let theme = 'dark';
|
||||||
|
|
||||||
|
$: if (theme !== undefined) {
|
||||||
|
const classes = document.body.classList;
|
||||||
|
const newClassName = `theme-${theme}`;
|
||||||
|
for (const className of classes) {
|
||||||
|
if (className.startsWith('theme-') && className !== newClassName) {
|
||||||
|
classes.remove(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.classList.add(newClassName);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
23
packages/ui/src/lib/helpers/StorySettings.svelte
Normal file
23
packages/ui/src/lib/helpers/StorySettings.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Select } from '$lib/index.js';
|
||||||
|
|
||||||
|
const themes = ['dark', 'light', 'catppuccin', 'solarized', 'high-contrast', 'nord', 'dracula'];
|
||||||
|
|
||||||
|
let value = 0;
|
||||||
|
export let theme = themes[value];
|
||||||
|
$: theme = themes[value];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="theme-select"> Select Theme </label>
|
||||||
|
<Select id="" bind:value options={themes} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -4,9 +4,9 @@
|
|||||||
import Integer from '$lib/elements/Integer.svelte';
|
import Integer from '$lib/elements/Integer.svelte';
|
||||||
import Vec3 from '$lib/elements/Vec3.svelte';
|
import Vec3 from '$lib/elements/Vec3.svelte';
|
||||||
|
|
||||||
let intValue = $state(0);
|
let intValue = 0;
|
||||||
let floatValue = $state(0.2);
|
let floatValue = 0.2;
|
||||||
let vecValue = $state([0.2, 0.3, 0.4]);
|
let vecValue = [0.2, 0.3, 0.4];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
1491
pnpm-lock.yaml
1491
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user