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
This commit is contained in:
2026-09-09 10:30:18 +02:00
parent 95c72340fe
commit 136afa9921
4 changed files with 35 additions and 3 deletions
@@ -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;
+17 -2
View File
@@ -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<T>(promise: Promise<T>, ms: number): Promise<T> {
let timer: ReturnType<typeof setTimeout>;
try {
+12
View File
@@ -0,0 +1,12 @@
// Optional UI preferences must never prevent the workspace from opening.
export function readWorkspacePreferences(key: string): Record<string, string> {
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<string, string>) {
try { localStorage.setItem(key, JSON.stringify(value)); } catch { /* Storage may be unavailable or full. */ }
}