fix: gravity node
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m35s

This commit is contained in:
max_richter 2024-05-02 18:49:08 +02:00
parent dca4469f55
commit 26d3f6a2f1
31 changed files with 1557 additions and 536 deletions

View File

@ -13,12 +13,13 @@
"@nodes/ui": "link:../packages/ui",
"@nodes/utils": "link:../packages/utils",
"@sveltejs/kit": "^2.5.7",
"@threlte/core": "next",
"@threlte/extras": "next",
"@threlte/core": "^7.3.0",
"@threlte/extras": "^8.11.2",
"@types/three": "^0.164.0",
"@unocss/reset": "^0.59.4",
"comlink": "^4.4.1",
"file-saver": "^2.0.5",
"idb": "^8.0.0",
"jsondiffpatch": "^0.6.0",
"three": "^0.164.1"
},
@ -26,11 +27,11 @@
"@iconify-json/tabler": "^1.1.110",
"@nodes/types": "link:../packages/types",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/vite-plugin-svelte": "next",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@tsconfig/svelte": "^5.0.4",
"@types/file-saver": "^2.0.7",
"@unocss/preset-icons": "^0.59.4",
"svelte": "5.0.0-next.118",
"svelte": "^4.2.15",
"svelte-check": "^3.7.0",
"tslib": "^2.6.2",
"typescript": "^5.4.5",

View File

@ -407,7 +407,10 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
// check if socket types match
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)) {
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;
if (!inputs) continue;
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]);
}
}

View File

