feat: add active node settings
This commit is contained in:
		| @@ -5,6 +5,7 @@ import EventEmitter from "./helpers/EventEmitter.js"; | ||||
| import throttle from "./helpers/throttle.js"; | ||||
| import { createLogger } from "./helpers/index.js"; | ||||
| import type { NodeInput } from "@nodes/types"; | ||||
| import { fastHashString } from "@nodes/utils"; | ||||
|  | ||||
| const logger = createLogger("graph-manager"); | ||||
|  | ||||
| @@ -70,7 +71,13 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, " | ||||
|   } | ||||
|  | ||||
|  | ||||
|   private lastSettingsHash = 0; | ||||
|   setSettings(settings: Record<string, unknown>) { | ||||
|  | ||||
|     let hash = fastHashString(JSON.stringify(settings)); | ||||
|     if (hash === this.lastSettingsHash) return; | ||||
|     this.lastSettingsHash = hash; | ||||
|  | ||||
|     this.settings = settings; | ||||
|     this.save(); | ||||
|     this.execute(); | ||||
|   | ||||
| @@ -39,10 +39,11 @@ | ||||
|   const edges = graph.edges; | ||||
|  | ||||
|   let wrapper: HTMLDivElement; | ||||
|   let rect: DOMRect; | ||||
|   $: rect = | ||||
|     wrapper && width | ||||
|       ? wrapper.getBoundingClientRect() | ||||
|       : { x: 0, y: 0, width: 0, height: 0 }; | ||||
|       : ({ x: 0, y: 0, width: 0, height: 0 } as DOMRect); | ||||
|  | ||||
|   let camera: OrthographicCamera; | ||||
|   const minZoom = 1; | ||||
| @@ -121,10 +122,13 @@ | ||||
|     const height = | ||||
|       5 + | ||||
|       10 * | ||||
|         Object.keys(node.inputs) | ||||
|           .filter((i) => i !== "seed") | ||||
|           .filter((p) => node?.inputs && !("setting" in node?.inputs?.[p])) | ||||
|           .length; | ||||
|         Object.keys(node.inputs).filter( | ||||
|           (p) => | ||||
|             p !== "seed" && | ||||
|             node?.inputs && | ||||
|             !("setting" in node?.inputs?.[p]) && | ||||
|             node.inputs[p].hidden !== true, | ||||
|         ).length; | ||||
|     nodeHeightCache[nodeTypeId] = height; | ||||
|     return height; | ||||
|   } | ||||
| @@ -756,7 +760,8 @@ | ||||
|       clickedNodeId === -1 && | ||||
|       !boxSelection && | ||||
|       cameraDown[0] === cameraPosition[0] && | ||||
|       cameraDown[1] === cameraPosition[1] | ||||
|       cameraDown[1] === cameraPosition[1] && | ||||
|       isBodyFocused() | ||||
|     ) { | ||||
|       $activeNodeId = -1; | ||||
|       $selectedNodes?.clear(); | ||||
| @@ -817,7 +822,6 @@ | ||||
|   function handleDragEnter(e: DragEvent) { | ||||
|     e.preventDefault(); | ||||
|     isDragging = true; | ||||
|     console.log(e); | ||||
|   } | ||||
|  | ||||
|   function handlerDragOver(e: DragEvent) { | ||||
| @@ -840,7 +844,7 @@ | ||||
|   }); | ||||
| </script> | ||||
|  | ||||
| <svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} /> | ||||
| <svelte:window on:mousemove={handleMouseMove} /> | ||||
|  | ||||
| <div | ||||
|   on:wheel={handleMouseScroll} | ||||
| @@ -851,6 +855,7 @@ | ||||
|   tabindex="0" | ||||
|   bind:clientWidth={width} | ||||
|   bind:clientHeight={height} | ||||
|   on:mouseup={handleMouseUp} | ||||
|   on:dragenter={handleDragEnter} | ||||
|   on:dragover={handlerDragOver} | ||||
|   on:drop={handleDrop} | ||||
|   | ||||
| @@ -1,17 +1,23 @@ | ||||
| <script lang="ts"> | ||||
|   import type { Graph, NodeRegistry } from "@nodes/types"; | ||||
|   import type { Graph, Node, NodeRegistry } from "@nodes/types"; | ||||
|   import GraphEl from "./Graph.svelte"; | ||||
|   import { GraphManager } from "../graph-manager.js"; | ||||
|   import { createEventDispatcher, setContext } from "svelte"; | ||||
|   import type { Writable } from "svelte/store"; | ||||
|   import { type Writable } from "svelte/store"; | ||||
|   import { debounce } from "$lib/helpers"; | ||||
|   import { createKeyMap } from "$lib/helpers/createKeyMap"; | ||||
|   import { activeNodeId } from "./stores"; | ||||
|  | ||||
|   export let registry: NodeRegistry; | ||||
|   export let graph: Graph; | ||||
|   export let settings: Writable<Record<string, any>> | undefined; | ||||
|  | ||||
|   export const manager = new GraphManager(registry); | ||||
|   export let activeNode: Node | undefined; | ||||
|   $: if ($activeNodeId !== -1) { | ||||
|     activeNode = manager.getNode($activeNodeId); | ||||
|   } else { | ||||
|     activeNode = undefined; | ||||
|   } | ||||
|  | ||||
|   export const status = manager.status; | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|   import NodeHeader from "./NodeHeader.svelte"; | ||||
|   import NodeParameter from "./NodeParameter.svelte"; | ||||
|   import { getContext, onMount } from "svelte"; | ||||
|   import Page from "../../../routes/+page.svelte"; | ||||
|   export let isActive = false; | ||||
|   export let isSelected = false; | ||||
|   export let inView = true; | ||||
| @@ -17,9 +18,10 @@ | ||||
|  | ||||
|   const type = node?.tmp?.type; | ||||
|  | ||||
|   const parameters = Object.entries(type?.inputs || {}) | ||||
|     .filter((p) => p[1].type !== "seed") | ||||
|     .filter((p) => !("setting" in p[1])); | ||||
|   const parameters = Object.entries(type?.inputs || {}).filter( | ||||
|     (p) => | ||||
|       p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true, | ||||
|   ); | ||||
|  | ||||
|   const updateNodePosition = | ||||
|     getContext<(n: Node) => void>("updateNodePosition"); | ||||
|   | ||||
							
								
								
									
										71
									
								
								app/src/lib/settings/ActiveNode.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app/src/lib/settings/ActiveNode.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| <script lang="ts"> | ||||
