karusic/front/src/scene/home/AddPage.tsx

639 lines
20 KiB
TypeScript

import { useCallback, useState } from 'react';
import {
Button,
Flex,
Input,
Table,
Text,
} from '@chakra-ui/react';
import { LuTrash } from 'react-icons/lu';
import { MdCloudUpload } from 'react-icons/md';
import {
Album,
AlbumResource,
Artist,
ArtistResource,
Gender,
GenderResource,
RestErrorResponse,
Track,
TrackResource,
} from '@/back-api';
import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar';
import { FormSelect } from '@/components/form/FormSelect';
import { useFormidable } from '@/components/form/Formidable';
import { PopUpUploadProgress } from '@/components/popup/PopUpUploadProgress';
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 {
constructor(
public id?: number,
public label?: string
) {
// nothing to do.
}
}
export class FileParsedElement {
public isSended: boolean = false;
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 uniqueId: number,
public file: File,
public reason: string
) {
console.log(`Unique element2: ${uniqueId}`);
// nothing to do.
}
}
type FormInsertData = {
genderId?: number;
artistId?: number;
titleAlbum?: string;
};
export const AddPage = () => {
const [parsedElement, setParsedElement] = useState<FileParsedElement[]>([]);
const [parsedFailedElement, setParsedFailedElement] = useState<
FileFailParsedElement[] | undefined
>(undefined);
const [needSend, setNeedSend] = useState<boolean>(false);
// list of all files already registered in the bdd to compare with the current list of files.
const [listFileInBdd, setListFileInBdd] = useState<any[] | undefined>(
undefined
);
const { dataGenders } = useOrderedGenders();
const { dataArtist } = useOrderedArtists();
const { dataAlbums } = useOrderedAlbums();
const { store: storeGender } = useGenderService();
const { store: storeArtist } = useArtistService();
const { store: storeAlbum } = useAlbumService();
const { store: storeTrack } = useTrackService();
const { session } = useServiceContext();
const form = useFormidable<FormInsertData>({});
const updateNeedSend = () => {
if (parsedElement.length === 0) {
setNeedSend(false);
return;
}
let tmp = true;
for (let iii = 0; iii < parsedElement.length; iii++) {
if (
isNullOrUndefined(parsedElement[iii].title) ||
parsedElement[iii].title === ''
) {
tmp = false;
}
}
setNeedSend(tmp);
};
const onTitle = (data: FileParsedElement, value: any): void => {
data.title = value;
setParsedElement([...parsedElement]);
updateNeedSend();
};
const removeElementFromList = (data: FileParsedElement, value: any): void => {
const parsedElementTmp = [...parsedElement];
for (let iii = 0; iii < parsedElementTmp.length; iii++) {
if (parsedElementTmp[iii] === data) {
parsedElementTmp.splice(iii, 1);
break;
}
}
setParsedElement(parsedElementTmp);
setParsedFailedElement((previous) => [
...(previous ?? []),
new FileFailParsedElement(
previous?.length ?? 0,
data.file,
'Removed by user.'
),
]);
updateNeedSend();
};
const onTrackId = (data: FileParsedElement, value: any): void => {
data.trackId = value;
setParsedElement([...parsedElement]);
updateNeedSend();
};
const clearData = () => {
setParsedElement([]);
setParsedFailedElement(undefined);
setListFileInBdd(undefined);
setSuggestedArtist(undefined);
setSuggestedAlbum(undefined);
};
const addFileWithMetaData = (file: File, id: number) => {
// parsedElement: FileParsedElement[] = [];
let artist: string | undefined = undefined;
let album: string | undefined = undefined;
let trackIdNumber: number | undefined = undefined;
let title: string = '';
form.restoreValues();
console.log(`select file ${file.name}`);
let tmpName = file.name.replace(/[ \t]*-[ \t]*/g, '-');
//tmpName = tmpName.replace(/_/g, '-');
//tmpName = tmpName.replace(/--/g, '-');
console.log(`select file ${tmpName}`);
const splitElement = tmpName.split('~');
if (splitElement.length > 1) {
artist = splitElement[0];
tmpName = tmpName.substring(artist.length + 1);
}
const splitElement2 = tmpName.split('#');
if (splitElement2.length > 1) {
album = splitElement2[0];
tmpName = tmpName.substring(album.length + 1);
}
//console.log("ploppppp " + tmpName);
const splitElement3 = tmpName.split('-');
if (splitElement3.length > 1) {
trackIdNumber = parseInt(splitElement3[0], 10);
tmpName = tmpName.substring(splitElement3[0].length + 1);
}
//console.log("KKKppppp " + tmpName);
//console.log(" ===> " + splitElement3[0]);
title = tmpName;
if (trackIdNumber && isNaN(trackIdNumber)) {
trackIdNumber = undefined;
}
// remove extension
title = title.replace(new RegExp('\\.(webm|WEBM|Webm)'), '');
let tmp = new FileParsedElement(
id,
file,
title,
artist,
album,
trackIdNumber
);
console.log(`==>${JSON.stringify(tmp)}`);
// add it in the list.
return tmp;
};
const [suggestedArtist, setSuggestedArtist] = useState<string | undefined>(
undefined
);
const [suggestedAlbum, setSuggestedAlbum] = useState<string | undefined>(
undefined
);
const onChangeFile = (value: any): void => {
clearData();
const parsedElementTmp: FileParsedElement[] = [];
const parsedFailedElementTmp: FileFailParsedElement[] = [];
for (let iii = 0; iii < value.target.files?.length; iii++) {
parsedElementTmp.push(addFileWithMetaData(value.target.files[iii], iii));
}
// check if all global parameters are generic:
if (parsedElementTmp.length === 0) {
updateNeedSend();
return;
}
// clean different artist:
for (let iii = 1; iii < parsedElementTmp.length; iii++) {
console.log(
`check artist [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].artist} !== ${parsedElementTmp[iii].artist}'`
);
if (parsedElementTmp[0].artist !== parsedElementTmp[iii].artist) {
parsedFailedElementTmp.push(
new FileFailParsedElement(
parsedFailedElementTmp.length,
parsedElementTmp[iii].file,
'Remove from list due to wrong artist value'
)
);
console.log(
`Remove from list (!= artist) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`
);
parsedElementTmp.splice(iii, 1);
iii--;
}
}
// clean different album:
for (let iii = 1; iii < parsedElementTmp.length; iii++) {
console.log(
`check album [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].album} !== ${parsedElementTmp[iii].album}'`
);
if (parsedElementTmp[0].album !== parsedElementTmp[iii].album) {
parsedFailedElementTmp.push(
new FileFailParsedElement(
parsedFailedElementTmp.length,
parsedElementTmp[iii].file,
'Remove from list due to wrong album value'
)
);
console.log(
`Remove from list (!= album) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`
);
parsedElementTmp.splice(iii, 1);
iii--;
}
}
setParsedElement(parsedElementTmp);
setParsedFailedElement(parsedFailedElementTmp);
console.log(`check : ${JSON.stringify(parsedElementTmp[0])}`);
// find artistId:
console.log(`try find artist : ${parsedElementTmp[0].artist}`);
let artistFound = false;
dataArtist.forEach((data) => {
if (
data.name?.toLowerCase() === parsedElementTmp[0].artist?.toLowerCase()
) {
console.log(` find artist : ${data.id}`);
form.setValues({ artistId: data.id });
artistFound = true;
}
});
if (!artistFound) {
console.log(` set Suggested artist : ${parsedElementTmp[0].artist}`);
setSuggestedArtist(parsedElementTmp[0].artist);
} else {
setSuggestedArtist(undefined);
}
// try to find album
console.log(`try find album : ${parsedElementTmp[0].album}`);
let albumFound = false;
dataAlbums.forEach((data) => {
if (
data.name?.toLowerCase() === parsedElementTmp[0].album?.toLowerCase()
) {
console.log(` find album : ${data.id}`);
form.setValues({ albumId: data.id });
albumFound = true;
}
});
if (!albumFound) {
console.log(` set Suggested album : ${parsedElementTmp[0].album}`);
setSuggestedAlbum(parsedElementTmp[0].album);
}
updateNeedSend();
};
const [indexUpload, setIndexUpload] = useState<number | undefined>(undefined);
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);
const progressUpload = useCallback(
(count: number, total: number) => {
setTotalPosition(total);
setCurrentPosition(count);
},
[setTotalPosition, setCurrentPosition]
);
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 {
setIndexUpload(undefined);
}
function OnUploadClose(): void {
setIndexUpload(undefined);
}
const addNewGender = (data: string): Promise<Gender> => {
return storeGender.update(
GenderResource.post({
restConfig: session.getRestConfig(),
data: {
name: data,
},
})
);
};
const addNewArtist = (data: string): Promise<Artist> => {
return storeArtist.update(
ArtistResource.post({
restConfig: session.getRestConfig(),
data: {
name: data,
},
})
);
};
const addNewAlbum = (data: string): Promise<Album> => {
return storeAlbum.update(
AlbumResource.post({
restConfig: session.getRestConfig(),
data: {
name: data,
},
})
);
};
return (
<>
<TopBar title="Add new media" />
<PageLayout>
<Flex
direction="column"
width="80%"
marginX="auto"
padding="10px"
gap="10px"
>
<Flex direction="column" width="full">
<Flex>
<Text flex={1}>format:</Text>
<Text flex={4}>
The format of the media permit to automatic find meta-data:
<br />
Artist~album#idTrack-my name of my media.webm
<br />
<b>example:</b> Clarika~Moi En Mieux#22-des bulles.webm
</Text>
</Flex>
<Flex>
<Text flex={1}>Media:</Text>
<Input
flex={4}
type="file"
placeholder="Select a media file"
accept=".webm"
multiple
onChange={onChangeFile}
/>
</Flex>
</Flex>
{parsedElement && parsedElement.length !== 0 && (
<>
<Text fontSize="30px">Meta-data:</Text>
<FormSelect
label="Gender"
form={form}
variableName="genderId"
options={dataGenders}
keyInputValue="name"
addNewItem={addNewGender}
/>
<FormSelect
label="Artist"
form={form}
variableName="artistId"
options={dataArtist}
keyInputValue="name"
addNewItem={addNewArtist}
suggestion={suggestedArtist}
/>
<FormSelect
label="Album"
form={form}
variableName="albumId"
options={dataAlbums}
keyInputValue="name"
addNewItem={addNewAlbum}
suggestion={suggestedAlbum}
/>
<Table.Root
colorPalette="striped"
colorScheme="teal"
background="gray.700"
>
<Table.Header>
<Table.Row>
<Table.ColumnHeader>track ID</Table.ColumnHeader>
<Table.ColumnHeader width="full">Title</Table.ColumnHeader>
<Table.ColumnHeader>actions</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{parsedElement.map((data) => (
<Table.Row key={data.uniqueId}>
<Table.Cell>
<Input
type="number"
pattern="[0-9]{0-4}"
placeholder="e?"
value={data.trackId}
onChange={(e) => onTrackId(data, e.target.value)}
backgroundColor={
data.trackIdDetected === true
? 'darkred'
: undefined
}
/>
</Table.Cell>
<Table.Cell>
<Input
type="text"
placeholder="Name of the Media"
value={data.title}
onChange={(e) => onTitle(data, e.target.value)}
backgroundColor={
data.title === '' ? 'darkred' : undefined
}
/>
{data.nameDetected === true && (
<>
<br />
<Text as="span" color="@danger">
^^^This title already exist !!!
</Text>
</>
)}
</Table.Cell>
<Table.Cell>
<Button
onClick={(e) =>
removeElementFromList(data, e.target)
}
>
<LuTrash /> Remove
</Button>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
<Flex marginY="15px">
<Button
colorPalette="@primary"
onClick={sendFile}
disabled={!needSend}
marginLeft="auto"
marginRight="30px"
>
<MdCloudUpload /> Upload
</Button>
</Flex>
</>
)}
{listFileInBdd && (
<Table.Root
fontPalette="striped"
colorScheme="teal"
background="gray.700"
>
<Table.Header>
<Table.Row>
<Table.ColumnHeader>track ID</Table.ColumnHeader>
<Table.ColumnHeader width="full">Title</Table.ColumnHeader>
<Table.ColumnHeader>actions</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{listFileInBdd.map((data) => (
<Table.Row>
<Table.Cell>
<Text
color={
data.episodeDetected === true ? 'red' : undefined
}
>
{data.trackId}
</Text>
</Table.Cell>
<Table.Cell>
<Text
color={
data.nameDetected === true ? 'red' : undefined
}
>
{data.title}
</Text>
</Table.Cell>
<Table.Cell></Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
)}
{parsedFailedElement && (
<>
<Text fontSize="30px">Rejected:</Text>
<Table.Root
colorPalette="striped"
colorScheme="teal"
background="gray.700"
>
<Table.Header>
<Table.Row>
<Table.ColumnHeader maxWidth="80%">file</Table.ColumnHeader>
<Table.ColumnHeader>Reason</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{parsedFailedElement.map((data) => (
<Table.Row key={data.uniqueId}>
<Table.Cell>{data.file.name}</Table.Cell>
<Table.Cell>{data.reason}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</>
)}
</Flex>
{indexUpload !== undefined && (
<PopUpUploadProgress
title="Upload File(s)"
currentSize={currentPosition}
totalSize={totalPosition}
index={indexUpload}
elements={listValues}
error={uploadError}
onAbort={onUploadAbort}
onClose={OnUploadClose}
isFinished={isFinishedUpload}
/>
)}
</PageLayout>
</>
);
};