feat: add active node settings
This commit is contained in:
parent
6ea4afa012
commit
198a868fc6
@ -5,6 +5,7 @@ import EventEmitter from "./helpers/EventEmitter.js";
|
|||||||
import throttle from "./helpers/throttle.js";
|
import throttle from "./helpers/throttle.js";
|
||||||
import { createLogger } from "./helpers/index.js";
|
import { createLogger } from "./helpers/index.js";
|
||||||
import type { NodeInput } from "@nodes/types";
|
import type { NodeInput } from "@nodes/types";
|
||||||
|
import { fastHashString } from "@nodes/utils";
|
||||||
|
|
||||||
const logger = createLogger("graph-manager");
|
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>) {
|
setSettings(settings: Record<string, unknown>) {
|
||||||
|
|
||||||
|
let hash = fastHashString(JSON.stringify(settings));
|
||||||
|
if (hash === this.lastSettingsHash) return;
|
||||||
|
this.lastSettingsHash = hash;
|
||||||
|
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.save();
|
this.save();
|
||||||
this.execute();
|
this.execute();
|
||||||
|
@ -39,10 +39,11 @@
|
|||||||
const edges = graph.edges;
|
const edges = graph.edges;
|
||||||
|
|
||||||
let wrapper: HTMLDivElement;
|
let wrapper: HTMLDivElement;
|
||||||
|
let rect: DOMRect;
|
||||||
$: rect =
|
$: rect =
|
||||||
wrapper && width
|
wrapper && width
|
||||||
? wrapper.getBoundingClientRect()
|
? wrapper.getBoundingClientRect()
|
||||||
: { x: 0, y: 0, width: 0, height: 0 };
|
: ({ x: 0, y: 0, width: 0, height: 0 } as DOMRect);
|
||||||
|
|
||||||
let camera: OrthographicCamera;
|
let camera: OrthographicCamera;
|
||||||
const minZoom = 1;
|
const minZoom = 1;
|
||||||
@ -121,10 +122,13 @@
|
|||||||
const height =
|
const height =
|
||||||
5 +
|
5 +
|
||||||
10 *
|
10 *
|
||||||
Object.keys(node.inputs)
|
Object.keys(node.inputs).filter(
|
||||||
.filter((i) => i !== "seed")
|
(p) =>
|
||||||
.filter((p) => node?.inputs && !("setting" in node?.inputs?.[p]))
|
p !== "seed" &&
|
||||||
.length;
|
node?.inputs &&
|
||||||
|
!("setting" in node?.inputs?.[p]) &&
|
||||||
|
node.inputs[p].hidden !== true,
|
||||||
|
).length;
|
||||||
nodeHeightCache[nodeTypeId] = height;
|
nodeHeightCache[nodeTypeId] = height;
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
@ -756,7 +760,8 @@
|
|||||||
clickedNodeId === -1 &&
|
clickedNodeId === -1 &&
|
||||||
!boxSelection &&
|
!boxSelection &&
|
||||||
cameraDown[0] === cameraPosition[0] &&
|
cameraDown[0] === cameraPosition[0] &&
|
||||||
cameraDown[1] === cameraPosition[1]
|
cameraDown[1] === cameraPosition[1] &&
|
||||||
|
isBodyFocused()
|
||||||
) {
|
) {
|
||||||
$activeNodeId = -1;
|
$activeNodeId = -1;
|
||||||
$selectedNodes?.clear();
|
$selectedNodes?.clear();
|
||||||
@ -817,7 +822,6 @@
|
|||||||
function handleDragEnter(e: DragEvent) {
|
function handleDragEnter(e: DragEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlerDragOver(e: DragEvent) {
|
function handlerDragOver(e: DragEvent) {
|
||||||
@ -840,7 +844,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} />
|
<svelte:window on:mousemove={handleMouseMove} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
on:wheel={handleMouseScroll}
|
on:wheel={handleMouseScroll}
|
||||||
@ -851,6 +855,7 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
bind:clientWidth={width}
|
bind:clientWidth={width}
|
||||||
bind:clientHeight={height}
|
bind:clientHeight={height}
|
||||||
|
on:mouseup={handleMouseUp}
|
||||||
on:dragenter={handleDragEnter}
|
on:dragenter={handleDragEnter}
|
||||||
on:dragover={handlerDragOver}
|
on:dragover={handlerDragOver}
|
||||||
on:drop={handleDrop}
|
on:drop={handleDrop}
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Graph, NodeRegistry } from "@nodes/types";
|
import type { Graph, Node, NodeRegistry } from "@nodes/types";
|
||||||
import GraphEl from "./Graph.svelte";
|
import GraphEl from "./Graph.svelte";
|
||||||
import { GraphManager } from "../graph-manager.js";
|
import { GraphManager } from "../graph-manager.js";
|
||||||
import { createEventDispatcher, setContext } from "svelte";
|
import { createEventDispatcher, setContext } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import { type Writable } from "svelte/store";
|
||||||
import { debounce } from "$lib/helpers";
|
import { debounce } from "$lib/helpers";
|
||||||
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
|
import { activeNodeId } from "./stores";
|
||||||
|
|
||||||
export let registry: NodeRegistry;
|
export let registry: NodeRegistry;
|
||||||
export let graph: Graph;
|
export let graph: Graph;
|
||||||
export let settings: Writable<Record<string, any>> | undefined;
|
export let settings: Writable<Record<string, any>> | undefined;
|
||||||
|
|
||||||
export const manager = new GraphManager(registry);
|
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;
|
export const status = manager.status;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import NodeHeader from "./NodeHeader.svelte";
|
import NodeHeader from "./NodeHeader.svelte";
|
||||||
import NodeParameter from "./NodeParameter.svelte";
|
import NodeParameter from "./NodeParameter.svelte";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
|
import Page from "../../../routes/+page.svelte";
|
||||||
export let isActive = false;
|
export let isActive = false;
|
||||||
export let isSelected = false;
|
export let isSelected = false;
|
||||||
export let inView = true;
|
export let inView = true;
|
||||||
@ -17,9 +18,10 @@
|
|||||||
|
|
||||||
const type = node?.tmp?.type;
|
const type = node?.tmp?.type;
|
||||||
|
|
||||||
const parameters = Object.entries(type?.inputs || {})
|
const parameters = Object.entries(type?.inputs || {}).filter(
|
||||||
.filter((p) => p[1].type !== "seed")
|
(p) =>
|
||||||
.filter((p) => !("setting" in p[1]));
|
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
|
||||||
|
);
|
||||||
|
|
||||||
const updateNodePosition =
|
const updateNodePosition =
|
||||||
getContext<(n: Node) => void>("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;
|
icon: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
hidden?: boolean;
|
||||||
|
props?: Record<string, unknown>;
|
||||||
component?: typeof SvelteComponent<{}, {}, {}>;
|
component?: typeof SvelteComponent<{}, {}, {}>;
|
||||||
definition: Record<string, NodeInput>;
|
definition: Record<string, NodeInput>;
|
||||||
settings: Writable<Record<string, unknown>>;
|
settings: Writable<Record<string, unknown>>;
|
||||||
@ -22,7 +24,7 @@
|
|||||||
);
|
);
|
||||||
$: keys = panels
|
$: keys = panels
|
||||||
? (Object.keys(panels) as unknown as (keyof typeof panels)[]).filter(
|
? (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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<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>
|
<h1 class="m-0 p-4">{panels[$activePanel].id}</h1>
|
||||||
{#key $activePanel}
|
{#key $activePanel}
|
||||||
{#if panels[$activePanel]?.component}
|
{#if panels[$activePanel]?.component}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
||||||
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
||||||
import * as templates from "$lib/graph-templates";
|
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 Viewer from "$lib/result-viewer/Viewer.svelte";
|
||||||
import Settings from "$lib/settings/Settings.svelte";
|
import Settings from "$lib/settings/Settings.svelte";
|
||||||
import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings";
|
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 Keymap from "$lib/settings/Keymap.svelte";
|
||||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
||||||
@ -17,7 +17,7 @@
|
|||||||
import { decodeNestedArray, encodeNestedArray } from "@nodes/utils";
|
import { decodeNestedArray, encodeNestedArray } from "@nodes/utils";
|
||||||
import type { PerspectiveCamera, Vector3 } from "three";
|
import type { PerspectiveCamera, Vector3 } from "three";
|
||||||
import type { OrbitControls } from "three/examples/jsm/Addons.js";
|
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 nodeRegistry = new RemoteNodeRegistry("");
|
||||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||||
@ -29,6 +29,7 @@
|
|||||||
let viewerCamera: PerspectiveCamera;
|
let viewerCamera: PerspectiveCamera;
|
||||||
let viewerControls: OrbitControls;
|
let viewerControls: OrbitControls;
|
||||||
let viewerCenter: Vector3;
|
let viewerCenter: Vector3;
|
||||||
|
let activeNode: Node | undefined;
|
||||||
|
|
||||||
let graph = localStorage.getItem("graph")
|
let graph = localStorage.getItem("graph")
|
||||||
? JSON.parse(localStorage.getItem("graph")!)
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
@ -65,6 +66,12 @@
|
|||||||
shortcuts: {},
|
shortcuts: {},
|
||||||
nodeStore: {},
|
nodeStore: {},
|
||||||
graph: {},
|
graph: {},
|
||||||
|
activeNode: {
|
||||||
|
id: "Active Node",
|
||||||
|
icon: "i-tabler-adjustments",
|
||||||
|
props: { node: undefined, manager },
|
||||||
|
component: ActiveNode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
$: if (keymap) {
|
$: if (keymap) {
|
||||||
@ -79,6 +86,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: if (manager) {
|
$: if (manager) {
|
||||||
|
settings.activeNode.props.manager = manager;
|
||||||
settings.nodeStore = {
|
settings.nodeStore = {
|
||||||
id: "Node Store",
|
id: "Node Store",
|
||||||
icon: "i-tabler-database",
|
icon: "i-tabler-database",
|
||||||
@ -88,6 +96,14 @@
|
|||||||
settings = settings;
|
settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if (activeNode) {
|
||||||
|
settings.activeNode.props.node = activeNode;
|
||||||
|
settings = settings;
|
||||||
|
} else {
|
||||||
|
settings.activeNode.props.node = undefined;
|
||||||
|
settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
function handleSettings(
|
function handleSettings(
|
||||||
ev: CustomEvent<{
|
ev: CustomEvent<{
|
||||||
values: Record<string, unknown>;
|
values: Record<string, unknown>;
|
||||||
@ -107,7 +123,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
settings.graph = {
|
settings.graph = {
|
||||||
icon: "i-tabler-chart-bar",
|
icon: "i-tabler-git-fork",
|
||||||
id: "graph",
|
id: "graph",
|
||||||
settings: writable(ev.detail.values),
|
settings: writable(ev.detail.values),
|
||||||
definition: {
|
definition: {
|
||||||
@ -139,6 +155,7 @@
|
|||||||
{#key graph}
|
{#key graph}
|
||||||
<GraphInterface
|
<GraphInterface
|
||||||
bind:manager
|
bind:manager
|
||||||
|
bind:activeNode
|
||||||
registry={nodeRegistry}
|
registry={nodeRegistry}
|
||||||
{graph}
|
{graph}
|
||||||
bind:keymap
|
bind:keymap
|
||||||
|
@ -17,14 +17,14 @@
|
|||||||
"min": 0.1,
|
"min": 0.1,
|
||||||
"max": 100
|
"max": 100
|
||||||
},
|
},
|
||||||
"seed": {
|
|
||||||
"type": "seed"
|
|
||||||
},
|
|
||||||
"fixBottom": {
|
"fixBottom": {
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 1
|
"max": 1
|
||||||
|
},
|
||||||
|
"seed": {
|
||||||
|
"type": "seed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ use wasm_bindgen::prelude::*;
|
|||||||
|
|
||||||
include_definition_file!("src/input.json");
|
include_definition_file!("src/input.json");
|
||||||
|
|
||||||
|
fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||||
|
a + t * (b - a)
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn execute(input: &[i32]) -> Vec<i32> {
|
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||||
set_panic_hook();
|
set_panic_hook();
|
||||||
@ -16,10 +20,23 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
let plants = get_args(args[0]);
|
let plants = get_args(args[0]);
|
||||||
let scale = evaluate_float(args[1]);
|
let scale = evaluate_float(args[1]);
|
||||||
let strength = evaluate_float(args[2]);
|
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);
|
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
|
let output: Vec<Vec<i32>> = plants
|
||||||
.iter()
|
.iter()
|
||||||
@ -33,8 +50,14 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
let a = i as f64 / (points - 1) as f64;
|
let a = i as f64 / (points - 1) as f64;
|
||||||
let px = Vector2::new(0.0, a * scale as f64);
|
let px = Vector2::new(0.0, a * scale as f64);
|
||||||
let pz = Vector2::new(a * scale as f64, 0.0);
|
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 nx = open_simplex_2d(px, &hasher) as f32
|
||||||
let nz = open_simplex_2d(pz, &hasher) as f32 * strength * 0.1 * a 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[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);
|
plant[5 + i * 4] = encode_float(decode_float(plant[5 + i * 4]) + nz);
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"input": {
|
"input": {
|
||||||
"type": [
|
"type": "plant",
|
||||||
"plant",
|
|
||||||
"model"
|
|
||||||
],
|
|
||||||
"external": true
|
"external": true
|
||||||
},
|
},
|
||||||
"resolution_circle": {
|
"resolution_circle": {
|
||||||
|
@ -49,8 +49,12 @@ pub fn include_definition_file(input: TokenStream) -> TokenStream {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Optionally, validate that the content is valid JSON
|
// Optionally, validate that the content is valid JSON
|
||||||
let _: NodeDefinition = serde_json::from_str(&json_content)
|
let _: NodeDefinition = serde_json::from_str(&json_content).unwrap_or_else(|err| {
|
||||||
.unwrap_or_else(|err| panic!("JSON file contains invalid JSON: {}", err));
|
panic!(
|
||||||
|
"JSON file contains invalid JSON: \n {} \n {}",
|
||||||
|
json_content, err
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
// Generate the function that returns the JSON string
|
// Generate the function that returns the JSON string
|
||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
|
@ -7,6 +7,7 @@ const DefaultOptionsSchema = z.object({
|
|||||||
label: z.string().optional(),
|
label: z.string().optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
accepts: z.array(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({
|
export const NodeInputSelectSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("select"),
|
type: z.literal("select"),
|
||||||
|
options: z.array(z.string()).optional(),
|
||||||
value: z.number().optional(),
|
value: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,4 +77,4 @@ export const NodeInputSchema = z.union([
|
|||||||
NodeInputPlantSchema
|
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 serde_json::Value;
|
||||||
use std::collections::HashMap;
|
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")]
|
#[serde(tag = "type")]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum InputTypes {
|
pub enum NodeInput {
|
||||||
float(NodeInputFloat),
|
float(NodeInputFloat),
|
||||||
integer(NodeInputInteger),
|
integer(NodeInputInteger),
|
||||||
boolean(NodeInputBoolean),
|
boolean(NodeInputBoolean),
|
||||||
select(NodeInputSelect),
|
select(NodeInputSelect),
|
||||||
seed(NodeInputSeed),
|
seed(NodeInputSeed),
|
||||||
|
vec3(NodeInputVec3),
|
||||||
model(NodeInputModel),
|
model(NodeInputModel),
|
||||||
plant(NodeInputPlant),
|
plant(NodeInputPlant),
|
||||||
vec3(NodeInputVec3),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize)]
|
||||||
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)]
|
|
||||||
pub struct NodeDefinition {
|
pub struct NodeDefinition {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub inputs: Option<HashMap<String, NodeInput>>,
|
pub inputs: Option<HashMap<String, NodeInput>>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub outputs: Option<Vec<String>>,
|
pub outputs: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,8 @@
|
|||||||
function handleMouseDown(ev: MouseEvent) {
|
function handleMouseDown(ev: MouseEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
inputEl.focus();
|
||||||
|
|
||||||
isMouseDown = true;
|
isMouseDown = true;
|
||||||
|
|
||||||
downV = value;
|
downV = value;
|
||||||
|
Loading…
Reference in New Issue
Block a user