feat: photo/video capture-date pipeline + premium UI/UX overhaul
Backend — Photos timeline now groups by real capture date instead of upload time. New MediaMetadataService (FileLifecycleHook) extracts EXIF DateTimeOriginal from images and container creation_time from videos (mov/mp4/mkv) via nom-exif, timezone-correct (OffsetTimeOriginal), persisting captured_at so the existing media_sort_date trigger takes over. Adds POST /admin/photos/metadata/reextract to backfill existing media. Falls back to upload date when no embedded date exists. Frontend — premium grid cards: combined metadata line (relative date · size, owner avatar when shared), custom selection checkbox with a clear checked state, uniform full-width 4:3 thumbnail tiles independent of filename length, centered file-type icons, and a hit-test fix so checkbox/star/kebab clicks reach the controls (the decorative thumbnail no longer captures pointer events). Notification messages internationalised across all 16 locales. Broader polish: design tokens, a11y/focus-visible states, brand + PWA assets. Chore — bump semver-compatible dependencies (cargo upgrade); add nom-exif 3.6.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+102
-7
@@ -157,7 +157,11 @@
|
||||
"close": "Sluiten",
|
||||
"delete_permanently": "Permanent verwijderen",
|
||||
"empty_trash": "Prullenbak legen",
|
||||
"open_parent_folder": "Naar bovenliggende map"
|
||||
"open_parent_folder": "Naar bovenliggende map",
|
||||
"add": "Add",
|
||||
"apply": "Apply",
|
||||
"clear": "Clear",
|
||||
"remove": "Remove"
|
||||
},
|
||||
"user_menu": {
|
||||
"appearance": "Weergave",
|
||||
@@ -197,7 +201,22 @@
|
||||
"shareCreated": "Deellink succesvol aangemaakt",
|
||||
"shareUpdated": "Deelinstellingen bijgewerkt",
|
||||
"shareRemoved": "Delen verwijderd",
|
||||
"inviteByEmail": "Uitnodigen via e-mail — uitnodiging wordt verzonden"
|
||||
"inviteByEmail": "Uitnodigen via e-mail — uitnodiging wordt verzonden",
|
||||
"directoryUnavailable": "User directory unavailable",
|
||||
"linkNamePlaceholder": "Link name (optional)",
|
||||
"newLink": "New link",
|
||||
"noExpiry": "No expiry",
|
||||
"pending": "Pending",
|
||||
"people": "People",
|
||||
"publicLinks": "Public links",
|
||||
"role": {
|
||||
"canEdit": "Can edit",
|
||||
"canManage": "Can manage",
|
||||
"canView": "Can view"
|
||||
},
|
||||
"searchPlaceholder": "Search people…",
|
||||
"shareOf": "Share of:",
|
||||
"sharedLink": "Shared link"
|
||||
},
|
||||
"share_dialogTitle": "Deellink",
|
||||
"share_linkLabel": "Deellink:",
|
||||
@@ -460,12 +479,15 @@
|
||||
"magicLinkSubmit": "Aanmeldlink versturen",
|
||||
"magicLinkSent": "Als er een account bestaat voor dat e-mailadres, is een aanmeldlink verzonden. Controleer uw inbox.",
|
||||
"magicLinkUnavailable": "Aanmelden per e-mail is niet beschikbaar op deze server.",
|
||||
"magicLinkNetworkError": "Kan de server niet bereiken: {{message}}"
|
||||
"magicLinkNetworkError": "Kan de server niet bereiken: {{message}}",
|
||||
"magicLinkToggle": "No password? Email me a sign-in link",
|
||||
"passwordsMatch": "Passwords match",
|
||||
"capsLock": "Caps Lock is on"
|
||||
},
|
||||
"storage": {
|
||||
"title": "Opslag",
|
||||
"calculating": "Bezig met berekenen...",
|
||||
"used": "{percentage}% gebruikt ({used} / {total})"
|
||||
"used": "{{percentage}}% gebruikt ({{used}} / {{total}})"
|
||||
},
|
||||
"viewer": {
|
||||
"unsupported_file": "Dit bestandstype kan niet bekeken worden.",
|
||||
@@ -532,9 +554,9 @@
|
||||
},
|
||||
"batch": {
|
||||
"one_selected": "1 item geselecteerd",
|
||||
"n_selected": "{count} items geselecteerd",
|
||||
"confirm_delete": "Weet je zeker dat je {count} items naar de prullenbak wilt verplaatsen?",
|
||||
"move_title": "Verplaats {count} item(s)",
|
||||
"n_selected": "{{count}} items geselecteerd",
|
||||
"confirm_delete": "Weet je zeker dat je {{count}} items naar de prullenbak wilt verplaatsen?",
|
||||
"move_title": "Verplaats {{count}} item(s)",
|
||||
"add_favorites": "Toevoegen aan favorieten",
|
||||
"move_copy": "Verplaatsen of kopiëren"
|
||||
},
|
||||
@@ -881,5 +903,78 @@
|
||||
"notifyRateLimited": "Te veel notificaties voor deze ontvanger — probeer het later opnieuw.",
|
||||
"removeAccess": "Toegang verwijderen",
|
||||
"resendInvitation": "Uitnodigingsmail opnieuw verzenden"
|
||||
},
|
||||
"sort": {
|
||||
"asc": "ascending",
|
||||
"desc": "descending"
|
||||
},
|
||||
"notif": {
|
||||
"errorTitle": "Error",
|
||||
"searchError": "Error performing search",
|
||||
"cleanupCompleted": "Cleanup completed",
|
||||
"cleanupCompletedBody": "Recent files history has been cleared",
|
||||
"batchCopy": "Batch copy",
|
||||
"batchCopyBody": "{{success}} copied, {{errors}} failed",
|
||||
"itemsCopied": "Items copied",
|
||||
"itemsCopiedBody": "{{count}} items copied successfully",
|
||||
"batchMove": "Batch move",
|
||||
"batchMoveBody": "{{success}} moved, {{errors}} failed",
|
||||
"itemsMoved": "Items moved",
|
||||
"itemsMovedBody": "{{count}} items moved successfully",
|
||||
"batchDelete": "Batch delete",
|
||||
"batchDeleteBody": "{{success}} moved to trash, {{errors}} failed",
|
||||
"movedToTrash": "Moved to trash",
|
||||
"movedToTrashBody": "{{count}} items moved to trash",
|
||||
"trashItemsError": "Could not move items to trash",
|
||||
"preparingDownload": "Preparing download",
|
||||
"preparingDownloadBody": "Preparing your download…",
|
||||
"downloadItemsError": "Could not download selected items",
|
||||
"favoritesAddError": "Could not add items to favorites",
|
||||
"invalidEmail": "Please enter a valid email address",
|
||||
"notificationSendError": "Could not send notification",
|
||||
"folderCreated": "Folder created",
|
||||
"folderCreatedBody": "\"{{name}}\" created successfully",
|
||||
"fileMoved": "File moved",
|
||||
"fileMovedBody": "File moved successfully",
|
||||
"fileMoveError": "Error moving the file: {{error}}",
|
||||
"fileMoveErrorGeneric": "Error moving the file",
|
||||
"folderMoved": "Folder moved",
|
||||
"folderMovedBody": "Folder moved successfully",
|
||||
"folderMoveError": "Error moving the folder: {{error}}",
|
||||
"folderMoveErrorGeneric": "Error moving the folder",
|
||||
"fileCopied": "File copied",
|
||||
"fileCopiedBody": "File copied successfully",
|
||||
"fileCopyError": "Error copying the file: {{error}}",
|
||||
"fileCopyErrorGeneric": "Error copying the file",
|
||||
"folderRenamed": "Folder renamed",
|
||||
"folderRenamedBody": "Folder renamed to \"{{name}}\"",
|
||||
"fileTrashed": "File moved to trash",
|
||||
"fileTrashedBody": "\"{{name}}\" moved to trash",
|
||||
"fileDeleted": "File deleted",
|
||||
"fileDeletedBody": "\"{{name}}\" deleted successfully",
|
||||
"fileDeleteError": "Error deleting the file",
|
||||
"folderTrashed": "Folder moved to trash",
|
||||
"folderTrashedBody": "\"{{name}}\" moved to trash",
|
||||
"folderDeleted": "Folder deleted",
|
||||
"folderDeletedBody": "\"{{name}}\" deleted successfully",
|
||||
"folderDeleteError": "Error deleting the folder",
|
||||
"itemRestored": "Item restored",
|
||||
"itemRestoredBody": "Item restored successfully",
|
||||
"itemRestoreError": "Error restoring the item",
|
||||
"itemDeleted": "Item deleted",
|
||||
"itemDeletedBody": "Item permanently deleted",
|
||||
"itemDeleteError": "Error deleting the item",
|
||||
"trashEmptied": "Trash emptied",
|
||||
"trashEmptiedBody": "The trash has been emptied successfully",
|
||||
"trashEmptyError": "Error emptying the trash",
|
||||
"cacheCleared": "Cache cleared",
|
||||
"cacheClearedBody": "Search cache cleared successfully",
|
||||
"cacheClearError": "Error clearing search cache",
|
||||
"wopiOpenError": "Could not open the document editor.",
|
||||
"linkCopied": "Link copied",
|
||||
"linkCopiedBody": "Link copied to clipboard",
|
||||
"linkCopyError": "Could not copy link",
|
||||
"notificationSent": "Notification sent",
|
||||
"notificationSentBody": "Notification sent to {{email}}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user