💄(custom-background) add upload indicator with preview
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.
This commit is contained in:
+4
-6
@@ -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
|
||||
|
||||
|
||||
@@ -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<ApiFileItem>(`/files/${res.id}/upload-ended/`, {
|
||||
method: 'POST',
|
||||
const createdFile = await fetchApi<ApiFileItem>(
|
||||
`/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],
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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<string | null>(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 && (
|
||||
<VisualOnlyTooltip
|
||||
tooltip={t('virtual.personal.uploadInProgress')}
|
||||
>
|
||||
<div className={css({ position: 'relative' })}>
|
||||
<ToggleButton
|
||||
variant="bigSquare"
|
||||
aria-label={t('virtual.personal.uploadInProgress')}
|
||||
isDisabled={true}
|
||||
data-attr={`virtual-upload-in-progress`}
|
||||
className={css({
|
||||
bgSize: 'cover',
|
||||
filter: 'grayscale(1)',
|
||||
})}
|
||||
style={{
|
||||
backgroundImage: `url(${fileBeingUploadedObjectUrlRef.current})`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
})}
|
||||
>
|
||||
<Spinner size={24} />
|
||||
</div>
|
||||
</div>
|
||||
</VisualOnlyTooltip>
|
||||
)}
|
||||
{canUploadBackground &&
|
||||
processorOptions.remoteCustomVirtualBackgrounds.map(
|
||||
(option) => (
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user