diff --git a/src/components/albums/potential-albums/PotentialAlbumsDates.tsx b/src/components/albums/potential-albums/PotentialAlbumsDates.tsx index bbe0c92..b602fe0 100644 --- a/src/components/albums/potential-albums/PotentialAlbumsDates.tsx +++ b/src/components/albums/potential-albums/PotentialAlbumsDates.tsx @@ -12,7 +12,7 @@ import { ArrowDown, ArrowUp, ArrowUpDownIcon, SortAsc, SortDesc } from "lucide-r export default function PotentialAlbumsDates() { const router = useRouter(); - const { updateContext } = usePotentialAlbumContext(); + const { updateContext, minAssets } = usePotentialAlbumContext(); const [dateRecords, setDateRecords] = React.useState< IPotentialAlbumsDatesResponse[] >([]); @@ -26,6 +26,7 @@ export default function PotentialAlbumsDates() { return listPotentialAlbumsDates({ sortBy: filters.sortBy, sortOrder: filters.sortOrder, + minAssets, }) .then(setDateRecords) .catch(setErrorMessage) @@ -43,7 +44,7 @@ export default function PotentialAlbumsDates() { useEffect(() => { fetchData(); - }, [filters]); + }, [filters, minAssets]); return (
diff --git a/src/components/assets/missing-location/MissingLocationDateItem.tsx b/src/components/assets/missing-location/MissingLocationDateItem.tsx index 4e4378e..67c5e0c 100644 --- a/src/components/assets/missing-location/MissingLocationDateItem.tsx +++ b/src/components/assets/missing-location/MissingLocationDateItem.tsx @@ -36,7 +36,7 @@ export default function MissingLocationDateItem({ record, onSelect, groupBy }: I } >

{dateLabel}

-

{record.asset_count} Orphan Assets

+

