diff --git a/src/assets/icons/album.svg b/src/assets/icons/album.svg new file mode 100644 index 0000000..02d9f34 --- /dev/null +++ b/src/assets/icons/album.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/media.svg b/src/assets/icons/media.svg new file mode 100644 index 0000000..6fdf419 --- /dev/null +++ b/src/assets/icons/media.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/lib/components/admin/AdminNavBar.svelte b/src/lib/components/admin/AdminNavBar.svelte index b386de3..9dab8f2 100644 --- a/src/lib/components/admin/AdminNavBar.svelte +++ b/src/lib/components/admin/AdminNavBar.svelte @@ -4,7 +4,8 @@ import AvatarSimple from '$lib/components/AvatarSimple.svelte' import WorkIcon from '$icons/work.svg?component' import UniverseIcon from '$icons/universe.svg?component' - import PhotosIcon from '$icons/photos.svg?component' + import MediaIcon from '$icons/media.svg?component' + import AlbumIcon from '$icons/album.svg?component' const currentPath = $derived($page.url.pathname) let isScrolled = $state(false) @@ -31,8 +32,8 @@ const navItems: NavItem[] = [ { text: 'Projects', href: '/admin/projects', icon: WorkIcon }, { text: 'Universe', href: '/admin/posts', icon: UniverseIcon }, - { text: 'Albums', href: '/admin/albums', icon: PhotosIcon }, - { text: 'Media', href: '/admin/media', icon: PhotosIcon } + { text: 'Albums', href: '/admin/albums', icon: AlbumIcon }, + { text: 'Media', href: '/admin/media', icon: MediaIcon } ] // Calculate active index based on current path diff --git a/src/lib/components/admin/GalleryUploader.svelte b/src/lib/components/admin/GalleryUploader.svelte index 2397da9..090c643 100644 --- a/src/lib/components/admin/GalleryUploader.svelte +++ b/src/lib/components/admin/GalleryUploader.svelte @@ -3,7 +3,7 @@ import Button from './Button.svelte' import Input from './Input.svelte' import SmartImage from '../SmartImage.svelte' - import MediaLibraryModal from './MediaLibraryModal.svelte' + import UnifiedMediaModal from './UnifiedMediaModal.svelte' import MediaDetailsModal from './MediaDetailsModal.svelte' import { authenticatedFetch } from '$lib/admin-auth' @@ -369,7 +369,7 @@ thumbnailUrl: media.thumbnailUrl, width: media.width, height: media.height, - altText: media.altText || '', + // altText removed - using description only description: media.description || '', isPhotography: media.isPhotography || false, createdAt: media.createdAt, @@ -387,7 +387,7 @@ if (index !== -1) { value[index] = { ...value[index], - altText: updatedMedia.altText, + // altText removed - using description only description: updatedMedia.description, isPhotography: updatedMedia.isPhotography, updatedAt: updatedMedia.updatedAt @@ -587,13 +587,13 @@ thumbnailUrl: media.thumbnailUrl, width: media.width, height: media.height, - altText: media.altText, + // altText removed - using description only description: media.description, isPhotography: media.isPhotography || false, createdAt: media.createdAt, updatedAt: media.updatedAt }} - alt={media.altText || media.filename || 'Gallery image'} + alt={media.description || media.filename || 'Gallery image'} containerWidth={300} loading="lazy" aspectRatio="1:1" @@ -675,7 +675,7 @@ - import Button from './Button.svelte' - import MediaLibraryModal from './MediaLibraryModal.svelte' + import UnifiedMediaModal from './UnifiedMediaModal.svelte' import type { Media } from '@prisma/client' interface Props { @@ -212,14 +212,15 @@ {/if} - (showModal = false)} /> diff --git a/src/lib/components/admin/ImageUploadPlaceholder.svelte b/src/lib/components/admin/ImageUploadPlaceholder.svelte deleted file mode 100644 index ddc6c31..0000000 --- a/src/lib/components/admin/ImageUploadPlaceholder.svelte +++ /dev/null @@ -1,296 +0,0 @@ - - - - - -
- {#if isUploading} -
-
- Uploading image... -
- {:else} - - - - {/if} -
- - - -
- - diff --git a/src/lib/components/admin/ImageUploader.svelte b/src/lib/components/admin/ImageUploader.svelte index be7ea2d..8ec669a 100644 --- a/src/lib/components/admin/ImageUploader.svelte +++ b/src/lib/components/admin/ImageUploader.svelte @@ -3,7 +3,7 @@ import Button from './Button.svelte' import Input from './Input.svelte' import SmartImage from '../SmartImage.svelte' - import MediaLibraryModal from './MediaLibraryModal.svelte' + import UnifiedMediaModal from './UnifiedMediaModal.svelte' import { authenticatedFetch } from '$lib/admin-auth' import RefreshIcon from '$icons/refresh.svg?component' @@ -15,7 +15,7 @@ aspectRatio?: string // e.g., "16:9", "1:1" required?: boolean error?: string - allowAltText?: boolean + allowAltText?: boolean // @deprecated - Now using description field for alt text maxFileSize?: number // MB limit placeholder?: string helpText?: string @@ -45,7 +45,7 @@ let uploadError = $state(null) let isDragOver = $state(false) let fileInputElement: HTMLInputElement - let altTextValue = $state(value?.altText || '') + // Removed altText - using only description field let descriptionValue = $state(value?.description || '') let isMediaLibraryOpen = $state(false) @@ -79,11 +79,9 @@ const formData = new FormData() formData.append('file', file) - if (allowAltText && altTextValue.trim()) { - formData.append('altText', altTextValue.trim()) - } + // Removed altText upload - description is handled separately - if (allowAltText && descriptionValue.trim()) { + if (descriptionValue.trim()) { formData.append('description', descriptionValue.trim()) } @@ -132,7 +130,7 @@ // Brief delay to show completion setTimeout(() => { value = uploadedMedia - altTextValue = uploadedMedia.altText || '' + // altText removed - using description only descriptionValue = uploadedMedia.description || '' onUpload(uploadedMedia) isUploading = false @@ -181,35 +179,13 @@ // Remove uploaded image function handleRemove() { value = null - altTextValue = '' + // altText removed descriptionValue = '' uploadError = null onRemove?.() } - // Update alt text on server - async function handleAltTextChange() { - if (!value) return - - try { - const response = await authenticatedFetch(`/api/media/${value.id}/metadata`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - altText: altTextValue.trim() || null - }) - }) - - if (response.ok) { - const updatedData = await response.json() - value = { ...value, altText: updatedData.altText, updatedAt: updatedData.updatedAt } - } - } catch (error) { - console.error('Failed to update alt text:', error) - } - } + // Removed handleAltTextChange - using only description async function handleDescriptionChange() { if (!value) return @@ -243,7 +219,7 @@ // Since this is single mode, selectedMedia will be a single Media object const media = selectedMedia as Media value = media - altTextValue = media.altText || '' + // altText removed - using description only descriptionValue = media.description || '' onUpload(media) } @@ -275,7 +251,7 @@
- - {#if allowAltText} - - {/if} + +
{:else} @@ -520,24 +485,16 @@ {/if} - - {#if allowAltText && hasValue && !compact} + + {#if hasValue && !compact} @@ -559,7 +516,7 @@ -([]) let loadingUsage = $state(false) + // Album management state + let albums = $state>([]) + let loadingAlbums = $state(false) + let showAlbumSelector = $state(false) + // EXIF toggle state let showExif = $state(false) // Initialize form when media changes $effect(() => { if (media) { - // Use description if available, otherwise fall back to altText for backwards compatibility - description = media.description || media.altText || '' + description = media.description || '' isPhotography = media.isPhotography || false error = '' successMessage = '' showExif = false loadUsage() + // Only load albums for images + if (media.mimeType?.startsWith('image/')) { + loadAlbums() + } } }) @@ -75,6 +85,27 @@ } } + // Load albums the media belongs to + async function loadAlbums() { + if (!media) return + + try { + loadingAlbums = true + + // Load albums this media belongs to + const mediaResponse = await authenticatedFetch(`/api/media/${media.id}/albums`) + if (mediaResponse.ok) { + const data = await mediaResponse.json() + albums = data.albums || [] + } + } catch (error) { + console.error('Error loading albums:', error) + albums = [] + } finally { + loadingAlbums = false + } + } + function handleClose() { description = '' isPhotography = false @@ -97,8 +128,6 @@ 'Content-Type': 'application/json' }, body: JSON.stringify({ - // Use description for both altText and description fields - altText: description.trim() || null, description: description.trim() || null, isPhotography: isPhotography }) @@ -205,11 +234,7 @@
{#if media.mimeType.startsWith('image/')}
- +
{:else}
@@ -303,103 +328,109 @@ Size {formatFileSize(media.size)}
- {#if media.width && media.height} -
- Dimensions - {media.width} × {media.height}px -
- {/if} - {#if media.dominantColor} -
- Dominant Color - - - {media.dominantColor} - -
- {:else} - - {/if} -
- Uploaded - {new Date(media.createdAt).toLocaleDateString()} -
- {#if media.exifData && Object.keys(media.exifData).length > 0} - {#if showExif} -
- {#if media.exifData.camera} + {#if showExif} +
+ + - {/if} - + + {#if media.exifData && Object.keys(media.exifData).length > 0} + + + {/if} +
{/if} + +
@@ -433,7 +464,19 @@
-

Used In

+
+

Used In

+ {#if media.mimeType?.startsWith('image/')} + + {/if} +
{#if loadingUsage}
@@ -473,6 +516,20 @@

This media file is not currently used in any content.

{/if}
+ + + {#if albums.length > 0} +
+

Albums

+
+ {#each albums as album} + + {album.title} + + {/each} +
+
+ {/if}
@@ -506,6 +563,21 @@ + + + {#if showAlbumSelector && media} + (showAlbumSelector = false)} size="medium"> + { + albums = updatedAlbums + showAlbumSelector = false + }} + onClose={() => (showAlbumSelector = false)} + /> + + {/if} {/if}