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:
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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. */ }
|
||||
}
|
||||
Reference in New Issue
Block a user