@ -12,7 +12,7 @@
import Camera from "../Camera.svelte";
import GraphView from "./GraphView.svelte";
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 {
activeNodeId,
@ -25,7 +25,6 @@
import { createKeyMap } from "../../helpers/createKeyMap";
import BoxSelection from "../BoxSelection.svelte";
import AddMenu from "../AddMenu.svelte";
import { createWasmWrapper } from "@nodes/utils";
import HelpView from "../HelpView.svelte";
import FileSaver from "file-saver";
@ -819,11 +818,10 @@
isDragging = false;
if (!event.dataTransfer) return;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
let mx = event.clientX - rect.x;
let my = event.clientY - rect.y;
if (nodeId) {
let mx = event.clientX - rect.x;
let my = event.clientY - rect.y;
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
let nodeOffsetY = event.dataTransfer.getData("data/node-offset-y");
if (nodeOffsetX && nodeOffsetY) {
@ -852,13 +850,16 @@
if (file.type === "application/wasm") {
const reader = new FileReader();
reader.onload = (e) => {
const buffer = e.target?.result as Buffer;
if (buffer) {
const wrapper = createWasmWrapper(buffer);
const definition = wrapper.get_definition();
const res = NodeDefinitionSchema.parse(definition);
console.log(res);
reader.onload = async (e) => {
const buffer = e.target?.result;
if (buffer?.constructor === ArrayBuffer) {
const nodeType = await manager.registry.register(buffer);
manager.createNode({
type: nodeType.id,
props: {},
position: projectScreenToWorld(mx, my),
});
}
};
reader.readAsArrayBuffer(file);

View 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'));
}
}

View File

@ -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 { createLogger } from "./helpers";
@ -10,6 +10,8 @@ export class RemoteNodeRegistry implements NodeRegistry {
status: "loading" | "ready" | "error" = "loading";
private nodes: Map<string, NodeDefinition> = new Map();
cache?: RuntimeCache<ArrayBuffer>;
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
constructor(private url: string) { }
@ -46,6 +48,22 @@ export class RemoteNodeRegistry implements NodeRegistry {
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}`[]) {
const a = performance.now();
@ -55,26 +73,12 @@ export class RemoteNodeRegistry implements NodeRegistry {
return this.nodes.get(id)!;
}
const response = await this.fetch(`${this.url}/nodes/${id}.wasm`);
if (!response.ok) {
throw new Error(`Failed to load node wasm ${id}`);
}
const wasmBuffer = await this.fetchNodeWasm(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;
@ -87,6 +91,31 @@ export class RemoteNodeRegistry implements NodeRegistry {
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) {
return this.nodes.get(id);
}

View File

@ -17,7 +17,7 @@
camera: Vector3Tuple;
target: Vector3Tuple;
}>("nodes.camera.transform", {
camera: [0, 0, 10],
camera: [10, 10, 10],
target: [0, 0, 0],
});

View File

@ -11,20 +11,43 @@
import { AppSettings } from "../settings/app-settings";
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 scene;
let geos: Group;
$: scene = geos;
export let geoGroup: Group;
export let scene: Group;
export let centerCamera: boolean = true;
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) {
return [
geo.attributes.position.array[i],
@ -32,14 +55,6 @@
geo.attributes.position.array[i + 2],
] 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>
<Camera {center} {centerCamera} />
@ -48,7 +63,7 @@
<T.GridHelper args={[20, 20]} />
{/if}
<T.Group bind:ref={geos}>
<T.Group>
{#each geometries as geo}
{#if $AppSettings.showIndices}
{#each geo.attributes.position.array as _, i}
@ -66,7 +81,7 @@
{/if}
{/each}
<T.Group bind:ref={geoGroup}></T.Group>
<T.Group bind:ref={scene}></T.Group>
</T.Group>
{#if $AppSettings.showStemLines && lines}

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Canvas } from "@threlte/core";
import Scene from "./Scene.svelte";
import { BufferGeometry, Group, Vector3 } from "three";
import { Group, Vector3 } from "three";
import { updateGeometries } from "./updateGeometries";
import { decodeFloat, splitNestedArray } from "@nodes/utils";
@ -12,9 +12,6 @@
export let perf: PerformanceStore;
export let scene: Group;
let geoGroup: Group;
let geometries: BufferGeometry[] = [];
let lines: Vector3[][] = [];
let invalidate: () => void;
@ -53,7 +50,7 @@
perf?.addPoint("update-geometries");
const { totalVertices, totalFaces } = updateGeometries(inputs, geoGroup);
const { totalVertices, totalFaces } = updateGeometries(inputs, scene);
perf?.endPoint();
perf?.addPoint("total-vertices", totalVertices);
@ -63,12 +60,5 @@
</script>
<Canvas>
<Scene
bind:scene
bind:geoGroup
bind:invalidate
{geometries}
{lines}
{centerCamera}
/>
<Scene bind:scene bind:invalidate {lines} {centerCamera} />
</Canvas>

View File

@ -1,6 +1,6 @@
import localStore from "$lib/helpers/localStore";
export const AppSettings = localStore("node-settings", {
export const AppSettings = localStore("node.settings", {
theme: 0,
showGrid: true,
showNodeGrid: true,

View File

@ -2,9 +2,12 @@ import { MemoryRuntimeExecutor, MemoryRuntimeCache } from "./runtime-executor";
import { RemoteNodeRegistry } from "./node-registry-client";
import type { Graph } from "@nodes/types";
import { createPerformanceStore } from "./performance/store";
import { IndexDBCache } from "./node-registry-cache";
const cache = new MemoryRuntimeCache();
const indexDbCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry("");
nodeRegistry.cache = indexDbCache;
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
const performanceStore = createPerformanceStore("worker");

View File

@ -13,7 +13,6 @@
import { createKeyMap } from "$lib/helpers/createKeyMap";
import NodeStore from "$lib/node-store/NodeStore.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager";
import { setContext } from "svelte";
import ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte";
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
import Panel from "$lib/settings/Panel.svelte";
@ -26,12 +25,15 @@
MemoryRuntimeCache,
MemoryRuntimeExecutor,
} from "$lib/runtime-executor";
import { IndexDBCache } from "$lib/node-registry-cache";
import { decodeNestedArray, fastHashString } from "@nodes/utils";
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
let performanceStore = createPerformanceStore("page");
const registryCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry("");
nodeRegistry.cache = registryCache;
const workerRuntime = new WorkerRuntimeExecutor();
const runtimeCache = new MemoryRuntimeCache();
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
@ -39,6 +41,15 @@
globalThis.decode = decodeNestedArray;
globalThis.clearCache = () => {
registryCache.clear();
runtimeCache.clear();
localStorage.clear();
setTimeout(() => {
window.location.reload();
}, 500);
};
$: runtime = $AppSettings.useWorker ? workerRuntime : memoryRuntime;
let activeNode: Node | undefined;
@ -51,6 +62,9 @@
let manager: GraphManager;
let managerStatus: Writable<"loading" | "error" | "idle">;
$: if (manager) {
managerStatus = manager.status;
}
async function randomGenerate() {
const g = manager.serialize();
@ -82,6 +96,7 @@
async function handleResult(_graph: Graph, _settings: Record<string, any>) {
if (!_settings) return;
if ($managerStatus !== "idle") return;
const inputHash = fastHashString(
JSON.stringify(_graph) + JSON.stringify(_settings),
);
@ -131,6 +146,10 @@
return true;
}
$: if ($managerStatus === "idle") {
handleResult(manager.serialize(), $graphSettings);
}
$: if (AppSettings) {
//@ts-ignore
AppSettingTypes.debug.stressTest.loadGrid.callback = () => {

View File

@ -1,9 +1,9 @@
use nodarium_macros::include_definition_file;
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 web_sys::console;
include_definition_file!("src/input.json");
@ -15,7 +15,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
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]);
@ -79,6 +79,10 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
calculate_normals(&mut cube_geometry);
wrap_arg(&cube_geometry)
let res = wrap_arg(&cube_geometry);
log!("WASM(cube): output: {:?}", res);
res
}

View File

@ -80,7 +80,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
}
// Correct midpoint length
mid_point *= mid_point.length() / length;
mid_point *= length / mid_point.length();
let final_end_point = start_point + mid_point;
let offset_end_point = end_point + offset_vec;

View File

@ -2,7 +2,7 @@ use nodarium_macros::include_definition_file;
use nodarium_utils::{
concat_args, evaluate_int,
geometry::{extrude_path, wrap_path},
log, split_args,
log, set_panic_hook, split_args,
};
use wasm_bindgen::prelude::*;
@ -10,16 +10,17 @@ include_definition_file!("src/inputs.json");
#[wasm_bindgen]
pub fn execute(input: &[i32]) -> Vec<i32> {
utils::set_panic_hook();
set_panic_hook();
let args = split_args(input);
log!("WASM(output) args: {:?}", args);
assert_eq!(args.len(), 2, "Expected 2 arguments, got {}", args.len());
let inputs = split_args(args[0]);
let resolution = evaluate_int(args[1]) as usize;
log!("output inputs: {:?}", inputs);
log!("inputs: {}, resolution: {}", inputs.len(), resolution);
let mut output: Vec<Vec<i32>> = Vec::new();

View File

@ -1,6 +1,8 @@
import { Graph, NodeDefinition, NodeId } from "./types";
export interface NodeRegistry {
/**
* 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
@ -25,6 +27,16 @@ export interface NodeRegistry {
* @returns An array of all nodes
*/
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 {
@ -49,13 +61,13 @@ export interface RuntimeCache<T = unknown> {
* @param key - The key to get the value for
* @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
* @param key - The key to set the value for
* @param value - The value to set
*/
set: (key: string, value: T) => void;
set: (key: string, value: T) => void | Promise<void>;
/**
* Clear the cache
*/

View 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(),
],
})

View File

@ -9,8 +9,11 @@
"prepublishOnly": "npm run package",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
"test": "vitest",
"lint": "eslint .",
"story:dev": "histoire dev",
"story:build": "histoire build",
"story:preview": "histoire preview"
},
"exports": {
".": {
@ -24,35 +27,36 @@
"!dist/**/*.test.*",
"!dist/**/*.spec.*"
],
"dependencies": {
"@nodes/types": "link:../types"
},
"peerDependencies": {
"svelte": "^5.0.0-next.1"
"svelte": "^4.0.0"
},
"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/kit": "^2.0.0",
"@sveltejs/package": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^8.56.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0-next.4",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"publint": "^0.1.9",
"svelte": "^5.0.0-next.1",
"svelte-check": "^3.6.0",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.11",
"vitest": "^1.5.3"
"@sveltejs/kit": "^2.5.7",
"@sveltejs/package": "^2.3.1",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@types/eslint": "^8.56.10",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"eslint": "^9.1.1",
"eslint-plugin-svelte": "^2.38.0",
"histoire": "^0.17.17",
"publint": "^0.2.7",
"svelte": "^4.2.15",
"svelte-check": "^3.7.0",
"tslib": "^2.6.2",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vitest": "^1.5.2"
},
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module"
"type": "module",
"dependencies": {
"@nodes/types": "link:../types",
"@threlte/core": "^7.3.0",
"@threlte/extras": "^8.11.2"
}
}

View File

@ -10,21 +10,6 @@
export let input: NodeInput;
export let value: any;
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>
{#if input.type === 'float'}
@ -34,7 +19,8 @@
{:else if input.type === 'boolean'}
<Checkbox {id} bind:value />
{:else if input.type === 'select'}
<Select {id} bind:value options={input?.options || []} />
<Select {id} bind:value options={input.options} />
{:else if input.type === 'vec3'}
<Vec3 {id} bind:value />
{/if}

View File

@ -94,3 +94,4 @@
display: block;
}
</style>

View 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>

View File

@ -1,19 +1,9 @@
<script lang="ts">
let {
onchange,
value = $bindable(0),
id,
step = 0.01,
min = 0,
max = 1
}: {
onchange?: (num: number) => void;
value?: number;
id?: string;
step?: number;
min?: number;
max?: number;
} = $props();
export let value = 0.5;
export let step = 0.01;
export let min = 0;
export let max = 1;
export let id = '';
if (min > max) {
[min, max] = [max, min];
@ -28,28 +18,21 @@
let inputEl: HTMLInputElement;
$effect(() => {
if ((value || 0).toString().length > 5) {
value = strip(value || 0);
}
});
$effect(() => {
if (value !== undefined) handleChange();
});
$: if ((value || 0).toString().length > 5) {
value = strip(value || 0);
}
$: value !== undefined && handleChange();
let oldValue: number;
function handleChange() {
if (value === oldValue) return;
oldValue = value;
onchange?.(value);
}
let width = $derived(
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px' : '20px'
);
$: width = Number.isFinite(value)
? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px'
: '20px';
let isMouseDown = $state(false);
let isMouseDown = false;
let downV = 0;
let vx = 0;
let rect: DOMRect;
@ -57,6 +40,8 @@
function handleMouseDown(ev: MouseEvent) {
ev.preventDefault();
inputEl.focus();
isMouseDown = true;
downV = value;
@ -97,7 +82,6 @@
function handleMouseMove(ev: MouseEvent) {
vx = (ev.clientX - rect.left) / rect.width;
/* vy = ev.clientY - downY; */
if (ev.ctrlKey) {
let v = min + (max - min) * vx;
@ -117,9 +101,9 @@
{step}
{max}
{min}
onkeydown={handleKeyDown}
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
on:keydown={handleKeyDown}
on:mousedown={handleMouseDown}
on:mouseup={handleMouseUp}
type="number"
style={`width:${width};`}
/>

View 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>

View File

@ -1,19 +1,13 @@
<script lang="ts">
let {
min = 0,
max = 10,
step = 1,
value = $bindable(0),
id,
onchange
}: {
min?: number;
max?: number;
step?: number;
value?: number;
id?: string;
onchange?: (num: number) => void;
} = $props();
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
// Styling
export let min: number | undefined = undefined;
export let max: number | undefined = undefined;
export let step = 1;
export let value = 0;
export let id = '';
if (!value) {
value = 0;
@ -21,22 +15,18 @@
let inputEl: HTMLInputElement;
let wrapper: HTMLDivElement;
$effect(() => {
if (value !== undefined) {
update();
}
});
$: value !== undefined && update();
let prev = -1;
function update() {
if (prev === value) return;
prev = value;
onchange?.(value);
dispatch('change', parseFloat(value + ''));
}
let width = $derived(
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px' : '20px'
);
$: width = Number.isFinite(value)
? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px'
: '20px';
function handleChange(change: number) {
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
@ -88,14 +78,13 @@
role="slider"
tabindex="0"
aria-valuenow={value}
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
on:mousedown={handleMouseDown}
on:mouseup={handleMouseUp}
>
{#if typeof min !== 'undefined' && typeof max !== 'undefined'}
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
></span>
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`} />
{/if}
<button onclick={() => handleChange(-step)}>-</button>
<button on:click={() => handleChange(-step)}>-</button>
<input
bind:value
bind:this={inputEl}
@ -107,7 +96,7 @@
style={`width:${width};`}
/>
<button onclick={() => handleChange(+step)}>+</button>
<button on:click={() => handleChange(+step)}>+</button>
</div>
<style>
@ -170,3 +159,4 @@
border-style: none;
}
</style>

View 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>

View File

@ -1,9 +1,7 @@
<script lang="ts">
let {
id,
value = $bindable(0),
options
}: { id: string; value: number; options: string[] } = $props();
export let options: string[] = [];
export let value: number = 0;
export let id = '';
</script>
<select {id} bind:value>
@ -23,3 +21,4 @@
border: none;
}
</style>

View 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>

View File

@ -1,7 +1,9 @@
<script lang="ts">
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>
<div>

View 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>

View 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>

View File

@ -4,9 +4,9 @@
import Integer from '$lib/elements/Integer.svelte';
import Vec3 from '$lib/elements/Vec3.svelte';
let intValue = $state(0);
let floatValue = $state(0.2);
let vecValue = $state([0.2, 0.3, 0.4]);
let intValue = 0;
let floatValue = 0.2;
let vecValue = [0.2, 0.3, 0.4];
</script>
<main>

File diff suppressed because it is too large Load Diff