639 lines
20 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
};
|