feat: add theming support

This commit is contained in:
2024-04-19 01:27:11 +02:00
parent a15a54c61e
commit d8ada83db3
27 changed files with 569 additions and 285 deletions

View File

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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,
},
}
}