chore: setup linting
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import NestedSettings from "./NestedSettings.svelte";
|
||||
import { localState } from "$lib/helpers/localState.svelte";
|
||||
import type { NodeInput } from "@nodarium/types";
|
||||
import Input from "@nodarium/ui";
|
||||
import { localState } from '$lib/helpers/localState.svelte';
|
||||
import type { NodeInput } from '@nodarium/types';
|
||||
import Input from '@nodarium/ui';
|
||||
import { onMount } from 'svelte';
|
||||
import NestedSettings from './NestedSettings.svelte';
|
||||
|
||||
type Button = { type: "button"; callback: () => void; label?: string };
|
||||
type Button = { type: 'button'; label?: string };
|
||||
|
||||
type InputType = NodeInput | Button;
|
||||
|
||||
@@ -12,7 +13,7 @@
|
||||
|
||||
interface SettingsGroup {
|
||||
title?: string;
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
type SettingsType = Record<string, SettingsNode>;
|
||||
@@ -31,44 +32,49 @@
|
||||
};
|
||||
|
||||
// Local persistent state for <details> sections
|
||||
const openSections = localState<Record<string, boolean>>("open-details", {});
|
||||
const openSections = localState<Record<string, boolean>>('open-details', {});
|
||||
|
||||
let { id, key = "", value = $bindable(), type, depth = 0 }: Props = $props();
|
||||
let { id, key = '', value = $bindable(), type, depth = 0 }: Props = $props();
|
||||
|
||||
function isNodeInput(v: SettingsNode | undefined): v is InputType {
|
||||
return !!v && typeof v === "object" && "type" in v;
|
||||
return !!v && typeof v === 'object' && 'type' in v;
|
||||
}
|
||||
|
||||
function getDefaultValue(): unknown {
|
||||
if (key === "" || key === "title") return;
|
||||
function getDefaultValue(): NodeInput['value'] | undefined {
|
||||
if (key === '' || key === 'title') return;
|
||||
|
||||
const node = type[key];
|
||||
const node = type[key] as SettingsNode;
|
||||
const inputValue = value[key];
|
||||
|
||||
if (!isNodeInput(node)) return;
|
||||
|
||||
const anyNode = node as any;
|
||||
|
||||
// select input: use index into options
|
||||
if (Array.isArray(anyNode.options)) {
|
||||
if (value?.[key] !== undefined) {
|
||||
return anyNode.options.indexOf(value[key]);
|
||||
if ('options' in node && Array.isArray(node.options)) {
|
||||
if (typeof inputValue === 'string') {
|
||||
return node.options.indexOf(inputValue);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (value?.[key] !== undefined) return value[key];
|
||||
// If the component is supplied with a default value use that
|
||||
if (inputValue !== undefined && typeof inputValue !== 'object') {
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
if ("value" in node && anyNode.value !== undefined) {
|
||||
return anyNode.value;
|
||||
if ('value' in node) {
|
||||
const nodeValue = node.value;
|
||||
if (nodeValue !== null && nodeValue !== undefined) {
|
||||
return nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "boolean":
|
||||
case 'boolean':
|
||||
return 0;
|
||||
case "float":
|
||||
case 'float':
|
||||
return 0.5;
|
||||
case "integer":
|
||||
case "select":
|
||||
case 'integer':
|
||||
case 'select':
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
@@ -77,52 +83,63 @@
|
||||
|
||||
let internalValue = $state(getDefaultValue());
|
||||
|
||||
let open = $state(openSections.value[id]);
|
||||
|
||||
// Persist <details> open/closed state for groups
|
||||
if (depth > 0 && !isNodeInput(type[key!])) {
|
||||
$effect(() => {
|
||||
if (open !== undefined) {
|
||||
openSections.value[id] = open;
|
||||
}
|
||||
});
|
||||
}
|
||||
let open = $state(false);
|
||||
|
||||
// Sync internalValue back into `value`
|
||||
$effect(() => {
|
||||
if (key === "" || internalValue === undefined) return;
|
||||
if (key === '' || internalValue === undefined) return;
|
||||
|
||||
const node = type[key];
|
||||
|
||||
if (
|
||||
isNodeInput(node) &&
|
||||
Array.isArray((node as any).options) &&
|
||||
typeof internalValue === "number"
|
||||
isNodeInput(node)
|
||||
&& 'options' in node
|
||||
&& Array.isArray(node.options)
|
||||
&& typeof internalValue === 'number'
|
||||
) {
|
||||
value[key] = (node as any)?.options?.[internalValue] as any;
|
||||
} else {
|
||||
value[key] = internalValue as any;
|
||||
value[key] = node?.options?.[internalValue];
|
||||
} else if (internalValue) {
|
||||
value[key] = internalValue;
|
||||
}
|
||||
});
|
||||
|
||||
function handleClick() {
|
||||
const callback = value[key] as unknown as () => void;
|
||||
callback();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
open = openSections.value[id];
|
||||
|
||||
// Persist <details> open/closed state for groups
|
||||
if (depth > 0 && !isNodeInput(type[key!])) {
|
||||
$effect(() => {
|
||||
if (open !== undefined) {
|
||||
openSections.value[id] = open;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if key && isNodeInput(type?.[key])}
|
||||
{@const inputType = type[key]}
|
||||
<!-- Leaf input -->
|
||||
<div class="input input-{type[key].type}" class:first-level={depth === 1}>
|
||||
{#if type[key].type === "button"}
|
||||
<button onclick={() => "callback" in type[key] && type[key].callback()}>
|
||||
{type[key].label || key}
|
||||
<div class="input input-{inputType.type}" class:first-level={depth === 1}>
|
||||
{#if inputType.type === 'button'}
|
||||
<button onclick={handleClick}>
|
||||
{inputType.label || key}
|
||||
</button>
|
||||
{:else}
|
||||
{#if type[key].label !== ""}
|
||||
<label for={id}>{type[key].label || key}</label>
|
||||
{#if inputType.label !== ''}
|
||||
<label for={id}>{inputType.label || key}</label>
|
||||
{/if}
|
||||
<Input {id} input={type[key]} bind:value={internalValue} />
|
||||
<Input {id} input={inputType} bind:value={internalValue} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if depth === 0}
|
||||
<!-- Root: iterate over top-level keys -->
|
||||
{#each Object.keys(type ?? {}).filter((k) => k !== "title") as childKey}
|
||||
{#each Object.keys(type ?? {}).filter((k) => k !== 'title') as childKey (childKey)}
|
||||
<NestedSettings
|
||||
id={`${id}.${childKey}`}
|
||||
key={childKey}
|
||||
@@ -140,7 +157,7 @@
|
||||
<details bind:open>
|
||||
<summary><p>{(type[key] as SettingsGroup).title || key}</p></summary>
|
||||
<div class="content">
|
||||
{#each Object.keys(type[key] as SettingsGroup).filter((k) => k !== "title") as childKey}
|
||||
{#each Object.keys(type[key] as SettingsGroup).filter((k) => k !== 'title') as childKey (childKey)}
|
||||
<NestedSettings
|
||||
id={`${id}.${childKey}`}
|
||||
key={childKey}
|
||||
|
||||
@@ -1,169 +1,163 @@
|
||||
import { localState } from "$lib/helpers/localState.svelte";
|
||||
import { localState } from '$lib/helpers/localState.svelte';
|
||||
|
||||
const themes = [
|
||||
"dark",
|
||||
"light",
|
||||
"catppuccin",
|
||||
"solarized",
|
||||
"high-contrast",
|
||||
"nord",
|
||||
"dracula",
|
||||
'dark',
|
||||
'light',
|
||||
'catppuccin',
|
||||
'solarized',
|
||||
'high-contrast',
|
||||
'nord',
|
||||
'dracula'
|
||||
] as const;
|
||||
|
||||
export const AppSettingTypes = {
|
||||
theme: {
|
||||
type: "select",
|
||||
type: 'select',
|
||||
options: themes,
|
||||
label: "Theme",
|
||||
value: themes[0],
|
||||
label: 'Theme',
|
||||
value: themes[0]
|
||||
},
|
||||
showGrid: {
|
||||
type: "boolean",
|
||||
label: "Show Grid",
|
||||
value: true,
|
||||
type: 'boolean',
|
||||
label: 'Show Grid',
|
||||
value: true
|
||||
},
|
||||
centerCamera: {
|
||||
type: "boolean",
|
||||
label: "Center Camera",
|
||||
value: true,
|
||||
type: 'boolean',
|
||||
label: 'Center Camera',
|
||||
value: true
|
||||
},
|
||||
nodeInterface: {
|
||||
title: "Node Interface",
|
||||
title: 'Node Interface',
|
||||
showNodeGrid: {
|
||||
type: "boolean",
|
||||
label: "Show Grid",
|
||||
value: true,
|
||||
type: 'boolean',
|
||||
label: 'Show Grid',
|
||||
value: true
|
||||
},
|
||||
snapToGrid: {
|
||||
type: "boolean",
|
||||
label: "Snap to Grid",
|
||||
value: true,
|
||||
type: 'boolean',
|
||||
label: 'Snap to Grid',
|
||||
value: true
|
||||
},
|
||||
showHelp: {
|
||||
type: "boolean",
|
||||
label: "Show Help",
|
||||
value: false,
|
||||
},
|
||||
type: 'boolean',
|
||||
label: 'Show Help',
|
||||
value: false
|
||||
}
|
||||
},
|
||||
debug: {
|
||||
title: "Debug",
|
||||
title: 'Debug',
|
||||
wireframe: {
|
||||
type: "boolean",
|
||||
label: "Wireframe",
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
label: 'Wireframe',
|
||||
value: false
|
||||
},
|
||||
useWorker: {
|
||||
type: "boolean",
|
||||
label: "Execute in WebWorker",
|
||||
value: true,
|
||||
type: 'boolean',
|
||||
label: 'Execute in WebWorker',
|
||||
value: true
|
||||
},
|
||||
showIndices: {
|
||||
type: "boolean",
|
||||
label: "Show Indices",
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
label: 'Show Indices',
|
||||
value: false
|
||||
},
|
||||
showPerformancePanel: {
|
||||
type: "boolean",
|
||||
label: "Show Performance Panel",
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
label: 'Show Performance Panel',
|
||||
value: false
|
||||
},
|
||||
showBenchmarkPanel: {
|
||||
type: "boolean",
|
||||
label: "Show Benchmark Panel",
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
label: 'Show Benchmark Panel',
|
||||
value: false
|
||||
},
|
||||
showVertices: {
|
||||
type: "boolean",
|
||||
label: "Show Vertices",
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
label: 'Show Vertices',
|
||||
value: false
|
||||
},
|
||||
showStemLines: {
|
||||
type: "boolean",
|
||||
label: "Show Stem Lines",
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
label: 'Show Stem Lines',
|
||||
value: false
|
||||
},
|
||||
showGraphJson: {
|
||||
type: "boolean",
|
||||
label: "Show Graph Source",
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
label: 'Show Graph Source',
|
||||
value: false
|
||||
},
|
||||
cache: {
|
||||
title: "Cache",
|
||||
title: 'Cache',
|
||||
useRuntimeCache: {
|
||||
type: "boolean",
|
||||
label: "Node Results",
|
||||
value: true,
|
||||
type: 'boolean',
|
||||
label: 'Node Results',
|
||||
value: true
|
||||
},
|
||||
useRegistryCache: {
|
||||
type: "boolean",
|
||||
label: "Node Source",
|
||||
value: true,
|
||||
},
|
||||
type: 'boolean',
|
||||
label: 'Node Source',
|
||||
value: true
|
||||
}
|
||||
},
|
||||
stressTest: {
|
||||
title: "Stress Test",
|
||||
title: 'Stress Test',
|
||||
amount: {
|
||||
type: "integer",
|
||||
type: 'integer',
|
||||
min: 2,
|
||||
max: 15,
|
||||
value: 4,
|
||||
value: 4
|
||||
},
|
||||
loadGrid: {
|
||||
type: "button",
|
||||
label: "Load Grid",
|
||||
type: 'button',
|
||||
label: 'Load Grid'
|
||||
},
|
||||
loadTree: {
|
||||
type: "button",
|
||||
label: "Load Tree",
|
||||
type: 'button',
|
||||
label: 'Load Tree'
|
||||
},
|
||||
lottaFaces: {
|
||||
type: "button",
|
||||
label: "Load 'lots of faces'",
|
||||
type: 'button',
|
||||
label: "Load 'lots of faces'"
|
||||
},
|
||||
lottaNodes: {
|
||||
type: "button",
|
||||
label: "Load 'lots of nodes'",
|
||||
type: 'button',
|
||||
label: "Load 'lots of nodes'"
|
||||
},
|
||||
lottaNodesAndFaces: {
|
||||
type: "button",
|
||||
label: "Load 'lots of nodes and faces'",
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'button',
|
||||
label: "Load 'lots of nodes and faces'"
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
type SettingsToStore<T> =
|
||||
T extends { value: infer V }
|
||||
? V extends readonly string[]
|
||||
? V[number]
|
||||
: V
|
||||
: T extends any[]
|
||||
? {}
|
||||
: T extends object
|
||||
? {
|
||||
[K in keyof T as T[K] extends object ? K : never]:
|
||||
SettingsToStore<T[K]>
|
||||
}
|
||||
type SettingsToStore<T> = T extends { type: 'button' } ? () => void
|
||||
: T extends { value: infer V } ? V extends readonly string[] ? V[number]
|
||||
: V
|
||||
: T extends object ? {
|
||||
-readonly [K in keyof T as T[K] extends object ? K : never]: SettingsToStore<T[K]>;
|
||||
}
|
||||
: never;
|
||||
|
||||
export function settingsToStore<T>(settings: T): SettingsToStore<T> {
|
||||
const result = {} as any;
|
||||
const result = {} as Record<string, unknown>;
|
||||
for (const key in settings) {
|
||||
const value = settings[key];
|
||||
if (value && typeof value === "object") {
|
||||
if ("value" in value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if ('value' in value) {
|
||||
result[key] = value.value;
|
||||
} else {
|
||||
result[key] = settingsToStore(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return result as SettingsToStore<T>;
|
||||
}
|
||||
|
||||
export let appSettings = localState(
|
||||
"app-settings",
|
||||
settingsToStore(AppSettingTypes),
|
||||
export const appSettings = localState(
|
||||
'app-settings',
|
||||
settingsToStore(AppSettingTypes)
|
||||
);
|
||||
|
||||
$effect.root(() => {
|
||||
@@ -173,7 +167,7 @@ $effect.root(() => {
|
||||
const newClassName = `theme-${theme}`;
|
||||
if (classes) {
|
||||
for (const className of classes) {
|
||||
if (className.startsWith("theme-") && className !== newClassName) {
|
||||
if (className.startsWith('theme-') && className !== newClassName) {
|
||||
classes.remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NodeInput } from "@nodarium/types";
|
||||
import type { NodeInput } from '@nodarium/types';
|
||||
|
||||
type Button = { type: "button"; label?: string };
|
||||
type Button = { type: 'button'; label?: string };
|
||||
|
||||
export type SettingsStore = {
|
||||
[key: string]: SettingsStore | string | number | boolean;
|
||||
@@ -23,5 +23,5 @@ export type SettingsValue = Record<
|
||||
>;
|
||||
|
||||
export function isNodeInput(v: SettingsNode | undefined): v is InputType {
|
||||
return !!v && "type" in v;
|
||||
return !!v && 'type' in v;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user