From 9a09dde557ae80a038c93255a84f1ea9af3bdff1 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 2 Jun 2025 08:32:36 -0700 Subject: [PATCH] Standardize metadata popovers --- .../admin/AlbumMetadataPopover.svelte | 101 ++++ src/lib/components/admin/Button.svelte | 19 +- .../components/admin/GalleryUploader.svelte | 189 +++++--- .../admin/GenericMetadataPopover.svelte | 450 ++++++++++++++++++ .../components/admin/MetadataPopover.svelte | 22 +- .../admin/PostMetadataPopover.svelte | 105 ++++ .../admin/albums/[id]/edit/+page.svelte | 212 +++++---- src/routes/admin/albums/new/+page.svelte | 360 ++++++++------ src/routes/admin/posts/[id]/edit/+page.svelte | 4 +- src/routes/admin/posts/new/+page.svelte | 4 +- 10 files changed, 1134 insertions(+), 332 deletions(-) create mode 100644 src/lib/components/admin/AlbumMetadataPopover.svelte create mode 100644 src/lib/components/admin/GenericMetadataPopover.svelte create mode 100644 src/lib/components/admin/PostMetadataPopover.svelte diff --git a/src/lib/components/admin/AlbumMetadataPopover.svelte b/src/lib/components/admin/AlbumMetadataPopover.svelte new file mode 100644 index 0000000..772edcc --- /dev/null +++ b/src/lib/components/admin/AlbumMetadataPopover.svelte @@ -0,0 +1,101 @@ + + + \ No newline at end of file diff --git a/src/lib/components/admin/Button.svelte b/src/lib/components/admin/Button.svelte index 2a9fde4..ce346a4 100644 --- a/src/lib/components/admin/Button.svelte +++ b/src/lib/components/admin/Button.svelte @@ -2,7 +2,7 @@ import type { HTMLButtonAttributes } from 'svelte/elements' interface Props extends HTMLButtonAttributes { - variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'text' | 'overlay' + variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'text' | 'overlay' | 'danger-text' buttonSize?: 'small' | 'medium' | 'large' | 'icon' iconOnly?: boolean iconPosition?: 'left' | 'right' @@ -343,6 +343,23 @@ } } + .btn-danger-text { + background: none; + color: #dc2626; + padding: $unit; + font-weight: 600; + + &:hover:not(:disabled) { + background-color: $grey-90; + color: #dc2626; + } + + &:active:not(:disabled) { + background-color: $grey-80; + color: #dc2626; + } + } + .btn-overlay { background-color: white; color: $grey-20; diff --git a/src/lib/components/admin/GalleryUploader.svelte b/src/lib/components/admin/GalleryUploader.svelte index 6f320ee..136706f 100644 --- a/src/lib/components/admin/GalleryUploader.svelte +++ b/src/lib/components/admin/GalleryUploader.svelte @@ -5,7 +5,6 @@ import SmartImage from '../SmartImage.svelte' import MediaLibraryModal from './MediaLibraryModal.svelte' import { authenticatedFetch } from '$lib/admin-auth' - import RefreshIcon from '$icons/refresh.svg?component' interface Props { label: string @@ -103,7 +102,7 @@ for (let i = 0; i < files.length; i++) { const file = files[i] const validationError = validateFile(file) - + if (validationError) { errors.push(`${file.name}: ${validationError}`) } else if (filesToUpload.length < remainingSlots) { @@ -126,10 +125,10 @@ try { // Initialize progress tracking const progressKeys = filesToUpload.map((file, index) => `${file.name}-${index}`) - uploadProgress = Object.fromEntries(progressKeys.map(key => [key, 0])) + uploadProgress = Object.fromEntries(progressKeys.map((key) => [key, 0])) // Simulate progress for user feedback - const progressIntervals = progressKeys.map(key => { + const progressIntervals = progressKeys.map((key) => { return setInterval(() => { if (uploadProgress[key] < 90) { uploadProgress[key] += Math.random() * 10 @@ -139,16 +138,16 @@ }) const uploadedMedia = await uploadFiles(filesToUpload) - + // Clear progress intervals - progressIntervals.forEach(interval => clearInterval(interval)) - + progressIntervals.forEach((interval) => clearInterval(interval)) + // Complete progress - progressKeys.forEach(key => { + progressKeys.forEach((key) => { uploadProgress[key] = 100 }) uploadProgress = { ...uploadProgress } - + // Brief delay to show completion setTimeout(() => { const newValue = [...(value || []), ...uploadedMedia] @@ -158,7 +157,6 @@ isUploading = false uploadProgress = {} }, 500) - } catch (err) { isUploading = false uploadProgress = {} @@ -180,7 +178,7 @@ function handleDrop(event: DragEvent) { event.preventDefault() isDragOver = false - + const files = event.dataTransfer?.files if (files) { handleFiles(files) @@ -202,7 +200,7 @@ // Remove individual image - now passes the item to be removed instead of doing it locally function handleRemoveImage(index: number) { if (!value || !value[index]) return - + const itemToRemove = value[index] // Call the onRemove callback if provided, otherwise fall back to onUpload if (onRemove) { @@ -219,7 +217,7 @@ // Update alt text on server async function handleAltTextChange(item: any, newAltText: string) { if (!item) return - + try { // For album photos, use mediaId; for direct media objects, use id const mediaId = item.mediaId || item.id @@ -227,7 +225,7 @@ console.error('No media ID found for alt text update') return } - + const response = await authenticatedFetch(`/api/media/${mediaId}/metadata`, { method: 'PATCH', headers: { @@ -241,9 +239,13 @@ if (response.ok) { const updatedData = await response.json() if (value) { - const index = value.findIndex(v => (v.mediaId || v.id) === mediaId) + const index = value.findIndex((v) => (v.mediaId || v.id) === mediaId) if (index !== -1) { - value[index] = { ...value[index], altText: updatedData.altText, updatedAt: updatedData.updatedAt } + value[index] = { + ...value[index], + altText: updatedData.altText, + updatedAt: updatedData.updatedAt + } value = [...value] } } @@ -275,25 +277,25 @@ function handleImageDrop(event: DragEvent, dropIndex: number) { event.preventDefault() - + if (draggedIndex === null || !value) return - + const newValue = [...value] const draggedItem = newValue[draggedIndex] - + // Remove from old position newValue.splice(draggedIndex, 1) - + // Insert at new position (adjust index if dragging to later position) const adjustedDropIndex = draggedIndex < dropIndex ? dropIndex - 1 : dropIndex newValue.splice(adjustedDropIndex, 0, draggedItem) - + value = newValue onUpload(newValue) if (onReorder) { onReorder(newValue) } - + draggedIndex = null draggedOverIndex = null } @@ -311,12 +313,12 @@ function handleMediaSelect(selectedMedia: any | any[]) { // For gallery mode, selectedMedia will be an array const mediaArray = Array.isArray(selectedMedia) ? selectedMedia : [selectedMedia] - + // Add selected media to existing gallery (avoid duplicates) // Check both id and mediaId to handle different object types - const currentIds = value?.map(m => m.mediaId || m.id) || [] - const newMedia = mediaArray.filter(media => !currentIds.includes(media.id)) - + const currentIds = value?.map((m) => m.mediaId || m.id) || [] + const newMedia = mediaArray.filter((media) => !currentIds.includes(media.id)) + if (newMedia.length > 0) { const updatedGallery = [...(value || []), ...newMedia] value = updatedGallery @@ -331,21 +333,9 @@ + + + + + + diff --git a/src/routes/admin/posts/[id]/edit/+page.svelte b/src/routes/admin/posts/[id]/edit/+page.svelte index ba6077b..cb6e93f 100644 --- a/src/routes/admin/posts/[id]/edit/+page.svelte +++ b/src/routes/admin/posts/[id]/edit/+page.svelte @@ -5,7 +5,7 @@ import AdminPage from '$lib/components/admin/AdminPage.svelte' import Editor from '$lib/components/admin/Editor.svelte' import LoadingSpinner from '$lib/components/admin/LoadingSpinner.svelte' - import MetadataPopover from '$lib/components/admin/MetadataPopover.svelte' + import PostMetadataPopover from '$lib/components/admin/PostMetadataPopover.svelte' import DeleteConfirmationModal from '$lib/components/admin/DeleteConfirmationModal.svelte' import Button from '$lib/components/admin/Button.svelte' import SaveActionsGroup from '$lib/components/admin/SaveActionsGroup.svelte' @@ -229,7 +229,7 @@ {#if showMetadata && metadataButtonRef} - {#if showMetadata && metadataButtonRef} -