[FEAT] better management of upload files
This commit is contained in:
parent
e9af64405b
commit
6107b95dcd
@ -38,10 +38,14 @@ export type PopUpUploadProgressProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
// current size send or receive
|
// current size send or receive
|
||||||
currentSize: number;
|
currentSize: number;
|
||||||
|
// in case of error this element is set to != undefined
|
||||||
|
error?: string;
|
||||||
// Total size to transfer
|
// Total size to transfer
|
||||||
totalSize: number;
|
totalSize: number;
|
||||||
// index of the file to transfer
|
// index of the file to transfer
|
||||||
index: number;
|
index: number;
|
||||||
|
// When finished the boolean is set to true
|
||||||
|
isFinished: boolean;
|
||||||
// List of element to Transfer
|
// List of element to Transfer
|
||||||
elements: string[];
|
elements: string[];
|
||||||
onAbort: () => void;
|
onAbort: () => void;
|
||||||
@ -49,15 +53,16 @@ export type PopUpUploadProgressProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PopUpUploadProgress = ({
|
export const PopUpUploadProgress = ({
|
||||||
title,
|
|
||||||
currentSize,
|
currentSize,
|
||||||
totalSize,
|
|
||||||
index,
|
|
||||||
elements,
|
elements,
|
||||||
|
error,
|
||||||
|
index,
|
||||||
|
isFinished,
|
||||||
onAbort,
|
onAbort,
|
||||||
onClose,
|
onClose,
|
||||||
|
title,
|
||||||
|
totalSize,
|
||||||
}: PopUpUploadProgressProps) => {
|
}: PopUpUploadProgressProps) => {
|
||||||
const disclosure = useDisclosure();
|
|
||||||
const initialRef = useRef(null);
|
const initialRef = useRef(null);
|
||||||
const finalRef = useRef(null);
|
const finalRef = useRef(null);
|
||||||
return (
|
return (
|
||||||
@ -75,16 +80,46 @@ export const PopUpUploadProgress = ({
|
|||||||
|
|
||||||
<ModalBody pb={6} paddingLeft="18px">
|
<ModalBody pb={6} paddingLeft="18px">
|
||||||
<Flex direction="column" gap="10px">
|
<Flex direction="column" gap="10px">
|
||||||
<Text fontSize="20px" fontWeight="bold">[{index}/{elements.length}] {elements[index]}</Text>
|
{isFinished ? (
|
||||||
<Progress colorScheme='green' hasStripe value={currentSize} isAnimated max={totalSize} height='24px' />
|
<Text fontSize="20px" fontWeight="bold">
|
||||||
<Flex><Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text><Text marginLeft="auto">{totalSize.toLocaleString('fr-FR')} Bytes</Text></Flex>
|
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>
|
</Flex>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
|
{isFinished ? (
|
||||||
Abort
|
<Button onClick={onClose} variant="@success">
|
||||||
</Button>
|
Ok
|
||||||
<Button onClick={onClose}>Close</Button>
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
|
||||||
|
Abort
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
import { useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
Input,
|
Input,
|
||||||
Table,
|
Table,
|
||||||
TableCaption,
|
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Tbody,
|
Tbody,
|
||||||
Td,
|
Td,
|
||||||
Text,
|
Text,
|
||||||
Tfoot,
|
|
||||||
Th,
|
Th,
|
||||||
Thead,
|
Thead,
|
||||||
Tr,
|
Tr,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { LuTrash } from 'react-icons/lu';
|
import { LuTrash } from 'react-icons/lu';
|
||||||
import { MdCloudUpload, MdRemove } from 'react-icons/md';
|
import { MdCloudUpload } from 'react-icons/md';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Album,
|
Album,
|
||||||
@ -31,7 +29,6 @@ import {
|
|||||||
} from '@/back-api';
|
} from '@/back-api';
|
||||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||||
import { TopBar } from '@/components/TopBar/TopBar';
|
import { TopBar } from '@/components/TopBar/TopBar';
|
||||||
import { FormInput } from '@/components/form/FormInput';
|
|
||||||
import { FormSelect } from '@/components/form/FormSelect';
|
import { FormSelect } from '@/components/form/FormSelect';
|
||||||
import { useFormidable } from '@/components/form/Formidable';
|
import { useFormidable } from '@/components/form/Formidable';
|
||||||
import { PopUpUploadProgress } from '@/components/popup/PopUpUploadProgress';
|
import { PopUpUploadProgress } from '@/components/popup/PopUpUploadProgress';
|
||||||
@ -39,6 +36,7 @@ import { useAlbumService, useOrderedAlbums } from '@/service/Album';
|
|||||||
import { useArtistService, useOrderedArtists } from '@/service/Artist';
|
import { useArtistService, useOrderedArtists } from '@/service/Artist';
|
||||||
import { useGenderService, useOrderedGenders } from '@/service/Gender';
|
import { useGenderService, useOrderedGenders } from '@/service/Gender';
|
||||||
import { useServiceContext } from '@/service/ServiceContext';
|
import { useServiceContext } from '@/service/ServiceContext';
|
||||||
|
import { useTrackService } from '@/service/Track';
|
||||||
import { isNullOrUndefined } from '@/utils/validator';
|
import { isNullOrUndefined } from '@/utils/validator';
|
||||||
|
|
||||||
export class ElementList {
|
export class ElementList {
|
||||||
@ -54,21 +52,24 @@ export class FileParsedElement {
|
|||||||
public nameDetected: boolean = false;
|
public nameDetected: boolean = false;
|
||||||
public trackIdDetected: boolean = false;
|
public trackIdDetected: boolean = false;
|
||||||
constructor(
|
constructor(
|
||||||
|
public uniqueId: number,
|
||||||
public file: File,
|
public file: File,
|
||||||
public title: string,
|
public title: string,
|
||||||
public artist?: string,
|
public artist?: string,
|
||||||
public album?: string,
|
public album?: string,
|
||||||
public trackId?: number
|
public trackId?: number
|
||||||
) {
|
) {
|
||||||
|
console.log(`Unique element: ${uniqueId}`);
|
||||||
// nothing to do.
|
// nothing to do.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class FileFailParsedElement {
|
export class FileFailParsedElement {
|
||||||
constructor(
|
constructor(
|
||||||
public id: number,
|
public uniqueId: number,
|
||||||
public file: File,
|
public file: File,
|
||||||
public reason: string
|
public reason: string
|
||||||
) {
|
) {
|
||||||
|
console.log(`Unique element2: ${uniqueId}`);
|
||||||
// nothing to do.
|
// nothing to do.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,6 +99,7 @@ export const AddPage = () => {
|
|||||||
const { store: storeGender } = useGenderService();
|
const { store: storeGender } = useGenderService();
|
||||||
const { store: storeArtist } = useArtistService();
|
const { store: storeArtist } = useArtistService();
|
||||||
const { store: storeAlbum } = useAlbumService();
|
const { store: storeAlbum } = useAlbumService();
|
||||||
|
const { store: storeTrack } = useTrackService();
|
||||||
const { session } = useServiceContext();
|
const { session } = useServiceContext();
|
||||||
|
|
||||||
const form = useFormidable<FormInsertData>({});
|
const form = useFormidable<FormInsertData>({});
|
||||||
@ -160,7 +162,7 @@ export const AddPage = () => {
|
|||||||
setSuggestedAlbum(undefined);
|
setSuggestedAlbum(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addFileWithMetaData = (file: File) => {
|
const addFileWithMetaData = (file: File, id: number) => {
|
||||||
// parsedElement: FileParsedElement[] = [];
|
// parsedElement: FileParsedElement[] = [];
|
||||||
let artist: string | undefined = undefined;
|
let artist: string | undefined = undefined;
|
||||||
let album: string | undefined = undefined;
|
let album: string | undefined = undefined;
|
||||||
@ -199,7 +201,14 @@ export const AddPage = () => {
|
|||||||
}
|
}
|
||||||
// remove extension
|
// remove extension
|
||||||
title = title.replace(new RegExp('\\.(webm|WEBM|Webm)'), '');
|
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)}`);
|
console.log(`==>${JSON.stringify(tmp)}`);
|
||||||
// add it in the list.
|
// add it in the list.
|
||||||
return tmp;
|
return tmp;
|
||||||
@ -218,7 +227,7 @@ export const AddPage = () => {
|
|||||||
const parsedFailedElementTmp: FileFailParsedElement[] = [];
|
const parsedFailedElementTmp: FileFailParsedElement[] = [];
|
||||||
|
|
||||||
for (let iii = 0; iii < value.target.files?.length; iii++) {
|
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:
|
// check if all global parameters are generic:
|
||||||
if (parsedElementTmp.length === 0) {
|
if (parsedElementTmp.length === 0) {
|
||||||
@ -311,51 +320,73 @@ export const AddPage = () => {
|
|||||||
const [listValues, setListValues] = useState<string[]>([]);
|
const [listValues, setListValues] = useState<string[]>([]);
|
||||||
const [currentPosition, setCurrentPosition] = useState<number>(0);
|
const [currentPosition, setCurrentPosition] = useState<number>(0);
|
||||||
const [totalPosition, setTotalPosition] = 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 {
|
const progressUpload = useCallback(
|
||||||
setTotalPosition(total);
|
(count: number, total: number) => {
|
||||||
setCurrentPosition(count);
|
setTotalPosition(total);
|
||||||
}
|
setCurrentPosition(count);
|
||||||
|
},
|
||||||
|
[setTotalPosition, setCurrentPosition]
|
||||||
|
);
|
||||||
|
|
||||||
const uploadNext = (): void => {
|
const uploadNext = useCallback(
|
||||||
setIndexUpload((previous) => previous ?? -1 + 1);
|
(index: number = 0): void => {
|
||||||
|
if (parsedElement.length <= index) {
|
||||||
TrackResource.uploadTrack({
|
console.log('end of upload');
|
||||||
restConfig: session.getRestConfig(),
|
setIsFinishedUpload(true);
|
||||||
data: {
|
return;
|
||||||
title: parsedElement[0].title,
|
}
|
||||||
file: parsedElement[0].file,
|
setIndexUpload(index);
|
||||||
albumId: form.values['albumId'] ?? undefined,
|
console.log(
|
||||||
artistId: form.values['artistId'] ?? undefined,
|
`Start upload of file: ${index}: ${parsedElement[index].title}`
|
||||||
genderId: form.values['genderId'] ?? undefined,
|
);
|
||||||
trackId: parsedElement[0].trackId ?? undefined,
|
storeTrack
|
||||||
},
|
.update(
|
||||||
callbacks: {
|
TrackResource.uploadTrack({
|
||||||
progressUpload: progressUpload,
|
restConfig: session.getRestConfig(),
|
||||||
},
|
data: {
|
||||||
})
|
title: parsedElement[index].title,
|
||||||
.then((data: Track) => {
|
file: parsedElement[index].file,
|
||||||
// TODO: add the element in the local base...
|
albumId: form.values['albumId'] ?? undefined,
|
||||||
console.log(`element added: ${JSON.stringify(data, null, 2)}`);
|
artistId: form.values['artistId'] ?? undefined,
|
||||||
})
|
genderId: form.values['genderId'] ?? undefined,
|
||||||
.catch((error: RestErrorResponse) => {
|
trackId: parsedElement[index].trackId ?? undefined,
|
||||||
// TODO: manage error
|
},
|
||||||
console.log(`element error: ${JSON.stringify(error, null, 2)}`);
|
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 => {
|
const sendFile = (): void => {
|
||||||
console.log(`Send file requested ... ${parsedElement.length}`);
|
console.log(`Send file requested ... ${parsedElement.length}`);
|
||||||
|
setUploadError(undefined);
|
||||||
|
setIsFinishedUpload(false);
|
||||||
setListValues(parsedElement.map((element) => element.file.name));
|
setListValues(parsedElement.map((element) => element.file.name));
|
||||||
uploadNext();
|
uploadNext();
|
||||||
};
|
};
|
||||||
|
|
||||||
function onUploadAbort(): void {
|
function onUploadAbort(): void {
|
||||||
//throw new Error('Function not implemented.');
|
setIndexUpload(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OnUploadClose(): void {
|
function OnUploadClose(): void {
|
||||||
//throw new Error('Function not implemented.');
|
setIndexUpload(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addNewGender = (data: string): Promise<Gender> => {
|
const addNewGender = (data: string): Promise<Gender> => {
|
||||||
@ -467,7 +498,7 @@ export const AddPage = () => {
|
|||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{parsedElement.map((data) => (
|
{parsedElement.map((data) => (
|
||||||
<Tr key={data.file.name}>
|
<Tr key={data.uniqueId}>
|
||||||
<Td>
|
<Td>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
@ -591,7 +622,7 @@ export const AddPage = () => {
|
|||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{parsedFailedElement.map((data) => (
|
{parsedFailedElement.map((data) => (
|
||||||
<Tr key={data.file.name}>
|
<Tr key={data.uniqueId}>
|
||||||
<Td>{data.file.name}</Td>
|
<Td>{data.file.name}</Td>
|
||||||
<Td>{data.reason}</Td>
|
<Td>{data.reason}</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
@ -609,8 +640,10 @@ export const AddPage = () => {
|
|||||||
totalSize={totalPosition}
|
totalSize={totalPosition}
|
||||||
index={indexUpload}
|
index={indexUpload}
|
||||||
elements={listValues}
|
elements={listValues}
|
||||||
|
error={uploadError}
|
||||||
onAbort={onUploadAbort}
|
onAbort={onUploadAbort}
|
||||||
onClose={OnUploadClose}
|
onClose={OnUploadClose}
|
||||||
|
isFinished={isFinishedUpload}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
@ -25,7 +25,7 @@ export const SSOPage = () => {
|
|||||||
clearToken();
|
clearToken();
|
||||||
}
|
}
|
||||||
}, [token, setToken, clearToken]);
|
}, [token, setToken, clearToken]);
|
||||||
const delay = isDevelopmentEnvironment() ? 6000 : 2000;
|
const delay = isDevelopmentEnvironment() ? 2000 : 2000;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state === SessionState.CONNECTED) {
|
if (state === SessionState.CONNECTED) {
|
||||||
const destination = data ? b64_to_utf8(data) : '/';
|
const destination = data ? b64_to_utf8(data) : '/';
|
||||||
|
@ -76,6 +76,15 @@ export default defineStyleConfig({
|
|||||||
color: mode('white', 'error.900')(props),
|
color: mode('white', 'error.900')(props),
|
||||||
boxColorFocus: mode('error.900', 'error.500')(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) => ({
|
'@progress': (props) => ({
|
||||||
...customVariant({
|
...customVariant({
|
||||||
|
@ -16,6 +16,7 @@ export type DataStoreType<TYPE> = {
|
|||||||
get: <MODEL>(value: MODEL, key?: string) => TYPE | undefined;
|
get: <MODEL>(value: MODEL, key?: string) => TYPE | undefined;
|
||||||
gets: <MODEL>(value: MODEL[] | undefined, key?: string) => TYPE[];
|
gets: <MODEL>(value: MODEL[] | undefined, key?: string) => TYPE[];
|
||||||
update: (request: Promise<TYPE>, key?: string) => Promise<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;
|
remove: (id: number | string, request: Promise<void>, key?: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,17 +96,24 @@ export const useDataStore = <TYPE>(
|
|||||||
[data]
|
[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(
|
const update = useCallback(
|
||||||
(request: Promise<TYPE>, key?: string): Promise<TYPE> => {
|
(request: Promise<TYPE>, key?: string): Promise<TYPE> => {
|
||||||
const keyValue = key ?? primaryKey;
|
|
||||||
return new Promise((resolve, rejects) => {
|
return new Promise((resolve, rejects) => {
|
||||||
request
|
request
|
||||||
.then((responseData: TYPE) => {
|
.then((responseData: TYPE) => {
|
||||||
const filterData = data.filter(
|
updateRaw(responseData, key);
|
||||||
(localData: TYPE) => localData[keyValue] !== responseData[keyValue]
|
|
||||||
);
|
|
||||||
filterData.push(responseData);
|
|
||||||
setData(filterData);
|
|
||||||
resolve(responseData);
|
resolve(responseData);
|
||||||
})
|
})
|
||||||
.catch((error: RestErrorResponse) => {
|
.catch((error: RestErrorResponse) => {
|
||||||
@ -114,7 +122,7 @@ export const useDataStore = <TYPE>(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[data, setData]
|
[data, updateRaw]
|
||||||
);
|
);
|
||||||
const remove = useCallback(
|
const remove = useCallback(
|
||||||
(id: number | string, request: Promise<void>, key?: string) => {
|
(id: number | string, request: Promise<void>, key?: string) => {
|
||||||
@ -136,5 +144,5 @@ export const useDataStore = <TYPE>(
|
|||||||
[data, setData]
|
[data, setData]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { isLoading, error, data, get, gets, update, remove };
|
return { isLoading, error, data, get, gets, update, updateRaw, remove };
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user