Add hidden, favorite options to bulk uploads

This commit is contained in:
Sam Becker 2025-03-15 00:20:36 -05:00
parent 7127bdf354
commit 0f0d9a32e3
9 changed files with 57 additions and 36 deletions

View File

@ -46,14 +46,12 @@ export default function ComponentsPage() {
'*:inline-flex *:gap-1 [&_input]:-translate-y-0.5', '*:inline-flex *:gap-1 [&_input]:-translate-y-0.5',
)}> )}>
<FieldSetWithStatus <FieldSetWithStatus
id="grid"
label="Grid" label="Grid"
type="checkbox" type="checkbox"
value={shouldShowBaselineGrid ? 'true' : 'false'} value={shouldShowBaselineGrid ? 'true' : 'false'}
onChange={e => setShouldShowBaselineGrid?.(e === 'true')} onChange={e => setShouldShowBaselineGrid?.(e === 'true')}
/> />
<FieldSetWithStatus <FieldSetWithStatus
id="components"
label="Components" label="Components"
type="checkbox" type="checkbox"
value={debugComponents ? 'true' : 'false'} value={debugComponents ? 'true' : 'false'}

View File

@ -1,6 +1,6 @@
import { getStorageUploadUrlsNoStore } from '@/platforms/storage/cache'; import { getStorageUploadUrlsNoStore } from '@/platforms/storage/cache';
import SiteGrid from '@/components/SiteGrid'; import SiteGrid from '@/components/SiteGrid';
import { getUniqueTagsCached, getUniqueRecipesCached } from '@/photo/cache'; import { getUniqueTagsCached } from '@/photo/cache';
import AdminUploadsClient from '@/admin/AdminUploadsClient'; import AdminUploadsClient from '@/admin/AdminUploadsClient';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { PATH_ADMIN_PHOTOS } from '@/app/paths'; import { PATH_ADMIN_PHOTOS } from '@/app/paths';
@ -10,7 +10,6 @@ export const maxDuration = 60;
export default async function AdminUploadsPage() { export default async function AdminUploadsPage() {
const urls = await getStorageUploadUrlsNoStore(); const urls = await getStorageUploadUrlsNoStore();
const uniqueTags = await getUniqueTagsCached(); const uniqueTags = await getUniqueTagsCached();
const uniqueRecipes = await getUniqueRecipesCached();
if (urls.length === 0) { if (urls.length === 0) {
redirect(PATH_ADMIN_PHOTOS); redirect(PATH_ADMIN_PHOTOS);
@ -21,7 +20,6 @@ export default async function AdminUploadsPage() {
<AdminUploadsClient {...{ <AdminUploadsClient {...{
urls, urls,
uniqueTags, uniqueTags,
uniqueRecipes,
}} />} }} />}
/> />
); );

View File

@ -43,11 +43,14 @@ export default function AdminBatchUploadActions({
}) { }) {
const { updateAdminData } = useAppState(); const { updateAdminData } = useAppState();
const [buttonText, setButtonText] = useState('Add All Uploads'); const [showBulkSettings, setShowBulkSettings] = useState(false);
const [showTags, setShowTags] = useState(false);
const [tags, setTags] = useState(''); const [tags, setTags] = useState('');
const [actionErrorMessage, setActionErrorMessage] = useState(''); const [favorite, setFavorite] = useState('false');
const [hidden, setHidden] = useState('false');
const [tagErrorMessage, setTagErrorMessage] = useState(''); const [tagErrorMessage, setTagErrorMessage] = useState('');
const [buttonText, setButtonText] = useState('Add All Uploads');
const [actionErrorMessage, setActionErrorMessage] = useState('');
const [addingProgress, setAddingProgress] = useState<number>(); const [addingProgress, setAddingProgress] = useState<number>();
const [isAddingComplete, setIsAddingComplete] = useState(false); const [isAddingComplete, setIsAddingComplete] = useState(false);
@ -58,7 +61,11 @@ export default function AdminBatchUploadActions({
try { try {
const stream = await addAllUploadsAction({ const stream = await addAllUploadsAction({
uploadUrls, uploadUrls,
tags: showTags ? tags : undefined, ...showBulkSettings && {
tags,
favorite,
hidden,
},
takenAtLocal: generateLocalPostgresString(), takenAtLocal: generateLocalPostgresString(),
takenAtNaiveLocal: generateLocalNaivePostgresString(), takenAtNaiveLocal: generateLocalNaivePostgresString(),
shouldRevalidateAllKeysAndPaths: isFinalBatch, shouldRevalidateAllKeysAndPaths: isFinalBatch,
@ -116,29 +123,43 @@ export default function AdminBatchUploadActions({
'grow', 'grow',
tagErrorMessage ? 'text-error' : 'text-main', tagErrorMessage ? 'text-error' : 'text-main',
)}> )}>
{showTags {showBulkSettings
? tagErrorMessage || 'Add tags to all uploads' ? tagErrorMessage || 'Apply to all uploads'
: `Found ${storageUrls.length} uploads`} : `Found ${storageUrls.length} uploads`}
</div> </div>
<FieldSetWithStatus <FieldSetWithStatus
id="show-tags" label="Apply Bulk Settings"
label="Apply tags"
type="checkbox" type="checkbox"
value={showTags ? 'true' : 'false'} value={showBulkSettings ? 'true' : 'false'}
onChange={value => setShowTags(value === 'true')} onChange={value => setShowBulkSettings(value === 'true')}
readOnly={isAdding} readOnly={isAdding}
/> />
</div> </div>
{showTags && !actionErrorMessage && {showBulkSettings && !actionErrorMessage &&
<PhotoTagFieldset <div className="space-y-3">
tags={tags} <PhotoTagFieldset
tagOptions={uniqueTags} label="Tags"
onChange={setTags} tags={tags}
onError={setTagErrorMessage} tagOptions={uniqueTags}
readOnly={isAdding} onChange={setTags}
openOnLoad onError={setTagErrorMessage}
hideLabel readOnly={isAdding}
/>} />
<FieldSetWithStatus
label="Favorite"
type="checkbox"
value={favorite}
onChange={setFavorite}
readOnly={isAdding}
/>
<FieldSetWithStatus
label="Hidden"
type="checkbox"
value={hidden}
onChange={setHidden}
readOnly={isAdding}
/>
</div>}
<div className="space-y-2"> <div className="space-y-2">
<ProgressButton <ProgressButton
primary primary

View File

@ -35,7 +35,6 @@ export default function AdminRecipeForm({
className="space-y-8" className="space-y-8"
> >
<FieldSetWithStatus <FieldSetWithStatus
id="updatedRecipeRaw"
label="New Recipe Name" label="New Recipe Name"
value={updatedRecipeRaw} value={updatedRecipeRaw}
onChange={setUpdatedRecipeRaw} onChange={setUpdatedRecipeRaw}

View File

@ -35,7 +35,6 @@ export default function AdminTagForm({
className="space-y-8" className="space-y-8"
> >
<FieldSetWithStatus <FieldSetWithStatus
id="updatedTagRaw"
label="New Tag Name" label="New Tag Name"
value={updatedTagRaw} value={updatedTagRaw}
onChange={setUpdatedTagRaw} onChange={setUpdatedTagRaw}

View File

@ -5,7 +5,6 @@ import AdminBatchUploadActions from './AdminBatchUploadActions';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { Tags } from '@/tag'; import { Tags } from '@/tag';
import AdminUploadsTable from './AdminUploadsTable'; import AdminUploadsTable from './AdminUploadsTable';
import { Recipes } from '@/recipe';
export type UrlAddStatus = StorageListItem & { export type UrlAddStatus = StorageListItem & {
status?: 'waiting' | 'adding' | 'added' status?: 'waiting' | 'adding' | 'added'
@ -19,7 +18,6 @@ export default function AdminUploadsClient({
}: { }: {
urls: StorageListResponse urls: StorageListResponse
uniqueTags?: Tags uniqueTags?: Tags
uniqueRecipes?: Recipes
}) { }) {
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const [urlAddStatuses, setUrlAddStatuses] = useState<UrlAddStatus[]>(urls); const [urlAddStatuses, setUrlAddStatuses] = useState<UrlAddStatus[]>(urls);

View File

@ -15,7 +15,6 @@ export default function PhotoTagFieldset(props: {
'tagOptions' 'tagOptions'
>>) { >>) {
const { const {
id,
tags, tags,
tagOptions, tagOptions,
onChange, onChange,
@ -42,7 +41,7 @@ export default function PhotoTagFieldset(props: {
<FieldSetWithStatus <FieldSetWithStatus
{...rest} {...rest}
inputRef={ref} inputRef={ref}
id={id ?? 'tags'} label="Tags"
value={tags} value={tags}
tagOptions={convertTagsForForm(tagOptions)} tagOptions={convertTagsForForm(tagOptions)}
onChange={tags => { onChange={tags => {

View File

@ -7,9 +7,10 @@ import { clsx } from 'clsx/lite';
import { FieldSetType, AnnotatedTag } from '@/photo/form'; import { FieldSetType, AnnotatedTag } from '@/photo/form';
import TagInput from './TagInput'; import TagInput from './TagInput';
import { FiChevronDown } from 'react-icons/fi'; import { FiChevronDown } from 'react-icons/fi';
import { parameterize } from '@/utility/string';
export default function FieldSetWithStatus({ export default function FieldSetWithStatus({
id, id: _id,
label, label,
note, note,
error, error,
@ -34,8 +35,8 @@ export default function FieldSetWithStatus({
hideLabel, hideLabel,
checkboxAccessory, checkboxAccessory,
}: { }: {
id: string id?: string
label?: string label: string
note?: string note?: string
error?: string error?: string
value: string value: string
@ -59,6 +60,8 @@ export default function FieldSetWithStatus({
hideLabel?: boolean hideLabel?: boolean
checkboxAccessory?: React.ReactNode checkboxAccessory?: React.ReactNode
}) { }) {
const id = _id || parameterize(label);
const { pending } = useFormStatus(); const { pending } = useFormStatus();
const renderInput = const renderInput =
@ -66,13 +69,13 @@ export default function FieldSetWithStatus({
ref={inputRef} ref={inputRef}
id={id} id={id}
name={id} name={id}
type={type}
value={value} value={value}
checked={type === 'checkbox' ? value === 'true' : undefined} checked={type === 'checkbox' ? value === 'true' : undefined}
placeholder={placeholder} placeholder={placeholder}
onChange={e => onChange?.(type === 'checkbox' onChange={e => onChange?.(type === 'checkbox'
? e.target.value === 'true' ? 'false' : 'true' ? e.target.value === 'true' ? 'false' : 'true'
: e.target.value)} : e.target.value)}
type={type}
spellCheck={spellCheck} spellCheck={spellCheck}
autoComplete="off" autoComplete="off"
autoCapitalize={!capitalize ? 'off' : undefined} autoCapitalize={!capitalize ? 'off' : undefined}

View File

@ -89,12 +89,16 @@ export const createPhotoAction = async (formData: FormData) =>
export const addAllUploadsAction = async ({ export const addAllUploadsAction = async ({
uploadUrls, uploadUrls,
tags, tags,
favorite,
hidden,
takenAtLocal, takenAtLocal,
takenAtNaiveLocal, takenAtNaiveLocal,
shouldRevalidateAllKeysAndPaths = true, shouldRevalidateAllKeysAndPaths = true,
}: { }: {
uploadUrls: string[] uploadUrls: string[]
tags?: string tags?: string
favorite?: string
hidden?: string
takenAtLocal: string takenAtLocal: string
takenAtNaiveLocal: string takenAtNaiveLocal: string
shouldRevalidateAllKeysAndPaths?: boolean shouldRevalidateAllKeysAndPaths?: boolean
@ -106,7 +110,7 @@ export const addAllUploadsAction = async ({
let currentUploadUrl = ''; let currentUploadUrl = '';
let progress = 0; let progress = 0;
const stream = createStreamableValue<UrlAddStatus>(); const stream = createStreamableValue<Omit<UrlAddStatus, 'fileName'>>();
const streamUpdate = ( const streamUpdate = (
statusMessage: string, statusMessage: string,
@ -157,6 +161,8 @@ export const addAllUploadsAction = async ({
title, title,
caption, caption,
tags: tags || aiTags, tags: tags || aiTags,
hidden,
favorite,
semanticDescription, semanticDescription,
takenAt: formDataFromExif.takenAt || takenAtLocal, takenAt: formDataFromExif.takenAt || takenAtLocal,
takenAtNaive: formDataFromExif.takenAtNaive || takenAtNaiveLocal, takenAtNaive: formDataFromExif.takenAtNaive || takenAtNaiveLocal,