This commit is contained in:
max_richter 2024-12-19 18:28:17 +01:00
parent 53f400a4f6
commit 33d5ed14dd
16 changed files with 232 additions and 240 deletions

View File

@ -1,6 +1,6 @@
import type { Edge, Graph, Node, NodeInput, NodeRegistry, Socket, } from "@nodes/types"; import type { Edge, Graph, Node, NodeInput, NodeRegistry, Socket, } from "@nodes/types";
import { fastHashString } from "@nodes/utils"; import { fastHashString } from "@nodes/utils";
import { get, writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import EventEmitter from "./helpers/EventEmitter.js"; import EventEmitter from "./helpers/EventEmitter.js";
import { createLogger } from "./helpers/index.js"; import { createLogger } from "./helpers/index.js";
import throttle from "./helpers/throttle.js"; import throttle from "./helpers/throttle.js";
@ -42,7 +42,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
history: HistoryManager = new HistoryManager(); history: HistoryManager = new HistoryManager();
execute = throttle(() => { execute = throttle(() => {
console.log("Props", get(this.nodes).values().find(n => n.type === "max/plantarium/gravity")?.props);
if (this.loaded === false) return; if (this.loaded === false) return;
this.emit("result", this.serialize()); this.emit("result", this.serialize());
}, 10); }, 10);

View File

@ -48,7 +48,7 @@
$effect(() => { $effect(() => {
if (graphState.activeNodeId !== -1) { if (graphState.activeNodeId !== -1) {
activeNode = manager.getNode(graphState.activeNodeId); activeNode = manager.getNode(graphState.activeNodeId);
} else { } else if (activeNode) {
activeNode = undefined; activeNode = undefined;
} }
}); });
@ -64,7 +64,7 @@
}); });
manager.on("settings", (_settings) => { manager.on("settings", (_settings) => {
settingTypes = _settings.types; settingTypes = { ...settingTypes, ..._settings.types };
settings = _settings.values; settings = _settings.values;
}); });

View File

