diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b35c7ea..8972624 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,4 +28,12 @@ You can find the API route here `pages/api/immich-proxy/[...path].ts` Since the `thumbnailPath` of Immich API doesnt give us a direct link to the image, I've created a proxy api that fetches the image and sends it back as a response. **For Person's Thumbnail** -You can find the API route here `pages/api/immich-proxy/thumbnail/[id].ts` \ No newline at end of file +You can find the API route here `pages/api/immich-proxy/thumbnail/[id].ts` + +### Branching Strategy + +- Always create a new branch for your changes +- Always rebase your branch before creating a PR +- Always squash your commits before merging +- Always merge your branch into the `prerelease` branch +- Always delete your branch after merging diff --git a/README.md b/README.md index 92ceefe..932d8bb 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ GEMINI_API_KEY= # Gemini API Key ``` Refer here for obtaining Immich API Key: https://immich.app/docs/features/command-line-interface#obtain-the-api-key +> [!NOTE] When creating the API key, make sure you select all the permissions for the API key. + #### Method 2 - Portainer If you're using portainer, run the docker using `docker run` and add the power tools to the same network as immich. @@ -149,4 +151,4 @@ Google Gemini 1.5 Flash model is used for parsing your search query in "Find" pa ## Contributing -Feel free to contribute to this project, I'm open to any suggestions and improvements. Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. \ No newline at end of file +Feel free to contribute to this project, I'm open to any suggestions and improvements. Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. diff --git a/src/components/albums/info/AlbumImages.tsx b/src/components/albums/info/AlbumImages.tsx index 0736e9f..50918ce 100644 --- a/src/components/albums/info/AlbumImages.tsx +++ b/src/components/albums/info/AlbumImages.tsx @@ -1,19 +1,15 @@ -import "yet-another-react-lightbox/styles.css"; import { listAlbumAssets } from "@/handlers/api/album.handler"; import { useConfig } from "@/contexts/ConfigContext"; import { IAlbum, IAlbumPerson } from "@/types/album"; import { IAsset } from "@/types/asset"; -import React, { useEffect, useMemo, useRef, useState, MouseEvent } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Hourglass } from "lucide-react"; import { useRouter } from "next/router"; import { Button } from "@/components/ui/button"; -import { Gallery } from "react-grid-gallery"; -import Lightbox from "yet-another-react-lightbox"; -import Captions from "yet-another-react-lightbox/plugins/captions"; -import LazyGridImage from "@/components/ui/lazy-grid-image"; import { usePhotoSelectionContext } from "@/contexts/PhotoSelectionContext"; import { PERSON_THUBNAIL_PATH } from "@/config/routes"; import Image from "next/image"; +import AssetGrid from "@/components/shared/AssetGrid"; interface AlbumImagesProps { album: IAlbum; @@ -40,8 +36,6 @@ export default function AlbumImages({ album, selectedPerson }: AlbumImagesProps) page: 1, }); const [hasMore, setHasMore] = useState(true); - const [index, setIndex] = useState(-1); - const [lastSelectedIndex, setLastSelectedIndex] = useState(-1); const fetchAssets = async () => { setLoading(true); @@ -58,74 +52,24 @@ export default function AlbumImages({ album, selectedPerson }: AlbumImagesProps) .finally(() => setLoading(false)); }; - const images = useMemo(() => { - return assets.map((p) => ({ - ...p, - src: p.url as string, - original: p.previewUrl as string, - width: p.exifImageWidth as number, - height: p.exifImageHeight as number, - isSelected: selectedIds.includes(p.id), - orientation: 1, + const processedAssets = useMemo(() => { + return assets.map((asset) => ({ + ...asset, tags: [ { title: "Immich Link", value: ( - + Open in Immich ), }, ], })); - }, [assets, selectedIds, exImmichUrl]); + }, [assets, exImmichUrl]); - const slides = useMemo( - () => - images.map(({ original, width, height }) => ({ - src: original, - width, - height, - })), - [images] - ); - - const handleClick = (idx: number, asset: IAsset, event: MouseEvent) => { - if (selectedIds.length > 0) { - handleSelect(idx, asset, event); - } else { - setIndex(idx); - } - }; - - const handleSelect = (_idx: number, asset: IAsset, event: MouseEvent) => { - event.stopPropagation(); - const isPresent = selectedIds.includes(asset.id); - if (isPresent) { - updateContext({ - selectedIds: selectedIds.filter((id) => id !== asset.id), - }); - } else { - const clickedIndex = images.findIndex((image) => { - return image.id === asset.id; - }); - if (event.shiftKey && lastSelectedIndex !== -1) { - const startIndex = Math.min(clickedIndex, lastSelectedIndex); - const endIndex = Math.max(clickedIndex, lastSelectedIndex); - if (startIndex >= 0 && endIndex < images.length) { - const newSelectedIds = images.slice(startIndex, endIndex + 1).map((image) => image.id); - const allSelectedIds = [...selectedIds, ...newSelectedIds]; - const uniqueSelectedIds = [...new Set(allSelectedIds)]; - updateContext({ selectedIds: uniqueSelectedIds }); - } else { - console.warn("Shift-select index out of bounds"); - updateContext({ selectedIds: [...selectedIds, asset.id] }); - } - } else { - updateContext({ selectedIds: [...selectedIds, asset.id] }); - } - setLastSelectedIndex(clickedIndex); - } + const handleSelectionChange = (ids: string[]) => { + updateContext({ selectedIds: ids }); }; useEffect(() => { @@ -160,46 +104,28 @@ export default function AlbumImages({ album, selectedPerson }: AlbumImagesProps) } return ( - <> - - = 0} - index={index} - close={() => setIndex(-1)} +
+ -
- { + setFilters({ + ...filters, + page: filters.page + 1, + }); }} - /> - {hasMore && ( - - )} -
- + disabled={loading} + > + {loading ? "Loading..." : "Load More"} + + )} +
); } diff --git a/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx b/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx index 548eb80..d0c8159 100644 --- a/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx +++ b/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx @@ -1,15 +1,10 @@ -import "yet-another-react-lightbox/styles.css"; import { usePhotoSelectionContext } from "@/contexts/PhotoSelectionContext"; import { listPotentialAlbumsAssets } from "@/handlers/api/album.handler"; import type { IAsset } from "@/types/asset"; -import React, { type MouseEvent, useEffect, useMemo, useState } from "react"; -import { Gallery } from "react-grid-gallery"; -import Lightbox from "yet-another-react-lightbox"; -import Captions from "yet-another-react-lightbox/plugins/captions"; +import React, { useEffect, useMemo, useState } from "react"; import { CalendarArrowUp, Hourglass } from "lucide-react"; -import Video from "yet-another-react-lightbox/plugins/video"; import { useConfig } from "@/contexts/ConfigContext"; -import LazyGridImage from "@/components/ui/lazy-grid-image"; +import AssetGrid from "@/components/shared/AssetGrid"; export default function PotentialAlbumsAssets() { const { exImmichUrl } = useConfig(); @@ -24,9 +19,6 @@ export default function PotentialAlbumsAssets() { const [loading, setLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const [index, setIndex] = useState(-1); - const [lastSelectedIndex, setLastSelectedIndex] = useState(-1); - const fetchAssets = async () => { if (!startDate) return; setLoading(true); @@ -40,7 +32,7 @@ export default function PotentialAlbumsAssets() { .finally(() => setLoading(false)); }; - const images = useMemo(() => { + const processedAssets = useMemo(() => { const sortedAssets = [...assets].sort((a, b) => { try { return new Date(a.dateTimeOriginal ?? 0).getTime() - new Date(b.dateTimeOriginal ?? 0).getTime(); @@ -49,90 +41,23 @@ export default function PotentialAlbumsAssets() { } }); - return sortedAssets.map((p) => ({ - ...p, - src: p.url as string, - original: p.previewUrl as string, - width: p.exifImageWidth as number, - height: p.exifImageHeight as number, - isSelected: selectedIds.includes(p.id), - orientation: 1, + return sortedAssets.map((asset) => ({ + ...asset, tags: [ { title: "Immich Link", value: ( - + Open in Immich ), }, ], })); - }, [assets, selectedIds, exImmichUrl]); - - - const slides = useMemo( - () => - images.map(({ original, width, height, type, videoURL}) => ({ - src: original, - width, - height, - type: (type === "VIDEO" ? "video" : "image") as any, - sources: - type === "VIDEO" - ? [ - { - src: videoURL, - type: "video/mp4", - }, - ] - : [], - })), - [images] - ); - - const handleClick = (idx: number, asset: IAsset, event: MouseEvent) => { - if (selectedIds.length > 0 && (event.ctrlKey || event.metaKey || event.shiftKey)) { - handleSelect(idx, asset, event); - } else { - setIndex(idx); - } - }; - - const handleSelect = (_idx: number, asset: IAsset, event: MouseEvent) => { - event.stopPropagation(); - const isPresent = selectedIds.includes(asset.id); - - if (isPresent) { - if (event.ctrlKey || event.metaKey || selectedIds.length === 1) { - updateContext({ - selectedIds: selectedIds.filter((id) => id !== asset.id), - }); - } else if (!event.shiftKey) { - updateContext({ selectedIds: [asset.id] }); - } - } else { - const clickedIndex = images.findIndex((image) => image.id === asset.id); - - if (event.shiftKey && lastSelectedIndex !== -1) { - const startIndex = Math.min(clickedIndex, lastSelectedIndex); - const endIndex = Math.max(clickedIndex, lastSelectedIndex); - if (startIndex >= 0 && endIndex < images.length) { - const newSelectedIds = images.slice(startIndex, endIndex + 1).map((image) => image.id); - const combined = event.ctrlKey || event.metaKey ? [...selectedIds, ...newSelectedIds] : newSelectedIds; - const uniqueSelectedIds = [...new Set(combined)]; - updateContext({ selectedIds: uniqueSelectedIds }); - } else { - updateContext({ selectedIds: [...selectedIds, asset.id] }); - } - } else if (event.ctrlKey || event.metaKey) { - updateContext({ selectedIds: [...selectedIds, asset.id] }); - } else { - updateContext({ selectedIds: [asset.id] }); - } - setLastSelectedIndex(clickedIndex); - } + }, [assets, exImmichUrl]); + const handleSelectionChange = (ids: string[]) => { + updateContext({ selectedIds: ids }); }; useEffect(() => { @@ -158,33 +83,15 @@ export default function PotentialAlbumsAssets() {

); + return ( - <> - = 0} - index={index} - close={() => setIndex(-1)} +
+ -
- -
- +
); } diff --git a/src/components/assets/missing-location/MissingLocationAssets.tsx b/src/components/assets/missing-location/MissingLocationAssets.tsx index e122784..a60b2b4 100644 --- a/src/components/assets/missing-location/MissingLocationAssets.tsx +++ b/src/components/assets/missing-location/MissingLocationAssets.tsx @@ -1,9 +1,5 @@ -import "yet-another-react-lightbox/styles.css"; import { IAsset } from "@/types/asset"; -import React, { useEffect, useMemo, useState, MouseEvent } from "react"; -import { Gallery } from "react-grid-gallery"; -import Lightbox from "yet-another-react-lightbox"; -import Captions from "yet-another-react-lightbox/plugins/captions"; +import React, { useEffect, useMemo, useState } from "react"; import { CalendarArrowUp, Hourglass, @@ -11,7 +7,7 @@ import { import { usePhotoSelectionContext } from "@/contexts/PhotoSelectionContext"; import { listMissingLocationAssets } from "@/handlers/api/asset.handler"; import { useConfig } from "@/contexts/ConfigContext"; -import LazyGridImage from "@/components/ui/lazy-grid-image"; +import AssetGrid from "@/components/shared/AssetGrid"; interface IProps { groupBy: "date" | "album"; @@ -30,9 +26,6 @@ export default function MissingLocationAssets({ groupBy }: IProps) { const [loading, setLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const [index, setIndex] = useState(-1); - const [lastSelectedIndex, setLastSelectedIndex] = useState(-1); - const fetchAssets = async () => { if (!startDate && !albumId) return; setLoading(true); @@ -46,7 +39,7 @@ export default function MissingLocationAssets({ groupBy }: IProps) { .finally(() => setLoading(false)); }; - const images = useMemo(() => { + const processedAssets = useMemo(() => { let sortedAssets: IAsset[]; try { if (sortOrder === "desc") @@ -58,76 +51,23 @@ export default function MissingLocationAssets({ groupBy }: IProps) { sortedAssets = [...assets]; } - return sortedAssets. - map((p) => ({ - ...p, - src: p.url as string, - original: p.previewUrl as string, - width: p.exifImageWidth as number, - height: p.exifImageHeight as number, - isSelected: selectedIds.includes(p.id), - orientation: 1, - tags: [ - { - title: "Immich Link", - value: ( - - Open in Immich - - ), - }, - ], - })); - }, [assets, selectedIds, sortOrder, exImmichUrl]); + return sortedAssets.map((asset) => ({ + ...asset, + tags: [ + { + title: "Immich Link", + value: ( + + Open in Immich + + ), + }, + ], + })); + }, [assets, sortOrder, exImmichUrl]); - const slides = useMemo( - () => - images.map(({ original, width, height }) => ({ - src: original, - width, - height, - })), - [images] - ); - - const handleClick = (idx: number, asset: IAsset, event: MouseEvent) => { - if (selectedIds.length > 0) { - handleSelect(idx, asset, event); - } - else - { - setIndex(idx); - } - }; - - const handleSelect = (_idx: number, asset: IAsset, event: MouseEvent) => { - event.stopPropagation(); - const isPresent = selectedIds.includes(asset.id); - if (isPresent) { - updateContext({ - selectedIds: selectedIds.filter((id) => id !== asset.id), - }); - } else { - const clickedIndex = images.findIndex((image) => { - return image.id === asset.id; - }); - if (event.shiftKey && lastSelectedIndex !== -1) { - const startIndex = Math.min(clickedIndex, lastSelectedIndex); - const endIndex = Math.max(clickedIndex, lastSelectedIndex); - if (startIndex >= 0 && endIndex < images.length) { - const newSelectedIds = images.slice(startIndex, endIndex + 1).map((image) => image.id); - const allSelectedIds = [...selectedIds, ...newSelectedIds]; - const uniqueSelectedIds = [...new Set(allSelectedIds)]; - updateContext({ selectedIds: uniqueSelectedIds }); - } else { - console.warn("Shift-select index out of bounds"); - updateContext({ selectedIds: [...selectedIds, asset.id] }); - } - } else { - updateContext({ selectedIds: [...selectedIds, asset.id] }); - } - setLastSelectedIndex(clickedIndex); - } + const handleSelectionChange = (ids: string[]) => { + updateContext({ selectedIds: ids }); }; useEffect(() => { @@ -155,30 +95,13 @@ export default function MissingLocationAssets({ groupBy }: IProps) { ); return ( - <> - = 0} - index={index} - close={() => setIndex(-1)} +
+ -
- -
- +
); }