From 6107b95dcdbd3d3614a51c6638d1e81b67ae3712 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Fri, 20 Sep 2024 22:19:10 +0200 Subject: [PATCH] [FEAT] better management of upload files --- .../components/popup/PopUpUploadProgress.tsx | 57 +++++++-- front/src/scene/home/AddPage.tsx | 119 +++++++++++------- front/src/scene/sso/SSOPage.tsx | 2 +- front/src/theme/components/button.ts | 9 ++ front/src/utils/data-store.ts | 24 ++-- 5 files changed, 148 insertions(+), 63 deletions(-) diff --git a/front/src/components/popup/PopUpUploadProgress.tsx b/front/src/components/popup/PopUpUploadProgress.tsx index 462123b..5231fad 100644 --- a/front/src/components/popup/PopUpUploadProgress.tsx +++ b/front/src/components/popup/PopUpUploadProgress.tsx @@ -38,10 +38,14 @@ export type PopUpUploadProgressProps = { title: string; // current size send or receive currentSize: number; + // in case of error this element is set to != undefined + error?: string; // Total size to transfer totalSize: number; // index of the file to transfer index: number; + // When finished the boolean is set to true + isFinished: boolean; // List of element to Transfer elements: string[]; onAbort: () => void; @@ -49,15 +53,16 @@ export type PopUpUploadProgressProps = { }; export const PopUpUploadProgress = ({ - title, currentSize, - totalSize, - index, elements, + error, + index, + isFinished, onAbort, onClose, + title, + totalSize, }: PopUpUploadProgressProps) => { - const disclosure = useDisclosure(); const initialRef = useRef(null); const finalRef = useRef(null); return ( @@ -75,16 +80,46 @@ export const PopUpUploadProgress = ({ - [{index}/{elements.length}] {elements[index]} - - {currentSize.toLocaleString('fr-FR')} Bytes{totalSize.toLocaleString('fr-FR')} Bytes + {isFinished ? ( + + All {elements.length} element have been sent + + ) : ( + + [{index + 1}/{elements.length}] {elements[index]} + + )} + + + {currentSize.toLocaleString('fr-FR')} Bytes + + {totalSize.toLocaleString('fr-FR')} Bytes + + + {error && ( + + {error} + + )} - - + {isFinished ? ( + + ) : ( + + )} diff --git a/front/src/scene/home/AddPage.tsx b/front/src/scene/home/AddPage.tsx index e8781d6..137130a 100644 --- a/front/src/scene/home/AddPage.tsx +++ b/front/src/scene/home/AddPage.tsx @@ -1,22 +1,20 @@ -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { Button, Flex, Input, Table, - TableCaption, TableContainer, Tbody, Td, Text, - Tfoot, Th, Thead, Tr, } from '@chakra-ui/react'; import { LuTrash } from 'react-icons/lu'; -import { MdCloudUpload, MdRemove } from 'react-icons/md'; +import { MdCloudUpload } from 'react-icons/md'; import { Album, @@ -31,7 +29,6 @@ import { } from '@/back-api'; import { PageLayout } from '@/components/Layout/PageLayout'; import { TopBar } from '@/components/TopBar/TopBar'; -import { FormInput } from '@/components/form/FormInput'; import { FormSelect } from '@/components/form/FormSelect'; import { useFormidable } from '@/components/form/Formidable'; import { PopUpUploadProgress } from '@/components/popup/PopUpUploadProgress'; @@ -39,6 +36,7 @@ import { useAlbumService, useOrderedAlbums } from '@/service/Album'; import { useArtistService, useOrderedArtists } from '@/service/Artist'; import { useGenderService, useOrderedGenders } from '@/service/Gender'; import { useServiceContext } from '@/service/ServiceContext'; +import { useTrackService } from '@/service/Track'; import { isNullOrUndefined } from '@/utils/validator'; export class ElementList { @@ -54,21 +52,24 @@ export class FileParsedElement { public nameDetected: boolean = false; public trackIdDetected: boolean = false; constructor( + public uniqueId: number, public file: File, public title: string, public artist?: string, public album?: string, public trackId?: number ) { + console.log(`Unique element: ${uniqueId}`); // nothing to do. } } export class FileFailParsedElement { constructor( - public id: number, + public uniqueId: number, public file: File, public reason: string ) { + console.log(`Unique element2: ${uniqueId}`); // nothing to do. } } @@ -98,6 +99,7 @@ export const AddPage = () => { const { store: storeGender } = useGenderService(); const { store: storeArtist } = useArtistService(); const { store: storeAlbum } = useAlbumService(); + const { store: storeTrack } = useTrackService(); const { session } = useServiceContext(); const form = useFormidable({}); @@ -160,7 +162,7 @@ export const AddPage = () => { setSuggestedAlbum(undefined); }; - const addFileWithMetaData = (file: File) => { + const addFileWithMetaData = (file: File, id: number) => { // parsedElement: FileParsedElement[] = []; let artist: string | undefined = undefined; let album: string | undefined = undefined; @@ -199,7 +201,14 @@ export const AddPage = () => { } // remove extension title = title.replace(new RegExp('\\.(webm|WEBM|Webm)'), ''); - let tmp = new FileParsedElement(file, title, artist, album, trackIdNumber); + let tmp = new FileParsedElement( + id, + file, + title, + artist, + album, + trackIdNumber + ); console.log(`==>${JSON.stringify(tmp)}`); // add it in the list. return tmp; @@ -218,7 +227,7 @@ export const AddPage = () => { const parsedFailedElementTmp: FileFailParsedElement[] = []; for (let iii = 0; iii < value.target.files?.length; iii++) { - parsedElementTmp.push(addFileWithMetaData(value.target.files[iii])); + parsedElementTmp.push(addFileWithMetaData(value.target.files[iii], iii)); } // check if all global parameters are generic: if (parsedElementTmp.length === 0) { @@ -311,51 +320,73 @@ export const AddPage = () => { const [listValues, setListValues] = useState([]); const [currentPosition, setCurrentPosition] = useState(0); const [totalPosition, setTotalPosition] = useState(0); + const [uploadError, setUploadError] = useState(undefined); + const [isFinishedUpload, setIsFinishedUpload] = useState(false); - function progressUpload(count: number, total: number): void { - setTotalPosition(total); - setCurrentPosition(count); - } + const progressUpload = useCallback( + (count: number, total: number) => { + setTotalPosition(total); + setCurrentPosition(count); + }, + [setTotalPosition, setCurrentPosition] + ); - const uploadNext = (): void => { - setIndexUpload((previous) => previous ?? -1 + 1); - - TrackResource.uploadTrack({ - restConfig: session.getRestConfig(), - data: { - title: parsedElement[0].title, - file: parsedElement[0].file, - albumId: form.values['albumId'] ?? undefined, - artistId: form.values['artistId'] ?? undefined, - genderId: form.values['genderId'] ?? undefined, - trackId: parsedElement[0].trackId ?? undefined, - }, - callbacks: { - progressUpload: progressUpload, - }, - }) - .then((data: Track) => { - // TODO: add the element in the local base... - console.log(`element added: ${JSON.stringify(data, null, 2)}`); - }) - .catch((error: RestErrorResponse) => { - // TODO: manage error - console.log(`element error: ${JSON.stringify(error, null, 2)}`); - }); - }; + const uploadNext = useCallback( + (index: number = 0): void => { + if (parsedElement.length <= index) { + console.log('end of upload'); + setIsFinishedUpload(true); + return; + } + setIndexUpload(index); + console.log( + `Start upload of file: ${index}: ${parsedElement[index].title}` + ); + storeTrack + .update( + TrackResource.uploadTrack({ + restConfig: session.getRestConfig(), + data: { + title: parsedElement[index].title, + file: parsedElement[index].file, + albumId: form.values['albumId'] ?? undefined, + artistId: form.values['artistId'] ?? undefined, + genderId: form.values['genderId'] ?? undefined, + trackId: parsedElement[index].trackId ?? undefined, + }, + callbacks: { + progressUpload: progressUpload, + }, + }) + ) + .then((data: Track) => { + // element sended good + // Send next ... + uploadNext(index + 1); + }) + .catch((error: RestErrorResponse) => { + // TODO: manage error + console.log(`element error: ${JSON.stringify(error, null, 2)}`); + setUploadError(JSON.stringify(error, null, 2)); + }); + }, + [setUploadError, setIndexUpload, storeTrack, parsedElement] + ); const sendFile = (): void => { console.log(`Send file requested ... ${parsedElement.length}`); + setUploadError(undefined); + setIsFinishedUpload(false); setListValues(parsedElement.map((element) => element.file.name)); uploadNext(); }; function onUploadAbort(): void { - //throw new Error('Function not implemented.'); + setIndexUpload(undefined); } function OnUploadClose(): void { - //throw new Error('Function not implemented.'); + setIndexUpload(undefined); } const addNewGender = (data: string): Promise => { @@ -467,7 +498,7 @@ export const AddPage = () => { {parsedElement.map((data) => ( - + { {parsedFailedElement.map((data) => ( - + {data.file.name} {data.reason} @@ -609,8 +640,10 @@ export const AddPage = () => { totalSize={totalPosition} index={indexUpload} elements={listValues} + error={uploadError} onAbort={onUploadAbort} onClose={OnUploadClose} + isFinished={isFinishedUpload} /> )} diff --git a/front/src/scene/sso/SSOPage.tsx b/front/src/scene/sso/SSOPage.tsx index 5d12c6d..0726650 100644 --- a/front/src/scene/sso/SSOPage.tsx +++ b/front/src/scene/sso/SSOPage.tsx @@ -25,7 +25,7 @@ export const SSOPage = () => { clearToken(); } }, [token, setToken, clearToken]); - const delay = isDevelopmentEnvironment() ? 6000 : 2000; + const delay = isDevelopmentEnvironment() ? 2000 : 2000; useEffect(() => { if (state === SessionState.CONNECTED) { const destination = data ? b64_to_utf8(data) : '/'; diff --git a/front/src/theme/components/button.ts b/front/src/theme/components/button.ts index de07089..4a4edd3 100644 --- a/front/src/theme/components/button.ts +++ b/front/src/theme/components/button.ts @@ -76,6 +76,15 @@ export default defineStyleConfig({ color: mode('white', 'error.900')(props), boxColorFocus: mode('error.900', 'error.500')(props), }), + '@success': (props) => + customVariant({ + theme: props.theme, + bg: mode('green.300', 'green.300')(props), + bgHover: mode('green.400', 'green.400')(props), + bgActive: mode('green.500', 'green.400')(props), + color: mode('white', 'green.900')(props), + boxColorFocus: mode('green.900', 'green.400')(props), + }), '@progress': (props) => ({ ...customVariant({ diff --git a/front/src/utils/data-store.ts b/front/src/utils/data-store.ts index 8206edd..6e78e90 100644 --- a/front/src/utils/data-store.ts +++ b/front/src/utils/data-store.ts @@ -16,6 +16,7 @@ export type DataStoreType = { get: (value: MODEL, key?: string) => TYPE | undefined; gets: (value: MODEL[] | undefined, key?: string) => TYPE[]; update: (request: Promise, key?: string) => Promise; + updateRaw: (data: TYPE, key?: string) => void; remove: (id: number | string, request: Promise, key?: string) => void; }; @@ -95,17 +96,24 @@ export const useDataStore = ( [data] ); + const updateRaw = useCallback( + (responseData: TYPE, key?: string): void => { + const keyValue = key ?? primaryKey; + const filterData = data.filter( + (localData: TYPE) => localData[keyValue] !== responseData[keyValue] + ); + filterData.push(responseData); + setData(filterData); + }, + [data, setData] + ); + const update = useCallback( (request: Promise, key?: string): Promise => { - const keyValue = key ?? primaryKey; return new Promise((resolve, rejects) => { request .then((responseData: TYPE) => { - const filterData = data.filter( - (localData: TYPE) => localData[keyValue] !== responseData[keyValue] - ); - filterData.push(responseData); - setData(filterData); + updateRaw(responseData, key); resolve(responseData); }) .catch((error: RestErrorResponse) => { @@ -114,7 +122,7 @@ export const useDataStore = ( }); }); }, - [data, setData] + [data, updateRaw] ); const remove = useCallback( (id: number | string, request: Promise, key?: string) => { @@ -136,5 +144,5 @@ export const useDataStore = ( [data, setData] ); - return { isLoading, error, data, get, gets, update, remove }; + return { isLoading, error, data, get, gets, update, updateRaw, remove }; };