@ -31,7 +31,7 @@
} }
let value = $state(getDefaultValue()); let value = $state(getDefaultValue());
$inspect({ nodeId: node.type, id, value });
$effect(() => { $effect(() => {
if (value !== undefined && node?.props?.[id] !== value) { if (value !== undefined && node?.props?.[id] !== value) {
node.props = { ...node.props, [id]: value }; node.props = { ...node.props, [id]: value };

View File

@ -0,0 +1,177 @@
<script module lang="ts">
let openSections = localState<Record<string, boolean>>("open-details", {});
</script>
<script lang="ts">
import NestedSettings from "./NestedSettings.svelte";
import { localState } from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types";
import Input from "@nodes/ui";
type Button = { type: "button"; label?: string };
type InputType = NodeInput | Button;
interface Nested {
[key: string]: (Nested & { title?: string }) | InputType;
}
type SettingsType = Record<string, Nested>;
type SettingsValue = Record<
string,
Record<string, unknown> | string | number | boolean | number[]
>;
type Props = {
id: string;
key?: string;
value: SettingsValue;
type: SettingsType;
depth?: number;
};
let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
function isNodeInput(v: InputType | Nested): v is InputType {
return v && "type" in v;
}
function getDefaultValue() {
if (key === "") return;
if (key === "title") return;
if (Array.isArray(type[key]?.options)) {
if (value?.[key] !== undefined) {
return type[key]?.options?.indexOf(value?.[key]);
} else {
return 0;
}
}
if (value?.[key] !== undefined) return value?.[key];
if (type[key]?.value !== undefined) return type[key]?.value;
if (isNodeInput(type[key])) {
if (type[key].type === "boolean") return 0;
if (type[key].type === "float") return 0.5;
if (type[key].type === "integer") return 0;
if (type[key].type === "select") return 0;
}
return 0;
}
let internalValue = $state(getDefaultValue());
let open = $state(openSections[id]);
if (depth > 0 && !isNodeInput(type[key])) {
$effect(() => {
if (open !== undefined) {
openSections[id] = open;
}
});
}
$effect(() => {
if (key === "" || internalValue === undefined) return;
if (
isNodeInput(type[key]) &&
Array.isArray(type[key]?.options) &&
typeof internalValue === "number"
) {
value[key] = type[key].options?.[internalValue];
} else {
value[key] = internalValue;
}
});
</script>
{#if key && isNodeInput(type?.[key])}
<div class="input input-{type[key].type}" class:first-level={depth === 1}>
{#if type[key].type === "button"}
<button onclick={() => console.log(type[key])}>
{type[key].label || key}
</button>
{:else}
<label for={id}>{type[key].label || key}</label>
<Input {id} input={type[key]} bind:value={internalValue} />
{/if}
</div>
{:else if depth === 0}
{#each Object.keys(type ?? {}).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
{value}
{type}
depth={depth + 1}
/>
{/each}
<hr />
{:else if key && type?.[key]}
{#if depth > 0}
<hr />
{/if}
<details bind:open>
<summary><p>{type[key]?.title || key}</p></summary>
<div class="content">
{#each Object.keys(type[key]).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
value={value[key] as SettingsValue}
type={type[key] as SettingsType}
depth={depth + 1}
/>
{/each}
</div>
</details>
{/if}
<style>
summary {
cursor: pointer;
user-select: none;
margin-bottom: 1em;
}
summary > p {
display: inline;
padding-left: 6px;
}
details {
padding: 1em;
padding-bottom: 0;
padding-left: 21px;
}
.input {
margin-top: 15px;
margin-bottom: 15px;
display: flex;
flex-direction: column;
gap: 10px;
padding-left: 20px;
}
.input-boolean {
display: flex;
flex-direction: row;
align-items: center;
}
.input-boolean > label {
order: 2;
}
.first-level.input {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 1px;
}
hr {
position: absolute;
margin: 0;
left: 0;
right: 0;
border: none;
border-bottom: solid thin var(--outline);
}
</style>

View File

@ -105,16 +105,26 @@ export const AppSettingTypes = {
} }
}, },
} }
} as const } as const;
type IsInputDefinition<T> = T extends NodeInput ? T : never; type IsInputDefinition<T> = T extends NodeInput ? T : never;
type HasTitle = { title: string }; type HasTitle = { title: string };
type Widen<T> = T extends boolean
? boolean
: T extends number
? number
: T extends string
? string
: T;
type ExtractSettingsValues<T> = { type ExtractSettingsValues<T> = {
[K in keyof T]: T[K] extends HasTitle -readonly [K in keyof T]: T[K] extends HasTitle
? ExtractSettingsValues<Omit<T[K], 'title'>> ? ExtractSettingsValues<Omit<T[K], 'title'>>
: T[K] extends IsInputDefinition<T[K]> : T[K] extends IsInputDefinition<T[K]>
? T[K] extends { value: any } ? T[K] extends { value: infer V }
? T[K]['value'] ? Widen<V>
: never : never
: T[K] extends Record<string, any> : T[K] extends Record<string, any>
? ExtractSettingsValues<T[K]> ? ExtractSettingsValues<T[K]>

View File

@ -0,0 +1,13 @@
import type { NodeInput } from "@nodes/types";
type Button = { type: "button"; label?: string };
type InputType = NodeInput | Button;
export interface SettingsType {
[key: string]: (SettingsType & { title?: string }) | InputType;
}
export type SettingsStore = {
[key: string]: SettingsStore | string | number | boolean
};

View File

@ -1,40 +0,0 @@
<script lang="ts">
import type { NodeInput } from "@nodes/types";
import NestedSettings from "./NestedSettings.svelte";
import type { Writable } from "svelte/store";
interface Nested {
[key: string]: NodeInput | Nested;
}
export let type: Record<string, NodeInput>;
export let store: Writable<Record<string, any>>;
function constructNested(type: Record<string, NodeInput>) {
const nested: Nested = {};
for (const key in type) {
const parts = key.split(".");
let current = nested;
for (let i = 0; i < parts.length; i++) {
if (i === parts.length - 1) {
current[parts[i]] = type[key];
} else {
current[parts[i]] = current[parts[i]] || {};
current = current[parts[i]] as Nested;
}
}
}
return nested;
}
$: settings = constructNested({
randomSeed: { type: "boolean", value: false },
...type,
});
</script>
{#key settings}
<NestedSettings id="graph-settings" {settings} {store} />
{/key}

View File

@ -1,157 +0,0 @@
<script module lang="ts">
let openSections = localState<Record<string,boolean>>("open-details", {});
</script>
<script lang="ts">
import NestedSettings from "./NestedSettings.svelte";
import {localState} from "$lib/helpers/localState.svelte";
import type { NodeInput } from "@nodes/types";
import Input from "@nodes/ui";
type Button = { type: "button"; label?: string };
type InputType = NodeInput | Button;
interface Nested {
[key: string]: (Nested & { title?: string }) | InputType;
}
type SettingsType = Record<string, Nested>;
type SettingsValue = Record<string, Record<string, unknown> | string | number | boolean>;
type Props = {
id: string;
key?: string;
value: SettingsValue;
type: SettingsType;
depth?: number;
};
let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
function isNodeInput(v: InputType | Nested): v is InputType {
return v && "type" in v;
}
let internalValue = $state(Array.isArray(type?.[key]?.options) ? type[key]?.options?.indexOf(value?.[key]) : value?.[key]);
let open = $state(openSections[id]);
if(depth > 0 && !isNodeInput(type[key])){
$effect(() => {
if(open !== undefined){
openSections[id] = open;
};
});
}
$effect(() => {
if(key === "" || internalValue === undefined) return;
if(isNodeInput(type[key]) && Array.isArray(type[key]?.options) && typeof internalValue === "number"){
value[key] = type[key].options?.[internalValue];
}else{
value[key] = internalValue;
}
})
</script>
{#if key && isNodeInput(type?.[key]) }
<div class="input input-{type[key].type}" class:first-level={depth === 1}>
{#if type[key].type === "button"}
<button onclick={() => console.log(type[key])}>
{type[key].label || key}
</button>
{:else}
<label for={id}>{type[key].label || key}</label>
<Input id={id} input={type[key]} bind:value={internalValue} />
{/if}
</div>
{:else}
{#if depth === 0}
{#each Object.keys(type).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
value={value}
type={type}
depth={depth + 1}
/>
{/each}
<hr />
{:else if key && type?.[key]}
{#if depth > 0}
<hr />
{/if}
<details bind:open>
<summary><p>{type[key]?.title||key}</p></summary>
<div class="content">
{#each Object.keys(type[key]).filter((key) => key !== "title") as childKey}
<NestedSettings
id={`${id}.${childKey}`}
key={childKey}
value={value[key] as SettingsValue}
type={type[key] as SettingsType}
depth={depth + 1}
/>
{/each}
</div>
</details>
{/if}
{/if}
<style>
summary {
cursor: pointer;
user-select: none;
margin-bottom: 1em;
}
summary::marker { }
summary > p {
display: inline;
padding-left: 6px;
}
details {
padding: 1em;
padding-bottom: 0;
padding-left: 21px;
}
.input {
margin-top: 15px;
margin-bottom: 15px;
display: flex;
flex-direction: column;
gap: 10px;
padding-left: 20px;
}
.input-boolean {
display: flex;
flex-direction: row;
align-items: center;
}
.input-boolean > label {
order: 2;
}
.first-level.input {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 1px;
}
hr {
position: absolute;
margin: 0;
left: 0;
right: 0;
border: none;
border-bottom: solid thin var(--outline);
}
</style>

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Node, NodeInput } from "@nodes/types"; import type { Node, NodeInput } from "@nodes/types";
import NestedSettings from "./NestedSettings.svelte"; import NestedSettings from "$lib/settings/NestedSettings.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager"; import type { GraphManager } from "$lib/graph-interface/graph-manager";
type Props = { type Props = {
@ -69,24 +69,14 @@
} }
$effect(() => { $effect(() => {
if (store && store) { if (store) {
updateNode(); updateNode();
} }
}); });
</script> </script>
{#if node}
{#key node.id}
{#if nodeDefinition && store && Object.keys(nodeDefinition).length > 0}
<NestedSettings <NestedSettings
id="activeNodeSettings" id="activeNodeSettings"
bind:value={store} bind:value={store}
type={nodeDefinition} type={nodeDefinition}
/> />
{:else}
<p class="mx-4">Active Node has no Settings</p>
{/if}
{/key}
{:else}
<p class="mx-4">No active node</p>
{/if}

View File

@ -3,7 +3,6 @@
import type { OBJExporter } from "three/addons/exporters/OBJExporter.js"; import type { OBJExporter } from "three/addons/exporters/OBJExporter.js";
import type { GLTFExporter } from "three/addons/exporters/GLTFExporter.js"; import type { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import { appSettings } from "../app-settings.svelte";
// Download // Download
const download = ( const download = (
@ -52,8 +51,6 @@
// download .obj file // download .obj file
download(result, "plant", "text/plain", "obj"); download(result, "plant", "text/plain", "obj");
} }
</script> </script>
<div class="p-2"> <div class="p-2">

View File

@ -4,21 +4,20 @@
import * as templates from "$lib/graph-templates"; import * as templates from "$lib/graph-templates";
import type { Graph, Node } 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 Sidebar from "$lib/sidebar/Sidebar.svelte";
import { import {
appSettings, appSettings,
AppSettingTypes, AppSettingTypes,
} from "$lib/settings/app-settings.svelte"; } from "$lib/settings/app-settings.svelte";
import Keymap from "$lib/settings/panels/Keymap.svelte"; import Keymap from "$lib/sidebar/panels/Keymap.svelte";
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 ActiveNodeSettings from "$lib/settings/panels/ActiveNodeSettings.svelte"; import ActiveNodeSettings from "$lib/sidebar/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/sidebar/Panel.svelte";
import GraphSettings from "$lib/settings/panels/GraphSettings.svelte"; import NestedSettings from "$lib/settings/NestedSettings.svelte";
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
import type { Group } from "three"; import type { Group } from "three";
import ExportSettings from "$lib/settings/panels/ExportSettings.svelte"; import ExportSettings from "$lib/sidebar/panels/ExportSettings.svelte";
import { import {
MemoryRuntimeCache, MemoryRuntimeCache,
WorkerRuntimeExecutor, WorkerRuntimeExecutor,
@ -26,7 +25,7 @@
} from "$lib/runtime"; } from "$lib/runtime";
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry"; import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
import { createPerformanceStore } from "@nodes/utils"; import { createPerformanceStore } from "@nodes/utils";
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte"; import BenchmarkPanel from "$lib/sidebar/panels/BenchmarkPanel.svelte";
import { debounceAsyncFunction } from "$lib/helpers"; import { debounceAsyncFunction } from "$lib/helpers";
import { onMount } from "svelte"; import { onMount } from "svelte";
@ -71,7 +70,9 @@
}, },
]); ]);
let graphSettings = $state<Record<string, any>>({}); let graphSettings = $state<Record<string, any>>({});
let graphSettingTypes = $state({}); let graphSettingTypes = $state({
randomSeed: { type: "boolean", value: false },
});
const handleUpdate = debounceAsyncFunction( const handleUpdate = debounceAsyncFunction(
async (g: Graph, s: Record<string, any> = graphSettings) => { async (g: Graph, s: Record<string, any> = graphSettings) => {
@ -162,7 +163,7 @@
onresult={(result) => handleUpdate(result)} onresult={(result) => handleUpdate(result)}
onsave={(graph) => handleSave(graph)} onsave={(graph) => handleSave(graph)}
/> />
<Settings> <Sidebar>
<Panel id="general" title="General" icon="i-tabler-settings"> <Panel id="general" title="General" icon="i-tabler-settings">
<NestedSettings <NestedSettings
id="general" id="general"
@ -219,9 +220,11 @@
classes="text-blue-400" classes="text-blue-400"
icon="i-custom-graph" icon="i-custom-graph"
> >
{#if Object.keys(graphSettingTypes).length > 0} <NestedSettings
<GraphSettings type={graphSettingTypes} store={graphSettings} /> id="graph-settings"
{/if} type={graphSettingTypes}
bind:value={graphSettings}
/>
</Panel> </Panel>
<Panel <Panel
id="active-node" id="active-node"
@ -231,7 +234,7 @@
> >
<ActiveNodeSettings {manager} node={activeNode} /> <ActiveNodeSettings {manager} node={activeNode} />
</Panel> </Panel>
</Settings> </Sidebar>
{/key} {/key}
</Grid.Cell> </Grid.Cell>
</Grid.Row> </Grid.Row>