DAM: Allow replacing files with same category instead of same mimetype - #6079
DAM: Allow replacing files with same category instead of same mimetype#6079VPS-julia wants to merge 7 commits into
Conversation
Replacing a file required the exact same mimetype, so a JPEG could not be replaced by a WebP even though both are pixel images. Editors have to delete and re-upload the file instead, which breaks its URL and usages. Replacement is now allowed within a category (pixel image, SVG image, audio, video, document), with SVG images kept separate from pixel images. The extension of the file's name is adjusted to match the new file so that name and mimetype stay consistent; the URL is built from the name without its extension and therefore stays unchanged. A name collision in the same folder is rejected with a validation error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
Replacing a PNG with a JPG renames the file to match the new extension. If that name is already taken, the editor only saw "An error occurred while replacing the file. Please try again later.", which neither explains the cause nor how to resolve it. The API now throws a dedicated CometFileNameAlreadyExistsException, which the Admin recognizes by its class name — the same mechanism already used for CometImageResolutionException during upload — and translates into a message naming both file extensions and the ways to resolve the conflict. Errors from the API were previously swallowed by replaceById, which parsed error responses as successful ones, so the dialog only ever appeared for network failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
Suggesting the editor pick a file of the original type contradicts their intent, since they deliberately chose a different format. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
The dialog fell back to the "Unknown error" error-type label, which says nothing about what failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
| if (fileWithSameName !== null && fileWithSameName.id !== fileToReplace.id) { | ||
| throw new CometFileNameAlreadyExistsException( | ||
| `File cannot be replaced because a file named '${name}' already exists in ${ | ||
| fileToReplace.folder ? `folder '${fileToReplace.folder.name}'` : "the root folder" | ||
| }`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Alternatively, we could add a "-2" suffix like we do in the page tree. @thomasdax98 what do you think?
There was a problem hiding this comment.
I think this would be easier and more user-friendly. We could show a snackbar in the admin to inform the user of the name-change. We also have to update the form data.
At first I thought this was a bad idea because the file/image url changes but it changes anyway because of the content hash in the URL. Also we need it to change as a cache buster. So the URL is no argument.
The fallback category matches every mimetype that is not an image, audio, or video file, not only documents. Name it "other" to reflect that a separate "document" category can be introduced later if a use case arises. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
The stored file name is already slugified, so re-slugifying its base name when only the extension changes is unnecessary; swapping the extension on the existing base name is enough. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
| | `svgImage` | SVG | | ||
| | `audio` | MP3, OGG, WAV | | ||
| | `video` | MP4, WebM, QuickTime | | ||
| | `other` | PDF, DOCX, VTT, ZIP | |
There was a problem hiding this comment.
I don't think we should allow replacing within the "other" category as usages could vary. For example VTT files are subtitles for videos that can be attached to a video, so it shouldn't be possible to replace it with, e.g., a PDF.
| pixelImage: filterByCategory(allAcceptedMimeTypes, "pixelImage"), | ||
| audio: filterByCategory(allAcceptedMimeTypes, "audio"), | ||
| video: filterByCategory(allAcceptedMimeTypes, "video"), | ||
| other: filterByCategory(allAcceptedMimeTypes, "other"), |
There was a problem hiding this comment.
It's somewhat strange that "other" would include "pdf" and "captions", although they have their own fields within this object. But I'm not quite sure how to handle it. Maybe include "pdf" and "captions" as categories within getDamFileCategory?
Also the rename from document to other breaks the public API, so we need to keep document (at least as an alias)
| if (fileWithSameName !== null && fileWithSameName.id !== fileToReplace.id) { | ||
| throw new CometFileNameAlreadyExistsException( | ||
| `File cannot be replaced because a file named '${name}' already exists in ${ | ||
| fileToReplace.folder ? `folder '${fileToReplace.folder.name}'` : "the root folder" | ||
| }`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
I think this would be easier and more user-friendly. We could show a snackbar in the admin to inform the user of the name-change. We also have to update the form data.
At first I thought this was a bad idea because the file/image url changes but it changes anyway because of the content hash in the URL. Also we need it to change as a cache buster. So the URL is no argument.
Task: https://vivid-planet.atlassian.net/browse/COM-3100
Summary
This change allows DAM files to be replaced with files of the same category rather than requiring an exact mimetype match. For example, a JPEG image can now be replaced with a WebP, and an MP3 audio file can be replaced with an OGG file, as long as they belong to the same category.
Key Changes
New file categorization utility: Created
getDamFileCategory()helper function in both API and admin packages that categorizes mimetypes into five categories:pixelImage: JPEG, PNG, WebP, GIF, etc.svgImage: SVG (kept separate from pixel images)audio: MP3, OGG, WAV, etc.video: MP4, WebM, QuickTime, etc.document: PDF, DOCX, VTT, ZIP, and other filesUpdated file replacement logic: Modified
FilesService.replaceFile()to:CometValidationExceptioninstead of genericErrorfor better error handlingphoto.jpg→photo.webp)Updated admin UI:
ReplaceFileButtonto accept all mimetypes in the same category as the current fileRefactored mime type filtering: Simplified
useDamAcceptedMimeTypes()to use the newgetDamFileCategory()helper instead of individual type-checking functionsAdded comprehensive tests: Created test suites for the new
getDamFileCategory()function in both packagesNotable Implementation Details
getDamFileCategory()function is duplicated between API and admin packages with a prominent comment warning about keeping them in syncScreenshots