|   import type { Node, NodeInput } from "@nodes/types"; | ||||
|   import NestedSettings from "./NestedSettings.svelte"; | ||||
|   import { writable } from "svelte/store"; | ||||
|   import type { GraphManager } from "$lib/graph-interface/graph-manager"; | ||||
|  | ||||
|   function filterInputs(inputs: Record<string, NodeInput>) { | ||||
|     return Object.fromEntries( | ||||
|       Object.entries(inputs).filter(([key, value]) => { | ||||
|         return value.hidden === true; | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   function createStore( | ||||
|     props: Node["props"], | ||||
|     inputs: Record<string, NodeInput>, | ||||
|   ) { | ||||
|     const store = {}; | ||||
|     Object.keys(inputs).forEach((key) => { | ||||
|       store[key] = props[key] || inputs[key].value; | ||||
|     }); | ||||
|     console.log({ store, props }); | ||||
|     return writable(store); | ||||
|   } | ||||
|  | ||||
|   export let manager: GraphManager; | ||||
|  | ||||
|   export let node: Node; | ||||
|   let nodeDefinition: Record<string, NodeInput> | undefined; | ||||
|   $: nodeDefinition = node?.tmp?.type | ||||
|     ? filterInputs(node.tmp.type.inputs) | ||||
|     : undefined; | ||||
|   $: store = node ? createStore(node.props, nodeDefinition) : undefined; | ||||
|  | ||||
|   function updateNode() { | ||||
|     if (!node || !$store) return; | ||||
|     Object.keys($store).forEach((_key: string) => { | ||||
|       node.props = node.props || {}; | ||||
|       const key = _key as keyof typeof $store; | ||||
|       let needsUpdate = false; | ||||
|       console.log({ key }); | ||||
|       if ( | ||||
|         node && | ||||
|         $store && | ||||
|         key in node.props && | ||||
|         node.props[key] !== $store[key] | ||||
|       ) { | ||||
|         needsUpdate = true; | ||||
|         node.props[key] = $store[key]; | ||||
|       } | ||||
|       if (needsUpdate) { | ||||
|         manager.execute(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   $: if (store && $store) { | ||||
|     updateNode(); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| {#if node} | ||||
|   {#if nodeDefinition && store && Object.keys(nodeDefinition).length > 0} | ||||
|     <NestedSettings id="activeNodeSettings" settings={nodeDefinition} {store} /> | ||||
|   {:else} | ||||
|     <p class="mx-4">Active Node has no Settings</p> | ||||
|   {/if} | ||||
| {:else} | ||||
|   <p class="mx-4">No active node</p> | ||||
| {/if} | ||||
| @@ -10,6 +10,8 @@ | ||||
|     { | ||||
|       icon: string; | ||||
|       id: string; | ||||
|       hidden?: boolean; | ||||
|       props?: Record<string, unknown>; | ||||
|       component?: typeof SvelteComponent<{}, {}, {}>; | ||||
|       definition: Record<string, NodeInput>; | ||||
|       settings: Writable<Record<string, unknown>>; | ||||
| @@ -22,7 +24,7 @@ | ||||
|   ); | ||||
|   $: keys = panels | ||||
|     ? (Object.keys(panels) as unknown as (keyof typeof panels)[]).filter( | ||||
|         (key) => !!panels[key]?.id, | ||||
|         (key) => !!panels[key]?.id && panels[key]?.hidden !== true, | ||||
|       ) | ||||
|     : []; | ||||
|  | ||||
| @@ -79,7 +81,7 @@ | ||||
|     {/each} | ||||
|   </div> | ||||
|   <div class="content"> | ||||
|     {#if $activePanel && panels[$activePanel]} | ||||
|     {#if $activePanel && panels[$activePanel] && panels[$activePanel].hidden !== true} | ||||
|       <h1 class="m-0 p-4">{panels[$activePanel].id}</h1> | ||||
|       {#key $activePanel} | ||||
|         {#if panels[$activePanel]?.component} | ||||
|   | ||||
| @@ -4,11 +4,11 @@ | ||||
|   import { MemoryRuntimeExecutor } from "$lib/runtime-executor"; | ||||
|   import { RemoteNodeRegistry } from "$lib/node-registry-client"; | ||||
|   import * as templates from "$lib/graph-templates"; | ||||
|   import type { Graph } from "@nodes/types"; | ||||
|   import type { Graph, Node } from "@nodes/types"; | ||||
|   import Viewer from "$lib/result-viewer/Viewer.svelte"; | ||||
|   import Settings from "$lib/settings/Settings.svelte"; | ||||
|   import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings"; | ||||
|   import { get, writable, type Writable } from "svelte/store"; | ||||
|   import { get, writable, type Readable, type Writable } from "svelte/store"; | ||||
|   import Keymap from "$lib/settings/Keymap.svelte"; | ||||
|   import type { createKeyMap } from "$lib/helpers/createKeyMap"; | ||||
|   import NodeStore from "$lib/node-store/NodeStore.svelte"; | ||||
| @@ -17,7 +17,7 @@ | ||||
|   import { decodeNestedArray, encodeNestedArray } from "@nodes/utils"; | ||||
|   import type { PerspectiveCamera, Vector3 } from "three"; | ||||
|   import type { OrbitControls } from "three/examples/jsm/Addons.js"; | ||||
|   import GraphView from "$lib/graph-interface/graph/GraphView.svelte"; | ||||
|   import ActiveNode from "$lib/settings/ActiveNode.svelte"; | ||||
|  | ||||
|   const nodeRegistry = new RemoteNodeRegistry(""); | ||||
|   const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry); | ||||
| @@ -29,6 +29,7 @@ | ||||
|   let viewerCamera: PerspectiveCamera; | ||||
|   let viewerControls: OrbitControls; | ||||
|   let viewerCenter: Vector3; | ||||
|   let activeNode: Node | undefined; | ||||
|  | ||||
|   let graph = localStorage.getItem("graph") | ||||
|     ? JSON.parse(localStorage.getItem("graph")!) | ||||
| @@ -65,6 +66,12 @@ | ||||
|     shortcuts: {}, | ||||
|     nodeStore: {}, | ||||
|     graph: {}, | ||||
|     activeNode: { | ||||
|       id: "Active Node", | ||||
|       icon: "i-tabler-adjustments", | ||||
|       props: { node: undefined, manager }, | ||||
|       component: ActiveNode, | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   $: if (keymap) { | ||||
| @@ -79,6 +86,7 @@ | ||||
|   } | ||||
|  | ||||
|   $: if (manager) { | ||||
|     settings.activeNode.props.manager = manager; | ||||
|     settings.nodeStore = { | ||||
|       id: "Node Store", | ||||
|       icon: "i-tabler-database", | ||||
| @@ -88,6 +96,14 @@ | ||||
|     settings = settings; | ||||
|   } | ||||
|  | ||||
|   $: if (activeNode) { | ||||
|     settings.activeNode.props.node = activeNode; | ||||
|     settings = settings; | ||||
|   } else { | ||||
|     settings.activeNode.props.node = undefined; | ||||
|     settings = settings; | ||||
|   } | ||||
|  | ||||
|   function handleSettings( | ||||
|     ev: CustomEvent<{ | ||||
|       values: Record<string, unknown>; | ||||
| @@ -107,7 +123,7 @@ | ||||
|       }; | ||||
|  | ||||
|     settings.graph = { | ||||
|       icon: "i-tabler-chart-bar", | ||||
|       icon: "i-tabler-git-fork", | ||||
|       id: "graph", | ||||
|       settings: writable(ev.detail.values), | ||||
|       definition: { | ||||
| @@ -139,6 +155,7 @@ | ||||
|       {#key graph} | ||||
|         <GraphInterface | ||||
|           bind:manager | ||||
|           bind:activeNode | ||||
|           registry={nodeRegistry} | ||||
|           {graph} | ||||
|           bind:keymap | ||||
|   | ||||
| @@ -17,14 +17,14 @@ | ||||
|       "min": 0.1, | ||||
|       "max": 100 | ||||
|     }, | ||||
|     "seed": { | ||||
|       "type": "seed" | ||||
|     }, | ||||
|     "fixBottom": { | ||||
|       "type": "float", | ||||
|       "hidden": true, | ||||
|       "min": 0, | ||||
|       "max": 1 | ||||
|     }, | ||||
|     "seed": { | ||||
|       "type": "seed" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,10 @@ use wasm_bindgen::prelude::*; | ||||
|  | ||||
| include_definition_file!("src/input.json"); | ||||
|  | ||||
| fn lerp(a: f32, b: f32, t: f32) -> f32 { | ||||
|     a + t * (b - a) | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen] | ||||
| pub fn execute(input: &[i32]) -> Vec<i32> { | ||||
|     set_panic_hook(); | ||||
| @@ -16,10 +20,23 @@ pub fn execute(input: &[i32]) -> Vec<i32> { | ||||
|     let plants = get_args(args[0]); | ||||
|     let scale = evaluate_float(args[1]); | ||||
|     let strength = evaluate_float(args[2]); | ||||
|     let seed = args[3][0]; | ||||
|     let _fix_bottom = evaluate_float(args[3]); | ||||
|     let fix_bottom = if _fix_bottom.is_finite() { | ||||
|         _fix_bottom | ||||
|     } else { | ||||
|         0.0 | ||||
|     }; | ||||
|  | ||||
|     let seed = args[4][0]; | ||||
|  | ||||
|     let hasher = PermutationTable::new(seed as u32); | ||||
|     log!("scale: {}, strength: {}, seed: {}", scale, strength, seed); | ||||
|     log!( | ||||
|         "scale: {}, strength: {}, fix_bottom: {}, seed: {}", | ||||
|         scale, | ||||
|         strength, | ||||
|         fix_bottom, | ||||
|         seed | ||||
|     ); | ||||
|  | ||||
|     let output: Vec<Vec<i32>> = plants | ||||
|         .iter() | ||||
| @@ -33,8 +50,14 @@ pub fn execute(input: &[i32]) -> Vec<i32> { | ||||
|                 let a = i as f64 / (points - 1) as f64; | ||||
|                 let px = Vector2::new(0.0, a * scale as f64); | ||||
|                 let pz = Vector2::new(a * scale as f64, 0.0); | ||||
|                 let nx = open_simplex_2d(px, &hasher) as f32 * strength * 0.1 * a as f32; | ||||
|                 let nz = open_simplex_2d(pz, &hasher) as f32 * strength * 0.1 * a as f32; | ||||
|                 let nx = open_simplex_2d(px, &hasher) as f32 | ||||
|                     * strength | ||||
|                     * 0.1 | ||||
|                     * lerp(1.0, a as f32, fix_bottom); | ||||
|                 let nz = open_simplex_2d(pz, &hasher) as f32 | ||||
|                     * strength | ||||
|                     * 0.1 | ||||
|                     * lerp(1.0, a as f32, fix_bottom); | ||||
|                 plant[3 + i * 4] = encode_float(decode_float(plant[3 + i * 4]) + nx); | ||||
|                 plant[5 + i * 4] = encode_float(decode_float(plant[5 + i * 4]) + nz); | ||||
|             } | ||||
|   | ||||
| @@ -3,10 +3,7 @@ | ||||
|   "outputs": [], | ||||
|   "inputs": { | ||||
|     "input": { | ||||
|       "type": [ | ||||
|         "plant", | ||||
|         "model" | ||||
|       ], | ||||
|       "type": "plant", | ||||
|       "external": true | ||||
|     }, | ||||
|     "resolution_circle": { | ||||
|   | ||||
| @@ -49,8 +49,12 @@ pub fn include_definition_file(input: TokenStream) -> TokenStream { | ||||
|     }); | ||||
|  | ||||
|     // Optionally, validate that the content is valid JSON | ||||
|     let _: NodeDefinition = serde_json::from_str(&json_content) | ||||
|         .unwrap_or_else(|err| panic!("JSON file contains invalid JSON: {}", err)); | ||||
|     let _: NodeDefinition = serde_json::from_str(&json_content).unwrap_or_else(|err| { | ||||
|         panic!( | ||||
|             "JSON file contains invalid JSON: \n {} \n {}", | ||||
|             json_content, err | ||||
|         ) | ||||
|     }); | ||||
|  | ||||
|     // Generate the function that returns the JSON string | ||||
|     let expanded = quote! { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ const DefaultOptionsSchema = z.object({ | ||||
|   label: z.string().optional(), | ||||
|   description: z.string().optional(), | ||||
|   accepts: z.array(z.string()).optional(), | ||||
|   hidden: z.boolean().optional(), | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -38,6 +39,7 @@ export const NodeInputBooleanSchema = z.object({ | ||||
| export const NodeInputSelectSchema = z.object({ | ||||
|   ...DefaultOptionsSchema.shape, | ||||
|   type: z.literal("select"), | ||||
|   options: z.array(z.string()).optional(), | ||||
|   value: z.number().optional(), | ||||
| }); | ||||
|  | ||||
| @@ -75,4 +77,4 @@ export const NodeInputSchema = z.union([ | ||||
|   NodeInputPlantSchema | ||||
| ]); | ||||
|  | ||||
| export type NodeInput = z.infer<typeof InputSchema>; | ||||
| export type NodeInput = z.infer<typeof NodeInputSchema>; | ||||
|   | ||||
| @@ -1,135 +1,142 @@ | ||||
| use serde::{Deserialize, Deserializer, Serialize}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_json::Value; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct DefaultOptions { | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub internal: Option<bool>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub external: Option<bool>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub setting: Option<String>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub label: Option<String>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub description: Option<String>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub accepts: Option<Vec<String>>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub hidden: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputFloat { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub element: Option<String>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<f64>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub min: Option<f64>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub max: Option<f64>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub step: Option<f64>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputInteger { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub element: Option<String>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<f64>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub min: Option<f64>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub max: Option<f64>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputBoolean { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputSelect { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<String>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub options: Option<HashMap<String, Value>>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputSeed { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<i32>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputVec3 { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<Vec<f64>>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputModel { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeInputPlant { | ||||
|     #[serde(flatten)] | ||||
|     pub default_options: DefaultOptions, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| #[serde(tag = "type")] | ||||
| #[allow(non_camel_case_types)] | ||||
| pub enum InputTypes { | ||||
| pub enum NodeInput { | ||||
|     float(NodeInputFloat), | ||||
|     integer(NodeInputInteger), | ||||
|     boolean(NodeInputBoolean), | ||||
|     select(NodeInputSelect), | ||||
|     seed(NodeInputSeed), | ||||
|     vec3(NodeInputVec3), | ||||
|     model(NodeInputModel), | ||||
|     plant(NodeInputPlant), | ||||
|     vec3(NodeInputVec3), | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputVec3 { | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<Vec<f64>>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputFloat { | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<f64>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub min: Option<f64>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub max: Option<f64>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub step: Option<f64>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputInteger { | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub element: Option<String>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<i64>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub min: Option<i64>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub max: Option<i64>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputBoolean { | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputSelect { | ||||
|     pub options: Vec<String>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<usize>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputSeed { | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub value: Option<usize>, | ||||
| } | ||||
|  | ||||
| // Assuming similar structure as other NodeInput types for Model and Plant | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputModel { | ||||
|     // Model-specific fields can be added here | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NodeInputPlant { | ||||
|     // Plant-specific fields can be added here | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct DefaultOptions { | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub internal: Option<bool>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub external: Option<bool>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub setting: Option<String>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub label: Option<serde_json::Value>, // To handle both String and false | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub hidden: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| #[serde(untagged)] | ||||
| pub enum NodeDefinitionOrArray { | ||||
|     Single(InputTypes), | ||||
|     Multiple(Vec<String>), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize)] | ||||
| pub struct NodeInput { | ||||
|     pub types: Vec<String>, | ||||
|     pub options: DefaultOptions, | ||||
| } | ||||
|  | ||||
| impl<'de> Deserialize<'de> for NodeInput { | ||||
|     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||||
|     where | ||||
|         D: Deserializer<'de>, | ||||
|     { | ||||
|         let raw_input: Value = Deserialize::deserialize(deserializer)?; | ||||
|         let options: DefaultOptions = | ||||
|             DefaultOptions::deserialize(&raw_input).map_err(serde::de::Error::custom)?; // Maps deserialization errors appropriately | ||||
|  | ||||
|         let types: Vec<String> = match raw_input.get("type") { | ||||
|             Some(Value::String(single_type)) => vec![single_type.clone()], | ||||
|             Some(Value::Array(types)) => types | ||||
|                 .iter() | ||||
|                 .map(|t| t.as_str().unwrap_or("").to_owned()) | ||||
|                 .collect(), | ||||
|             _ => return Err(serde::de::Error::custom("Invalid or missing 'type' field")), | ||||
|         }; | ||||
|  | ||||
|         Ok(NodeInput { types, options }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug, Serialize)] | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct NodeDefinition { | ||||
|     pub id: String, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub inputs: Option<HashMap<String, NodeInput>>, | ||||
|  | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub outputs: Option<Vec<String>>, | ||||
| } | ||||
|   | ||||
| @@ -42,6 +42,8 @@ | ||||
|   function handleMouseDown(ev: MouseEvent) { | ||||
|     ev.preventDefault(); | ||||
|  | ||||
|     inputEl.focus(); | ||||
|  | ||||
|     isMouseDown = true; | ||||
|  | ||||
|     downV = value; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user