feat: implement settings
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import type { Graph, Node, Edge, Socket, NodeRegistry, } from "@nodes/types";
|
||||
import { HistoryManager } from "./history-manager.js"
|
||||
import EventEmitter from "./helpers/EventEmitter.js";
|
||||
@ -15,7 +15,7 @@ function areSocketsCompatible(output: string | undefined, inputs: string | strin
|
||||
return inputs === output;
|
||||
}
|
||||
|
||||
export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }> {
|
||||
export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "settings": { types: Record<string, NodeInput>, values: Record<string, unknown> } }> {
|
||||
|
||||
status: Writable<"loading" | "idle" | "error"> = writable("loading");
|
||||
loaded = false;
|
||||
@ -25,8 +25,8 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
||||
|
||||
private _nodes: Map<number, Node> = new Map();
|
||||
nodes: Writable<Map<number, Node>> = writable(new Map());
|
||||
settingTypes: NodeInput[] = [];
|
||||
settings: Writable<Record<string, any>> = writable({});
|
||||
settingTypes: Record<string, NodeInput> = {};
|
||||
settings: Record<string, unknown> = {};
|
||||
private _edges: Edge[] = [];
|
||||
edges: Writable<Edge[]> = writable([]);
|
||||
|
||||
@ -61,14 +61,20 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
||||
props: node.props,
|
||||
})) as Node[];
|
||||
const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]) as Graph["edges"];
|
||||
const settings = get(this.settings);
|
||||
const serialized = { id: this.graph.id, settings, nodes, edges };
|
||||
console.log(serialized);
|
||||
const serialized = { id: this.graph.id, settings: this.settings, nodes, edges };
|
||||
logger.groupEnd();
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
|
||||
setSettings(settings: Record<string, unknown>) {
|
||||
this.settings = settings;
|
||||
this.save();
|
||||
this.execute();
|
||||
}
|
||||
|
||||
|
||||
execute() { }
|
||||
_execute() {
|
||||
if (this.loaded === false) return;
|
||||
@ -157,12 +163,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
||||
this.status.set("loading");
|
||||
this.id.set(graph.id);
|
||||
|
||||
if (graph.settings) {
|
||||
this.settings.set(graph.settings);
|
||||
} else {
|
||||
this.settings.set({});
|
||||
}
|
||||
|
||||
const nodeIds = Array.from(new Set([...graph.nodes.map(n => n.type)]));
|
||||
await this.nodeRegistry.load(nodeIds);
|
||||
|
||||
@ -178,19 +178,28 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
||||
node.tmp.type = nodeType;
|
||||
}
|
||||
|
||||
let settings: Record<string, NodeInput> = {};
|
||||
|
||||
// load settings
|
||||
const settingTypes: Record<string, NodeInput> = {};
|
||||
const settingValues = graph.settings || {};
|
||||
const types = this.getNodeTypes();
|
||||
for (const type of types) {
|
||||
if (type.inputs) {
|
||||
for (const key in type.inputs) {
|
||||
let settingId = type.inputs[key].setting;
|
||||
if (settingId) {
|
||||
settings[settingId] = type.inputs[key];
|
||||
settingTypes[settingId] = type.inputs[key];
|
||||
if (settingValues[settingId] === undefined && "value" in type.inputs[key]) {
|
||||
settingValues[settingId] = type.inputs[key].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.settings = settingValues;
|
||||
this.emit("settings", { types: settingTypes, values: settingValues });
|
||||
|
||||
this.history.reset();
|
||||
this._init(this.graph);
|
||||
|
||||
@ -426,6 +435,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
||||
save() {
|
||||
if (this.currentUndoGroup) return;
|
||||
const state = this.serialize();
|
||||
console.log(state);
|
||||
this.history.save(state);
|
||||
this.emit("save", state);
|
||||
logger.log("saving graphs", state);
|
||||
|
@ -1,25 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { Graph, NodeRegistry } from '@nodes/types';
|
||||
import GraphEl from './Graph.svelte';
|
||||
import { GraphManager } from '../graph-manager.js';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Graph, NodeRegistry } from "@nodes/types";
|
||||
import GraphEl from "./Graph.svelte";
|
||||
import { GraphManager } from "../graph-manager.js";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { debounce } from "$lib/helpers";
|
||||
|
||||
export let registry: NodeRegistry;
|
||||
export let graph: Graph;
|
||||
export let registry: NodeRegistry;
|
||||
export let graph: Graph;
|
||||
export let settings: Writable<Record<string, any>> | undefined;
|
||||
|
||||
const manager = new GraphManager(registry);
|
||||
const manager = new GraphManager(registry);
|
||||
|
||||
manager.on('result', (result) => {
|
||||
dispatch('result', result);
|
||||
});
|
||||
const updateSettings = debounce((s) => {
|
||||
manager.setSettings(s);
|
||||
}, 200);
|
||||
|
||||
manager.on('save', (save) => {
|
||||
dispatch('save', save);
|
||||
});
|
||||
$: if (settings && $settings) {
|
||||
updateSettings($settings);
|
||||
}
|
||||
|
||||
manager.load(graph);
|
||||
manager.on("settings", (settings) => {
|
||||
dispatch("settings", settings);
|
||||
});
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
manager.on("result", (result) => {
|
||||
dispatch("result", result);
|
||||
});
|
||||
|
||||
manager.on("save", (save) => {
|
||||
dispatch("save", save);
|
||||
});
|
||||
|
||||
manager.load(graph);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<GraphEl graph={manager} />
|
||||
|
@ -15,7 +15,7 @@ const nodeTypes: NodeType[] = [
|
||||
{
|
||||
id: "max/plantarium/math",
|
||||
inputs: {
|
||||
"op_type": { label: "type", type: "select", labels: ["add", "subtract", "multiply", "divide"], value: 0 },
|
||||
"op_type": { label: "type", type: "select", options: ["add", "subtract", "multiply", "divide"], value: 0 },
|
||||
"a": { type: "float" },
|
||||
"b": { type: "float" },
|
||||
},
|
||||
|
@ -89,7 +89,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
return [outputNode, nodes] as const;
|
||||
}
|
||||
|
||||
execute(graph: Graph) {
|
||||
execute(graph: Graph, settings: Record<string, unknown>) {
|
||||
|
||||
// Then we add some metadata to the graph
|
||||
const [outputNode, nodes] = this.addMetaData(graph);
|
||||
@ -124,6 +124,19 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input.setting) {
|
||||
if (settings[input.setting] === undefined) {
|
||||
if (input.value !== undefined) {
|
||||
inputs[key] = input.value;
|
||||
} else {
|
||||
console.warn(`Setting ${input.setting} is not defined`);
|
||||
}
|
||||
} else {
|
||||
inputs[key] = settings[input.setting] as number;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the input is connected to another node
|
||||
const inputNode = node.tmp.inputNodes?.[key];
|
||||
if (inputNode) {
|
||||
|
38
app/src/lib/settings/Panel.svelte
Normal file
38
app/src/lib/settings/Panel.svelte
Normal file
@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import type { NodeInput } from "@nodes/types";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { Input } from "@nodes/ui";
|
||||
|
||||
export let setting: {
|
||||
icon: string;
|
||||
id: string;
|
||||
definition: Record<string, NodeInput>;
|
||||
settings: Writable<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
const store = setting.settings;
|
||||
|
||||
const keys = setting?.definition
|
||||
? (Object.keys(
|
||||
setting.definition,
|
||||
) as unknown as (keyof typeof setting.definition)[])
|
||||
: [];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<h1 class="m-0">{setting.id}</h1>
|
||||
{#each keys as key}
|
||||
<div>
|
||||
{#if setting.definition && key in setting.definition}
|
||||
<Input
|
||||
id="test"
|
||||
input={setting.definition[key]}
|
||||
bind:value={$store[key]}
|
||||
></Input>
|
||||
{/if}
|
||||
<label for="test">
|
||||
{key}
|
||||
</label>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
120
app/src/lib/settings/Settings.svelte
Normal file
120
app/src/lib/settings/Settings.svelte
Normal file
@ -0,0 +1,120 @@
|
||||
<script lang="ts">
|
||||
import type { NodeInput } from "@nodes/types";
|
||||
import type { Writable } from "svelte/store";
|
||||
import SettingsComponent from "./Panel.svelte";
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
|
||||
export let settings: Record<
|
||||
string,
|
||||
{
|
||||
icon: string;
|
||||
id: string;
|
||||
definition: Record<string, NodeInput>;
|
||||
settings: Writable<Record<string, unknown>>;
|
||||
}
|
||||
>;
|
||||
|
||||
const activePanel = localStore<keyof typeof settings | false>(
|
||||
"nodes.settings.activePanel",
|
||||
false,
|
||||
);
|
||||
$: keys = Object.keys(settings) as unknown as (keyof typeof settings)[];
|
||||
|
||||
function setActivePanel(panel: keyof typeof settings | false) {
|
||||
if (panel === $activePanel) {
|
||||
$activePanel = false;
|
||||
} else if (panel) {
|
||||
$activePanel = panel;
|
||||
} else {
|
||||
$activePanel = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper" class:visible={$activePanel}>
|
||||
<div class="tabs">
|
||||
<button
|
||||
on:click={() => {
|
||||
setActivePanel($activePanel ? false : keys[0]);
|
||||
}}
|
||||
>
|
||||
<span class="absolute i-tabler-chevron-left w-6 h-6 block"></span>
|
||||
</button>
|
||||
{#each keys as panel (settings[panel].id)}
|
||||
<button
|
||||
class="tab"
|
||||
class:active={panel === $activePanel}
|
||||
on:click={() => setActivePanel(panel)}
|
||||
>
|
||||
<i class={`block w-6 h-6 ${settings[panel].icon}`} />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="content p-2">
|
||||
{#if $activePanel && settings[$activePanel]}
|
||||
{#key $activePanel}
|
||||
<SettingsComponent setting={settings[$activePanel]} />
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
top: 0px;
|
||||
position: absolute;
|
||||
display: grid;
|
||||
grid-template-columns: 30px 1fr;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
right: 0px;
|
||||
transform: translateX(calc(100% - 30px));
|
||||
transition:
|
||||
transform 0.2s,
|
||||
background 0.2s ease;
|
||||
width: 30%;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: solid thin white;
|
||||
}
|
||||
|
||||
.tabs > button {
|
||||
height: 30px;
|
||||
background: none;
|
||||
background: blue;
|
||||
color: white;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: solid thin white;
|
||||
border-left: solid thin white;
|
||||
}
|
||||
|
||||
.tabs > button.active {
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.visible .tabs > button {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.visible .tabs {
|
||||
margin-left: -1px;
|
||||
border-left: solid thin white;
|
||||
}
|
||||
|
||||
.visible > .tabs button:first-child {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.visible {
|
||||
transform: translateX(0);
|
||||
border-left: solid thin white;
|
||||
background: black;
|
||||
}
|
||||
</style>
|
37
app/src/lib/settings/app-settings.ts
Normal file
37
app/src/lib/settings/app-settings.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
|
||||
export const AppSettings = localStore("node-settings", {
|
||||
theme: 0,
|
||||
showGrid: true,
|
||||
wireframes: false,
|
||||
showIndices: false,
|
||||
});
|
||||
|
||||
AppSettings.subscribe((value) => {
|
||||
if (value.theme === 0) {
|
||||
document.body.classList.remove("theme-catppuccin");
|
||||
} else {
|
||||
document.body.classList.add("theme-catppuccin");
|
||||
}
|
||||
});
|
||||
|
||||
export const AppSettingTypes = {
|
||||
theme: {
|
||||
type: "select",
|
||||
options: ["dark", "cat"],
|
||||
value: "dark",
|
||||
},
|
||||
showGrid: {
|
||||
type: "boolean",
|
||||
value: true,
|
||||
},
|
||||
wireframe: {
|
||||
type: "boolean",
|
||||
value: false,
|
||||
},
|
||||
showIndices: {
|
||||
type: "boolean",
|
||||
value: false,
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const settings = writable({
|
||||
useHtml: false
|
||||
});
|
@ -3,6 +3,7 @@
|
||||
import { Text } from "@threlte/extras";
|
||||
import type { BufferGeometry } from "three";
|
||||
import { OrbitControls } from "@threlte/extras";
|
||||
import { AppSettings } from "../settings/app-settings";
|
||||
|
||||
export let geometry: BufferGeometry[];
|
||||
|
||||
@ -16,7 +17,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<T.GridHelper args={[20, 20]} />
|
||||
{#if $AppSettings.showGrid}
|
||||
<T.GridHelper args={[20, 20]} />
|
||||
{/if}
|
||||
|
||||
<T.PerspectiveCamera position={[-10, 10, 10]} makeDefault fov={50}>
|
||||
<OrbitControls />
|
||||
@ -26,7 +29,7 @@
|
||||
<T.AmbientLight intensity={0.5} />
|
||||
|
||||
{#each geometry as geo}
|
||||
{#if false}
|
||||
{#if $AppSettings.showIndices}
|
||||
{#each geo.attributes.position.array as _, i}
|
||||
{#if i % 3 === 0}
|
||||
<Text text={i / 3} fontSize={0.25} position={getPosition(geo, i)} />
|
||||
@ -39,6 +42,6 @@
|
||||
</T.Points>
|
||||
{/if}
|
||||
<T.Mesh geometry={geo}>
|
||||
<T.MeshStandardMaterial color="green" />
|
||||
<T.MeshStandardMaterial color="green" wireframe={$AppSettings.wireframe} />
|
||||
</T.Mesh>
|
||||
{/each}
|
||||
|
@ -38,8 +38,6 @@
|
||||
);
|
||||
index = index + vertexCount * 3;
|
||||
|
||||
console.log({ vertices, normals, indices });
|
||||
|
||||
// Add data to geometry
|
||||
geometry.setIndex([...indices]);
|
||||
geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3));
|
||||
@ -95,8 +93,6 @@
|
||||
$: if (result) {
|
||||
const inputs = parse_args(result);
|
||||
|
||||
console.log({ inputs });
|
||||
|
||||
geometries = inputs
|
||||
.map((input) => {
|
||||
if (input[0] === 1) {
|
||||
|
@ -1,5 +1,11 @@
|
||||
<script lang="ts">
|
||||
import "@nodes/ui/app.css";
|
||||
import "virtual:uno.css";
|
||||
import "@unocss/reset/normalize.css";
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
||||
{#if false}
|
||||
<span class="absolute i-tabler-settings w-6 h-6 block"></span>
|
||||
{/if}
|
||||
|
@ -5,48 +5,10 @@
|
||||
import { RemoteNodeRegistry } from "$lib/node-registry";
|
||||
import * as templates from "$lib/graph-templates";
|
||||
import type { Graph } from "@nodes/types";
|
||||
import { decode, encode, decodeFloat, encodeFloat } from "@nodes/utils";
|
||||
import Viewer from "$lib/viewer/Viewer.svelte";
|
||||
|
||||
globalThis.decode = decode;
|
||||
globalThis.encode = encode;
|
||||
globalThis.df = decodeFloat;
|
||||
globalThis.en = encodeFloat;
|
||||
|
||||
globalThis.ci = function createIndeces(resX: number, stemLength = 1) {
|
||||
const index = new Uint16Array(resX * (Math.max(stemLength, 1) - 1) * 6);
|
||||
|
||||
for (let i = 0; i < stemLength; i++) {
|
||||
const indexOffset = i * resX * 6;
|
||||
const positionOffset = i * resX;
|
||||
for (let j = 0; j < resX; j++) {
|
||||
const _indexOffset = indexOffset + j * 6;
|
||||
const _positionOffset = positionOffset + j;
|
||||
|
||||
console.log(`iio: ${_indexOffset} pio: ${_positionOffset} j: ${j}`);
|
||||
|
||||
if (j === resX - 1) {
|
||||
index[_indexOffset + 0] = _positionOffset + 1;
|
||||
index[_indexOffset + 1] = _positionOffset - resX + 1;
|
||||
index[_indexOffset + 2] = _positionOffset;
|
||||
|
||||
index[_indexOffset + 3] = _positionOffset;
|
||||
index[_indexOffset + 4] = _positionOffset + resX;
|
||||
index[_indexOffset + 5] = _positionOffset + 1;
|
||||
} else {
|
||||
index[_indexOffset + 0] = _positionOffset + resX + 1;
|
||||
index[_indexOffset + 1] = _positionOffset + 1;
|
||||
index[_indexOffset + 2] = _positionOffset;
|
||||
|
||||
index[_indexOffset + 3] = _positionOffset;
|
||||
index[_indexOffset + 4] = _positionOffset + resX;
|
||||
index[_indexOffset + 5] = _positionOffset + resX + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
};
|
||||
import Settings from "$lib/settings/Settings.svelte";
|
||||
import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings";
|
||||
import { get, writable } from "svelte/store";
|
||||
|
||||
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||
@ -60,7 +22,7 @@
|
||||
|
||||
function handleResult(event: CustomEvent<Graph>) {
|
||||
let a = performance.now();
|
||||
res = runtimeExecutor.execute(event.detail);
|
||||
res = runtimeExecutor.execute(event.detail, get(settings?.graph?.settings));
|
||||
time = performance.now() - a;
|
||||
console.log({ res, time });
|
||||
}
|
||||
@ -68,6 +30,32 @@
|
||||
function handleSave(event: CustomEvent<Graph>) {
|
||||
localStorage.setItem("graph", JSON.stringify(event.detail));
|
||||
}
|
||||
|
||||
let settings: Record<string, any> = {
|
||||
general: {
|
||||
id: "general",
|
||||
icon: "i-tabler-settings",
|
||||
settings: AppSettings,
|
||||
definition: AppSettingTypes,
|
||||
},
|
||||
};
|
||||
|
||||
function handleSettings(
|
||||
ev: CustomEvent<{
|
||||
values: Record<string, unknown>;
|
||||
types: Record<string, unknown>;
|
||||
}>,
|
||||
) {
|
||||
settings = {
|
||||
...settings,
|
||||
graph: {
|
||||
icon: "i-tabler-chart-bar",
|
||||
id: "graph",
|
||||
settings: writable(ev.detail.values),
|
||||
definition: ev.detail.types,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
@ -88,9 +76,12 @@
|
||||
<GraphInterface
|
||||
registry={nodeRegistry}
|
||||
{graph}
|
||||
settings={settings?.graph?.settings}
|
||||
on:settings={handleSettings}
|
||||
on:result={handleResult}
|
||||
on:save={handleSave}
|
||||
/>
|
||||
<Settings {settings}></Settings>
|
||||
{/key}
|
||||
</Grid.Cell>
|
||||
</Grid.Row>
|
||||
|
Reference in New Issue
Block a user