feat: setup mode selection, per-workspace settings in kebab menu

Setup screen now offers Local vs WebDAV mode choice with cancel button
when workspaces exist. Settings moved from drawer bottom into workspace
kebab menu, scoped per-workspace. WebDAV section hidden for local
workspaces, theme dropdown replaces dark mode toggle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tristan Michael 2026-04-03 03:58:12 -07:00
parent a1e97bc0fe
commit 12afc91110
3 changed files with 293 additions and 144 deletions

View file

@ -2,7 +2,10 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { app } from "../stores/app.svelte"; import { app } from "../stores/app.svelte";
let { onclose }: { onclose?: () => void } = $props(); let { onclose, workspaceName }: { onclose?: () => void; workspaceName: string } = $props();
let ws = $derived(app.config?.workspaces[workspaceName]);
let isWebdav = $derived(ws?.mode === "webdav");
let webdavUrl = $state(""); let webdavUrl = $state("");
let webdavUser = $state(""); let webdavUser = $state("");
@ -10,19 +13,15 @@
let testStatus = $state<"idle" | "testing" | "ok" | "fail">("idle"); let testStatus = $state<"idle" | "testing" | "ok" | "fail">("idle");
$effect(() => { $effect(() => {
const ws = app.config?.current_workspace; if (!ws?.webdav_url) return;
if (!ws) return; webdavUrl = ws.webdav_url;
const cfg = app.config?.workspaces[ws]; try {
if (cfg?.webdav_url) { const domain = new URL(ws.webdav_url).hostname;
webdavUrl = cfg.webdav_url; invoke<[string, string]>("load_credentials", { domain }).then(([u, p]) => {
try { webdavUser = u;
const domain = new URL(cfg.webdav_url).hostname; webdavPass = p;
invoke<[string, string]>("load_credentials", { domain }).then(([u, p]) => { }).catch(() => {});
webdavUser = u; } catch {}
webdavPass = p;
}).catch(() => {});
} catch {}
}
}); });
async function testConnection() { async function testConnection() {
@ -40,9 +39,9 @@
} }
async function saveWebdav() { async function saveWebdav() {
if (!app.config?.current_workspace || !webdavUrl.trim()) return; if (!webdavUrl.trim()) return;
await invoke("set_webdav_config", { await invoke("set_webdav_config", {
workspaceName: app.config.current_workspace, workspaceName,
webdavUrl: webdavUrl.trim(), webdavUrl: webdavUrl.trim(),
}); });
if (webdavUser && webdavPass) { if (webdavUser && webdavPass) {
@ -55,13 +54,12 @@
} }
await app.loadConfig(); await app.loadConfig();
} }
</script> </script>
<header <header
class="flex items-center justify-between border-b border-border-light px-4 py-3 dark:border-border-dark" class="flex items-center justify-between border-b border-border-light px-4 py-3 dark:border-border-dark"
> >
<h1 class="text-lg font-bold">Settings</h1> <h1 class="text-lg font-bold">{workspaceName} Settings</h1>
<button <button
onclick={() => onclose?.()} onclick={() => onclose?.()}
class="rounded-lg p-1.5 hover:bg-black/5 dark:hover:bg-white/10" class="rounded-lg p-1.5 hover:bg-black/5 dark:hover:bg-white/10"
@ -75,53 +73,53 @@
</header> </header>
<main class="flex-1 overflow-y-auto p-4"> <main class="flex-1 overflow-y-auto p-4">
<!-- WebDAV Sync --> <!-- WebDAV Sync (only for webdav workspaces) -->
<section class="mb-6"> {#if isWebdav}
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wide opacity-50"> <section class="mb-6">
WebDAV Sync <h2 class="mb-3 text-sm font-semibold uppercase tracking-wide opacity-50">
</h2> WebDAV Sync
<div class="rounded-xl border border-border-light p-4 dark:border-border-dark"> </h2>
<label class="mb-1 block text-xs font-medium opacity-60">Server URL</label> <div class="rounded-xl border border-border-light p-4 dark:border-border-dark">
<input <label class="mb-1 block text-xs font-medium opacity-60">Server URL</label>
type="url" <input
bind:value={webdavUrl} type="url"
placeholder="https://dav.example.com/tasks/" bind:value={webdavUrl}
class="mb-3 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark" placeholder="https://dav.example.com/tasks/"
/> class="mb-3 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark"
/>
<label class="mb-1 block text-xs font-medium opacity-60">Username</label> <label class="mb-1 block text-xs font-medium opacity-60">Username</label>
<input <input
type="text" type="text"
bind:value={webdavUser} bind:value={webdavUser}
class="mb-3 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark" class="mb-3 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark"
/> />
<label class="mb-1 block text-xs font-medium opacity-60">Password</label> <label class="mb-1 block text-xs font-medium opacity-60">Password</label>
<input <input
type="password" type="password"
bind:value={webdavPass} bind:value={webdavPass}
class="mb-4 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark" class="mb-4 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark"
/> />
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
onclick={testConnection} onclick={testConnection}
disabled={!webdavUrl.trim()} disabled={!webdavUrl.trim()}
class="rounded-lg border border-border-light px-4 py-2 text-sm font-medium hover:bg-black/5 disabled:opacity-40 dark:border-border-dark dark:hover:bg-white/10" class="rounded-lg border border-border-light px-4 py-2 text-sm font-medium hover:bg-black/5 disabled:opacity-40 dark:border-border-dark dark:hover:bg-white/10"
> >
{testStatus === "testing" ? "Testing…" : testStatus === "ok" ? "Connected" : testStatus === "fail" ? "Failed — Retry" : "Test Connection"} {testStatus === "testing" ? "Testing..." : testStatus === "ok" ? "Connected" : testStatus === "fail" ? "Failed -- Retry" : "Test Connection"}
</button> </button>
<button <button
onclick={saveWebdav} onclick={saveWebdav}
disabled={!webdavUrl.trim()} disabled={!webdavUrl.trim()}
class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-40" class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-40"
> >
Save Save
</button> </button>
</div>
</div> </div>
</div>
{#if app.config?.current_workspace}
<div class="mt-3 flex items-center gap-2"> <div class="mt-3 flex items-center gap-2">
<select <select
value={app.syncMode} value={app.syncMode}
@ -137,11 +135,11 @@
disabled={app.syncing} disabled={app.syncing}
class="flex-1 rounded-lg bg-primary py-2 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-40" class="flex-1 rounded-lg bg-primary py-2 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-40"
> >
{app.syncing ? "Syncing" : "Sync Now"} {app.syncing ? "Syncing..." : "Sync Now"}
</button> </button>
</div> </div>
{#if app.config.workspaces[app.config.current_workspace]?.last_sync} {#if ws?.last_sync}
{@const lastSync = new Date(app.config.workspaces[app.config.current_workspace].last_sync!)} {@const lastSync = new Date(ws.last_sync)}
{@const secsAgo = Math.floor((Date.now() - lastSync.getTime()) / 1000)} {@const secsAgo = Math.floor((Date.now() - lastSync.getTime()) / 1000)}
{@const relTime = secsAgo < 60 ? "just now" : secsAgo < 3600 ? `${Math.floor(secsAgo / 60)}m ago` : `${Math.floor(secsAgo / 3600)}h ago`} {@const relTime = secsAgo < 60 ? "just now" : secsAgo < 3600 ? `${Math.floor(secsAgo / 60)}m ago` : `${Math.floor(secsAgo / 3600)}h ago`}
<p class="mt-1.5 text-xs opacity-40"> <p class="mt-1.5 text-xs opacity-40">
@ -151,27 +149,32 @@
{/if} {/if}
</p> </p>
{/if} {/if}
{/if} </section>
</section> {/if}
<!-- Theme --> <!-- Theme -->
<section> <section>
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wide opacity-50"> <h2 class="mb-3 text-sm font-semibold uppercase tracking-wide opacity-50">
Appearance Appearance
</h2> </h2>
<button <div class="rounded-xl border border-border-light p-4 dark:border-border-dark">
onclick={() => app.toggleDarkMode()} <label class="mb-1 block text-xs font-medium opacity-60">Theme</label>
class="flex w-full items-center justify-between rounded-xl border border-border-light p-4 dark:border-border-dark" <select
> value={ws?.theme ?? ""}
<span class="text-sm font-medium">Dark mode</span> onchange={(e) => {
<div const val = (e.target as HTMLSelectElement).value;
class="h-6 w-11 rounded-full transition-colors {app.darkMode ? 'bg-primary' : 'bg-gray-300 dark:bg-gray-600'}" app.setTheme(val || null);
}}
class="w-full appearance-none rounded-lg border border-border-light bg-surface-light px-3 py-2 text-sm text-text-light outline-none focus:border-primary dark:border-border-dark dark:bg-surface-dark dark:text-text-dark"
> >
<div <option value="">System default</option>
class="h-5 w-5 translate-y-0.5 rounded-full bg-white shadow transition-transform {app.darkMode ? 'translate-x-5.5' : 'translate-x-0.5'}" <option value="light">Light</option>
></div> <option value="dark">Dark</option>
</div> <option value="nord">Nord</option>
</button> <option value="dracula">Dracula</option>
<option value="solarized">Solarized Dark</option>
</select>
</div>
</section> </section>
<p class="mt-8 text-center text-xs opacity-30">Tauri v2 + Svelte</p> <p class="mt-8 text-center text-xs opacity-30">Tauri v2 + Svelte</p>

View file

@ -1,16 +1,25 @@
<script lang="ts"> <script lang="ts">
import { invoke } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-dialog"; import { open } from "@tauri-apps/plugin-dialog";
import { app } from "../stores/app.svelte"; import { app } from "../stores/app.svelte";
import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
import { platform } from "@tauri-apps/plugin-os"; import { platform } from "@tauri-apps/plugin-os";
let { cancellable = false }: { cancellable?: boolean } = $props();
const appWindow = getCurrentWindow(); const appWindow = getCurrentWindow();
const currentPlatform = platform(); const currentPlatform = platform();
const isDesktop = currentPlatform === "linux" || currentPlatform === "windows"; const isDesktop = currentPlatform === "linux" || currentPlatform === "windows";
const isWindows = currentPlatform === "windows"; const isWindows = currentPlatform === "windows";
const isMobile = currentPlatform === "android" || currentPlatform === "ios";
let mode = $state<"local" | "webdav" | null>(isMobile ? "webdav" : null);
let name = $state(""); let name = $state("");
let path = $state(""); let path = $state("");
let webdavUrl = $state("");
let webdavUser = $state("");
let webdavPass = $state("");
let testStatus = $state<"idle" | "testing" | "ok" | "fail">("idle");
async function pickFolder() { async function pickFolder() {
const selected = await open({ directory: true, multiple: false }); const selected = await open({ directory: true, multiple: false });
@ -26,23 +35,63 @@
const selected = await open({ directory: true, multiple: false }); const selected = await open({ directory: true, multiple: false });
if (!selected) return; if (!selected) return;
const folder = selected as string; const folder = selected as string;
// Derive workspace name from folder name
const parts = folder.replace(/\\/g, "/").split("/"); const parts = folder.replace(/\\/g, "/").split("/");
const wsName = parts[parts.length - 1] || "workspace"; const wsName = parts[parts.length - 1] || "workspace";
await app.addWorkspace(wsName, folder); await app.addWorkspace(wsName, folder);
} }
async function testConnection() {
testStatus = "testing";
try {
await invoke("test_webdav_connection", {
url: webdavUrl,
username: webdavUser,
password: webdavPass,
});
testStatus = "ok";
} catch {
testStatus = "fail";
}
}
async function handleCreateWebdav() {
if (!name.trim() || !webdavUrl.trim()) return;
await app.addWebdavWorkspace(name.trim(), webdavUrl.trim(), webdavUser, webdavPass);
}
function handleDrag(e: MouseEvent) { function handleDrag(e: MouseEvent) {
if (e.button !== 0) return; if (e.button !== 0) return;
if ((e.target as HTMLElement).closest("button, input")) return; if ((e.target as HTMLElement).closest("button, input")) return;
if (isDesktop) appWindow.startDragging(); if (isDesktop) appWindow.startDragging();
} }
function goBack() {
mode = null;
name = "";
path = "";
webdavUrl = "";
webdavUser = "";
webdavPass = "";
testStatus = "idle";
}
</script> </script>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="flex h-full flex-col" onmousedown={handleDrag}> <div class="flex h-full flex-col" onmousedown={handleDrag}>
<!-- Title bar area with window controls --> <!-- Title bar area with window controls -->
<header class="flex h-11 shrink-0 items-center justify-end px-2"> <header class="flex h-11 shrink-0 items-center justify-between px-2">
<div>
{#if cancellable}
<button
onclick={() => app.setScreen("tasks")}
class="rounded-lg p-1.5 opacity-50 hover:bg-black/10 hover:opacity-80 dark:hover:bg-white/10"
>
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z" />
</svg>
</button>
{/if}
</div>
{#if isDesktop} {#if isDesktop}
<div class="flex items-center gap-0.5"> <div class="flex items-center gap-0.5">
{#if isWindows} {#if isWindows}
@ -72,58 +121,162 @@
class="w-full max-w-sm rounded-2xl bg-card-light p-8 shadow-lg dark:bg-card-dark" class="w-full max-w-sm rounded-2xl bg-card-light p-8 shadow-lg dark:bg-card-dark"
> >
<h1 class="mb-1 text-2xl font-bold">Onyx</h1> <h1 class="mb-1 text-2xl font-bold">Onyx</h1>
<p class="mb-6 text-sm text-text-secondary-light dark:text-text-secondary-dark">
Create a new workspace or open an existing one.
</p>
<label class="mb-1 block text-sm font-medium"> {#if mode === null}
Workspace name <!-- Step 1: Choose mode -->
<input <p class="mb-6 text-sm text-text-secondary-light dark:text-text-secondary-dark">
type="text" How would you like to store your tasks?
bind:value={name} </p>
placeholder="My Tasks"
class="mt-1 mb-4 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm font-normal outline-none focus:border-primary dark:border-border-dark"
/>
</label>
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="mb-1 block text-sm font-medium">Folder</label>
<div class="mb-6 flex gap-2">
<input
type="text"
bind:value={path}
readonly
placeholder="Select a folder…"
class="min-w-0 flex-1 rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm dark:border-border-dark"
/>
<button <button
onclick={pickFolder} onclick={() => (mode = "local")}
class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover" class="mb-3 w-full rounded-xl border border-border-light p-4 text-left hover:bg-black/5 dark:border-border-dark dark:hover:bg-white/10"
> >
Browse <p class="text-sm font-semibold">Local Folder</p>
<p class="mt-0.5 text-xs text-text-secondary-light dark:text-text-secondary-dark">
Pick a folder on your computer. Files stay local.
</p>
</button> </button>
</div>
<button <button
onclick={handleCreate} onclick={() => (mode = "webdav")}
disabled={!name.trim() || !path.trim()} class="w-full rounded-xl border border-border-light p-4 text-left hover:bg-black/5 dark:border-border-dark dark:hover:bg-white/10"
class="w-full rounded-lg bg-primary py-2.5 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-40" >
> <p class="text-sm font-semibold">WebDAV Server</p>
Create Workspace <p class="mt-0.5 text-xs text-text-secondary-light dark:text-text-secondary-dark">
</button> Connect to a WebDAV server. The app manages local files automatically.
</p>
</button>
<div class="my-4 flex items-center gap-3"> {:else if mode === "local"}
<div class="h-px flex-1 bg-border-light dark:bg-border-dark"></div> <!-- Step 2a: Local workspace -->
<span class="text-xs opacity-40">or</span> <p class="mb-6 text-sm text-text-secondary-light dark:text-text-secondary-dark">
<div class="h-px flex-1 bg-border-light dark:bg-border-dark"></div> Create a new workspace or open an existing one.
</div> </p>
<button <label class="mb-1 block text-sm font-medium">
onclick={handleOpen} Workspace name
class="w-full rounded-lg border border-border-light py-2.5 text-sm font-medium hover:bg-black/5 dark:border-border-dark dark:hover:bg-white/10" <input
> type="text"
Open Existing Folder bind:value={name}
</button> placeholder="My Tasks"
class="mt-1 mb-4 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm font-normal outline-none focus:border-primary dark:border-border-dark"
/>
</label>
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="mb-1 block text-sm font-medium">Folder</label>
<div class="mb-6 flex gap-2">
<input
type="text"
bind:value={path}
readonly
placeholder="Select a folder..."
class="min-w-0 flex-1 rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm dark:border-border-dark"
/>
<button
onclick={pickFolder}
class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover"
>
Browse
</button>
</div>
<button
onclick={handleCreate}
disabled={!name.trim() || !path.trim()}
class="w-full rounded-lg bg-primary py-2.5 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-40"
>
Create Workspace
</button>
<div class="my-4 flex items-center gap-3">
<div class="h-px flex-1 bg-border-light dark:bg-border-dark"></div>
<span class="text-xs opacity-40">or</span>
<div class="h-px flex-1 bg-border-light dark:bg-border-dark"></div>
</div>
<button
onclick={handleOpen}
class="mb-3 w-full rounded-lg border border-border-light py-2.5 text-sm font-medium hover:bg-black/5 dark:border-border-dark dark:hover:bg-white/10"
>
Open Existing Folder
</button>
{#if !isMobile}
<button
onclick={goBack}
class="w-full rounded-lg py-2 text-sm opacity-50 hover:opacity-80"
>
Back
</button>
{/if}
{:else}
<!-- Step 2b: WebDAV workspace -->
<p class="mb-6 text-sm text-text-secondary-light dark:text-text-secondary-dark">
Connect to a WebDAV server for cloud-synced tasks.
</p>
<label class="mb-1 block text-sm font-medium">
Workspace name
<input
type="text"
bind:value={name}
placeholder="My Tasks"
class="mt-1 mb-4 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm font-normal outline-none focus:border-primary dark:border-border-dark"
/>
</label>
<label class="mb-1 block text-xs font-medium opacity-60">Server URL</label>
<input
type="url"
bind:value={webdavUrl}
placeholder="https://dav.example.com/tasks/"
class="mb-3 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark"
/>
<label class="mb-1 block text-xs font-medium opacity-60">Username</label>
<input
type="text"
bind:value={webdavUser}
class="mb-3 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark"
/>
<label class="mb-1 block text-xs font-medium opacity-60">Password</label>
<input
type="password"
bind:value={webdavPass}
class="mb-4 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm outline-none focus:border-primary dark:border-border-dark"
/>
<div class="mb-4 flex gap-2">
<button
onclick={testConnection}
disabled={!webdavUrl.trim()}
class="rounded-lg border border-border-light px-4 py-2 text-sm font-medium hover:bg-black/5 disabled:opacity-40 dark:border-border-dark dark:hover:bg-white/10"
>
{testStatus === "testing" ? "Testing..." : testStatus === "ok" ? "Connected" : testStatus === "fail" ? "Failed -- Retry" : "Test Connection"}
</button>
</div>
<button
onclick={handleCreateWebdav}
disabled={!name.trim() || !webdavUrl.trim()}
class="w-full rounded-lg bg-primary py-2.5 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-40"
>
Create Workspace
</button>
{#if !isMobile}
<button
onclick={goBack}
class="mt-3 w-full rounded-lg py-2 text-sm opacity-50 hover:opacity-80"
>
Back
</button>
{/if}
{/if}
</div> </div>
</div> </div>
</div> </div>

View file

@ -35,6 +35,7 @@
let showDrawer = $state(false); let showDrawer = $state(false);
let showSettings = $state(false); let showSettings = $state(false);
let settingsWorkspace = $state<string | null>(null);
let showNewList = $state(false); let showNewList = $state(false);
let showWorkspacePicker = $state(false); let showWorkspacePicker = $state(false);
let workspacePickerEl = $state<HTMLDivElement | null>(null); let workspacePickerEl = $state<HTMLDivElement | null>(null);
@ -152,7 +153,7 @@
clone.style.position = "absolute"; clone.style.position = "absolute";
clone.style.top = "-9999px"; clone.style.top = "-9999px";
clone.style.left = "-9999px"; clone.style.left = "-9999px";
if (app.darkMode) { if (app.isDark) {
clone.classList.add("dark"); clone.classList.add("dark");
clone.style.backgroundColor = "var(--color-surface-dark)"; clone.style.backgroundColor = "var(--color-surface-dark)";
clone.style.color = "var(--color-text-dark)"; clone.style.color = "var(--color-text-dark)";
@ -192,12 +193,9 @@
showNewList = false; showNewList = false;
} }
function openSettings() {
showSettings = true;
}
function closeSettings() { function closeSettings() {
showSettings = false; showSettings = false;
settingsWorkspace = null;
} }
function handleHeaderMouseDown(e: MouseEvent) { function handleHeaderMouseDown(e: MouseEvent) {
@ -256,7 +254,7 @@
{/if} {/if}
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<p class="truncate text-sm">{name}</p> <p class="truncate text-sm">{name}</p>
<p class="truncate text-xs opacity-40">{ws?.path ?? ""}</p> <p class="truncate text-xs opacity-40">{ws?.mode === "webdav" ? ws.webdav_url ?? "WebDAV" : ws?.path ?? ""}</p>
</div> </div>
</button> </button>
<div class="relative shrink-0" data-ws-menu> <div class="relative shrink-0" data-ws-menu>
@ -270,6 +268,15 @@
</button> </button>
{#if wsMenuName === name} {#if wsMenuName === name}
<div class="absolute right-0 top-full z-40 mt-1 min-w-[140px] rounded-lg border border-border-light bg-surface-light py-1 shadow-lg dark:border-border-dark dark:bg-surface-dark"> <div class="absolute right-0 top-full z-40 mt-1 min-w-[140px] rounded-lg border border-border-light bg-surface-light py-1 shadow-lg dark:border-border-dark dark:bg-surface-dark">
<button
onclick={() => { wsMenuName = null; settingsWorkspace = name; showSettings = true; showWorkspacePicker = false; }}
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-black/5 dark:hover:bg-white/10"
>
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg>
Settings
</button>
<button <button
onclick={() => { wsMenuName = null; confirmRemoveWorkspace = name; }} onclick={() => { wsMenuName = null; confirmRemoveWorkspace = name; }}
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10" class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10"
@ -347,20 +354,6 @@
</div> </div>
</div> </div>
<!-- Settings -->
<button
onclick={openSettings}
class="flex shrink-0 items-center gap-2 border-t border-border-light px-5 py-3 text-sm opacity-50 hover:bg-black/5 hover:opacity-80 dark:border-border-dark dark:hover:bg-white/10"
>
<svg class="h-4.5 w-4.5" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
clip-rule="evenodd"
/>
</svg>
Settings
</button>
</div> </div>
<!-- Main content panel --> <!-- Main content panel -->
@ -632,7 +625,7 @@
class="relative flex h-full w-full flex-col overflow-hidden rounded-2xl bg-surface-light transition-transform duration-200 dark:bg-surface-dark {showSettings ? 'scale-100' : 'scale-95'}" class="relative flex h-full w-full flex-col overflow-hidden rounded-2xl bg-surface-light transition-transform duration-200 dark:bg-surface-dark {showSettings ? 'scale-100' : 'scale-95'}"
style="border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 25px 60px rgba(0,0,0,0.7), 0 10px 20px rgba(0,0,0,0.5)" style="border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 25px 60px rgba(0,0,0,0.7), 0 10px 20px rgba(0,0,0,0.5)"
> >
<SettingsScreen onclose={closeSettings} /> <SettingsScreen onclose={closeSettings} workspaceName={settingsWorkspace ?? app.config?.current_workspace ?? ""} />
</div> </div>
</div> </div>