updated UI to remove odd flashing

This commit is contained in:
2026-03-31 23:01:09 -04:00
parent 1d6edaa1de
commit 120392e11b
10 changed files with 207 additions and 89 deletions

View File

@@ -432,7 +432,17 @@ export default function JobManager() {
}
const data = await apiJson<Job[]>(`/api/jobs/table?${params}`);
setJobs(data);
setJobs((prev) =>
data.map((serverJob) => {
const local = prev.find((j) => j.id === serverJob.id);
const terminal = ["completed", "skipped", "failed", "cancelled"];
if (local && terminal.includes(local.status)) {
// Keep the terminal state from SSE to prevent flickering back to a stale poll state.
return { ...serverJob, status: local.status };
}
return serverJob;
})
);
setActionError(null);
} catch (e) {
const message = isApiError(e) ? e.message : "Failed to fetch jobs";

View File

@@ -74,9 +74,9 @@ export default function SettingsPanel() {
navItemRefs.current[tab.id] = node;
}}
onClick={() => paginate(tab.id)}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-md text-sm font-bold transition-all duration-200 group ${isActive
? "text-helios-ink bg-helios-surface-soft shadow-sm border border-helios-line/20"
: "text-helios-slate hover:text-helios-ink hover:bg-helios-surface-soft/50"
className={`w-full flex items-center gap-3 px-4 py-3 rounded-md text-sm font-bold border transition-all duration-200 group ${isActive
? "text-helios-ink bg-helios-surface-soft shadow-sm border-helios-line/20"
: "text-helios-slate border-transparent hover:text-helios-ink hover:bg-helios-surface-soft/50"
}`}
>
<span className="flex items-center gap-3">

View File

@@ -169,33 +169,26 @@ export default function WatchFolders() {
}
try {
// Add to BOTH config (canonical) and DB (profiles)
const bundle = await apiJson<SettingsBundleResponse>("/api/settings/bundle");
if (!bundle.settings.scanner.directories.includes(normalized)) {
await apiAction("/api/settings/bundle", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...bundle.settings,
scanner: {
...bundle.settings.scanner,
directories: [...bundle.settings.scanner.directories, normalized],
},
}),
});
}
try {
await apiAction("/api/settings/watch-dirs", {
const currentDirs = bundle.settings.scanner.directories;
if (currentDirs.includes(normalized)) {
// Even if it's in config, sync it to ensure it's in DB for profiles
await apiAction("/api/settings/folders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path: normalized, is_recursive: true }),
body: JSON.stringify({
dirs: currentDirs.map(d => ({ path: d, is_recursive: true }))
}),
});
} else {
await apiAction("/api/settings/folders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
dirs: [...currentDirs, normalized].map(d => ({ path: d, is_recursive: true }))
}),
});
} catch (innerE) {
// If it's just a duplicate DB error we can ignore it since we successfully added to canonical
if (!(isApiError(innerE) && innerE.status === 409)) {
throw innerE;
}
}
setDirInput("");
@@ -214,30 +207,16 @@ export default function WatchFolders() {
if (!dir) return;
try {
// Remove from canonical config if present
const bundle = await apiJson<SettingsBundleResponse>("/api/settings/bundle");
const filteredDirs = bundle.settings.scanner.directories.filter(candidate => candidate !== dir.path);
const filteredDirs = bundle.settings.scanner.directories.filter(candidate => candidate !== dirPath);
if (filteredDirs.length !== bundle.settings.scanner.directories.length) {
await apiAction("/api/settings/bundle", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...bundle.settings,
scanner: {
...bundle.settings.scanner,
directories: filteredDirs,
},
}),
});
}
// Remove from DB if it has a real ID
if (dir.id > 0) {
await apiAction(`/api/settings/watch-dirs/${dir.id}`, {
method: "DELETE",
});
}
await apiAction("/api/settings/folders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
dirs: filteredDirs.map(d => ({ path: d, is_recursive: true }))
}),
});
setError(null);
await fetchDirs();

View File

@@ -15,7 +15,7 @@ interface LibraryStepProps {
}
interface FsBreadcrumb {
name: string;
label: string;
path: string;
}
@@ -193,7 +193,7 @@ export default function LibraryStep({
</div>
{pickerOpen ? (
<div className="flex h-[420px] flex-col gap-4 overflow-hidden rounded-lg border border-helios-line/30 bg-helios-surface p-4">
<div className="flex h-[540px] flex-col gap-4 overflow-hidden rounded-lg border border-helios-line/30 bg-helios-surface p-4">
<div className="shrink-0 flex items-start justify-between gap-4">
<div className="min-w-0 space-y-3">
<div className="space-y-1">
@@ -245,7 +245,7 @@ export default function LibraryStep({
: "rounded-lg px-2 py-1 transition-colors hover:bg-helios-surface-soft hover:text-helios-ink"
}
>
{crumb.name.replace(/^\//, "")}
{crumb.label.replace(/^\//, "")}
</button>
</div>
);
@@ -316,7 +316,7 @@ export default function LibraryStep({
..
</span>
<span className="block truncate text-xs text-helios-slate">
Go up to {parentBreadcrumb.name.replace(/^\//, "")}
Go up to {parentBreadcrumb.label.replace(/^\//, "")}
</span>
</div>
</button>

View File

@@ -117,7 +117,7 @@ export default function ServerDirectoryPicker({
/>
<div className="absolute inset-0 flex items-center justify-center px-4 py-6">
<div className="w-full max-w-5xl rounded-xl border border-helios-line/30 bg-helios-surface shadow-2xl overflow-hidden flex flex-col max-h-[min(90vh,800px)]">
<div className="w-full max-w-7xl rounded-xl border border-helios-line/30 bg-helios-surface shadow-2xl overflow-hidden flex flex-col max-h-[min(95vh,1000px)]">
<div className="border-b border-helios-line/20 px-6 py-5 flex items-start justify-between gap-4">
<div>
<div className="flex items-center gap-3">