@@ -4,12 +4,13 @@ import { listPotentialAlbumsAssets } from "@/handlers/api/album.handler";
|
||||
import { IAsset } from "@/types/asset";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Gallery } from "react-grid-gallery";
|
||||
import Lightbox from "yet-another-react-lightbox";
|
||||
import Lightbox, { SlideImage, SlideTypes } from "yet-another-react-lightbox";
|
||||
import Captions from "yet-another-react-lightbox/plugins/captions";
|
||||
import { CalendarArrowDown, CalendarArrowUp, Hourglass } from "lucide-react";
|
||||
|
||||
import Video from "yet-another-react-lightbox/plugins/video";
|
||||
export default function PotentialAlbumsAssets() {
|
||||
const { startDate, selectedIds, assets, updateContext } = usePotentialAlbumContext();
|
||||
const { startDate, selectedIds, assets, updateContext } =
|
||||
usePotentialAlbumContext();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
@@ -20,7 +21,7 @@ export default function PotentialAlbumsAssets() {
|
||||
setLoading(true);
|
||||
updateContext({
|
||||
assets: [],
|
||||
})
|
||||
});
|
||||
return listPotentialAlbumsAssets({ startDate })
|
||||
.then((assets) => updateContext({ assets }))
|
||||
.catch(setErrorMessage)
|
||||
@@ -40,10 +41,20 @@ export default function PotentialAlbumsAssets() {
|
||||
|
||||
const slides = useMemo(
|
||||
() =>
|
||||
images.map(({ original, width, height }) => ({
|
||||
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]
|
||||
);
|
||||
@@ -51,41 +62,44 @@ export default function PotentialAlbumsAssets() {
|
||||
const handleClick = (idx: number) => setIndex(idx);
|
||||
|
||||
const handleSelect = (_idx: number, asset: IAsset) => {
|
||||
const isPresent =selectedIds.includes(asset.id)
|
||||
if(isPresent){
|
||||
updateContext({selectedIds:selectedIds.filter((id)=>id!==asset.id)})
|
||||
const isPresent = selectedIds.includes(asset.id);
|
||||
if (isPresent) {
|
||||
updateContext({
|
||||
selectedIds: selectedIds.filter((id) => id !== asset.id),
|
||||
});
|
||||
} else {
|
||||
updateContext({ selectedIds: [...selectedIds, asset.id] });
|
||||
}
|
||||
else{
|
||||
updateContext({selectedIds:[...selectedIds,asset.id]})
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (startDate) fetchAssets();
|
||||
}, [startDate]);
|
||||
|
||||
if (loading) return (
|
||||
<div className="flex flex-col gap-2 h-full justify-center items-center w-full">
|
||||
<Hourglass />
|
||||
<p className="text-lg">Loading...</p>
|
||||
</div>
|
||||
)
|
||||
if (loading)
|
||||
return (
|
||||
<div className="flex flex-col gap-2 min-h-full justify-center items-center w-full">
|
||||
<Hourglass />
|
||||
<p className="text-lg">Loading...</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!startDate) return (
|
||||
<div className="flex flex-col gap-2 h-full justify-center items-center w-full">
|
||||
<CalendarArrowUp />
|
||||
<p className="text-lg">Please select a date</p>
|
||||
<p className="text-sm">
|
||||
When you select a date from the left, you will see all the orphan assets captured on that date
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
if (!startDate)
|
||||
return (
|
||||
<div className="flex flex-col gap-2 min-h-full justify-center items-center w-full">
|
||||
<CalendarArrowUp />
|
||||
<p className="text-lg">Please select a date</p>
|
||||
<p className="text-sm text-zinc-700">
|
||||
When you select a date from the left, you will see all the orphan
|
||||
assets captured on that date
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Lightbox
|
||||
slides={slides}
|
||||
plugins={[Captions]}
|
||||
plugins={[Captions, Video]}
|
||||
open={index >= 0}
|
||||
index={index}
|
||||
close={() => setIndex(-1)}
|
||||
@@ -96,7 +110,6 @@ export default function PotentialAlbumsAssets() {
|
||||
onClick={handleClick}
|
||||
enableImageSelection={true}
|
||||
onSelect={handleSelect}
|
||||
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -25,4 +25,5 @@ export const LIST_MISSING_LOCATION_DATES_PATH = BASE_API_ENDPOINT + "/assets/mis
|
||||
export const LIST_MISSING_LOCATION_ASSETS_PATH = BASE_API_ENDPOINT + "/assets/missing-location-assets";
|
||||
|
||||
export const ASSET_THUMBNAIL_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/asset/thumbnail/" + id;
|
||||
export const ASSET_PREVIEW_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/asset/thumbnail/" + id + "?size=preview";
|
||||
export const ASSET_PREVIEW_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/asset/thumbnail/" + id + "?size=preview";
|
||||
export const ASSET_VIDEO_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/asset/video/" + id;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ASSET_PREVIEW_PATH, ASSET_THUMBNAIL_PATH, PERSON_THUBNAIL_PATH } from "@/config/routes"
|
||||
import { ASSET_PREVIEW_PATH, ASSET_THUMBNAIL_PATH, ASSET_VIDEO_PATH, PERSON_THUBNAIL_PATH } from "@/config/routes"
|
||||
import { IPerson } from "@/types/person"
|
||||
import { parseDate } from "./date.helper"
|
||||
import { IAsset } from "@/types/asset"
|
||||
@@ -9,5 +9,6 @@ export const cleanUpAsset = (asset: IAsset): IAsset => {
|
||||
...asset,
|
||||
url: ASSET_THUMBNAIL_PATH(asset.id),
|
||||
previewUrl: ASSET_PREVIEW_PATH(asset.id),
|
||||
videoURL: ASSET_VIDEO_PATH(asset.id),
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ const SELECT_ORPHAN_PHOTOS = (date: string, ownerId: string) =>
|
||||
aaa."albumsId" IS NULL
|
||||
AND a."ownerId" = '${ownerId}'
|
||||
AND a."localDateTime"::date = '${date}'
|
||||
AND a."type" = 'VIDEO'
|
||||
`);
|
||||
|
||||
export default async function handler(
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// pages/api/proxy.js
|
||||
|
||||
import { ENV } from '@/config/environment';
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'GET') {
|
||||
return res.status(405).json({ message: 'Method Not Allowed' })
|
||||
}
|
||||
|
||||
const { id } = req.query;
|
||||
const targetUrl = `${ENV.IMMICH_URL}/api/assets/${id}/original`;
|
||||
|
||||
try {
|
||||
// Forward the request to the target API
|
||||
const response = await fetch(targetUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'x-api-key': ENV.IMMICH_API_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('HTTP error:', response)
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
// Get the image data from the response
|
||||
const imageBuffer = await response.arrayBuffer()
|
||||
|
||||
// Set the appropriate headers for the image response
|
||||
res.setHeader('Content-Type', response.headers.get('Content-Type') || 'image/png')
|
||||
res.setHeader('Content-Length', imageBuffer.byteLength)
|
||||
|
||||
// Send the image data
|
||||
res.send(Buffer.from(imageBuffer))
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -18,6 +18,7 @@ export interface IAsset {
|
||||
exifImageHeight: number;
|
||||
url: string;
|
||||
previewUrl: string;
|
||||
videoURL?: string;
|
||||
}
|
||||
|
||||
export interface IAssetThumbhash {
|
||||
|
||||
Reference in New Issue
Block a user