From 04be495351db350a8f6108d11820b14c3c0f6a86 Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Thu, 19 Mar 2026 11:27:46 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84(custom-background)=20add=20upload?= =?UTF-8?q?=20indicator=20with=20preview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When uploading an image, depending on the available network there might be a bit of wait while the image is being uploaded. In this commit we add a preview (grayscale + spinner) to have UI feedback that the upload is in progress. --- CHANGELOG.md | 10 ++-- .../src/features/files/api/createFile.ts | 23 +++++---- .../effects/EffectsConfiguration.tsx | 47 +++++++++++++++++++ src/frontend/src/locales/de/rooms.json | 1 + src/frontend/src/locales/en/rooms.json | 1 + src/frontend/src/locales/fr/rooms.json | 1 + src/frontend/src/locales/nl/rooms.json | 1 + 7 files changed, 68 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c382f0ac..22010024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,15 +13,13 @@ and this project adheres to - ♿️(frontend) fix sidepanel accessibility aria-label #1182 - ♿️(frontend) fix more tools heading hierarchy #1181 - ♿️(fronted) improve button descriptions for More tools actions #1184 - -### Changed - -- 💄(spinner) enforce spinner height +- 💄(spinner) enforce spinner height #1183 +- 💄(custom-background) add upload indicator with preview #1183 ### Fixed -- 🐛(frontend) disable personal custom background while deleting -- 🐛(frontend) auto-select new custom background when not logged in +- 🐛(frontend) disable personal custom background while deleting #1183 +- 🐛(frontend) auto-select new custom background when not logged in #1183 ## [1.11.0] - 2026-03-19 diff --git a/src/frontend/src/features/files/api/createFile.ts b/src/frontend/src/features/files/api/createFile.ts index 3ffc6d39..ffccdac2 100644 --- a/src/frontend/src/features/files/api/createFile.ts +++ b/src/frontend/src/features/files/api/createFile.ts @@ -1,7 +1,8 @@ import { fetchApi } from '@/api/fetchApi' -import { useMutation, useQueryClient } from '@tanstack/react-query' +import { useMutation } from '@tanstack/react-query' import { ApiFileItem } from '@/features/files/api/types.ts' import { keys } from '@/api/queryKeys.ts' +import { queryClient } from '@/api/queryClient.ts' /** * Upload a file, using XHR so we can report on progress through a handler. @@ -71,20 +72,22 @@ export const createFile = async ({ } const policy = res.policy await uploadFile(policy, file, onProgress) - return await fetchApi(`/files/${res.id}/upload-ended/`, { - method: 'POST', + const createdFile = await fetchApi( + `/files/${res.id}/upload-ended/`, + { + method: 'POST', + } + ) + + // We invalidate the files query to make sure the new file is immediately available. + await queryClient.invalidateQueries({ + queryKey: [keys.files], }) + return createdFile } export const useCreateFile = () => { - const queryClient = useQueryClient() - return useMutation({ mutationFn: createFile, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: [keys.files], - }) - }, }) } diff --git a/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx b/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx index 115d119c..d230f305 100644 --- a/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx +++ b/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx @@ -32,6 +32,7 @@ import { ApiFileItem } from '@/features/files/api/types.ts' import { useConfig } from '@/api/useConfig.ts' import { usePersistentUserChoices } from '@/features/rooms/livekit/hooks/usePersistentUserChoices.ts' import { proxy, useSnapshot } from 'valtio' +import { Spinner } from '@/primitives/Spinner.tsx' enum BlurRadius { NONE = 0, @@ -268,6 +269,15 @@ export const EffectsConfiguration = ({ 'file_too_large' | 'invalid_file_type' | null >(null) const createFileMutation = useCreateFile() + const fileBeingUploadedObjectUrlRef = useRef(null) + useEffect(() => { + return () => { + if (fileBeingUploadedObjectUrlRef.current) { + URL.revokeObjectURL(fileBeingUploadedObjectUrlRef.current) + fileBeingUploadedObjectUrlRef.current = null + } + } + }, []) const deleteFileMutation = useDeleteFile() const filesQ = useListMyFiles(listFilesQueryParams) const hasReachedMaxNbBackgrounds = @@ -335,6 +345,11 @@ export const EffectsConfiguration = ({ } else { // Otherwise we create the file in the backend and automatically select it // when it's uploaded. + // We create a local version so that we can quickly display it in the frontend. + if (fileBeingUploadedObjectUrlRef.current) { + URL.revokeObjectURL(fileBeingUploadedObjectUrlRef.current) + } + fileBeingUploadedObjectUrlRef.current = URL.createObjectURL(file) createFileMutation.mutate( { file, @@ -676,6 +691,38 @@ export const EffectsConfiguration = ({ flexWrap: 'wrap', })} > + {createFileMutation.isPending && + fileBeingUploadedObjectUrlRef.current && ( + +
+ +
+ +
+
+
+ )} {canUploadBackground && processorOptions.remoteCustomVirtualBackgrounds.map( (option) => ( diff --git a/src/frontend/src/locales/de/rooms.json b/src/frontend/src/locales/de/rooms.json index 5f64ac21..9aff22bf 100644 --- a/src/frontend/src/locales/de/rooms.json +++ b/src/frontend/src/locales/de/rooms.json @@ -279,6 +279,7 @@ "notLoggedInWarning": "Du bist nicht angemeldet, der persönliche Hintergrund wird nicht von einem Meeting zum anderen gespeichert.", "warningUploadDisabled": "Persönliche Hintergründe werden derzeit nicht von einem Meeting zum anderen gespeichert.", "uploadLimitReached": "Du kannst keine weiteren persönlichen Hintergründe hinzufügen.", + "uploadInProgress": "Datei wird hochgeladen…", "errors": { "close": "Schließen", "file_too_large": { diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json index 5f7c4c57..85c3efef 100644 --- a/src/frontend/src/locales/en/rooms.json +++ b/src/frontend/src/locales/en/rooms.json @@ -279,6 +279,7 @@ "notLoggedInWarning": "You are not logged-in, personal backgrounds won't be saved from one meeting to the other.", "warningUploadDisabled": "Personal backgrounds are currently not saved from one meeting to the other.", "uploadLimitReached": "You cannot upload more personal backgrounds.", + "uploadInProgress": "Image is being uploaded…", "errors": { "close": "Close", "file_too_large": { diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json index 7227932f..02f0a21f 100644 --- a/src/frontend/src/locales/fr/rooms.json +++ b/src/frontend/src/locales/fr/rooms.json @@ -279,6 +279,7 @@ "notLoggedInWarning": "Vous n'êtes pas connecté, l'arrière-plan personnel ne sera pas sauvegardé d'une réunion à l'autre.", "warningUploadDisabled": "Les arrière-plans personnels ne sont actuellement pas sauvegardés d'une réunion à l'autre.", "uploadLimitReached": "Vous ne pouvez pas ajouter plus d'arrière-plans personnels.", + "uploadInProgress": "Image en cours d'envoi…", "errors": { "close": "Fermer", "file_too_large": { diff --git a/src/frontend/src/locales/nl/rooms.json b/src/frontend/src/locales/nl/rooms.json index 09ed6d59..b6364333 100644 --- a/src/frontend/src/locales/nl/rooms.json +++ b/src/frontend/src/locales/nl/rooms.json @@ -279,6 +279,7 @@ "notLoggedInWarning": "U bent niet ingelogd, persoonlijke achtergronden worden niet opgeslagen van de ene vergadering naar de andere.", "warningUploadDisabled": "Persoonlijke achtergronden worden momenteel niet opgeslagen van de ene vergadering naar de andere.", "uploadLimitReached": "U kunt geen persoonlijke achtergronden meer uploaden.", + "uploadInProgress": "Afbeelding wordt geüpload…", "errors": { "close": "Sluiten", "file_too_large": {