From 136afa9921eb5642ecd7bfcfff47a3fae571d106 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Wed, 9 Sep 2026 10:30:18 +0200 Subject: [PATCH] feat(workspace): persist workspace UI filters and disable drag-drop Add a lightweight workspace preferences helper to store optional UI settings in localStorage and integrate it with the issues views so filters, queries, and view mode are restored per source. The integration board now remembers repository and query filters, and the Issue Center restores state filters and view mode across sessions. Also disable drag and drop for the main Tauri window to avoid accidental file drops. - Add read/write helpers for workspace preferences in localStorage - Persist and restore filters, queries, and view mode for issue views - Disable window drag-and-drop to prevent accidental file drops --- src-tauri/tauri.conf.json | 1 + .../components/IntegrationBoardView.svelte | 6 +++++- src/lib/components/IssueCenter.svelte | 19 +++++++++++++++++-- src/lib/workspacePreferences.ts | 12 ++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/lib/workspacePreferences.ts diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 1747ec6..e555b42 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -13,6 +13,7 @@ "windows": [ { "label": "main", + "dragDropEnabled": false, "title": "Gitty", "width": 1200, "height": 800, diff --git a/src/lib/components/IntegrationBoardView.svelte b/src/lib/components/IntegrationBoardView.svelte index 50ac53b..be3993e 100644 --- a/src/lib/components/IntegrationBoardView.svelte +++ b/src/lib/components/IntegrationBoardView.svelte @@ -6,6 +6,7 @@ import { onMount, onDestroy } from "svelte"; import { Columns3, ExternalLink, RefreshCw, Search, X } from "@lucide/svelte"; import SelectMenu from "./SelectMenu.svelte"; + import { readWorkspacePreferences, writeWorkspacePreferences } from "../workspacePreferences"; import IssueLabels from "./IssueLabels.svelte"; import IssueAssignees from "./IssueAssignees.svelte"; import { issueTone } from "../issuePresentation"; @@ -33,6 +34,7 @@ let error = ""; let query = ""; let repositoryFilter = ""; + $: if (activeUrl) writeWorkspacePreferences(`${preferenceKey}:filters:${activeUrl}`, { repositoryFilter, query }); let selected: IntegrationBoardCard | null = null; let selectedColumnId = ""; let generation = 0; @@ -115,7 +117,9 @@ const requestGeneration = ++generation; if (requestedUrl !== activeUrl) { selected = null; - repositoryFilter = ""; + const saved = readWorkspacePreferences(`${preferenceKey}:filters:${requestedUrl}`); + repositoryFilter = saved.repositoryFilter ?? ""; + query = saved.query ?? ""; board = boardCache.get(`${preferenceKey}:${requestedUrl}`) ?? null; } activeUrl = requestedUrl; diff --git a/src/lib/components/IssueCenter.svelte b/src/lib/components/IssueCenter.svelte index e9f2277..030be3b 100644 --- a/src/lib/components/IssueCenter.svelte +++ b/src/lib/components/IssueCenter.svelte @@ -14,6 +14,7 @@ import { issueTone, issueStateLabel } from "../issuePresentation"; import IntegrationBoardView from "./IntegrationBoardView.svelte"; import SelectMenu from "./SelectMenu.svelte"; + import { readWorkspacePreferences, writeWorkspacePreferences } from "../workspacePreferences"; import { listIntegrationIssues, closeIntegrationIssue, openInBrowser } from "../git"; import { configuredIntegrationSources, integrationCredentialKey } from "../integrations"; import type { GitIntegrationSettings, IntegrationIssue, StoredCredential } from "../types"; @@ -44,6 +45,7 @@ let mounted = false; let generation = 0; let lastKey = ""; + const preferenceKey = "gitty.issues.v1"; $: de = language === "de"; $: sources = configuredIntegrationSources(integrations); $: source = sources.find(item => item.id === sourceId) ?? sources[0]; @@ -56,11 +58,14 @@ issues = saved?.issues ?? []; nextCursor = saved?.nextCursor ?? null; selectedId = ""; - stateFilter = ""; - repositoryFilter = ""; + restoreFilters(sourceKey); error = ""; if (source) void loadIssues(); } + $: if (mounted && sourceKey && sourceKey === lastKey) { + writeWorkspacePreferences(preferenceKey, { sourceId: source?.id ?? "", viewMode }); + writeWorkspacePreferences(`${preferenceKey}:${sourceKey}`, { stateFilter, repositoryFilter, query }); + } $: normalizedQuery = query.trim().toLocaleLowerCase(); $: filtered = issues.filter(issue => (!repositoryFilter || issue.repositoryName === repositoryFilter) && (!stateFilter || issue.state === stateFilter) && `${issue.title} ${issue.number} ${issue.repositoryName} ${issue.author} ${issue.labels.join(" ")} ${issue.assignees.join(" ")}`.toLocaleLowerCase().includes(normalizedQuery)); @@ -71,6 +76,9 @@ $: description = selected ? plainDescription(selected) : ""; onMount(() => { + const saved = readWorkspacePreferences(preferenceKey); + sourceId = saved.sourceId ?? ""; + viewMode = saved.viewMode === "board" ? "board" : "list"; mounted = true; reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const closeDetailsOutside = (event: PointerEvent) => { @@ -87,6 +95,13 @@ }); onDestroy(() => { generation++; }); + function restoreFilters(key: string) { + const saved = readWorkspacePreferences(`${preferenceKey}:${key}`); + stateFilter = saved.stateFilter ?? ""; + repositoryFilter = saved.repositoryFilter ?? ""; + query = saved.query ?? ""; + } + async function withTimeout(promise: Promise, ms: number): Promise { let timer: ReturnType; try { diff --git a/src/lib/workspacePreferences.ts b/src/lib/workspacePreferences.ts new file mode 100644 index 0000000..2890262 --- /dev/null +++ b/src/lib/workspacePreferences.ts @@ -0,0 +1,12 @@ +// Optional UI preferences must never prevent the workspace from opening. +export function readWorkspacePreferences(key: string): Record { + try { + const value: unknown = JSON.parse(localStorage.getItem(key) ?? "{}"); + if (!value || typeof value !== "object" || Array.isArray(value)) return {}; + return Object.fromEntries(Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === "string")); + } catch { return {}; } +} + +export function writeWorkspacePreferences(key: string, value: Record) { + try { localStorage.setItem(key, JSON.stringify(value)); } catch { /* Storage may be unavailable or full. */ } +}