From 34d2b48578532d387b1466c82ae4a761cd1d1a4f Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 2 Nov 2025 20:13:57 +0000 Subject: feat: Add view options to show tag/title and control image fit. Fixes #1960 --- apps/web/components/dashboard/ViewOptions.tsx | 154 ++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 7 deletions(-) (limited to 'apps/web/components/dashboard/ViewOptions.tsx') diff --git a/apps/web/components/dashboard/ViewOptions.tsx b/apps/web/components/dashboard/ViewOptions.tsx index c86f43fb..e7b459fe 100644 --- a/apps/web/components/dashboard/ViewOptions.tsx +++ b/apps/web/components/dashboard/ViewOptions.tsx @@ -1,6 +1,12 @@ "use client"; -import { createElement, useEffect, useState } from "react"; +import { + createElement, + useEffect, + useOptimistic, + useState, + useTransition, +} from "react"; import { ButtonWithTooltip } from "@/components/ui/button"; import { DropdownMenu, @@ -21,10 +27,15 @@ import { import { updateBookmarksLayout, updateGridColumns, + updateImageFit, updateShowNotes, + updateShowTags, + updateShowTitle, } from "@/lib/userLocalSettings/userLocalSettings"; import { Check, + Heading, + Image, LayoutDashboard, LayoutGrid, LayoutList, @@ -32,6 +43,7 @@ import { LucideIcon, NotepadText, Settings, + Tag, } from "lucide-react"; type LayoutType = "masonry" | "grid" | "list" | "compact"; @@ -47,16 +59,73 @@ export default function ViewOptions() { const { t } = useTranslation(); const layout = useBookmarkLayout(); const gridColumns = useGridColumns(); - const { showNotes } = useBookmarkDisplaySettings(); + const actualDisplaySettings = useBookmarkDisplaySettings(); const [tempColumns, setTempColumns] = useState(gridColumns); + const [, startTransition] = useTransition(); + + // Optimistic state for all toggles + const [optimisticDisplaySettings, setOptimisticDisplaySettings] = + useOptimistic(actualDisplaySettings); + + const [optimisticLayout, setOptimisticLayout] = useOptimistic(layout); - const showColumnSlider = layout === "grid" || layout === "masonry"; + const [optimisticImageFit, setOptimisticImageFit] = useOptimistic( + actualDisplaySettings.imageFit, + ); + + const showColumnSlider = + optimisticLayout === "grid" || optimisticLayout === "masonry"; // Update temp value when actual value changes useEffect(() => { setTempColumns(gridColumns); }, [gridColumns]); + // Handlers with optimistic updates + const handleLayoutChange = (newLayout: LayoutType) => { + startTransition(async () => { + setOptimisticLayout(newLayout); + await updateBookmarksLayout(newLayout); + }); + }; + + const handleShowNotesChange = (checked: boolean) => { + startTransition(async () => { + setOptimisticDisplaySettings({ + ...optimisticDisplaySettings, + showNotes: checked, + }); + await updateShowNotes(checked); + }); + }; + + const handleShowTagsChange = (checked: boolean) => { + startTransition(async () => { + setOptimisticDisplaySettings({ + ...optimisticDisplaySettings, + showTags: checked, + }); + await updateShowTags(checked); + }); + }; + + const handleShowTitleChange = (checked: boolean) => { + startTransition(async () => { + setOptimisticDisplaySettings({ + ...optimisticDisplaySettings, + showTitle: checked, + }); + await updateShowTitle(checked); + }); + }; + + const handleImageFitChange = (fit: "cover" | "contain") => { + startTransition(async () => { + setOptimisticImageFit(fit); + await updateImageFit(fit); + }); + }; + return ( @@ -76,13 +145,16 @@ export default function ViewOptions() { await updateBookmarksLayout(key as LayoutType)} + onSelect={(e) => { + e.preventDefault(); + handleLayoutChange(key); + }} >
{createElement(iconMap[key as LayoutType], { size: 18 })} {t(`layouts.${key}`)}
- {layout === key && } + {optimisticLayout === key && }
))} @@ -131,11 +203,79 @@ export default function ViewOptions() { + + +
+ + +
+ +
+ +
+ + +
+ {t("view_options.image_options")} +
+ +
+ { + e.preventDefault(); + handleImageFitChange("cover"); + }} + > +
+ + {t("view_options.image_fit_cover")} +
+ {optimisticImageFit === "cover" && ( + + )} +
+ { + e.preventDefault(); + handleImageFitChange("contain"); + }} + > +
+ + {t("view_options.image_fit_contain")} +
+ {optimisticImageFit === "contain" && ( + + )} +
+
); -- cgit v1.2.3-70-g09d2