Skip to content

Commit 3e022ac

Browse files
authored
Merge pull request #162 from varun-raj/july-release
July release
2 parents 6c0afe7 + 263212c commit 3e022ac

26 files changed

+979
-200
lines changed
Lines changed: 116 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,133 @@
11
import "yet-another-react-lightbox/styles.css";
22
import { listAlbumAssets } from "@/handlers/api/album.handler";
33
import { useConfig } from "@/contexts/ConfigContext";
4-
import { IAlbum } from "@/types/album";
4+
import { IAlbum, IAlbumPerson } from "@/types/album";
55
import { IAsset } from "@/types/asset";
6-
import React, { useEffect, useMemo, useRef, useState } from "react";
6+
import React, { useEffect, useMemo, useRef, useState, MouseEvent } from "react";
77
import { Hourglass } from "lucide-react";
88
import { useRouter } from "next/router";
99
import { Button } from "@/components/ui/button";
10-
import AssetGrid from "@/components/shared/AssetGrid";
11-
import PhotoSelectionContext, {
12-
IPhotoSelectionContext,
13-
} from "@/contexts/PhotoSelectionContext";
10+
import { Gallery } from "react-grid-gallery";
11+
import Lightbox from "yet-another-react-lightbox";
12+
import Captions from "yet-another-react-lightbox/plugins/captions";
13+
import LazyGridImage from "@/components/ui/lazy-grid-image";
14+
import { usePhotoSelectionContext } from "@/contexts/PhotoSelectionContext";
15+
import { PERSON_THUBNAIL_PATH } from "@/config/routes";
16+
import Image from "next/image";
1417

1518
interface AlbumImagesProps {
1619
album: IAlbum;
20+
selectedPerson: IAlbumPerson | null;
1721
}
1822

