From 79ce458fd5bc6c2ee20ac4716481cfe6d0f3a9d5 Mon Sep 17 00:00:00 2001 From: LyAhn Date: Mon, 29 Jun 2026 20:26:49 +0100 Subject: [PATCH] feat(tags): open the tag manager from Settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an "Open tag manager" button under a new Tag library group in Settings → AI Workspace. It closes Settings and jumps to Explore's tag Manage mode. To make manage mode reachable from outside the Explore view, lift its flag out of TagCloud's local state into the store (tagManagerOpen / setTagManagerOpen) behind an openTagManager() action. Manage mode is reset whenever Explore is entered normally or the visual-cluster view is selected, so openTagManager() stays the only path that opens it programmatically. --- CHANGELOG.md | 3 ++- src/components/SettingsModal.tsx | 12 ++++++++++++ src/components/TagCloud.tsx | 6 ++++-- src/store.ts | 31 ++++++++++++++++++++++++++----- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efd20c..6cc4c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,8 @@ aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - **Tag management** — Explore → Tag Cloud gains a Manage mode with a flat tag list where you can rename a tag, merge it into another (rename it to an existing tag's name), or delete it from every image. Changes apply across the - whole library. + whole library. Settings → AI Workspace also has an "Open tag manager" button + that jumps straight into it. - **Camera info in the lightbox** — the image info panel now shows EXIF details (camera, lens, aperture, shutter, ISO, focal length) and, when a photo is geotagged, its GPS coordinates as a link that opens the location in your diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index c9a2a41..92e43e1 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -247,6 +247,7 @@ export function SettingsModal() { const openOnboarding = useGalleryStore((state) => state.openOnboarding); const theme = useGalleryStore((state) => state.theme); const setTheme = useGalleryStore((state) => state.setTheme); + const openTagManager = useGalleryStore((state) => state.openTagManager); const lightboxAutoplay = useGalleryStore((state) => state.lightboxAutoplay); const setLightboxAutoplay = useGalleryStore((state) => state.setLightboxAutoplay); const lightboxAutoMute = useGalleryStore((state) => state.lightboxAutoMute); @@ -686,6 +687,17 @@ export function SettingsModal() { {taggerQueueStatus ?

{taggerQueueStatus}

: null} + + + + + + ) : (
diff --git a/src/components/TagCloud.tsx b/src/components/TagCloud.tsx index c38026a..380ae5d 100644 --- a/src/components/TagCloud.tsx +++ b/src/components/TagCloud.tsx @@ -983,7 +983,9 @@ export function TagCloud() { const renameTag = useGalleryStore((state) => state.renameTag); const deleteTag = useGalleryStore((state) => state.deleteTag); const selectedFolderId = useGalleryStore((state) => state.selectedFolderId); - const [manageTags, setManageTags] = useState(false); + // Manage mode lives in the store so it can be opened from elsewhere (Settings). + const manageTags = useGalleryStore((state) => state.tagManagerOpen); + const setManageTags = useGalleryStore((state) => state.setTagManagerOpen); const handleDeleteTag = async (tag: string) => { await deleteTag(tag); }; useEffect(() => { @@ -1028,7 +1030,7 @@ export function TagCloud() { ? "border-white/15 bg-white/10 text-white" : "border-white/8 bg-white/[0.03] text-gray-500 hover:text-gray-300" }`} - onClick={() => setManageTags((v) => !v)} + onClick={() => setManageTags(!manageTags)} > {manageTags ? "Done" : "Manage"} diff --git a/src/store.ts b/src/store.ts index f8e0d52..6a2fa65 100644 --- a/src/store.ts +++ b/src/store.ts @@ -375,20 +375,21 @@ interface GalleryState { galleryScrollResetKey: number; activeView: ActiveView; exploreMode: ExploreMode; + tagManagerOpen: boolean; tagCloudEntries: TagCloudEntry[]; tagCloudLoading: boolean; tagCloudFolderId: number | null | undefined; // undefined = never loaded exploreTagEntries: ExploreTagEntry[]; exploreTagLoading: boolean; - exploreTagsFolderId: number | null | undefined; // Cache-freshness key: the folder the loaded tags belong to. Set to undefined // by content mutations (tag add/remove/rename/delete) to mark the cache dirty // and force the next load to refetch. - relatedTagsByKey: Record; + exploreTagsFolderId: number | null | undefined; // The folder whose tags are actually on screen. Kept separate from the dirty // marker above so a same-folder invalidation isn't mistaken for a folder switch // (which would wipe the visible list and remount manager UI mid-refresh). exploreTagsShownFolderId: number | null | undefined; + relatedTagsByKey: Record; indexingProgress: Record; mediaJobProgress: Record; cacheDir: string; @@ -494,6 +495,8 @@ interface GalleryState { closeImage: () => void; setView: (view: ActiveView) => void; setExploreMode: (mode: ExploreMode) => void; + setTagManagerOpen: (open: boolean) => void; + openTagManager: () => void; loadTagCloud: (options?: { force?: boolean }) => Promise; loadExploreTags: (options?: { force?: boolean }) => Promise; loadRelatedTags: (tag: string) => Promise; @@ -874,17 +877,18 @@ export const useGalleryStore = create((set, get) => ({ galleryScrollResetKey: 0, activeView: "gallery", exploreMode: "visual", + tagManagerOpen: false, tagCloudEntries: [], tagCloudLoading: false, tagCloudFolderId: undefined, exploreTagEntries: [], exploreTagLoading: false, exploreTagsFolderId: undefined, + exploreTagsShownFolderId: undefined, relatedTagsByKey: {}, indexingProgress: {}, mediaJobProgress: {}, cacheDir: "", - exploreTagsShownFolderId: undefined, captionModelStatus: null, captionModelPreparing: false, captionModelError: null, @@ -1399,10 +1403,27 @@ export const useGalleryStore = create((set, get) => ({ return; } } - set({ activeView, similarSourceAlbumId: null, similarScope: similarScopeReset }); + // Entering Explore normally always starts in browse mode; openTagManager() is + // the only path that re-opens manage mode (it runs this then sets the flag). + set({ + activeView, + similarSourceAlbumId: null, + similarScope: similarScopeReset, + ...(activeView === "explore" ? { tagManagerOpen: false } : {}), + }); }, - setExploreMode: (exploreMode) => set({ exploreMode }), + setExploreMode: (exploreMode) => + // Manage mode only exists for the tag view; drop it when switching to visual + // clusters so re-entering the tag view starts in the normal browse state. + set(exploreMode === "visual" ? { exploreMode, tagManagerOpen: false } : { exploreMode }), + setTagManagerOpen: (tagManagerOpen) => set({ tagManagerOpen }), + // Jump straight to the tag manager from anywhere (e.g. the Settings panel): + // switch to Explore, select the tag view, open manage mode, and close Settings. + openTagManager: () => { + get().setView("explore"); + set({ exploreMode: "tags", tagManagerOpen: true, settingsOpen: false }); + }, loadTagCloud: async (options) => { const { selectedFolderId, tagCloudFolderId, tagCloudLoading } = get();