feat: add outline to themes

This commit is contained in:
max_richter 2024-04-19 02:36:11 +02:00
parent d8ada83db3
commit c62cfbf75e
15 changed files with 315 additions and 277 deletions

View File

@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import type { GraphManager } from './graph-manager.js'; import type { GraphManager } from "./graph-manager.js";
import { HTML } from '@threlte/extras'; import { HTML } from "@threlte/extras";
import { onMount } from 'svelte'; import { onMount } from "svelte";
export let position: [x: number, y: number] | null; export let position: [x: number, y: number] | null;
export let graph: GraphManager; export let graph: GraphManager;
let input: HTMLInputElement; let input: HTMLInputElement;
let value: string = ''; let value: string = "";
let activeNodeId: string = ''; let activeNodeId: string = "";
const allNodes = graph.getNodeTypes(); const allNodes = graph.getNodeTypes();
@ -17,9 +17,9 @@
return allNodes.filter((node) => node.id.includes(value)); return allNodes.filter((node) => node.id.includes(value));
} }
$: nodes = value === '' ? allNodes : filterNodes(); $: nodes = value === "" ? allNodes : filterNodes();
$: if (nodes) { $: if (nodes) {
if (activeNodeId === '') { if (activeNodeId === "") {
activeNodeId = nodes[0].id; activeNodeId = nodes[0].id;
} else if (nodes.length) { } else if (nodes.length) {
const node = nodes.find((node) => node.id === activeNodeId); const node = nodes.find((node) => node.id === activeNodeId);
@ -31,26 +31,25 @@
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
const value = (event.target as HTMLInputElement).value;
if (event.key === 'Escape') { if (event.key === "Escape") {
position = null; position = null;
return; return;
} }
if (event.key === 'ArrowDown') { if (event.key === "ArrowDown") {
const index = nodes.findIndex((node) => node.id === activeNodeId); const index = nodes.findIndex((node) => node.id === activeNodeId);
activeNodeId = nodes[(index + 1) % nodes.length].id; activeNodeId = nodes[(index + 1) % nodes.length].id;
return; return;
} }
if (event.key === 'ArrowUp') { if (event.key === "ArrowUp") {
const index = nodes.findIndex((node) => node.id === activeNodeId); const index = nodes.findIndex((node) => node.id === activeNodeId);
activeNodeId = nodes[(index - 1 + nodes.length) % nodes.length].id; activeNodeId = nodes[(index - 1 + nodes.length) % nodes.length].id;
return; return;
} }
if (event.key === 'Enter') { if (event.key === "Enter") {
if (activeNodeId && position) { if (activeNodeId && position) {
graph.createNode({ type: activeNodeId, position }); graph.createNode({ type: activeNodeId, position });
position = null; position = null;
@ -89,16 +88,16 @@
tabindex="0" tabindex="0"
aria-selected={node.id === activeNodeId} aria-selected={node.id === activeNodeId}
on:keydown={(event) => { on:keydown={(event) => {
if (event.key === 'Enter') { if (event.key === "Enter") {
if (position) { if (position) {
graph.createNode({ type: node.id, position }); graph.createNode({ type: node.id, position, props: {} });
position = null; position = null;
} }
} }
}} }}
on:mousedown={() => { on:mousedown={() => {
if (position) { if (position) {
graph.createNode({ type: node.id, position }); graph.createNode({ type: node.id, position, props: {} });
position = null; position = null;
} }
}} }}
@ -119,7 +118,7 @@
<style> <style>
input { input {
background: var(--background-color-lighter); background: var(--layer-0);
font-family: var(--font-family); font-family: var(--font-family);
border: none; border: none;
color: var(--text-color); color: var(--text-color);
@ -137,10 +136,10 @@
.add-menu-wrapper { .add-menu-wrapper {
position: absolute; position: absolute;
background: var(--background-color); background: var(--layer-1);
border-radius: 7px; border-radius: 7px;
overflow: hidden; overflow: hidden;
border: solid 2px var(--background-color-lighter); border: solid 2px var(--layer-2);
width: 150px; width: 150px;
} }
.content { .content {
@ -151,14 +150,14 @@
.result { .result {
padding: 1em 0.9em; padding: 1em 0.9em;
border-bottom: solid 1px var(--background-color-lighter); border-bottom: solid 1px var(--layer-2);
opacity: 0.7; opacity: 0.7;
font-size: 0.9em; font-size: 0.9em;
cursor: pointer; cursor: pointer;
} }
.result[aria-selected='true'] { .result[aria-selected="true"] {
background: var(--background-color-lighter); background: var(--layer-2);
opacity: 1; opacity: 1;
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { HTML } from '@threlte/extras'; import { HTML } from "@threlte/extras";
export let p1 = { x: 0, y: 0 }; export let p1 = { x: 0, y: 0 };
export let p2 = { x: 0, y: 0 }; export let p2 = { x: 0, y: 0 };
@ -14,14 +14,17 @@
</script> </script>
<HTML position.x={x} position.z={y} transform={false}> <HTML position.x={x} position.z={y} transform={false}>
<div class="box-selection" style={`width: ${width}px; height: ${height}px;`}></div> <div
class="box-selection"
style={`width: ${width}px; height: ${height}px;`}
></div>
</HTML> </HTML>
<style> <style>
.box-selection { .box-selection {
width: 40px; width: 40px;
height: 20px; height: 20px;
border: solid 0.2px rgba(200, 200, 200, 0.8); border: solid 0.2px var(--outline);
border-style: dashed; border-style: dashed;
border-radius: 2px; border-radius: 2px;
} }

View File

@ -746,7 +746,7 @@
<div <div
on:wheel={handleMouseScroll} on:wheel={handleMouseScroll}
bind:this={wrapper} bind:this={wrapper}
class="wrapper" class="graph-wrapper"
aria-label="Graph" aria-label="Graph"
role="button" role="button"
tabindex="0" tabindex="0"
@ -794,8 +794,9 @@
</div> </div>
<style> <style>
.wrapper { .graph-wrapper {
position: relative; position: relative;
transition: opacity 0.3s ease;
height: 100%; height: 100%;
} }
</style> </style>

View File

@ -12,6 +12,8 @@
const manager = new GraphManager(registry); const manager = new GraphManager(registry);
export const status = manager.status;
const updateSettings = debounce((s) => { const updateSettings = debounce((s) => {
manager.setSettings(s); manager.setSettings(s);
}, 200); }, 200);

View File

@ -16,6 +16,8 @@ export const colors = writable({
layer2: new Color().setStyle("#2D2D2D"), layer2: new Color().setStyle("#2D2D2D"),
layer3: new Color().setStyle("#A6A6A6"), layer3: new Color().setStyle("#A6A6A6"),
outline: new Color().setStyle("#000000"), outline: new Color().setStyle("#000000"),
active: new Color().setStyle("#c65a19"),
selected: new Color().setStyle("#ffffff"),
}); });
if ("getComputedStyle" in globalThis) { if ("getComputedStyle" in globalThis) {
@ -30,6 +32,8 @@ if ("getComputedStyle" in globalThis) {
const layer2 = style.getPropertyValue("--layer-2"); const layer2 = style.getPropertyValue("--layer-2");
const layer3 = style.getPropertyValue("--layer-3"); const layer3 = style.getPropertyValue("--layer-3");
const outline = style.getPropertyValue("--outline"); const outline = style.getPropertyValue("--outline");
const active = style.getPropertyValue("--active");
const selected = style.getPropertyValue("--selected");
colors.update(col => { colors.update(col => {
col.layer0.setStyle(layer0); col.layer0.setStyle(layer0);
@ -42,6 +46,10 @@ if ("getComputedStyle" in globalThis) {
col.layer3.convertLinearToSRGB(); col.layer3.convertLinearToSRGB();
col.outline.setStyle(outline); col.outline.setStyle(outline);
col.outline.convertLinearToSRGB(); col.outline.convertLinearToSRGB();
col.active.setStyle(active);
col.active.convertLinearToSRGB();
col.selected.setStyle(selected);
col.selected.convertLinearToSRGB();
return col; return col;
}); });

View File

@ -6,12 +6,8 @@ uniform float uHeight;
uniform vec3 uColorDark; uniform vec3 uColorDark;
uniform vec3 uColorBright; uniform vec3 uColorBright;
uniform vec3 uSelectedColor;
uniform vec3 uActiveColor;
uniform bool uSelected;
uniform bool uActive;
uniform vec3 uStrokeColor;
uniform float uStrokeWidth; uniform float uStrokeWidth;
float msign(in float x) { return (x < 0.0) ? -1.0 : 1.0; } float msign(in float x) { return (x < 0.0) ? -1.0 : 1.0; }
@ -47,16 +43,9 @@ void main(){
// outside // outside
gl_FragColor = vec4(0.0,0.0,0.0, 0.0); gl_FragColor = vec4(0.0,0.0,0.0, 0.0);
}else{ }else{
if (distance.w > -uStrokeWidth || mod(y+5.0, 10.0) < uStrokeWidth/2.0) { if (distance.w > -uStrokeWidth || mod(y+5.0, 10.0) < uStrokeWidth/2.0) {
// draw the outer stroke // draw the outer stroke
if (uSelected) { gl_FragColor = vec4(uStrokeColor, 1.0);
gl_FragColor = vec4(uSelectedColor, 1.0);
} else if (uActive) {
gl_FragColor = vec4(uActiveColor, 1.0);
} else {
gl_FragColor = vec4(uColorBright, 1.0);
}
}else if (y<5.0){ }else if (y<5.0){
// draw the header // draw the header
gl_FragColor = vec4(uColorBright, 1.0); gl_FragColor = vec4(uColorBright, 1.0);

View File

@ -67,18 +67,18 @@
uniforms={{ uniforms={{
uColorBright: { value: new Color("#171717") }, uColorBright: { value: new Color("#171717") },
uColorDark: { value: new Color("#151515") }, uColorDark: { value: new Color("#151515") },
uSelectedColor: { value: new Color("#9d5f28") }, uStrokeColor: { value: new Color("#9d5f28") },
uActiveColor: { value: new Color("white") },
uSelected: { value: false },
uActive: { value: false },
uStrokeWidth: { value: 1.0 }, uStrokeWidth: { value: 1.0 },
uWidth: { value: 20 }, uWidth: { value: 20 },
uHeight: { value: height }, uHeight: { value: height },
}} }}
uniforms.uSelected.value={isSelected}
uniforms.uActive.value={isActive}
uniforms.uColorBright.value={$colors.layer2} uniforms.uColorBright.value={$colors.layer2}
uniforms.uColorDark.value={$colors.layer1} uniforms.uColorDark.value={$colors.layer1}
uniforms.uStrokeColor.value={isSelected
? $colors.selected
: isActive
? $colors.active
: $colors.outline}
uniforms.uStrokeWidth.value={(7 - z) / 3} uniforms.uStrokeWidth.value={(7 - z) / 3}
/> />
</T.Mesh> </T.Mesh>
@ -122,12 +122,12 @@
} }
.node.active { .node.active {
--stroke: white; --stroke: var(--active);
--stroke-width: 1px; --stroke-width: 2px;
} }
.node.selected { .node.selected {
--stroke: #9d5f28; --stroke: var(--selected);
--stroke-width: 1px; --stroke-width: 2px;
} }
</style> </style>

View File

@ -0,0 +1,4 @@
<script lang="ts">
</script>
duuude

View File

@ -11,7 +11,6 @@
export let store: Writable<Record<string, any>>; export let store: Writable<Record<string, any>>;
export let depth = 0; export let depth = 0;
console.log(settings);
const keys = Object.keys(settings); const keys = Object.keys(settings);
function isNodeInput(v: NodeInput | Nested): v is NodeInput { function isNodeInput(v: NodeInput | Nested): v is NodeInput {
return "type" in v; return "type" in v;
@ -23,12 +22,18 @@
<div class="wrapper" class:first-level={depth === 0}> <div class="wrapper" class:first-level={depth === 0}>
{#if isNodeInput(value)} {#if isNodeInput(value)}
<div class="input input-{settings[key].type}"> <div class="input input-{settings[key].type}">
{#if settings[key].type === "button"}
<button on:click={() => settings[key]?.callback?.()}
>{key || settings[key].label}</button
>
{:else}
<label for={key}>{settings[key].label || key}</label> <label for={key}>{settings[key].label || key}</label>
<Input <Input
id={key} id={key}
input={value} input={value}
bind:value={$store[value?.setting || key]} bind:value={$store[value?.setting || key]}
/> />
{/if}
</div> </div>
{:else} {:else}
<details> <details>

View File

@ -1,49 +0,0 @@
<script lang="ts">
import type { NodeInput } from "@nodes/types";
import type { Writable } from "svelte/store";
import NestedSettings from "./NestedSettings.svelte";
export let setting: {
icon: string;
id: string;
definition: Record<string, NodeInput>;
settings: Writable<Record<string, unknown>>;
};
const store = setting.settings;
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">
<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

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { NodeInput } from "@nodes/types"; import type { NodeInput } from "@nodes/types";
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import SettingsComponent from "./Panel.svelte";
import localStore from "$lib/helpers/localStore"; import localStore from "$lib/helpers/localStore";
import type { SvelteComponent } from "svelte"; import type { SvelteComponent } from "svelte";
import NestedSettings from "./NestedSettings.svelte";
export let settings: Record< export let panels: Record<
string, string,
{ {
icon: string; icon: string;
@ -16,13 +16,15 @@
} }
>; >;
const activePanel = localStore<keyof typeof settings | false>( const activePanel = localStore<keyof typeof panels | false>(
"nodes.settings.activePanel", "nodes.settings.activePanel",
false, false,
); );
$: keys = Object.keys(settings) as unknown as (keyof typeof settings)[]; $: keys = panels
? (Object.keys(panels) as unknown as (keyof typeof panels)[])
: [];
function setActivePanel(panel: keyof typeof settings | false) { function setActivePanel(panel: keyof typeof panels | false) {
if (panel === $activePanel) { if (panel === $activePanel) {
$activePanel = false; $activePanel = false;
} else if (panel) { } else if (panel) {
@ -31,6 +33,28 @@
$activePanel = false; $activePanel = false;
} }
} }
interface Nested {
[key: string]: NodeInput | Nested;
}
function constructNested(panel: (typeof panels)[keyof typeof panels]) {
const nested: Nested = {};
for (const key in panel.definition) {
const parts = key.split(".");
let current = nested;
for (let i = 0; i < parts.length; i++) {
if (i === parts.length - 1) {
current[parts[i]] = panel.definition[key];
} else {
current[parts[i]] = current[parts[i]] || {};
current = current[parts[i]] as Nested;
}
}
}
return nested;
}
</script> </script>
<div class="wrapper" class:visible={$activePanel}> <div class="wrapper" class:visible={$activePanel}>
@ -42,26 +66,32 @@
> >
<span class="absolute i-tabler-chevron-left w-6 h-6 block"></span> <span class="absolute i-tabler-chevron-left w-6 h-6 block"></span>
</button> </button>
{#each keys as panel (settings[panel].id)} {#each keys as panel (panels[panel].id)}
<button <button
class="tab" class="tab"
class:active={panel === $activePanel} class:active={panel === $activePanel}
on:click={() => setActivePanel(panel)} on:click={() => setActivePanel(panel)}
> >
<i class={`block w-6 h-6 ${settings[panel].icon}`} /> <i class={`block w-6 h-6 ${panels[panel].icon}`} />
</button> </button>
{/each} {/each}
</div> </div>
<div class="content"> <div class="content">
{#if $activePanel && settings[$activePanel]} {#if $activePanel && panels[$activePanel]}
<h1 class="m-0 p-4">{panels[$activePanel].id}</h1>
{#key $activePanel} {#key $activePanel}
{#if settings[$activePanel].component} {#if panels[$activePanel]?.component}
<svelte:component <svelte:component
this={settings[$activePanel].component} this={panels[$activePanel].component}
{...settings[$activePanel]} {...panels[$activePanel]}
/> />
{:else} {:else}
<SettingsComponent setting={settings[$activePanel]} /> <div class="flex flex-col">
<NestedSettings
settings={constructNested(panels[$activePanel])}
store={panels[$activePanel].settings}
/>
</div>
{/if} {/if}
{/key} {/key}
{/if} {/if}
@ -84,6 +114,10 @@
min-width: 300px; min-width: 300px;
} }
h1 {
border-bottom: solid thin var(--outline);
}
.content { .content {
background: var(--layer-1); background: var(--layer-1);
} }
@ -97,6 +131,7 @@
.tabs > button { .tabs > button {
height: 30px; height: 30px;
padding: 5px; padding: 5px;
border-radius: 0px;
background: none; background: none;
color: var(--outline); color: var(--outline);
border: none; border: none;

View File

@ -33,6 +33,19 @@ export const AppSettingTypes = {
label: "Show Grid", label: "Show Grid",
value: true, value: true,
}, },
stressTest: {
amount: {
type: "integer",
min: 2,
max: 15
},
loadGrid: {
type: "button"
},
loadTree: {
type: "button"
},
},
debug: { debug: {
wireframe: { wireframe: {
type: "boolean", type: "boolean",

View File

@ -8,7 +8,8 @@
import Viewer from "$lib/viewer/Viewer.svelte"; import Viewer from "$lib/viewer/Viewer.svelte";
import Settings from "$lib/settings/Settings.svelte"; import Settings from "$lib/settings/Settings.svelte";
import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings"; import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings";
import { get, writable } from "svelte/store"; import { get, writable, type Writable } from "svelte/store";
import Keymap from "$lib/settings/Keymap.svelte";
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001"); const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry); const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
@ -19,6 +20,8 @@
? JSON.parse(localStorage.getItem("graph")!) ? JSON.parse(localStorage.getItem("graph")!)
: templates.grid(3, 3); : templates.grid(3, 3);
let managerStatus: Writable<"loading" | "error" | "idle">;
function handleResult(event: CustomEvent<Graph>) { function handleResult(event: CustomEvent<Graph>) {
res = runtimeExecutor.execute(event.detail, get(settings?.graph?.settings)); res = runtimeExecutor.execute(event.detail, get(settings?.graph?.settings));
} }
@ -34,6 +37,12 @@
settings: AppSettings, settings: AppSettings,
definition: AppSettingTypes, definition: AppSettingTypes,
}, },
graph: {},
shortcuts: {
id: "shortcuts",
icon: "i-tabler-keyboard",
component: Keymap,
},
}; };
function handleSettings( function handleSettings(
@ -42,27 +51,28 @@
types: Record<string, unknown>; types: Record<string, unknown>;
}>, }>,
) { ) {
settings = { settings.general.definition.stressTest.loadGrid.callback = function () {
...settings, const store = get(settings.general.settings);
graph: { graph = templates.grid(store.amount, store.amount);
};
settings.general.definition.stressTest.loadTree.callback = function () {
const store = get(settings.general.settings);
graph = templates.tree(store.amount);
};
settings.graph = {
icon: "i-tabler-chart-bar", icon: "i-tabler-chart-bar",
id: "graph", id: "graph",
settings: writable(ev.detail.values), settings: writable(ev.detail.values),
definition: ev.detail.types, definition: ev.detail.types,
},
}; };
settings = settings;
} }
</script> </script>
<div class="wrapper"> <div class="wrapper manager-{$managerStatus}">
<header> <header></header>
header
<button
on:click={() => {
graph = templates.grid(15, 15);
}}>grid stress-test</button
>
</header>
<Grid.Row> <Grid.Row>
<Grid.Cell> <Grid.Cell>
<Viewer result={res} /> <Viewer result={res} />
@ -72,12 +82,13 @@
<GraphInterface <GraphInterface
registry={nodeRegistry} registry={nodeRegistry}
{graph} {graph}
bind:status={managerStatus}
settings={settings?.graph?.settings} settings={settings?.graph?.settings}
on:settings={handleSettings} on:settings={handleSettings}
on:result={handleResult} on:result={handleResult}
on:save={handleSave} on:save={handleSave}
/> />
<Settings {settings}></Settings> <Settings panels={settings}></Settings>
{/key} {/key}
</Grid.Cell> </Grid.Cell>
</Grid.Row> </Grid.Row>
@ -97,6 +108,17 @@
grid-template-rows: 50px 1fr; grid-template-rows: 50px 1fr;
} }
.wrapper :global(canvas) {
transition: opacity 0.3s ease;
opacity: 1;
}
.manager-loading :global(.graph-wrapper),
.manager-loading :global(canvas) {
opacity: 0.2;
pointer-events: none;
}
:global(html) { :global(html) {
background: rgb(13, 19, 32); background: rgb(13, 19, 32);
background: linear-gradient( background: linear-gradient(

View File

View File

@ -57,6 +57,9 @@ body {
--layer-2: var(--neutral-400); --layer-2: var(--neutral-400);
--layer-3: var(--neutral-200); --layer-3: var(--neutral-200);
--active: #ffffff;
--selected: #c65a19;
--outline: var(--neutral-400); --outline: var(--neutral-400);
--text-color: var(--neutral-200); --text-color: var(--neutral-200);
@ -76,6 +79,8 @@ body.theme-light {
--layer-1: var(--neutral-100); --layer-1: var(--neutral-100);
--layer-2: var(--neutral-100); --layer-2: var(--neutral-100);
--layer-3: var(--neutral-500); --layer-3: var(--neutral-500);
--active: #000000;
--selected: #c65a19;
} }
body.theme-solarized { body.theme-solarized {
@ -83,8 +88,10 @@ body.theme-solarized {
--outline: #93a1a1; --outline: #93a1a1;
--layer-0: #fdf6e3; --layer-0: #fdf6e3;
--layer-1: #eee8d5; --layer-1: #eee8d5;
--layer-2: #93a1a1; --layer-2: #c4c0b4;
--layer-3: #586e75; --layer-3: #586e75;
--active: #000000;
--selected: #268bd2;
} }
body.theme-catppuccin { body.theme-catppuccin {
@ -107,17 +114,13 @@ body.theme-high-contrast {
body.theme-nord { body.theme-nord {
--text-color: #D8DEE9; --text-color: #D8DEE9;
/* Nord snow */
--outline: #4C566A; --outline: #4C566A;
/* Nord frost */
--layer-0: #2E3440; --layer-0: #2E3440;
/* Nord polar night */
--layer-1: #3B4252; --layer-1: #3B4252;
/* Nord polar night */
--layer-2: #434C5E; --layer-2: #434C5E;
/* Nord polar night */
--layer-3: #5E81AC; --layer-3: #5E81AC;
/* Nord frost */ --active: #8999bd;
--selected: #b76c3f
} }
body.theme-dracula { body.theme-dracula {
@ -133,6 +136,9 @@ body {
margin: 0; margin: 0;
} }
/* canvas { */ button {
/* display: none !important; */ background-color: var(--layer-2);
/* } */ border: 1px solid var(--outline);
padding: 8px 9px;
border-radius: 4px;
}