feat: multi select in potential album and missing location using shift key
Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import "yet-another-react-lightbox/styles.css";
|
||||
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
|
||||
import { listPotentialAlbumsAssets } from "@/handlers/api/album.handler";
|
||||
import { IAsset } from "@/types/asset";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import type { IAsset } from "@/types/asset";
|
||||
import React, { type MouseEvent, useEffect, useMemo, useState } from "react";
|
||||
import { Gallery } from "react-grid-gallery";
|
||||
import Lightbox, { SlideImage, SlideTypes } from "yet-another-react-lightbox";
|
||||
import Captions from "yet-another-react-lightbox/plugins/captions";
|
||||
@@ -20,6 +20,7 @@ export default function PotentialAlbumsAssets() {
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const [index, setIndex] = useState(-1);
|
||||
const [lastSelectedIndex, setLastSelectedIndex] = useState(-1);
|
||||
|
||||
const fetchAssets = async () => {
|
||||
setLoading(true);
|
||||
@@ -44,14 +45,15 @@ export default function PotentialAlbumsAssets() {
|
||||
{
|
||||
title: "Immich Link",
|
||||
value: (
|
||||
<a href={exImmichUrl + "/photos/" + p.id} target="_blank">
|
||||
<a href={`${exImmichUrl}/photos/${p.id}`} target="_blank" rel="noreferrer">
|
||||
Open in Immich
|
||||
</a>
|
||||
),
|
||||
},
|
||||
],
|
||||
}));
|
||||
}, [assets, selectedIds]);
|
||||
}, [assets, selectedIds, exImmichUrl]);
|
||||
|
||||
|
||||
const slides = useMemo(
|
||||
() =>
|
||||
@@ -73,17 +75,31 @@ export default function PotentialAlbumsAssets() {
|
||||
[images]
|
||||
);
|
||||
|
||||
const handleClick = (idx: number) => setIndex(idx);
|
||||
|
||||
const handleSelect = (_idx: number, asset: IAsset) => {
|
||||
const handleSelect = (_idx: number, asset: IAsset, event: MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
const isPresent = selectedIds.includes(asset.id);
|
||||
if (isPresent) {
|
||||
updateContext({
|
||||
selectedIds: selectedIds.filter((id) => id !== asset.id),
|
||||
});
|
||||
} else {
|
||||
updateContext({ selectedIds: [...selectedIds, asset.id] });
|
||||
const clickedIndex = images.findIndex((image) => {
|
||||
return image.id === asset.id
|
||||
});
|
||||
if (event.shiftKey) {
|
||||
const startIndex = Math.min(clickedIndex, lastSelectedIndex);
|
||||
const endIndex = Math.max(clickedIndex, lastSelectedIndex);
|
||||
const newSelectedIds = images.slice(startIndex, endIndex + 1).map((image) => image.id);
|
||||
const allSelectedIds = [...selectedIds, ...newSelectedIds];
|
||||
const uniqueSelectedIds = [...new Set(allSelectedIds)];
|
||||
updateContext({ selectedIds: uniqueSelectedIds });
|
||||
} else {
|
||||
updateContext({ selectedIds: [...selectedIds, asset.id] });
|
||||
}
|
||||
setLastSelectedIndex(clickedIndex);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -121,7 +137,7 @@ export default function PotentialAlbumsAssets() {
|
||||
<div className="w-full overflow-y-auto max-h-[calc(100vh-60px)]">
|
||||
<Gallery
|
||||
images={images}
|
||||
onClick={handleClick}
|
||||
onClick={setIndex}
|
||||
enableImageSelection={true}
|
||||
onSelect={handleSelect}
|
||||
thumbnailImageComponent={LazyGridImage}
|
||||
|
||||
@@ -2,7 +2,7 @@ import "yet-another-react-lightbox/styles.css";
|
||||
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
|
||||
import { listPotentialAlbumsAssets } from "@/handlers/api/album.handler";
|
||||
import { IAsset } from "@/types/asset";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
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";
|
||||
@@ -33,6 +33,7 @@ export default function MissingLocationAssets({ groupBy }: IProps) {
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const [index, setIndex] = useState(-1);
|
||||
const [lastSelectedIndex, setLastSelectedIndex] = useState(-1);
|
||||
|
||||
const fetchAssets = async () => {
|
||||
if (!startDate && !albumId) return;
|
||||
@@ -80,14 +81,28 @@ export default function MissingLocationAssets({ groupBy }: IProps) {
|
||||
|
||||
const handleClick = (idx: number) => setIndex(idx);
|
||||
|
||||
const handleSelect = (_idx: number, asset: IAsset) => {
|
||||
const handleSelect = (_idx: number, asset: IAsset, event: MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
const isPresent = selectedIds.includes(asset.id);
|
||||
if (isPresent) {
|
||||
updateContext({
|
||||
selectedIds: selectedIds.filter((id) => id !== asset.id),
|
||||
});
|
||||
} else {
|
||||
updateContext({ selectedIds: [...selectedIds, asset.id] });
|
||||
const clickedIndex = images.findIndex((image) => {
|
||||
return image.id === asset.id;
|
||||
});
|
||||
if (event.shiftKey) {
|
||||
const startIndex = Math.min(clickedIndex, lastSelectedIndex);
|
||||
const endIndex = Math.max(clickedIndex, lastSelectedIndex);
|
||||
const newSelectedIds = images.slice(startIndex, endIndex + 1).map((image) => image.id);
|
||||
const allSelectedIds = [...selectedIds, ...newSelectedIds];
|
||||
const uniqueSelectedIds = [...new Set(allSelectedIds)];
|
||||
updateContext({ selectedIds: uniqueSelectedIds });
|
||||
} else {
|
||||
updateContext({ selectedIds: [...selectedIds, asset.id] });
|
||||
}
|
||||
setLastSelectedIndex(clickedIndex);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+4
-6
@@ -12,7 +12,6 @@ import { IPlace } from "@/types/common";
|
||||
import React, { useState } from "react";
|
||||
import TagMissingLocationSearchAndAdd from "./TagMissingLocationSearchAndAdd";
|
||||
import TagMissingLocationSearchLatLong from "./TagMissingLocationSearchLatLong";
|
||||
import { MapPinCheck } from "lucide-react";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const LazyMap = dynamic(() => import("./Map"), {
|
||||
@@ -46,12 +45,11 @@ export default function TagMissingLocationDialog({
|
||||
Tagging a location will add the location to the selected assets.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Tabs defaultValue="search" className="border rounded-lg">
|
||||
<TabsList className="flex justify-between">
|
||||
<Tabs defaultValue="search">
|
||||
<TabsList className="flex justify-center">
|
||||
<TabsTrigger value="search">Search and Pick</TabsTrigger>
|
||||
<TabsTrigger value="latlong">
|
||||
Latitude and Longitude
|
||||
|
||||
Lat & Long
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="maps">Map</TabsTrigger>
|
||||
</TabsList>
|
||||
@@ -63,7 +61,7 @@ export default function TagMissingLocationDialog({
|
||||
onOpenChange={setOpen} location={mapPosition} onLocationChange={setMapPosition} />
|
||||
</TabsContent>
|
||||
<TabsContent value="maps">
|
||||
<div className="py-10 flex flex-col gap-6 items-center ">
|
||||
<div className="flex flex-col gap-6 items-center ">
|
||||
<LazyMap location={mapPosition} onLocationChange={setMapPosition} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
+2
-2
@@ -110,7 +110,7 @@ export default function TagMissingLocationSearchLatLong(
|
||||
return (
|
||||
<div className="flex flex-col gap-4 py-4 px-2">
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2 flex-1">
|
||||
<Label>Latitude</Label>
|
||||
<Input
|
||||
placeholder="Latitude"
|
||||
@@ -121,7 +121,7 @@ export default function TagMissingLocationSearchLatLong(
|
||||
onBlur={(e) => handleBlur()}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2 flex-1">
|
||||
<Label>
|
||||
Longitude
|
||||
</Label>
|
||||
|
||||
Reference in New Issue
Block a user