{record.asset_count} Assets

); } diff --git a/src/components/shared/AssetGrid.tsx b/src/components/shared/AssetGrid.tsx index bd5a74b..ffac294 100644 --- a/src/components/shared/AssetGrid.tsx +++ b/src/components/shared/AssetGrid.tsx @@ -20,6 +20,15 @@ export default function AssetGrid({ assets, isInternal = true, selectable = fals const [selectedIds, setSelectedIds] = useState([]); const [lastSelectedIndex, setLastSelectedIndex] = useState(-1); + + const handleClick = (index: number, asset: IAsset, event: React.MouseEvent) => { + if (selectedIds.length > 0) { + handleSelect(index, asset, event); + } else { + setIndex(index); + } + } + const handleSelect = (_idx: number, asset: IAsset, event: React.MouseEvent) => { event.stopPropagation(); @@ -55,13 +64,13 @@ export default function AssetGrid({ assets, isInternal = true, selectable = fals type: (asset.type === "VIDEO" ? "video" : "image") as any, sources: asset.type === "VIDEO" - ? [ - { - src: asset.downloadUrl as string, - type: "video/mp4", - }, - ] - : undefined, + ? [ + { + src: asset.downloadUrl as string, + type: "video/mp4", + }, + ] + : undefined, height: asset.exifImageHeight as number, width: asset.exifImageWidth as number, downloadUrl: asset.downloadUrl as string, @@ -104,7 +113,7 @@ export default function AssetGrid({ assets, isInternal = true, selectable = fals />

{user?.name}

- {!user.isUsingAPIKey && ( - - )} +
+ {!user.isUsingAPIKey && ( + + )} + +
)} diff --git a/src/contexts/PotentialAlbumContext.tsx b/src/contexts/PotentialAlbumContext.tsx index a02d408..c4c01aa 100644 --- a/src/contexts/PotentialAlbumContext.tsx +++ b/src/contexts/PotentialAlbumContext.tsx @@ -5,6 +5,7 @@ export interface IPotentialAlbumConfig { startDate?: string; selectedIds: string[]; assets: IAsset[]; + minAssets?: number; } export interface IPotentialAlbumContext extends IPotentialAlbumConfig { @@ -14,6 +15,7 @@ const PotentialAlbumContext = createContext({ startDate: undefined, selectedIds: [], assets: [], + minAssets: 1, updateContext: () => { }, }); diff --git a/src/handlers/api/album.handler.ts b/src/handlers/api/album.handler.ts index 6cb08d7..32541f7 100644 --- a/src/handlers/api/album.handler.ts +++ b/src/handlers/api/album.handler.ts @@ -9,6 +9,7 @@ interface IPotentialAlbumsDatesFilters { endDate?: string; sortBy?: string; sortOrder?: string; + minAssets?: number; } export interface IPotentialAlbumsDatesResponse { date: string; diff --git a/src/pages/albums/potential-albums.tsx b/src/pages/albums/potential-albums.tsx index e11536e..e715b5a 100644 --- a/src/pages/albums/potential-albums.tsx +++ b/src/pages/albums/potential-albums.tsx @@ -8,6 +8,7 @@ import Header from "@/components/shared/Header"; import { AlertDialog } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { useToast } from "@/components/ui/use-toast"; import { useCurrentUser } from "@/contexts/CurrentUserContext"; import PotentialAlbumContext, { @@ -29,6 +30,7 @@ export default function PotentialAlbums() { startDate: startDate || undefined, selectedIds: [], assets: [], + minAssets: 1, }); const selectedAssets = useMemo(() => config.assets.filter((a) => config.selectedIds.includes(a.id)), [config.assets, config.selectedIds]); @@ -108,6 +110,19 @@ export default function PotentialAlbums() {
+ { + if (e.target.value) { + setConfig({ ...config, minAssets: parseInt(e.target.value) }); + } + }} + /> + + )} /> - -
-

- {config.selectedIds.length} Selected -

-
- {config.selectedIds.length && config.selectedIds.length > 0 ? ( - - ) : ( - - )} - - -
+ {selectedAssets.length && + +
+

+ {config.selectedIds.length} Selected +

+
+ + + - + > + Select all + + + +
+ + + + +
-
- + + } ); diff --git a/src/pages/api/albums/potential-albums-dates.ts b/src/pages/api/albums/potential-albums-dates.ts index 041cb27..ebbd867 100644 --- a/src/pages/api/albums/potential-albums-dates.ts +++ b/src/pages/api/albums/potential-albums-dates.ts @@ -3,52 +3,49 @@ import { db } from "@/config/db"; import { IPotentialAlbumsDatesResponse } from "@/handlers/api/album.handler"; import { getCurrentUser } from "@/handlers/serverUtils/user.utils"; import { parseDate } from "@/helpers/date.helper"; -import { sql } from "drizzle-orm"; +import { albumsAssetsAssets } from "@/schema/albumAssetsAssets.schema"; +import { albums } from "@/schema/albums.schema"; +import { assets } from "@/schema/assets.schema"; +import { exif } from "@/schema/exif.schema"; +import { and, count, desc, eq, gte, isNotNull, isNull, sql } from "drizzle-orm"; import type { NextApiRequest, NextApiResponse } from "next"; +import { number } from "zod"; -const SELECT_ORPHAN_PHOTOS = (ownerId: string) => sql` -SELECT - DATE(e."dateTimeOriginal") AS "date", - COUNT(a."id") AS "asset_count" -FROM - "assets" a -LEFT JOIN - "albums_assets_assets" aaa ON a."id" = aaa."assetsId" -LEFT JOIN - "exif" e ON a."id" = e."assetId" -WHERE - aaa."albumsId" IS NULL - AND a."ownerId" = ${ownerId} - AND a."isVisible" = true - AND e."dateTimeOriginal" IS NOT NULL -GROUP BY - DATE(e."dateTimeOriginal") -ORDER BY - "asset_count" DESC; -`; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { try { - const { sortBy = "date", sortOrder = "desc" } = req.query; + const { sortBy = "date", sortOrder = "desc", minAssets = 1 } = req.query as any; const currentUser = await getCurrentUser(req) - const data = await db.execute(SELECT_ORPHAN_PHOTOS(currentUser.id)) as any; - const rows = data.rows as IPotentialAlbumsDatesResponse[]; + const rows = await db.select({ + date: sql`DATE(${exif.dateTimeOriginal})`, + asset_count: desc(count(assets.id)), + }).from(assets) + .leftJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetsId)) + .leftJoin(exif, eq(assets.id, exif.assetId)) + .where(and ( + eq(assets.ownerId, currentUser.id), + eq(assets.isVisible, true), + isNull(albumsAssetsAssets.albumsId), + isNotNull(exif.dateTimeOriginal), + + )) + .groupBy(sql`DATE(${exif.dateTimeOriginal})`) as IPotentialAlbumsDatesResponse[]; + + const filteredRows = rows.filter((row) => Number(row.asset_count) >= Number(minAssets)); if (sortBy === "date") { - rows.sort((a, b) => { - const aDate = parseDate(a.date, "yyyy-MM-dd"); - const bDate = parseDate(b.date, "yyyy-MM-dd"); + filteredRows.sort((a, b) => { + const aDate = parseDate(a.date as string, "yyyy-MM-dd"); + const bDate = parseDate(b.date as string, "yyyy-MM-dd"); return sortOrder === "asc" ? aDate.getTime() - bDate.getTime() : bDate.getTime() - aDate.getTime(); }); } else if (sortBy === "asset_count") { - rows.sort((a, b) => { - return sortOrder === "asc" ? a.asset_count - b.asset_count : b.asset_count - a.asset_count; - }); + filteredRows.sort((a, b) => sortOrder === "asc" ? a.asset_count - b.asset_count : b.asset_count - a.asset_count); } - return res.status(200).json(rows); + return res.status(200).json(filteredRows); } catch (error: any) { res.status(500).json({ error: error?.message, diff --git a/src/pages/assets/missing-locations.tsx b/src/pages/assets/missing-locations.tsx index cbdc167..a395a77 100644 --- a/src/pages/assets/missing-locations.tsx +++ b/src/pages/assets/missing-locations.tsx @@ -19,6 +19,7 @@ import FloatingBar from "@/components/shared/FloatingBar"; import { Select, SelectValue, SelectTrigger, SelectContent, SelectItem } from "@/components/ui/select"; import { AlertDialog } from "@/components/ui/alert-dialog"; import AssetOffsetDialog from "@/components/assets/assets-options/AssetOffsetDialog"; +import { Input } from "@/components/ui/input"; export default function MissingLocations() { const { query, push } = useRouter(); @@ -110,57 +111,58 @@ export default function MissingLocations() {
- -
-

- {config.selectedIds.length} Selected -

-
- {config.selectedIds.length === config.assets.length ? ( - + ) : ( + + )} + {/* Seperator */} + + +
+ - Unselect all - - ) : ( - - )} - {/* Seperator */} - - - -
- - - + +
+
- -
+
+ } ); diff --git a/src/styles/globals.scss b/src/styles/globals.scss index fc4c145..afc4d8c 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -92,4 +92,10 @@ #ReactGridGallery img { max-width: none !important; height: none !important; +} + +.dark { + .ReactGridGallery_tile { + @apply bg-zinc-800 !important; + } } \ No newline at end of file