From a2edf9914b976e2a93f3eb6d094ddcc7b0e4a078 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 16 Apr 2026 12:02:01 +0200 Subject: [PATCH] feat: improve label edition --- .../src/components/inline-label-editor.css.ts | 1 + .../src/components/inline-label-editor.tsx | 3 -- .../src/ui/pages/feature-request.tsx | 40 +++++++++---------- packages/kanban/src/ui/pages/task.tsx | 40 +++++++++---------- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/packages/client/src/components/inline-label-editor.css.ts b/packages/client/src/components/inline-label-editor.css.ts index acb19be..85ec7c5 100644 --- a/packages/client/src/components/inline-label-editor.css.ts +++ b/packages/client/src/components/inline-label-editor.css.ts @@ -9,6 +9,7 @@ export const editButton = style({ display: "inline-flex", alignItems: "center", gap: "4px", + inlineSize: "3.5rem", background: "none", border: "none", color: vars.color.textMuted, diff --git a/packages/client/src/components/inline-label-editor.tsx b/packages/client/src/components/inline-label-editor.tsx index 563b83b..c408894 100644 --- a/packages/client/src/components/inline-label-editor.tsx +++ b/packages/client/src/components/inline-label-editor.tsx @@ -9,12 +9,10 @@ export function InlineLabelEditor({ labels, selectedIds, onToggle, - disabled = false, }: { labels: LabelOption[]; selectedIds: string[]; onToggle: (labelId: string, selected: boolean) => void; - disabled?: boolean; }) { const open = useSignal(false); const wrapperRef = useRef(null); @@ -65,7 +63,6 @@ export function InlineLabelEditor({ onToggle(label.id, !isSelected)} /> diff --git a/packages/feature-requests/src/ui/pages/feature-request.tsx b/packages/feature-requests/src/ui/pages/feature-request.tsx index 92e4f4d..5605568 100644 --- a/packages/feature-requests/src/ui/pages/feature-request.tsx +++ b/packages/feature-requests/src/ui/pages/feature-request.tsx @@ -1,4 +1,4 @@ -import { useSignal, useComputed } from "@preact/signals"; +import { useSignal, useComputed, batch } from "@preact/signals"; import { auth } from "@exosphere/client/auth"; import { useLocation, useRoute, spherePath } from "@exosphere/client/router"; import { useCanDo } from "@exosphere/client/permissions"; @@ -568,7 +568,7 @@ export function FeatureRequestPage() { const availableLabels = useSignal([]); const localLabelIds = useSignal(null); - const savingLabels = useSignal(false); + const labelSaveTimer = useRef | null>(null); useEffect(() => { if (canChangeStatus.value) { @@ -635,25 +635,26 @@ export function FeatureRequestPage() { } }; - const handleLabelToggle = async (labelId: string, selected: boolean) => { - if (!fr || savingLabels.value) return; + const handleLabelToggle = (labelId: string, selected: boolean) => { + if (!fr) return; const label = availableLabels.value.find((l) => l.id === labelId); if (!label) return; - const prev = fr.labels; - const prevIds = prev.map((l) => l.id); - const nextIds = selected ? [...prevIds, labelId] : prevIds.filter((id) => id !== labelId); - const nextLabels = selected ? [...prev, label] : prev.filter((l) => l.id !== labelId); - fr.labels = nextLabels; - localLabelIds.value = nextIds; - savingLabels.value = true; - try { - await updateFeatureRequestLabels(fr.id, nextIds); - } catch { - fr.labels = prev; - localLabelIds.value = prev.map((l) => l.id); - } finally { - savingLabels.value = false; - } + const nextLabels = selected + ? [...fr.labels, label] + : fr.labels.filter((l) => l.id !== labelId); + const nextIds = nextLabels.map((l) => l.id); + batch(() => { + fr.labels = nextLabels; + localLabelIds.value = nextIds; + }); + if (labelSaveTimer.current) clearTimeout(labelSaveTimer.current); + labelSaveTimer.current = setTimeout(async () => { + try { + await updateFeatureRequestLabels(fr.id, localLabelIds.value!); + } catch { + // Refetch to get the true server state + } + }, 300); }; return ( @@ -726,7 +727,6 @@ export function FeatureRequestPage() { labels={availableLabels.value} selectedIds={localLabelIds.value ?? fr.labels.map((l) => l.id)} onToggle={handleLabelToggle} - disabled={savingLabels.value} /> ) : null} diff --git a/packages/kanban/src/ui/pages/task.tsx b/packages/kanban/src/ui/pages/task.tsx index 0876b79..d7820d8 100644 --- a/packages/kanban/src/ui/pages/task.tsx +++ b/packages/kanban/src/ui/pages/task.tsx @@ -1,4 +1,4 @@ -import { useSignal } from "@preact/signals"; +import { useSignal, batch } from "@preact/signals"; import { auth } from "@exosphere/client/auth"; import { useLocation, useRoute, spherePath } from "@exosphere/client/router"; import { useCanDo } from "@exosphere/client/permissions"; @@ -396,7 +396,7 @@ export function TaskPage() { const availableLabels = useSignal([]); const localLabelIds = useSignal(null); - const savingLabels = useSignal(false); + const labelSaveTimer = useRef | null>(null); useEffect(() => { if (canManage.value) { @@ -466,25 +466,26 @@ export function TaskPage() { } }; - const handleLabelToggle = async (labelId: string, selected: boolean) => { - if (!task || savingLabels.value) return; + const handleLabelToggle = (labelId: string, selected: boolean) => { + if (!task) return; const label = availableLabels.value.find((l) => l.id === labelId); if (!label) return; - const prev = task.labels; - const prevIds = prev.map((l) => l.id); - const nextIds = selected ? [...prevIds, labelId] : prevIds.filter((id) => id !== labelId); - const nextLabels = selected ? [...prev, label] : prev.filter((l) => l.id !== labelId); - task.labels = nextLabels; - localLabelIds.value = nextIds; - savingLabels.value = true; - try { - await updateTaskLabels(task.id, nextIds); - } catch { - task.labels = prev; - localLabelIds.value = prev.map((l) => l.id); - } finally { - savingLabels.value = false; - } + const nextLabels = selected + ? [...task.labels, label] + : task.labels.filter((l) => l.id !== labelId); + const nextIds = nextLabels.map((l) => l.id); + batch(() => { + task.labels = nextLabels; + localLabelIds.value = nextIds; + }); + if (labelSaveTimer.current) clearTimeout(labelSaveTimer.current); + labelSaveTimer.current = setTimeout(async () => { + try { + await updateTaskLabels(task.id, localLabelIds.value!); + } catch { + // Refetch to get the true server state + } + }, 300); }; return ( @@ -607,7 +608,6 @@ export function TaskPage() { labels={availableLabels.value} selectedIds={localLabelIds.value ?? task.labels.map((l) => l.id)} onToggle={handleLabelToggle} - disabled={savingLabels.value} /> ) : task.labels.length > 0 ? (
-- 2.51.2