feat: add theming support
This commit is contained in:
0
app/src/lib/settings/Keymap.svelte
Normal file
0
app/src/lib/settings/Keymap.svelte
Normal file
81
app/src/lib/settings/NestedSettings.svelte
Normal file
81
app/src/lib/settings/NestedSettings.svelte
Normal file
@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import type { NodeInput } from "@nodes/types";
|
||||
import Input, { Details } from "@nodes/ui";
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
interface Nested {
|
||||
[key: string]: Nested | NodeInput;
|
||||
}
|
||||
|
||||
export let settings: Nested;
|
||||
export let store: Writable<Record<string, any>>;
|
||||
export let depth = 0;
|
||||
|
||||
console.log(settings);
|
||||
const keys = Object.keys(settings);
|
||||
function isNodeInput(v: NodeInput | Nested): v is NodeInput {
|
||||
return "type" in v;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each keys as key}
|
||||
{@const value = settings[key]}
|
||||
<div class="wrapper" class:first-level={depth === 0}>
|
||||
{#if isNodeInput(value)}
|
||||
<div class="input input-{settings[key].type}">
|
||||
<label for={key}>{settings[key].label || key}</label>
|
||||
<Input
|
||||
id={key}
|
||||
input={value}
|
||||
bind:value={$store[value?.setting || key]}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<details>
|
||||
<summary>{key}</summary>
|
||||
<div class="content">
|
||||
<svelte:self settings={settings[key]} {store} depth={depth + 1} />
|
||||
</div>
|
||||
</details>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
summary {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
details {
|
||||
padding: 1rem;
|
||||
border-bottom: solid thin var(--outline);
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.input-boolean {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.input-boolean > label {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.first-level > .input {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.first-level {
|
||||
border-bottom: solid thin var(--outline);
|
||||
}
|
||||
.first-level > details {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { NodeInput } from "@nodes/types";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { Input } from "@nodes/ui";
|
||||
import NestedSettings from "./NestedSettings.svelte";
|
||||
|
||||
export let setting: {
|
||||
icon: string;
|
||||
@ -12,27 +12,38 @@
|
||||
|
||||
const store = setting.settings;
|
||||
|
||||
const keys = setting?.definition
|
||||
? (Object.keys(
|
||||
setting.definition,
|
||||
) as unknown as (keyof typeof setting.definition)[])
|
||||
: [];
|
||||
interface Nested {
|
||||
[key: string]: NodeInput | Nested;
|
||||
}
|
||||
|
||||
$: nestedSettings = constructNested();
|
||||
|
||||
function constructNested() {
|
||||
const nested: Nested = {};
|
||||
|
||||
for (const key in setting.definition) {
|
||||
const parts = key.split(".");
|
||||
let current = nested;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === parts.length - 1) {
|
||||
current[parts[i]] = setting.definition[key];
|
||||
} else {
|
||||
current[parts[i]] = current[parts[i]] || {};
|
||||
current = current[parts[i]] as Nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nested;
|
||||
}
|
||||
</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 class="flex flex-col">
|
||||
<h1 class="m-0 p-4">{setting.id}</h1>
|
||||
<NestedSettings settings={nestedSettings} {store} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
border-bottom: solid thin var(--outline);
|
||||
}
|
||||
</style>
|
||||
|
@ -3,12 +3,14 @@
|
||||
import type { Writable } from "svelte/store";
|
||||
import SettingsComponent from "./Panel.svelte";
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import type { SvelteComponent } from "svelte";
|
||||
|
||||
export let settings: Record<
|
||||
string,
|
||||
{
|
||||
icon: string;
|
||||
id: string;
|
||||
component?: typeof SvelteComponent<{}, {}, {}>;
|
||||
definition: Record<string, NodeInput>;
|
||||
settings: Writable<Record<string, unknown>>;
|
||||
}
|
||||
@ -50,10 +52,17 @@
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="content p-2">
|
||||
<div class="content">
|
||||
{#if $activePanel && settings[$activePanel]}
|
||||
{#key $activePanel}
|
||||
<SettingsComponent setting={settings[$activePanel]} />
|
||||
{#if settings[$activePanel].component}
|
||||
<svelte:component
|
||||
this={settings[$activePanel].component}
|
||||
{...settings[$activePanel]}
|
||||
/>
|
||||
{:else}
|
||||
<SettingsComponent setting={settings[$activePanel]} />
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
@ -66,7 +75,6 @@
|
||||
display: grid;
|
||||
grid-template-columns: 30px 1fr;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
right: 0px;
|
||||
transform: translateX(calc(100% - 30px));
|
||||
transition:
|
||||
@ -76,27 +84,31 @@
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--layer-1);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: solid thin white;
|
||||
border-right: solid thin var(--outline);
|
||||
}
|
||||
|
||||
.tabs > button {
|
||||
height: 30px;
|
||||
padding: 5px;
|
||||
background: none;
|
||||
background: blue;
|
||||
color: white;
|
||||
color: var(--outline);
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: solid thin white;
|
||||
border-left: solid thin white;
|
||||
border-bottom: solid thin var(--outline);
|
||||
border-left: solid thin var(--outline);
|
||||
}
|
||||
|
||||
.tabs > button.active {
|
||||
color: black;
|
||||
background: white;
|
||||
color: var(--layer-3);
|
||||
background: var(--layer-1);
|
||||
}
|
||||
|
||||
.visible .tabs > button {
|
||||
@ -105,7 +117,7 @@
|
||||
|
||||
.visible .tabs {
|
||||
margin-left: -1px;
|
||||
border-left: solid thin white;
|
||||
border-left: solid thin var(--outline);
|
||||
}
|
||||
|
||||
.visible > .tabs button:first-child {
|
||||
@ -114,7 +126,7 @@
|
||||
|
||||
.visible {
|
||||
transform: translateX(0);
|
||||
border-left: solid thin white;
|
||||
background: black;
|
||||
border-left: solid thin var(--outline);
|
||||
background: var(--layer-0);
|
||||
}
|
||||
</style>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import { label } from "three/examples/jsm/nodes/Nodes.js";
|
||||
|
||||
export const AppSettings = localStore("node-settings", {
|
||||
theme: 0,
|
||||
@ -7,31 +8,42 @@ export const AppSettings = localStore("node-settings", {
|
||||
showIndices: false,
|
||||
});
|
||||
|
||||
const themes = ["dark", "light", "catppuccin", "solarized", "high-contrast", "nord", "dracula"];
|
||||
|
||||
AppSettings.subscribe((value) => {
|
||||
if (value.theme === 0) {
|
||||
document.body.classList.remove("theme-catppuccin");
|
||||
} else {
|
||||
document.body.classList.add("theme-catppuccin");
|
||||
const classes = document.body.classList;
|
||||
const newClassName = `theme-${themes[value.theme]}`;
|
||||
for (const className of classes) {
|
||||
if (className.startsWith("theme-") && className !== newClassName) {
|
||||
classes.remove(className);
|
||||
}
|
||||
}
|
||||
document.body.classList.add(newClassName);
|
||||
});
|
||||
|
||||
export const AppSettingTypes = {
|
||||
theme: {
|
||||
type: "select",
|
||||
options: ["dark", "cat"],
|
||||
value: "dark",
|
||||
options: themes,
|
||||
label: "Theme",
|
||||
value: themes[0],
|
||||
},
|
||||
showGrid: {
|
||||
type: "boolean",
|
||||
label: "Show Grid",
|
||||
value: true,
|
||||
},
|
||||
wireframe: {
|
||||
type: "boolean",
|
||||
value: false,
|
||||
},
|
||||
showIndices: {
|
||||
type: "boolean",
|
||||
value: false,
|
||||
},
|
||||
debug: {
|
||||
wireframe: {
|
||||
type: "boolean",
|
||||
label: "Wireframe",
|
||||
value: false,
|
||||
},
|
||||
showIndices: {
|
||||
type: "boolean",
|
||||
label: "Show Indices",
|
||||
value: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user