diff --git a/apps/webapp/src/script/components/FileFullscreenModal/FileFullscreenModal.test.tsx b/apps/webapp/src/script/components/FileFullscreenModal/FileFullscreenModal.test.tsx
index 19c4073f0ff..6276ebb2cfc 100644
--- a/apps/webapp/src/script/components/FileFullscreenModal/FileFullscreenModal.test.tsx
+++ b/apps/webapp/src/script/components/FileFullscreenModal/FileFullscreenModal.test.tsx
@@ -48,7 +48,11 @@ jest.mock('./FileLoader/FileLoader', () => ({
}));
jest.mock('./ImageFileView/ImageFileView', () => ({
- ImageFileView: () =>

{
expect(isPreviewableImage({})).toBe(false);
});
});
+
+ describe('getBestPreviewSource', () => {
+ it('returns the original fileUrl for browser-previewable formats (JPEG, PNG, WebP, GIF)', () => {
+ expect(getBestPreviewSource({fileExtension: 'jpg', fileUrl: Maybe.of('original.jpg'), filePreviewUrl: Maybe.of('preview.jpg')})).toStrictEqual(Maybe.just('original.jpg'));
+ expect(getBestPreviewSource({fileExtension: 'png', fileUrl: Maybe.of('original.png'), filePreviewUrl: Maybe.of('preview.jpg')})).toStrictEqual(Maybe.just('original.png'));
+ expect(getBestPreviewSource({fileExtension: 'webp', fileUrl: Maybe.of('original.webp'), filePreviewUrl: Maybe.of('preview.jpg')})).toStrictEqual(Maybe.just('original.webp'));
+ expect(getBestPreviewSource({fileExtension: 'gif', fileUrl: Maybe.of('original.gif'), filePreviewUrl: Maybe.of('preview.jpg')})).toStrictEqual(Maybe.just('original.gif'));
+ });
+
+ it('returns the server-generated preview for HEIC files (not natively decodable by browsers)', () => {
+ expect(getBestPreviewSource({fileExtension: 'heic', fileUrl: Maybe.of('original.heic'), filePreviewUrl: Maybe.of('preview.jpg')})).toStrictEqual(Maybe.just('preview.jpg'));
+ });
+
+ it('falls back to filePreviewUrl when fileUrl is absent for a previewable format', () => {
+ expect(getBestPreviewSource({fileExtension: 'jpg', fileUrl: Maybe.nothing(), filePreviewUrl: Maybe.of('preview.jpg')})).toStrictEqual(Maybe.just('preview.jpg'));
+ });
+
+ it('returns Nothing when both fileUrl and filePreviewUrl are absent', () => {
+ expect(getBestPreviewSource({fileExtension: 'jpg', fileUrl: Maybe.nothing(), filePreviewUrl: Maybe.nothing()})).toStrictEqual(Maybe.nothing());
+ });
+
+ it('can be safely unwrapped to undefined for UI src props when no source exists', () => {
+ const source = getBestPreviewSource({
+ fileExtension: 'jpg',
+ fileUrl: Maybe.nothing(),
+ filePreviewUrl: Maybe.nothing(),
+ }).unwrapOr(undefined);
+
+ expect(source).toBeUndefined();
+ });
+
+ it('can be safely unwrapped to a string for UI src props when a source exists', () => {
+ const source = getBestPreviewSource({
+ fileExtension: 'jpg',
+ fileUrl: Maybe.of('original.jpg'),
+ filePreviewUrl: Maybe.nothing(),
+ }).unwrapOr(undefined);
+
+ expect(source).toBe('original.jpg');
+ });
+ });
});
diff --git a/apps/webapp/src/script/util/ImageUtil.ts b/apps/webapp/src/script/util/ImageUtil.ts
index e7d3cfbf271..2a960b71a44 100644
--- a/apps/webapp/src/script/util/ImageUtil.ts
+++ b/apps/webapp/src/script/util/ImageUtil.ts
@@ -17,6 +17,8 @@
*
*/
+import {Maybe} from 'true-myth';
+
export const stripImageExifData = async (image: Blob): Promise
=> {
const url = URL.createObjectURL(image);
try {
@@ -179,3 +181,19 @@ export const isPreviewableImage = ({
return !!normalizedExtension && PREVIEWABLE_IMAGE_EXTENSIONS.has(normalizedExtension);
};
+
+type GetBestPreviewSourceOptions = {
+ readonly fileExtension: string;
+ readonly fileUrl: Maybe;
+ readonly filePreviewUrl: Maybe;
+};
+
+export function getBestPreviewSource(options: GetBestPreviewSourceOptions): Maybe {
+ const {fileExtension, fileUrl, filePreviewUrl} = options;
+
+ if (!isPreviewableImage({extension: fileExtension})) {
+ return filePreviewUrl;
+ }
+
+ return fileUrl.or(filePreviewUrl);
+}