diff --git a/src/components/BulkActionBar.tsx b/src/components/BulkActionBar.tsx index e3e480f..699c1e3 100644 --- a/src/components/BulkActionBar.tsx +++ b/src/components/BulkActionBar.tsx @@ -1,12 +1,14 @@ import { useEffect, useRef, useState } from "react"; import { useGalleryStore } from "../store"; -import { AlbumPicker } from "./AlbumPicker"; +import { BulkAlbumPopover } from "./bulk/BulkAlbumPopover"; +import { BulkDeleteConfirm } from "./bulk/BulkDeleteConfirm"; +import { BulkRatingPopover } from "./bulk/BulkRatingPopover"; +import { BulkSelectionSummary } from "./bulk/BulkSelectionSummary"; import { BulkTagPopover } from "./bulk/BulkTagPopover"; +import { type BulkPanel } from "./bulk/types"; import { useDismissable } from "./menu"; -import { Tooltip } from "./Tooltip" -import { CloseIcon, StarIcon, WarningIcon } from "./icons"; - -type Panel = "tag" | "rating" | "album" | "delete" | null; +import { Tooltip } from "./Tooltip"; +import { CloseIcon } from "./icons"; export function BulkActionBar() { const selectedCount = useGalleryStore((state) => state.gallerySelectedIds.size); @@ -23,7 +25,7 @@ export function BulkActionBar() { const addToAlbum = useGalleryStore((state) => state.addToAlbum); const removeFromAlbum = useGalleryStore((state) => state.removeFromAlbum); - const [panel, setPanel] = useState(null); + const [panel, setPanel] = useState(null); const [deleting, setDeleting] = useState(false); const barRef = useRef(null); @@ -39,7 +41,7 @@ export function BulkActionBar() { const ids = Array.from(selectedIds); const inAlbumView = activeView === "album" && selectedAlbumId !== null; - const togglePanel = (next: Exclude) => setPanel((current) => (current === next ? null : next)); + const togglePanel = (next: Exclude) => setPanel((current) => (current === next ? null : next)); const handleDelete = async () => { setDeleting(true); @@ -66,19 +68,12 @@ export function BulkActionBar() { className="pointer-events-auto absolute bottom-6 left-1/2 z-30 flex -translate-x-1/2 items-center gap-1 rounded-xl border border-white/10 bg-gray-950/95 px-2 py-1.5 shadow-2xl shadow-black/50 backdrop-blur" onClick={(event) => event.stopPropagation()} > -
- {selectedCount} selected - {loadedCount < totalImages || loadedCount > selectedCount ? ( - - - - ) : null} -
+
@@ -93,39 +88,7 @@ export function BulkActionBar() { - {panel === "rating" ? ( -
- {Array.from({ length: 5 }, (_, index) => { - const rating = index + 1; - return ( - - - - ); - })} - -
- ) : null} + {panel === "rating" ? setPanel(null)} /> : null}
- {panel === "album" ? ( -
- -
- ) : null} + {panel === "album" ? : null} {inAlbumView ? ( @@ -159,52 +115,34 @@ export function BulkActionBar() {
- + {panel === "delete" ? ( -
-
- -

Delete from disk

-
-

- Permanently delete {selectedCount} file{selectedCount === 1 ? "" : "s"} from your computer. - This removes the actual file{selectedCount === 1 ? "" : "s"} from disk and cannot be undone. -

-
- - -
-
+ setPanel(null)} + onDelete={handleDelete} + /> ) : null}
- - + + ); diff --git a/src/components/bulk/BulkAlbumPopover.tsx b/src/components/bulk/BulkAlbumPopover.tsx new file mode 100644 index 0000000..b52e4e2 --- /dev/null +++ b/src/components/bulk/BulkAlbumPopover.tsx @@ -0,0 +1,16 @@ +import { AlbumPicker } from "../AlbumPicker"; + +interface BulkAlbumPopoverProps { + onPick: (albumId: number) => Promise; +} + +export function BulkAlbumPopover({ onPick }: BulkAlbumPopoverProps) { + return ( +
+ void onPick(albumId)} /> +
+ ); +} diff --git a/src/components/bulk/BulkDeleteConfirm.tsx b/src/components/bulk/BulkDeleteConfirm.tsx new file mode 100644 index 0000000..1c41558 --- /dev/null +++ b/src/components/bulk/BulkDeleteConfirm.tsx @@ -0,0 +1,41 @@ +import { WarningIcon } from "../icons"; + +interface BulkDeleteConfirmProps { + deleting: boolean; + selectedCount: number; + onCancel: () => void; + onDelete: () => Promise; +} + +export function BulkDeleteConfirm({ deleting, selectedCount, onCancel, onDelete }: BulkDeleteConfirmProps) { + return ( +
+
+ +

Delete from disk

+
+

+ Permanently delete {selectedCount} file{selectedCount === 1 ? "" : "s"} from your computer. This removes + the actual file{selectedCount === 1 ? "" : "s"} from disk and cannot be undone. +

+
+ + +
+
+ ); +} diff --git a/src/components/bulk/BulkRatingPopover.tsx b/src/components/bulk/BulkRatingPopover.tsx new file mode 100644 index 0000000..3728c72 --- /dev/null +++ b/src/components/bulk/BulkRatingPopover.tsx @@ -0,0 +1,41 @@ +import { Tooltip } from "../Tooltip"; +import { StarIcon } from "../icons"; + +interface BulkRatingPopoverProps { + onClose: () => void; + onSetRating: (rating: number) => Promise; +} + +export function BulkRatingPopover({ onClose, onSetRating }: BulkRatingPopoverProps) { + const setRating = async (rating: number) => { + await onSetRating(rating); + onClose(); + }; + + return ( +
+ {Array.from({ length: 5 }, (_, index) => { + const rating = index + 1; + return ( + + + + ); + })} + +
+ ); +} diff --git a/src/components/bulk/BulkSelectionSummary.tsx b/src/components/bulk/BulkSelectionSummary.tsx new file mode 100644 index 0000000..27f1913 --- /dev/null +++ b/src/components/bulk/BulkSelectionSummary.tsx @@ -0,0 +1,30 @@ +import { Tooltip } from "../Tooltip"; + +interface BulkSelectionSummaryProps { + loadedCount: number; + selectedCount: number; + totalImages: number; + onSelectAll: () => void; +} + +export function BulkSelectionSummary({ + loadedCount, + selectedCount, + totalImages, + onSelectAll, +}: BulkSelectionSummaryProps) { + const showSelectAll = loadedCount < totalImages || loadedCount > selectedCount; + + return ( +
+ {selectedCount} selected + {showSelectAll ? ( + + + + ) : null} +
+ ); +} diff --git a/src/components/bulk/types.ts b/src/components/bulk/types.ts new file mode 100644 index 0000000..7f0aac5 --- /dev/null +++ b/src/components/bulk/types.ts @@ -0,0 +1 @@ +export type BulkPanel = "tag" | "rating" | "album" | "delete" | null;