feat: implement settings
This commit is contained in:
parent
e7f43020dc
commit
36faeae886
@ -23,17 +23,20 @@
|
|||||||
"@threlte/extras": "^8.11.2",
|
"@threlte/extras": "^8.11.2",
|
||||||
"@threlte/flex": "^1.0.2",
|
"@threlte/flex": "^1.0.2",
|
||||||
"@types/three": "^0.163.0",
|
"@types/three": "^0.163.0",
|
||||||
|
"@unocss/reset": "^0.59.4",
|
||||||
"comlink": "^4.4.1",
|
"comlink": "^4.4.1",
|
||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
"three": "^0.163.0"
|
"three": "^0.163.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@histoire/plugin-svelte": "^0.17.17",
|
"@histoire/plugin-svelte": "^0.17.17",
|
||||||
|
"@iconify-json/tabler": "^1.1.110",
|
||||||
"@nodes/types": "link:../packages/types",
|
"@nodes/types": "link:../packages/types",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||||
"@tauri-apps/cli": "2.0.0-beta.3",
|
"@tauri-apps/cli": "2.0.0-beta.3",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
|
"@unocss/preset-icons": "^0.59.4",
|
||||||
"@zerodevx/svelte-json-view": "^1.0.9",
|
"@zerodevx/svelte-json-view": "^1.0.9",
|
||||||
"histoire": "^0.17.17",
|
"histoire": "^0.17.17",
|
||||||
"internal-ip": "^8.0.0",
|
"internal-ip": "^8.0.0",
|
||||||
@ -41,6 +44,7 @@
|
|||||||
"svelte-check": "^3.6.9",
|
"svelte-check": "^3.6.9",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
|
"unocss": "^0.59.4",
|
||||||
"vite": "^5.2.9",
|
"vite": "^5.2.9",
|
||||||
"vite-plugin-comlink": "^4.0.3",
|
"vite-plugin-comlink": "^4.0.3",
|
||||||
"vite-plugin-glsl": "^1.3.0",
|
"vite-plugin-glsl": "^1.3.0",
|
||||||
|
@ -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 type { Graph, Node, Edge, Socket, NodeRegistry, } from "@nodes/types";
|
||||||
import { HistoryManager } from "./history-manager.js"
|
import { HistoryManager } from "./history-manager.js"
|
||||||
import EventEmitter from "./helpers/EventEmitter.js";
|
import EventEmitter from "./helpers/EventEmitter.js";
|
||||||
@ -15,7 +15,7 @@ function areSocketsCompatible(output: string | undefined, inputs: string | strin
|
|||||||
return inputs === output;
|
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");
|
status: Writable<"loading" | "idle" | "error"> = writable("loading");
|
||||||
loaded = false;
|
loaded = false;
|
||||||
@ -25,8 +25,8 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
|||||||
|
|
||||||
private _nodes: Map<number, Node> = new Map();
|
private _nodes: Map<number, Node> = new Map();
|
||||||
nodes: Writable<Map<number, Node>> = writable(new Map());
|
nodes: Writable<Map<number, Node>> = writable(new Map());
|
||||||
settingTypes: NodeInput[] = [];
|
settingTypes: Record<string, NodeInput> = {};
|
||||||
settings: Writable<Record<string, any>> = writable({});
|
settings: Record<string, unknown> = {};
|
||||||
private _edges: Edge[] = [];
|
private _edges: Edge[] = [];
|
||||||
edges: Writable<Edge[]> = writable([]);
|
edges: Writable<Edge[]> = writable([]);
|
||||||
|
|
||||||
@ -61,14 +61,20 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
|||||||
props: node.props,
|
props: node.props,
|
||||||
})) as Node[];
|
})) as Node[];
|
||||||
const edges = this._edges.map(edge => [edge[0].id, edge[1], edge[2].id, edge[3]]) as Graph["edges"];
|
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: this.settings, nodes, edges };
|
||||||
const serialized = { id: this.graph.id, settings, nodes, edges };
|
|
||||||
console.log(serialized);
|
|
||||||
logger.groupEnd();
|
logger.groupEnd();
|
||||||
|
|
||||||
return serialized;
|
return serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setSettings(settings: Record<string, unknown>) {
|
||||||
|
this.settings = settings;
|
||||||
|
this.save();
|
||||||
|
this.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
execute() { }
|
execute() { }
|
||||||
_execute() {
|
_execute() {
|
||||||
if (this.loaded === false) return;
|
if (this.loaded === false) return;
|
||||||
@ -157,12 +163,6 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
|||||||
this.status.set("loading");
|
this.status.set("loading");
|
||||||
this.id.set(graph.id);
|
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)]));
|
const nodeIds = Array.from(new Set([...graph.nodes.map(n => n.type)]));
|
||||||
await this.nodeRegistry.load(nodeIds);
|
await this.nodeRegistry.load(nodeIds);
|
||||||
|
|
||||||
@ -178,18 +178,27 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
|||||||
node.tmp.type = nodeType;
|
node.tmp.type = nodeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
let settings: Record<string, NodeInput> = {};
|
|
||||||
|
// load settings
|
||||||
|
const settingTypes: Record<string, NodeInput> = {};
|
||||||
|
const settingValues = graph.settings || {};
|
||||||
const types = this.getNodeTypes();
|
const types = this.getNodeTypes();
|
||||||
for (const type of types) {
|
for (const type of types) {
|
||||||
if (type.inputs) {
|
if (type.inputs) {
|
||||||
for (const key in type.inputs) {
|
for (const key in type.inputs) {
|
||||||
let settingId = type.inputs[key].setting;
|
let settingId = type.inputs[key].setting;
|
||||||
if (settingId) {
|
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.history.reset();
|
||||||
this._init(this.graph);
|
this._init(this.graph);
|
||||||
@ -426,6 +435,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any }>
|
|||||||
save() {
|
save() {
|
||||||
if (this.currentUndoGroup) return;
|
if (this.currentUndoGroup) return;
|
||||||
const state = this.serialize();
|
const state = this.serialize();
|
||||||
|
console.log(state);
|
||||||
this.history.save(state);
|
this.history.save(state);
|
||||||
this.emit("save", state);
|
this.emit("save", state);
|
||||||
logger.log("saving graphs", state);
|
logger.log("saving graphs", state);
|
||||||
|
@ -1,20 +1,35 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Graph, NodeRegistry } from '@nodes/types';
|
import type { Graph, 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 } from 'svelte';
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
|
import { debounce } from "$lib/helpers";
|
||||||
|
|
||||||
export let registry: NodeRegistry;
|
export let registry: NodeRegistry;
|
||||||
export let graph: Graph;
|
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) => {
|
const updateSettings = debounce((s) => {
|
||||||
dispatch('result', result);
|
manager.setSettings(s);
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
$: if (settings && $settings) {
|
||||||
|
updateSettings($settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.on("settings", (settings) => {
|
||||||
|
dispatch("settings", settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.on('save', (save) => {
|
manager.on("result", (result) => {
|
||||||
dispatch('save', save);
|
dispatch("result", result);
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.on("save", (save) => {
|
||||||
|
dispatch("save", save);
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.load(graph);
|
manager.load(graph);
|
||||||
|
@ -15,7 +15,7 @@ const nodeTypes: NodeType[] = [
|
|||||||
{
|
{
|
||||||
id: "max/plantarium/math",
|
id: "max/plantarium/math",
|
||||||
inputs: {
|
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" },
|
"a": { type: "float" },
|
||||||
"b": { type: "float" },
|
"b": { type: "float" },
|
||||||
},
|
},
|
||||||
|
@ -89,7 +89,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
return [outputNode, nodes] as const;
|
return [outputNode, nodes] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(graph: Graph) {
|
execute(graph: Graph, settings: Record<string, unknown>) {
|
||||||
|
|
||||||
// Then we add some metadata to the graph
|
// Then we add some metadata to the graph
|
||||||
const [outputNode, nodes] = this.addMetaData(graph);
|
const [outputNode, nodes] = this.addMetaData(graph);
|
||||||
@ -124,6 +124,19 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
continue;
|
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
|
// check if the input is connected to another node
|
||||||
const inputNode = node.tmp.inputNodes?.[key];
|
const inputNode = node.tmp.inputNodes?.[key];
|
||||||
if (inputNode) {
|
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 { Text } from "@threlte/extras";
|
||||||
import type { BufferGeometry } from "three";
|
import type { BufferGeometry } from "three";
|
||||||
import { OrbitControls } from "@threlte/extras";
|
import { OrbitControls } from "@threlte/extras";
|
||||||
|
import { AppSettings } from "../settings/app-settings";
|
||||||
|
|
||||||
export let geometry: BufferGeometry[];
|
export let geometry: BufferGeometry[];
|
||||||
|
|
||||||
@ -16,7 +17,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<T.GridHelper args={[20, 20]} />
|
{#if $AppSettings.showGrid}
|
||||||
|
<T.GridHelper args={[20, 20]} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<T.PerspectiveCamera position={[-10, 10, 10]} makeDefault fov={50}>
|
<T.PerspectiveCamera position={[-10, 10, 10]} makeDefault fov={50}>
|
||||||
<OrbitControls />
|
<OrbitControls />
|
||||||
@ -26,7 +29,7 @@
|
|||||||
<T.AmbientLight intensity={0.5} />
|
<T.AmbientLight intensity={0.5} />
|
||||||
|
|
||||||
{#each geometry as geo}
|
{#each geometry as geo}
|
||||||
{#if false}
|
{#if $AppSettings.showIndices}
|
||||||
{#each geo.attributes.position.array as _, i}
|
{#each geo.attributes.position.array as _, i}
|
||||||
{#if i % 3 === 0}
|
{#if i % 3 === 0}
|
||||||
<Text text={i / 3} fontSize={0.25} position={getPosition(geo, i)} />
|
<Text text={i / 3} fontSize={0.25} position={getPosition(geo, i)} />
|
||||||
@ -39,6 +42,6 @@
|
|||||||
</T.Points>
|
</T.Points>
|
||||||
{/if}
|
{/if}
|
||||||
<T.Mesh geometry={geo}>
|
<T.Mesh geometry={geo}>
|
||||||
<T.MeshStandardMaterial color="green" />
|
<T.MeshStandardMaterial color="green" wireframe={$AppSettings.wireframe} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -38,8 +38,6 @@
|
|||||||
);
|
);
|
||||||
index = index + vertexCount * 3;
|
index = index + vertexCount * 3;
|
||||||
|
|
||||||
console.log({ vertices, normals, indices });
|
|
||||||
|
|
||||||
// Add data to geometry
|
// Add data to geometry
|
||||||
geometry.setIndex([...indices]);
|
geometry.setIndex([...indices]);
|
||||||
geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3));
|
geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3));
|
||||||
@ -95,8 +93,6 @@
|
|||||||
$: if (result) {
|
$: if (result) {
|
||||||
const inputs = parse_args(result);
|
const inputs = parse_args(result);
|
||||||
|
|
||||||
console.log({ inputs });
|
|
||||||
|
|
||||||
geometries = inputs
|
geometries = inputs
|
||||||
.map((input) => {
|
.map((input) => {
|
||||||
if (input[0] === 1) {
|
if (input[0] === 1) {
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@nodes/ui/app.css";
|
import "@nodes/ui/app.css";
|
||||||
|
import "virtual:uno.css";
|
||||||
|
import "@unocss/reset/normalize.css";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
<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 { RemoteNodeRegistry } from "$lib/node-registry";
|
||||||
import * as templates from "$lib/graph-templates";
|
import * as templates from "$lib/graph-templates";
|
||||||
import type { Graph } from "@nodes/types";
|
import type { Graph } from "@nodes/types";
|
||||||
import { decode, encode, decodeFloat, encodeFloat } from "@nodes/utils";
|
|
||||||
import Viewer from "$lib/viewer/Viewer.svelte";
|
import Viewer from "$lib/viewer/Viewer.svelte";
|
||||||
|
import Settings from "$lib/settings/Settings.svelte";
|
||||||
globalThis.decode = decode;
|
import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings";
|
||||||
globalThis.encode = encode;
|
import { get, writable } from "svelte/store";
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
||||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||||
@ -60,7 +22,7 @@
|
|||||||
|
|
||||||
function handleResult(event: CustomEvent<Graph>) {
|
function handleResult(event: CustomEvent<Graph>) {
|
||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
res = runtimeExecutor.execute(event.detail);
|
res = runtimeExecutor.execute(event.detail, get(settings?.graph?.settings));
|
||||||
time = performance.now() - a;
|
time = performance.now() - a;
|
||||||
console.log({ res, time });
|
console.log({ res, time });
|
||||||
}
|
}
|
||||||
@ -68,6 +30,32 @@
|
|||||||
function handleSave(event: CustomEvent<Graph>) {
|
function handleSave(event: CustomEvent<Graph>) {
|
||||||
localStorage.setItem("graph", JSON.stringify(event.detail));
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
@ -88,9 +76,12 @@
|
|||||||
<GraphInterface
|
<GraphInterface
|
||||||
registry={nodeRegistry}
|
registry={nodeRegistry}
|
||||||
{graph}
|
{graph}
|
||||||
|
settings={settings?.graph?.settings}
|
||||||
|
on:settings={handleSettings}
|
||||||
on:result={handleResult}
|
on:result={handleResult}
|
||||||
on:save={handleSave}
|
on:save={handleSave}
|
||||||
/>
|
/>
|
||||||
|
<Settings {settings}></Settings>
|
||||||
{/key}
|
{/key}
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
11
app/uno.config.ts
Normal file
11
app/uno.config.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// uno.config.ts
|
||||||
|
import { defineConfig } from 'unocss'
|
||||||
|
import presetIcons from '@unocss/preset-icons'
|
||||||
|
import { presetUno } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetUno(),
|
||||||
|
presetIcons(),
|
||||||
|
]
|
||||||
|
})
|
@ -3,10 +3,12 @@ import { defineConfig } from 'vite'
|
|||||||
import glsl from "vite-plugin-glsl";
|
import glsl from "vite-plugin-glsl";
|
||||||
import wasm from "vite-plugin-wasm";
|
import wasm from "vite-plugin-wasm";
|
||||||
import comlink from 'vite-plugin-comlink';
|
import comlink from 'vite-plugin-comlink';
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
comlink(),
|
comlink(),
|
||||||
|
UnoCSS(),
|
||||||
sveltekit(),
|
sveltekit(),
|
||||||
glsl(),
|
glsl(),
|
||||||
wasm()
|
wasm()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"op_type": {
|
"op_type": {
|
||||||
"label": "type",
|
"label": "type",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"labels": [
|
"options": [
|
||||||
"add",
|
"add",
|
||||||
"subtract",
|
"subtract",
|
||||||
"multiply",
|
"multiply",
|
||||||
@ -22,11 +22,6 @@
|
|||||||
"b": {
|
"b": {
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"value": 2
|
"value": 2
|
||||||
},
|
|
||||||
"clip": {
|
|
||||||
"type": "boolean",
|
|
||||||
"value": 0,
|
|
||||||
"setting": "math.clipping"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,13 @@
|
|||||||
"model"
|
"model"
|
||||||
],
|
],
|
||||||
"external": true
|
"external": true
|
||||||
|
},
|
||||||
|
"resolution_circle": {
|
||||||
|
"type": "integer",
|
||||||
|
"value": 32,
|
||||||
|
"min": 3,
|
||||||
|
"max": 64,
|
||||||
|
"setting": "resolution.circle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use glam::{Mat4, Vec3};
|
use glam::{Mat4, Vec3};
|
||||||
use macros::include_definition_file;
|
use macros::include_definition_file;
|
||||||
use utils::{
|
use utils::{
|
||||||
concat_args,
|
concat_args, evaluate_arg,
|
||||||
geometry::{extrude_path, transform_geometry},
|
geometry::{extrude_path, transform_geometry},
|
||||||
get_args,
|
get_args, log,
|
||||||
};
|
};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
@ -15,15 +15,20 @@ pub fn execute(input: Vec<i32>) -> Vec<i32> {
|
|||||||
|
|
||||||
let args = get_args(input.as_slice());
|
let args = get_args(input.as_slice());
|
||||||
|
|
||||||
|
let inputs = get_args(args[0]);
|
||||||
|
let resolution = evaluate_arg(args[1]) as usize;
|
||||||
|
|
||||||
|
log!("inputs: {}, resolution: {}", inputs.len(), resolution);
|
||||||
|
|
||||||
let mut output: Vec<Vec<i32>> = Vec::new();
|
let mut output: Vec<Vec<i32>> = Vec::new();
|
||||||
for arg in args {
|
for arg in inputs {
|
||||||
if arg.len() < 3 {
|
if arg.len() < 3 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if arg[2] == 0 {
|
if arg[2] == 0 {
|
||||||
let _arg = &arg[3..];
|
let _arg = &arg[3..];
|
||||||
let mut geometry = extrude_path(_arg, 4);
|
let mut geometry = extrude_path(_arg, resolution);
|
||||||
let matrix = Mat4::from_translation(Vec3::new(0.0, 0.0, 0.0));
|
let matrix = Mat4::from_translation(Vec3::new(0.0, 0.0, 0.0));
|
||||||
geometry = transform_geometry(geometry, matrix);
|
geometry = transform_geometry(geometry, matrix);
|
||||||
output.push(geometry);
|
output.push(geometry);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
"element": "slider",
|
"element": "slider",
|
||||||
"value": 2
|
"value": 2
|
||||||
},
|
},
|
||||||
"resolution": {
|
"resolution_curve": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"value": 32,
|
"value": 32,
|
||||||
"setting": "resolution.stem"
|
"min": 3,
|
||||||
|
"max": 64,
|
||||||
|
"setting": "resolution.curve"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use macros::include_definition_file;
|
use macros::include_definition_file;
|
||||||
use utils::{evaluate_float, evaluate_vec3, get_args, log, set_panic_hook, wrap_arg};
|
use utils::{evaluate_arg, evaluate_float, evaluate_vec3, get_args, set_panic_hook, wrap_arg};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
include_definition_file!("src/input.json");
|
include_definition_file!("src/input.json");
|
||||||
@ -10,18 +10,13 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
|
|
||||||
let args = get_args(input);
|
let args = get_args(input);
|
||||||
|
|
||||||
log!("Args: {:?}", args);
|
|
||||||
|
|
||||||
let origin = evaluate_vec3(args[0]);
|
let origin = evaluate_vec3(args[0]);
|
||||||
log!("Origin: {:?}", origin);
|
|
||||||
let length = evaluate_float(args[1]);
|
let length = evaluate_float(args[1]);
|
||||||
let thickness = evaluate_float(args[2]);
|
let thickness = evaluate_float(args[2]);
|
||||||
let resolution = 16;
|
let res_curve = evaluate_arg(args[3]) as usize;
|
||||||
|
|
||||||
let mut path: Vec<i32> = vec![0; resolution * 4 + 1];
|
let mut path: Vec<i32> = vec![0; res_curve * 4 + 1];
|
||||||
path.resize(resolution * 4 + 1, 0);
|
path.resize(res_curve * 4 + 1, 0);
|
||||||
|
|
||||||
path[0] = 0;
|
|
||||||
|
|
||||||
let slice = &mut path[1..];
|
let slice = &mut path[1..];
|
||||||
|
|
||||||
@ -34,8 +29,8 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
std::slice::from_raw_parts_mut(slice.as_ptr() as *mut f32, slice.len())
|
std::slice::from_raw_parts_mut(slice.as_ptr() as *mut f32, slice.len())
|
||||||
};
|
};
|
||||||
|
|
||||||
for i in 0..resolution {
|
for i in 0..res_curve {
|
||||||
let a = i as f32 / resolution as f32;
|
let a = i as f32 / (res_curve - 1) as f32;
|
||||||
path_p[i * 4] = origin[0] + (a * 8.0).sin() * 0.2;
|
path_p[i * 4] = origin[0] + (a * 8.0).sin() * 0.2;
|
||||||
path_p[i * 4 + 1] = origin[1] + a * length;
|
path_p[i * 4 + 1] = origin[1] + a * length;
|
||||||
path_p[i * 4 + 2] = origin[2] + 0.0;
|
path_p[i * 4 + 2] = origin[2] + 0.0;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wasm-pack build --release --out-name index --no-default-features",
|
"build": "wasm-pack build --release --out-name index --no-default-features",
|
||||||
"dev": "cargo watch -s 'pnpm build'"
|
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ export interface RuntimeExecutor {
|
|||||||
* @param graph - The graph to execute
|
* @param graph - The graph to execute
|
||||||
* @returns The result of the execution
|
* @returns The result of the execution
|
||||||
*/
|
*/
|
||||||
execute: (graph: Graph) => unknown;
|
execute: (graph: Graph, settings: Record<string, unknown>) => unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ type NodeInputBoolean = {
|
|||||||
|
|
||||||
type NodeInputSelect = {
|
type NodeInputSelect = {
|
||||||
type: "select";
|
type: "select";
|
||||||
labels: string[];
|
options: string[];
|
||||||
value?: number;
|
value?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ pub struct NodeInputBoolean {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct NodeInputSelect {
|
pub struct NodeInputSelect {
|
||||||
pub labels: Vec<String>,
|
pub options: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub value: Option<usize>,
|
pub value: Option<usize>,
|
||||||
}
|
}
|
||||||
@ -130,4 +130,3 @@ pub struct NodeType {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub outputs: Option<Vec<String>>,
|
pub outputs: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,11 +12,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if input.type === "float"}
|
{#if input.type === "float"}
|
||||||
<Float {id} bind:value />
|
<Float {id} bind:value min={input?.min} max={input?.max} />
|
||||||
{:else if input.type === "integer"}
|
{:else if input.type === "integer"}
|
||||||
<Integer {id} bind:value />
|
<Integer {id} bind:value min={input?.min} max={input?.max} />
|
||||||
{:else if input.type === "boolean"}
|
{:else if input.type === "boolean"}
|
||||||
<Checkbox {id} bind:value />
|
<Checkbox {id} bind:value />
|
||||||
{:else if input.type === "select"}
|
{:else if input.type === "select"}
|
||||||
<Select {id} bind:value labels={input.labels} />
|
<Select {id} bind:value options={input.options} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -49,6 +49,8 @@ body {
|
|||||||
--background-color-darker: #101010;
|
--background-color-darker: #101010;
|
||||||
--text-color: #aeaeae;
|
--text-color: #aeaeae;
|
||||||
|
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
background-color: var(--background-color-darker);
|
background-color: var(--background-color-darker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,60 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let value: boolean;
|
export let value: boolean;
|
||||||
|
|
||||||
|
$: if (typeof value === "string") {
|
||||||
|
value = value === "true";
|
||||||
|
} else if (typeof value === "number") {
|
||||||
|
value = value === 1;
|
||||||
|
}
|
||||||
|
|
||||||
export let id: string;
|
export let id: string;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input {id} type="checkbox" bind:checked={value} />
|
<input {id} type="checkbox" bind:checked={value} />
|
||||||
|
|
||||||
<style>
|
|
||||||
input[type="checkbox"] {
|
|
||||||
/* Add if not using autoprefixer */
|
|
||||||
-webkit-appearance: none;
|
|
||||||
/* Remove most all native input styles */
|
|
||||||
appearance: none;
|
|
||||||
/* For iOS < 15 */
|
|
||||||
background-color: var(--form-background);
|
|
||||||
/* Not removed via appearance */
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
font: inherit;
|
|
||||||
color: currentColor;
|
|
||||||
width: 1.15em;
|
|
||||||
height: 1.15em;
|
|
||||||
border: 0.15em solid currentColor;
|
|
||||||
border-radius: 0.15em;
|
|
||||||
transform: translateY(-0.075em);
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"]::before {
|
|
||||||
content: "";
|
|
||||||
width: 0.65em;
|
|
||||||
height: 0.65em;
|
|
||||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
|
||||||
transform: scale(0);
|
|
||||||
transform-origin: bottom left;
|
|
||||||
transition: 120ms transform ease-in-out;
|
|
||||||
box-shadow: inset 1em 1em var(--form-control-color);
|
|
||||||
/* Windows High Contrast Mode */
|
|
||||||
background-color: CanvasText;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"]:checked::before {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"]:focus {
|
|
||||||
outline: max(2px, 0.15em) solid currentColor;
|
|
||||||
outline-offset: max(2px, 0.15em);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"]:disabled {
|
|
||||||
--form-control-color: var(--form-control-disabled);
|
|
||||||
|
|
||||||
color: var(--form-control-disabled);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,20 +1,169 @@
|
|||||||
|
<svelte:options accessors />
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let value: number = 0;
|
import { createEventDispatcher } from "svelte";
|
||||||
export let min = 0;
|
const dispatch = createEventDispatcher();
|
||||||
export let max = 10;
|
|
||||||
|
// Styling
|
||||||
|
export let min: number | undefined = undefined;
|
||||||
|
export let max: number | undefined = undefined;
|
||||||
export let step = 1;
|
export let step = 1;
|
||||||
export let id: string;
|
export let value = 0;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEl: HTMLInputElement;
|
||||||
|
let wrapper: HTMLDivElement;
|
||||||
|
$: value !== undefined && update();
|
||||||
|
|
||||||
|
let prev = -1;
|
||||||
|
function update() {
|
||||||
|
if (prev === value) return;
|
||||||
|
prev = value;
|
||||||
|
dispatch("change", parseFloat(value + ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
$: width = Number.isFinite(value)
|
||||||
|
? Math.max((value?.toString().length ?? 1) * 8, 30) + "px"
|
||||||
|
: "20px";
|
||||||
|
|
||||||
|
function handleChange(change: number) {
|
||||||
|
value = Math.max(
|
||||||
|
min ?? -Infinity,
|
||||||
|
Math.min(+value + change, max ?? Infinity),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let downX = 0;
|
||||||
|
let downV = 0;
|
||||||
|
let rect: DOMRect;
|
||||||
|
|
||||||
|
function handleMouseDown(ev: MouseEvent) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
downV = value;
|
||||||
|
downX = ev.clientX;
|
||||||
|
rect = wrapper.getBoundingClientRect();
|
||||||
|
|
||||||
|
window.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
window.addEventListener("mousemove", handleMouseMove);
|
||||||
|
window.addEventListener("mouseup", handleMouseUp);
|
||||||
|
document.body.style.cursor = "ew-resize";
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseUp() {
|
||||||
|
if (downV === value) {
|
||||||
|
inputEl.focus();
|
||||||
|
} else {
|
||||||
|
inputEl.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.style.cursor = "unset";
|
||||||
|
window.removeEventListener("mouseup", handleMouseUp);
|
||||||
|
window.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseMove(ev: MouseEvent) {
|
||||||
|
if (!ev.ctrlKey && typeof min === "number" && typeof max === "number") {
|
||||||
|
const vx = (ev.clientX - rect.left) / rect.width;
|
||||||
|
value = Math.max(Math.min(Math.floor(min + (max - min) * vx), max), min);
|
||||||
|
} else {
|
||||||
|
const vx = ev.clientX - downX;
|
||||||
|
value = downV + Math.floor(vx / 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input {id} type="number" bind:value {min} {max} {step} />
|
<div
|
||||||
|
class="component-wrapper"
|
||||||
|
bind:this={wrapper}
|
||||||
|
role="slider"
|
||||||
|
tabindex="0"
|
||||||
|
aria-valuenow={value}
|
||||||
|
on:mousedown={handleMouseDown}
|
||||||
|
on:mouseup={handleMouseUp}
|
||||||
|
>
|
||||||
|
{#if typeof min !== "undefined" && typeof max !== "undefined"}
|
||||||
|
<span
|
||||||
|
class="overlay"
|
||||||
|
style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<button on:click={() => handleChange(-step)}>-</button>
|
||||||
|
<input
|
||||||
|
bind:value
|
||||||
|
bind:this={inputEl}
|
||||||
|
{step}
|
||||||
|
{max}
|
||||||
|
{min}
|
||||||
|
type="number"
|
||||||
|
style={`width:${width};`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button on:click={() => handleChange(+step)}>+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
input {
|
.component-wrapper {
|
||||||
background: var(--background-color-lighter);
|
position: relative;
|
||||||
color: var(--text-color);
|
display: flex;
|
||||||
|
background-color: var(--background-color-lighter, #4b4b4b);
|
||||||
|
border-radius: 2px;
|
||||||
|
user-select: none;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
outline: none !important;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--border-radius, 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
font-family: var(--font-family);
|
font-family: var(--font-family);
|
||||||
padding: 0.8em 1em;
|
padding-top: 8px;
|
||||||
border-radius: 5px;
|
flex: 1;
|
||||||
|
width: 72%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--text-color);
|
||||||
|
opacity: 0.3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 0px;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-inline: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div input[type="number"] {
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: transparent;
|
||||||
|
padding: var(--padding, 6px);
|
||||||
|
padding-inline: 0px;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
border-style: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let labels: string[] = [];
|
export let options: string[] = [];
|
||||||
export let value: number = 0;
|
export let value: number = 0;
|
||||||
export let id: string;
|
export let id: string;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<select {id} bind:value>
|
<select {id} bind:value>
|
||||||
{#each labels as label, i}
|
{#each options as label, i}
|
||||||
<option value={i}>{label}</option>
|
<option value={i}>{label}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use crate::log;
|
||||||
|
|
||||||
use super::{create_geometry_data, wrap_geometry_data};
|
use super::{create_geometry_data, wrap_geometry_data};
|
||||||
use glam::{Mat4, Quat, Vec3};
|
use glam::{Mat4, Quat, Vec3};
|
||||||
|
|
||||||
@ -65,9 +67,11 @@ pub fn extrude_path(input_path: &[i32], res_x: usize) -> Vec<i32> {
|
|||||||
let i_index_offset = index_offset + j * 6;
|
let i_index_offset = index_offset + j * 6;
|
||||||
let i_position_offset = position_offset + j;
|
let i_position_offset = position_offset + j;
|
||||||
|
|
||||||
|
log!("i: {}, j: {}, i_index_offset: {}, i_position_offset: {} res_x: {}", i, j, i_index_offset, i_position_offset,res_x);
|
||||||
|
|
||||||
if j == res_x - 1 {
|
if j == res_x - 1 {
|
||||||
indices[i_index_offset ] = (i_position_offset + 1) as i32;
|
indices[i_index_offset ] = (i_position_offset + 1) as i32;
|
||||||
indices[i_index_offset + 1] = (i_position_offset - res_x + 1) as i32;
|
indices[i_index_offset + 1] = (i_position_offset + 1 - res_x) as i32;
|
||||||
indices[i_index_offset + 2] = (i_position_offset) as i32;
|
indices[i_index_offset + 2] = (i_position_offset) as i32;
|
||||||
indices[i_index_offset + 3] = (i_position_offset) as i32;
|
indices[i_index_offset + 3] = (i_position_offset) as i32;
|
||||||
indices[i_index_offset + 4] = (i_position_offset + res_x) as i32;
|
indices[i_index_offset + 4] = (i_position_offset + res_x) as i32;
|
||||||
|
@ -7,6 +7,7 @@ pub use helpers::*;
|
|||||||
pub use tree::*;
|
pub use tree::*;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! log {
|
macro_rules! log {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
@ -15,6 +16,14 @@ macro_rules! log {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
// This will expand to nothing in release builds
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_panic_hook() {
|
pub fn set_panic_hook() {
|
||||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
// `set_panic_hook` function at least once during initialization, and then
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
|
853
pnpm-lock.yaml
853
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user