1923
interface IAssetFilter {
2024
faceId?: string;
2125
page: number;
2226
}
23-
export default function AlbumImages({ album }: AlbumImagesProps) {
27+
28+
export default function AlbumImages({ album, selectedPerson }: AlbumImagesProps) {
2429
const { exImmichUrl } = useConfig();
2530
const router = useRouter();
2631
const { faceId } = router.query as { faceId: string };
27-
const [assets, setAssets] = useState<IAsset[]>([]);
32+
33+
// Use context from parent instead of managing own
34+
const { selectedIds, updateContext, assets } = usePhotoSelectionContext();
35+
2836
const [loading, setLoading] = useState(false);
2937
const [errorMessage, setErrorMessage] = useState<string | null>(null);
3038
const [filters, setFilters] = useState<IAssetFilter>({
3139
faceId,
3240
page: 1,
3341
});
3442
const [hasMore, setHasMore] = useState(true);
35-
36-
// Initialize context state
37-
const [contextState, setContextState] = useState<IPhotoSelectionContext>({
38-
selectedIds: [],
39-
assets: [],
40-
config: {},
41-
updateContext: (newConfig: Partial<IPhotoSelectionContext>) => {
42-
setContextState((prevState) => ({
43-
...prevState,
44-
...newConfig,
45-
// No deep merge needed for config here as it's simple
46-
config: newConfig.config
47-
? { ...prevState.config, ...newConfig.config }
48-
: prevState.config,
49-
}));
50-
},
51-
});
43+
const [index, setIndex] = useState(-1);
44+
const [lastSelectedIndex, setLastSelectedIndex] = useState(-1);
5245

5346
const fetchAssets = async () => {
5447
setLoading(true);
5548
return listAlbumAssets(album.id, filters)
5649
.then((newAssets) => {
5750
if (filters.page === 1) {
58-
setAssets(newAssets);
51+
updateContext({ assets: newAssets });
5952
} else {
60-
setAssets((prevAssets) => [...prevAssets, ...newAssets]);
53+
updateContext({ assets: [...assets, ...newAssets] });
6154
}
6255
setHasMore(newAssets.length === 100);
6356
})
6457
.catch(setErrorMessage)
6558
.finally(() => setLoading(false));
6659
};
6760

61+
const images = useMemo(() => {
62+
return assets.map((p) => ({
63+
...p,
64+
src: p.url as string,
65+
original: p.previewUrl as string,
66+
width: p.exifImageWidth as number,
67+
height: p.exifImageHeight as number,
68+
isSelected: selectedIds.includes(p.id),
69+
orientation: 1,
70+
tags: [
71+
{
72+
title: "Immich Link",
73+
value: (
74+
<a href={exImmichUrl + "/photos/" + p.id} target="_blank" rel="noopener noreferrer">
75+
Open in Immich
76+
</a>
77+
),
78+
},
79+
],
80+
}));
81+
}, [assets, selectedIds, exImmichUrl]);
82+
83+
const slides = useMemo(
84+
() =>
85+
images.map(({ original, width, height }) => ({
86+
src: original,
87+
width,
88+
height,
89+
})),
90+
[images]
91+
);
92+
93+
const handleClick = (idx: number, asset: IAsset, event: MouseEvent<HTMLElement>) => {
94+
if (selectedIds.length > 0) {
95+
handleSelect(idx, asset, event);
96+
} else {
97+
setIndex(idx);
98+
}
99+
};
100+
101+
const handleSelect = (_idx: number, asset: IAsset, event: MouseEvent<HTMLElement>) => {
102+
event.stopPropagation();
103+
const isPresent = selectedIds.includes(asset.id);
104+
if (isPresent) {
105+
updateContext({
106+
selectedIds: selectedIds.filter((id) => id !== asset.id),
107+
});
108+
} else {
109+
const clickedIndex = images.findIndex((image) => {
110+
return image.id === asset.id;
111+
});
112+
if (event.shiftKey && lastSelectedIndex !== -1) {
113+
const startIndex = Math.min(clickedIndex, lastSelectedIndex);
114+
const endIndex = Math.max(clickedIndex, lastSelectedIndex);
115+
if (startIndex >= 0 && endIndex < images.length) {
116+
const newSelectedIds = images.slice(startIndex, endIndex + 1).map((image) => image.id);
117+
const allSelectedIds = [...selectedIds, ...newSelectedIds];
118+
const uniqueSelectedIds = [...new Set(allSelectedIds)];
119+
updateContext({ selectedIds: uniqueSelectedIds });
120+
} else {
121+
console.warn("Shift-select index out of bounds");
122+
updateContext({ selectedIds: [...selectedIds, asset.id] });
123+
}
124+
} else {
125+
updateContext({ selectedIds: [...selectedIds, asset.id] });
126+
}
127+
setLastSelectedIndex(clickedIndex);
128+
}
129+
};
130+
68131
useEffect(() => {
69132
if (album.id) {
70133
fetchAssets();
@@ -76,7 +139,7 @@ export default function AlbumImages({ album }: AlbumImagesProps) {
76139
faceId,
77140
page: 1,
78141
});
79-
setAssets([]);
142+
updateContext({ assets: [], selectedIds: [] });
80143
setHasMore(true);
81144
}, [faceId]);
82145

@@ -97,11 +160,30 @@ export default function AlbumImages({ album }: AlbumImagesProps) {
97160
}
98161

99162
return (
100-
<PhotoSelectionContext.Provider
101-
value={{ ...contextState, updateContext: contextState.updateContext }}
102-
>
103-
<div className="w-full p-2">
104-
<AssetGrid assets={assets} />
163+
<>
164+
165+
<Lightbox
166+
slides={slides}
167+
plugins={[Captions]}
168+
open={index >= 0}
169+
index={index}
170+
close={() => setIndex(-1)}
171+
/>
172+
<div className="w-full p-2 overflow-y-auto max-h-[calc(100vh-60px)]">
173+
<Gallery
174+
images={images}
175+
onClick={handleClick}
176+
enableImageSelection={true}
177+
onSelect={handleSelect}
178+
thumbnailImageComponent={LazyGridImage}
179+
tagStyle={{
180+
color: "white",
181+
fontSize: "12px",
182+
backgroundColor: "rgba(0, 0, 0)",
183+
padding: "2px",
184+
borderRadius: "5px",
185+
}}
186+
/>
105187
{hasMore && (
106188
<Button
107189
variant="outline"
@@ -118,6 +200,6 @@ export default function AlbumImages({ album }: AlbumImagesProps) {
118200
</Button>
119201
)}
120202
</div>
121-
</PhotoSelectionContext.Provider>
203+
</>
122204
);
123205
}

0 commit comments

Comments
 (0)