Add multi-tag query to batch edit

This commit is contained in:
Sam Becker 2024-07-20 22:41:03 -05:00
parent dc8dedd806
commit 38d372dd72
4 changed files with 43 additions and 10 deletions

View File

@ -12,6 +12,7 @@ import { Tags } from '@/tag';
import { usePathname } from 'next/navigation';
import { PATH_GRID_INFERRED } from '@/site/paths';
import PhotoTagFieldset from './PhotoTagFieldset';
import { tagMultiplePhotosAction } from '@/photo/actions';
export default function AdminBatchEditPanelClient({
uniqueTags,
@ -26,6 +27,8 @@ export default function AdminBatchEditPanelClient({
setSelectedPhotoIds,
} = useAppState();
const [isLoading, setIsLoading] = useState(false);
const [tags, setTags] = useState<string>();
const [tagErrorMessage, setTagErrorMessage] = useState('');
const isTagging = tags !== undefined;
@ -44,6 +47,7 @@ export default function AdminBatchEditPanelClient({
setTags(undefined);
setTagErrorMessage('');
}}
disabled={isLoading}
>
Cancel
</LoaderButton>
@ -51,7 +55,15 @@ export default function AdminBatchEditPanelClient({
className="min-h-[2.5rem]"
// eslint-disable-next-line max-len
confirmText={`Are you sure you want to apply tags to ${selectedPhotoIds?.length} ${photosPlural}? This action cannot be undone.`}
disabled={!tags || Boolean(tagErrorMessage)}
onClick={() => {
setIsLoading(true);
tagMultiplePhotosAction(
tags,
selectedPhotoIds ?? [],
)
.finally(() => setIsLoading(false));
}}
disabled={!tags || Boolean(tagErrorMessage) || isLoading}
primary
>
Apply Tags
@ -60,10 +72,13 @@ export default function AdminBatchEditPanelClient({
: <>
{(selectedPhotoIds?.length ?? 0) > 0 &&
<>
<LoaderButton onClick={() => setTags('')}>
<LoaderButton
onClick={() => setTags('')}
isLoading={isLoading}
>
Tag ...
</LoaderButton>
<DeleteButton />
<DeleteButton disabled={isLoading} />
</>}
<LoaderButton
icon={<IoCloseSharp size={20} className="translate-y-[-1.5px]" />}

View File

@ -8,6 +8,7 @@ import {
renamePhotoTagGlobally,
getPhoto,
getPhotos,
addTagsToPhotos,
} from '@/photo/db/query';
import { GetPhotosOptions, areOptionsSensitive } from './db';
import {
@ -47,6 +48,7 @@ import { generateAiImageQueries } from './ai/server';
import { createStreamableValue } from 'ai/rsc';
import { convertUploadToPhoto } from './storage';
import { UrlAddStatus } from '@/admin/AdminUploadsClient';
import { convertStringToArray } from '@/utility/string';
// Private actions
@ -203,6 +205,18 @@ export const updatePhotoAction = async (formData: FormData) =>
redirect(PATH_ADMIN_PHOTOS);
});
export const tagMultiplePhotosAction = (
tags: string,
photoIds: string[],
) =>
runAuthenticatedAdminServerAction(async () => {
await addTagsToPhotos(
convertStringToArray(tags, false) ?? [],
photoIds,
);
revalidateAllKeysAndPaths();
});
export const toggleFavoritePhotoAction = async (
photoId: string,
shouldRedirect?: boolean,

View File

@ -244,17 +244,19 @@ export const renamePhotoTagGlobally = (tag: string, updatedTag: string) =>
`, 'renamePhotoTagGlobally');
export const addTagsToPhotos = (tags: string[], photoIds: string[]) =>
safelyQueryPhotos(() => sql`
safelyQueryPhotos(() => query(`
UPDATE photos
SET tags = (
SELECT array_agg(DISTINCT elem)
FROM unnest(
array_cat(tags, ARRAY${convertArrayToPostgresString(tags, 'brackets')})
array_cat(tags, $1)
) AS elem
)
WHERE id IN ${convertArrayToPostgresString(photoIds, 'brackets')}
LIMIT ${photoIds.length}
`, 'addTagsToPhotos');
WHERE id = ANY($2)
`, [
convertArrayToPostgresString(tags),
convertArrayToPostgresString(photoIds),
]), 'addTagsToPhotos');
export const deletePhoto = (id: string) =>
safelyQueryPhotos(() => sql`

View File

@ -43,11 +43,13 @@ export const sql = <T extends QueryResultRow>(
export const convertArrayToPostgresString = (
array?: string[],
type: 'braces' | 'brackets' = 'braces',
type: 'braces' | 'brackets' | 'parentheses' = 'braces',
) => array
? type === 'braces'
? `{${array.join(',')}}`
: `[${array.map(i => `'${i}'`).join(',')}]`
: type === 'brackets'
? `[${array.map(i => `'${i}'`).join(',')}]`
: `(${array.map(i => `'${i}'`).join(',')})`
: null;
const isTemplateStringsArray = (