Files
phokus/src/App.tsx
T
LyAhn 6bef90b7fb feat: manual albums + gallery multi-select with bulk actions
Albums (manual collections):
- New albums/album_images tables with FK cascades; DB functions and Tauri
  commands for create/rename/delete/delete-many/reorder/list, add/remove
  images, and paginated get_album_images.
- Distinct sidebar "ALBUMS" section with cover thumbnails, create/rename/
  delete, and a Manage multi-select mode for bulk album deletion.
- Album view reuses the gallery grid (activeView "album" + selectedAlbumId);
  spans folders; add from the bulk bar or the lightbox, remove from within.

Gallery multi-select + bulk actions:
- gallerySelectedIds selection model with a top-left corner checkbox that
  reveals on corner hover; click-to-toggle in selection mode, double-click
  to open.
- Floating BulkActionBar: tag (inline autocomplete popover), rating,
  favorite, add-to-album, and a delete with an explicit "from disk"
  confirmation. Batch commands bulk_update_details/bulk_add_tags/
  bulk_remove_tag.

Also:
- Duplicate Finder delete now requires confirmation with clear "from disk"
  wording (was single-click fire-and-forget).
- CPU/CUDA build-variant badge in Settings (get_build_variant).
- Rating/favorite no longer re-sorts derived collections (similar/region/
  semantic/tag/album results); single and bulk paths replace in place there.
- Album-aware indexed-images/media-updated handlers so thumbnails paint and
  newly-indexed files don't leak into an album view.
- CHANGELOG updated.
2026-06-27 15:23:54 +01:00

113 lines
4.3 KiB
TypeScript

import { useEffect } from "react";
import { useGalleryStore } from "./store";
import { Sidebar } from "./components/Sidebar";
import { BackgroundTasks } from "./components/BackgroundTasks";
import { Toolbar } from "./components/Toolbar";
import { Gallery } from "./components/Gallery";
import { Lightbox } from "./components/Lightbox";
import { TagCloud } from "./components/TagCloud";
import { DuplicateFinder } from "./components/DuplicateFinder";
import { Timeline } from "./components/Timeline";
import { TitleBar } from "./components/TitleBar";
import { SettingsModal } from "./components/SettingsModal";
import { FolderPickerModal } from "./components/FolderPickerModal";
import { UpdateToast } from "./components/UpdateToast";
import { WhatsNewToast } from "./components/WhatsNewToast";
import { WhatsNewModal } from "./components/WhatsNewModal";
import { OnboardingOverlay } from "./components/onboarding/OnboardingOverlay";
import { DemoPanel } from "./components/DemoPanel";
import { initializeNotifications } from "./notifications";
export default function App() {
const loadFolders = useGalleryStore((state) => state.loadFolders);
const loadBackgroundJobProgress = useGalleryStore((state) => state.loadBackgroundJobProgress);
const loadImages = useGalleryStore((state) => state.loadImages);
const loadCaptionModelStatus = useGalleryStore((state) => state.loadCaptionModelStatus);
const loadDuplicateScanCache = useGalleryStore((state) => state.loadDuplicateScanCache);
const loadAlbums = useGalleryStore((state) => state.loadAlbums);
const loadMutedFolderIds = useGalleryStore((state) => state.loadMutedFolderIds);
const loadNotificationsPaused = useGalleryStore((state) => state.loadNotificationsPaused);
const subscribeToProgress = useGalleryStore((state) => state.subscribeToProgress);
const loadAppVersion = useGalleryStore((state) => state.loadAppVersion);
const checkForUpdates = useGalleryStore((state) => state.checkForUpdates);
const loadFfmpegStatus = useGalleryStore((state) => state.loadFfmpegStatus);
const loadOnboardingCompleted = useGalleryStore((state) => state.loadOnboardingCompleted);
const initWhatsNew = useGalleryStore((state) => state.initWhatsNew);
const activeView = useGalleryStore((state) => state.activeView);
useEffect(() => {
void initializeNotifications();
void loadMutedFolderIds();
void loadNotificationsPaused();
void loadFfmpegStatus();
void loadOnboardingCompleted();
// Load the app version first so the What's New toast/modal (which read
// appVersion from the store) have it before the greeting can appear.
void loadAppVersion().then(() => initWhatsNew());
// Quiet launch check — dev builds have no signed artifacts to update to.
if (import.meta.env.PROD) {
void checkForUpdates({ quiet: true });
}
loadFolders().then(() => {
void loadBackgroundJobProgress();
void loadCaptionModelStatus();
void loadDuplicateScanCache();
void loadAlbums();
return loadImages(true);
});
let unlisten: (() => void) | undefined;
subscribeToProgress().then((fn) => {
unlisten = fn;
});
return () => {
unlisten?.();
};
}, []);
return (
<div className="flex h-screen flex-col bg-gray-950 text-white overflow-hidden select-none">
{/* Custom title bar — sits at the very top */}
<TitleBar />
{/* Main app content below the title bar */}
<div className="flex flex-1 min-h-0">
<Sidebar />
<main className="flex-1 flex flex-col min-w-0">
{activeView === "timeline" ? (
<>
<Toolbar />
<BackgroundTasks />
<Timeline />
</>
) : activeView === "explore" ? (
<>
<BackgroundTasks />
<TagCloud />
</>
) : activeView === "duplicates" ? (
<>
<BackgroundTasks />
<DuplicateFinder />
</>
) : (
<>
<Toolbar />
<BackgroundTasks />
<Gallery />
</>
)}
</main>
</div>
<Lightbox />
<SettingsModal />
<FolderPickerModal />
<UpdateToast />
<WhatsNewToast />
<WhatsNewModal />
<OnboardingOverlay />
{import.meta.env.DEV && <DemoPanel />}
</div>
);
}