[FEAT] better management of upload files

This commit is contained in:
Edouard DUPIN 2024-09-20 22:19:10 +02:00
parent e9af64405b
commit 6107b95dcd
5 changed files with 148 additions and 63 deletions

View File

@ -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 = ({
<ModalBody pb={6} paddingLeft="18px">
<Flex direction="column" gap="10px">
<Text fontSize="20px" fontWeight="bold">[{index}/{elements.length}] {elements[index]}</Text>
<Progress colorScheme='green' hasStripe value={currentSize} isAnimated max={totalSize} height='24px' />
<Flex><Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text><Text marginLeft="auto">{totalSize.toLocaleString('fr-FR')} Bytes</Text></Flex>
{isFinished ? (
<Text fontSize="20px" fontWeight="bold">
All {elements.length} element have been sent
</Text>
) : (
<Text fontSize="20px" fontWeight="bold">
[{index + 1}/{elements.length}] {elements[index]}
</Text>
)}
<Progress
colorScheme="green"
hasStripe
value={currentSize}
isAnimated
max={totalSize}
height="24px"
/>
<Flex>
<Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text>
<Text marginLeft="auto">
{totalSize.toLocaleString('fr-FR')} Bytes
</Text>
</Flex>
{error && (
<Text fontWeight="bold" color="darkred">
{error}
</Text>
)}
</Flex>
</ModalBody>
<ModalFooter>
<Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
Abort
</Button>
<Button onClick={onClose}>Close</Button>
{isFinished ? (
<Button onClick={onClose} variant="@success">
Ok
</Button>
) : (
<Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
Abort
</Button>
)}
</ModalFooter>
</ModalContent>
</Modal>

View File

@ -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<FormInsertData>({});
@ -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<string[]>([]);
const [currentPosition, setCurrentPosition] = useState<number>(0);
const [totalPosition, setTotalPosition] = useState<number>(0);
const [uploadError, setUploadError] = useState<string | undefined>(undefined);
const [isFinishedUpload, setIsFinishedUpload] = useState<boolean>(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<Gender> => {
@ -467,7 +498,7 @@ export const AddPage = () => {
</Thead>
<Tbody>
{parsedElement.map((data) => (
<Tr key={data.file.name}>
<Tr key={data.uniqueId}>
<Td>
<Input
type="number"
@ -591,7 +622,7 @@ export const AddPage = () => {
</Thead>
<Tbody>
{parsedFailedElement.map((data) => (
<Tr key={data.file.name}>
<Tr key={data.uniqueId}>
<Td>{data.file.name}</Td>
<Td>{data.reason}</Td>
</Tr>
@ -609,8 +640,10 @@ export const AddPage = () => {
totalSize={totalPosition}
index={indexUpload}
elements={listValues}
error={uploadError}
onAbort={onUploadAbort}
onClose={OnUploadClose}
isFinished={isFinishedUpload}
/>
)}
</PageLayout>

View File

@ -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) : '/';

View File

@ -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({

View File

@ -16,6 +16,7 @@ export type DataStoreType<TYPE> = {
get: <MODEL>(value: MODEL, key?: string) => TYPE | undefined;
gets: <MODEL>(value: MODEL[] | undefined, key?: string) => TYPE[];
update: (request: Promise<TYPE>, key?: string) => Promise<TYPE>;
updateRaw: (data: TYPE, key?: string) => void;
remove: (id: number | string, request: Promise<void>, key?: string) => void;
};
@ -95,17 +96,24 @@ export const useDataStore = <TYPE>(
[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<TYPE>, key?: string): Promise<TYPE> => {
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 = <TYPE>(
});
});
},
[data, setData]
[data, updateRaw]
);
const remove = useCallback(
(id: number | string, request: Promise<void>, key?: string) => {
@ -136,5 +144,5 @@ export const useDataStore = <TYPE>(
[data, setData]
);
return { isLoading, error, data, get, gets, update, remove };
return { isLoading, error, data, get, gets, update, updateRaw, remove };
};