Extract common upload add routine
This commit is contained in:
parent
b115b98ea5
commit
5704597a4f
@ -1,20 +0,0 @@
|
|||||||
import { BiImageAdd } from 'react-icons/bi';
|
|
||||||
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
|
|
||||||
import { ComponentProps } from 'react';
|
|
||||||
|
|
||||||
export default function AddButton({
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: ComponentProps<typeof PathLoaderButton>) {
|
|
||||||
return (
|
|
||||||
<PathLoaderButton
|
|
||||||
{...props}
|
|
||||||
icon={<BiImageAdd
|
|
||||||
size={18}
|
|
||||||
className="translate-x-[1px] translate-y-[1px]"
|
|
||||||
/>}
|
|
||||||
>
|
|
||||||
{children || 'Add'}
|
|
||||||
</PathLoaderButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -8,13 +8,14 @@ import clsx from 'clsx/lite';
|
|||||||
import ResponsiveDate from '@/components/ResponsiveDate';
|
import ResponsiveDate from '@/components/ResponsiveDate';
|
||||||
import Spinner from '@/components/Spinner';
|
import Spinner from '@/components/Spinner';
|
||||||
import { FaRegCircleCheck } from 'react-icons/fa6';
|
import { FaRegCircleCheck } from 'react-icons/fa6';
|
||||||
import AddButton from './AddButton';
|
|
||||||
import { pathForAdminUploadUrl } from '@/app/paths';
|
import { pathForAdminUploadUrl } from '@/app/paths';
|
||||||
import DeleteBlobButton from './DeleteUploadButton';
|
import DeleteBlobButton from './DeleteUploadButton';
|
||||||
import { Dispatch, SetStateAction, useEffect, useRef } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useRef } from 'react';
|
||||||
import { isElementEntirelyInViewport } from '@/utility/dom';
|
import { isElementEntirelyInViewport } from '@/utility/dom';
|
||||||
import FieldSetWithStatus from '@/components/FieldSetWithStatus';
|
import FieldSetWithStatus from '@/components/FieldSetWithStatus';
|
||||||
import EditButton from './EditButton';
|
import EditButton from './EditButton';
|
||||||
|
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||||
|
import { BiImageAdd } from 'react-icons/bi';
|
||||||
|
|
||||||
export default function AdminUploadsTableRow({
|
export default function AdminUploadsTableRow({
|
||||||
url,
|
url,
|
||||||
@ -55,6 +56,8 @@ export default function AdminUploadsTableRow({
|
|||||||
}
|
}
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
|
const isRowLoading = isAdding || isDeleting || isComplete || Boolean(status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -97,7 +100,7 @@ export default function AdminUploadsTableRow({
|
|||||||
}}
|
}}
|
||||||
placeholder="Title (optional)"
|
placeholder="Title (optional)"
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
readOnly={isAdding || isDeleting || isComplete || Boolean(status)}
|
readOnly={isRowLoading}
|
||||||
hideLabel
|
hideLabel
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -112,14 +115,20 @@ export default function AdminUploadsTableRow({
|
|||||||
/>}
|
/>}
|
||||||
</>
|
</>
|
||||||
: <>
|
: <>
|
||||||
<AddButton
|
<LoaderButton
|
||||||
path={pathForAdminUploadUrl(url)}
|
icon={<BiImageAdd
|
||||||
disabled={isDeleting}
|
size={18}
|
||||||
|
className="translate-x-[1px] translate-y-[1px]"
|
||||||
|
/>}
|
||||||
|
disabled={isRowLoading}
|
||||||
tooltip="Add directly"
|
tooltip="Add directly"
|
||||||
hideText="never"
|
hideText="never"
|
||||||
/>
|
>
|
||||||
|
Add
|
||||||
|
</LoaderButton>
|
||||||
<EditButton
|
<EditButton
|
||||||
path={pathForAdminUploadUrl(url)}
|
path={pathForAdminUploadUrl(url)}
|
||||||
|
disabled={isRowLoading}
|
||||||
tooltip="Review photo details"
|
tooltip="Review photo details"
|
||||||
hideText="always"
|
hideText="always"
|
||||||
/>
|
/>
|
||||||
@ -133,7 +142,7 @@ export default function AdminUploadsTableRow({
|
|||||||
setUrlAddStatuses?.(statuses => statuses
|
setUrlAddStatuses?.(statuses => statuses
|
||||||
.filter(({ url: urlToRemove }) => urlToRemove !== url));
|
.filter(({ url: urlToRemove }) => urlToRemove !== url));
|
||||||
}}
|
}}
|
||||||
isLoading={isDeleting}
|
disabled={isRowLoading}
|
||||||
tooltip="Delete upload"
|
tooltip="Delete upload"
|
||||||
/>
|
/>
|
||||||
</>}
|
</>}
|
||||||
|
|||||||
@ -85,23 +85,108 @@ export const createPhotoAction = async (formData: FormData) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const addUploadsAction = async ({
|
// Helper function for:
|
||||||
uploadUrls,
|
// - addUploadAction
|
||||||
uploadTitles,
|
// - addUploadsAction
|
||||||
|
const addUpload = async ({
|
||||||
|
url,
|
||||||
|
title,
|
||||||
tags,
|
tags,
|
||||||
favorite,
|
favorite,
|
||||||
hidden,
|
hidden,
|
||||||
takenAtLocal,
|
takenAtLocal,
|
||||||
takenAtNaiveLocal,
|
takenAtNaiveLocal,
|
||||||
shouldRevalidateAllKeysAndPaths = true,
|
onStreamUpdate,
|
||||||
}: {
|
onFinish,
|
||||||
uploadUrls: string[]
|
}:{
|
||||||
uploadTitles: string[]
|
url: string
|
||||||
|
title?: string
|
||||||
tags?: string
|
tags?: string
|
||||||
favorite?: string
|
favorite?: string
|
||||||
hidden?: string
|
hidden?: string
|
||||||
takenAtLocal: string
|
takenAtLocal: string
|
||||||
takenAtNaiveLocal: string
|
takenAtNaiveLocal: string
|
||||||
|
onStreamUpdate: (
|
||||||
|
statusMessage: string,
|
||||||
|
status?: UrlAddStatus['status'],
|
||||||
|
) => void
|
||||||
|
onFinish?: (url: string) => void
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
formDataFromExif,
|
||||||
|
imageResizedBase64,
|
||||||
|
shouldStripGpsData,
|
||||||
|
fileBytes,
|
||||||
|
} = await extractImageDataFromBlobPath(url, {
|
||||||
|
includeInitialPhotoFields: true,
|
||||||
|
generateBlurData: BLUR_ENABLED,
|
||||||
|
generateResizedImage: AI_TEXT_GENERATION_ENABLED,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (formDataFromExif) {
|
||||||
|
if (AI_TEXT_GENERATION_ENABLED) {
|
||||||
|
onStreamUpdate('Generating AI text');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title: aiTitle,
|
||||||
|
caption,
|
||||||
|
tags: aiTags,
|
||||||
|
semanticDescription,
|
||||||
|
} = await generateAiImageQueries(
|
||||||
|
imageResizedBase64,
|
||||||
|
Boolean(title)
|
||||||
|
? AI_TEXT_AUTO_GENERATED_FIELDS
|
||||||
|
.filter(field => field !== 'title')
|
||||||
|
: AI_TEXT_AUTO_GENERATED_FIELDS,
|
||||||
|
title,
|
||||||
|
);
|
||||||
|
|
||||||
|
const form: Partial<PhotoFormData> = {
|
||||||
|
...formDataFromExif,
|
||||||
|
title: title || aiTitle,
|
||||||
|
caption,
|
||||||
|
tags: tags || aiTags,
|
||||||
|
hidden,
|
||||||
|
favorite,
|
||||||
|
semanticDescription,
|
||||||
|
takenAt: formDataFromExif.takenAt || takenAtLocal,
|
||||||
|
takenAtNaive: formDataFromExif.takenAtNaive || takenAtNaiveLocal,
|
||||||
|
};
|
||||||
|
|
||||||
|
onStreamUpdate('Transferring to photo storage');
|
||||||
|
|
||||||
|
const updatedUrl = await convertUploadToPhoto({
|
||||||
|
urlOrigin: url,
|
||||||
|
fileBytes,
|
||||||
|
shouldStripGpsData,
|
||||||
|
});
|
||||||
|
if (updatedUrl) {
|
||||||
|
const subheadFinal = 'Adding to database';
|
||||||
|
onStreamUpdate(subheadFinal);
|
||||||
|
const photo =
|
||||||
|
await convertFormDataToPhotoDbInsertAndLookupRecipeTitle(form);
|
||||||
|
photo.url = updatedUrl;
|
||||||
|
await insertPhoto(photo);
|
||||||
|
onFinish?.(url);
|
||||||
|
// Re-submit with updated url
|
||||||
|
onStreamUpdate(subheadFinal, 'added');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addUploadsAction = async ({
|
||||||
|
uploadUrls,
|
||||||
|
uploadTitles,
|
||||||
|
shouldRevalidateAllKeysAndPaths = true,
|
||||||
|
tags,
|
||||||
|
favorite,
|
||||||
|
hidden,
|
||||||
|
takenAtLocal,
|
||||||
|
takenAtNaiveLocal,
|
||||||
|
}: Parameters<typeof addUpload>[0] & {
|
||||||
|
uploadUrls: string[]
|
||||||
|
uploadTitles: string[]
|
||||||
shouldRevalidateAllKeysAndPaths?: boolean
|
shouldRevalidateAllKeysAndPaths?: boolean
|
||||||
}) =>
|
}) =>
|
||||||
runAuthenticatedAdminServerAction(async () => {
|
runAuthenticatedAdminServerAction(async () => {
|
||||||
@ -128,71 +213,23 @@ export const addUploadsAction = async ({
|
|||||||
try {
|
try {
|
||||||
for (const [index, url] of uploadUrls.entries()) {
|
for (const [index, url] of uploadUrls.entries()) {
|
||||||
currentUploadUrl = url;
|
currentUploadUrl = url;
|
||||||
const title = uploadTitles[index];
|
|
||||||
progress = 0;
|
progress = 0;
|
||||||
|
const title = uploadTitles[index];
|
||||||
streamUpdate('Parsing EXIF data');
|
streamUpdate('Parsing EXIF data');
|
||||||
|
|
||||||
const {
|
await addUpload({
|
||||||
formDataFromExif,
|
url,
|
||||||
imageResizedBase64,
|
title,
|
||||||
shouldStripGpsData,
|
tags,
|
||||||
fileBytes,
|
favorite,
|
||||||
} = await extractImageDataFromBlobPath(url, {
|
hidden,
|
||||||
includeInitialPhotoFields: true,
|
takenAtLocal,
|
||||||
generateBlurData: BLUR_ENABLED,
|
takenAtNaiveLocal,
|
||||||
generateResizedImage: AI_TEXT_GENERATION_ENABLED,
|
onStreamUpdate: streamUpdate,
|
||||||
});
|
onFinish: () => {
|
||||||
|
|
||||||
if (formDataFromExif) {
|
|
||||||
if (AI_TEXT_GENERATION_ENABLED) {
|
|
||||||
streamUpdate('Generating AI text');
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
title: aiTitle,
|
|
||||||
caption,
|
|
||||||
tags: aiTags,
|
|
||||||
semanticDescription,
|
|
||||||
} = await generateAiImageQueries(
|
|
||||||
imageResizedBase64,
|
|
||||||
Boolean(title)
|
|
||||||
? AI_TEXT_AUTO_GENERATED_FIELDS
|
|
||||||
.filter(field => field !== 'title')
|
|
||||||
: AI_TEXT_AUTO_GENERATED_FIELDS,
|
|
||||||
title,
|
|
||||||
);
|
|
||||||
|
|
||||||
const form: Partial<PhotoFormData> = {
|
|
||||||
...formDataFromExif,
|
|
||||||
title: title || aiTitle,
|
|
||||||
caption,
|
|
||||||
tags: tags || aiTags,
|
|
||||||
hidden,
|
|
||||||
favorite,
|
|
||||||
semanticDescription,
|
|
||||||
takenAt: formDataFromExif.takenAt || takenAtLocal,
|
|
||||||
takenAtNaive: formDataFromExif.takenAtNaive || takenAtNaiveLocal,
|
|
||||||
};
|
|
||||||
|
|
||||||
streamUpdate('Transferring to photo storage');
|
|
||||||
|
|
||||||
const updatedUrl = await convertUploadToPhoto({
|
|
||||||
urlOrigin: url,
|
|
||||||
fileBytes,
|
|
||||||
shouldStripGpsData,
|
|
||||||
});
|
|
||||||
if (updatedUrl) {
|
|
||||||
const subheadFinal = 'Adding to database';
|
|
||||||
streamUpdate(subheadFinal);
|
|
||||||
const photo =
|
|
||||||
await convertFormDataToPhotoDbInsertAndLookupRecipeTitle(form);
|
|
||||||
photo.url = updatedUrl;
|
|
||||||
await insertPhoto(photo);
|
|
||||||
addedUploadUrls.push(url);
|
addedUploadUrls.push(url);
|
||||||
// Re-submit with updated url
|
},
|
||||||
streamUpdate(subheadFinal, 'added');
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user