[FEAT] upgrade upload of data...

This commit is contained in:
Edouard DUPIN 2025-02-26 15:00:14 +01:00
parent 6df71e3341
commit 88f65f0806
13 changed files with 553 additions and 309 deletions

View File

@ -1,6 +1,6 @@
import { useRef, useState } from 'react'; import { useRef } from 'react';
import { Button, Text, useDisclosure } from '@chakra-ui/react'; import { Button, Tabs, Text, useDisclosure } from '@chakra-ui/react';
import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md'; import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
@ -9,15 +9,14 @@ import { FormGroupShow } from '@/components/form/FormGroup';
import { FormInput } from '@/components/form/FormInput'; import { FormInput } from '@/components/form/FormInput';
import { FormNumber } from '@/components/form/FormNumber'; import { FormNumber } from '@/components/form/FormNumber';
import { FormSelect } from '@/components/form/FormSelect'; import { FormSelect } from '@/components/form/FormSelect';
import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
import { FormTextarea } from '@/components/form/FormTextarea'; import { FormTextarea } from '@/components/form/FormTextarea';
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp'; import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
import { import {
DialogBody, DialogBody,
DialogContent, DialogContent,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogRoot, DialogRoot,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { useMediaService, useSpecificMedia } from '@/service/Media'; import { useMediaService, useSpecificMedia } from '@/service/Media';
import { useOrderedSeasons } from '@/service/Season'; import { useOrderedSeasons } from '@/service/Season';
@ -31,32 +30,31 @@ import { Formidable, useFormidable } from '../formidable';
export type MediaEditPopUpProps = {}; export type MediaEditPopUpProps = {};
export const MediaEditPopUp = ({}: MediaEditPopUpProps) => { export const MediaEditPopUp = ({}: MediaEditPopUpProps) => {
const { MediaId } = useParams(); const { mediaId } = useParams();
const MediaIdInt = isNullOrUndefined(MediaId) const mediaIdInt = isNullOrUndefined(mediaId)
? undefined ? undefined
: parseInt(MediaId, 10); : parseInt(mediaId, 10);
const { session } = useServiceContext(); const { session } = useServiceContext();
const { dataTypes } = useOrderedTypes(undefined); const { dataTypes } = useOrderedTypes();
const { dataSeries } = useOrderedSeries(undefined); const { dataSeries } = useOrderedSeries();
const { dataSeasons } = useOrderedSeasons(undefined); const { dataSeasons } = useOrderedSeasons();
const { store } = useMediaService(); const { store } = useMediaService();
const { dataMedia } = useSpecificMedia(MediaIdInt); const { dataMedia } = useSpecificMedia(mediaIdInt);
const [admin, setAdmin] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const disclosure = useDisclosure(); const disclosure = useDisclosure();
const onClose = () => { const onClose = () => {
navigate('../../', { relative: 'path' }); navigate('../../..', { relative: 'path' });
}; };
const onRemove = () => { const onRemove = () => {
if (isNullOrUndefined(MediaIdInt)) { if (isNullOrUndefined(mediaIdInt)) {
return; return;
} }
store.remove( store.remove(
MediaIdInt, mediaIdInt,
MediaResource.remove({ MediaResource.remove({
restConfig: session.getRestConfig(), restConfig: session.getRestConfig(),
params: { params: {
id: MediaIdInt, id: mediaIdInt,
}, },
}) })
); );
@ -69,7 +67,7 @@ export const MediaEditPopUp = ({}: MediaEditPopUpProps) => {
deltaConfig: { omit: ['covers'] }, deltaConfig: { omit: ['covers'] },
}); });
const onSave = async (dataDelta: MediaWrite) => { const onSave = async (dataDelta: MediaWrite) => {
if (isNullOrUndefined(MediaIdInt)) { if (isNullOrUndefined(mediaIdInt)) {
return; return;
} }
console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`); console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`);
@ -78,7 +76,7 @@ export const MediaEditPopUp = ({}: MediaEditPopUpProps) => {
restConfig: session.getRestConfig(), restConfig: session.getRestConfig(),
data: dataDelta, data: dataDelta,
params: { params: {
id: MediaIdInt, id: mediaIdInt,
}, },
}) })
); );
@ -99,8 +97,48 @@ export const MediaEditPopUp = ({}: MediaEditPopUpProps) => {
{/* <DialogCloseButton ref={finalRef} /> */} {/* <DialogCloseButton ref={finalRef} /> */}
<DialogBody pb={6} gap="0px" paddingLeft="18px"> <DialogBody pb={6} gap="0px" paddingLeft="18px">
{admin && ( <Tabs.Root defaultValue="edit" variant="outline">
<> <Tabs.List>
<Tabs.Trigger value="edit">
<MdEdit />
Edit
</Tabs.Trigger>
<Tabs.Trigger value="admin">
<MdAdminPanelSettings />
Admin
</Tabs.Trigger>
</Tabs.List>
{/* ---------------------- Other Tabs --------------------------- */}
<Tabs.Content value="edit">
<FormInput
name="name"
isRequired
label="Title"
ref={initialRef}
/>
<FormTextarea name="description" label="Description" />
<FormSelect name="typeId" options={dataTypes} label="Type" />
<FormSelect
name="seriesId"
options={dataSeries}
label="Series(s)"
/>
<FormSelect
name="seasonId"
options={dataSeasons}
label="Season"
/>
<FormNumber
name="episode"
label="Episode n°"
step={1}
//defaultValue={0}
min={0}
max={1000}
/>
</Tabs.Content>
{/* ---------------------- Other Tabs --------------------------- */}
<Tabs.Content value="admin">
<FormGroupShow isRequired label="Id"> <FormGroupShow isRequired label="Id">
<Text>{dataMedia?.id}</Text> <Text>{dataMedia?.id}</Text>
</FormGroupShow> </FormGroupShow>
@ -123,62 +161,18 @@ export const MediaEditPopUp = ({}: MediaEditPopUpProps) => {
confirmTitle="Remove" confirmTitle="Remove"
onConfirm={onRemove} onConfirm={onRemove}
/> />
</> </Tabs.Content>
)} </Tabs.Root>
{!admin && (
<>
<FormInput
name="name"
isRequired
label="Title"
ref={initialRef}
/>
<FormTextarea name="description" label="Description" />
<FormSelect
name="TypeId"
options={dataTypes}
label="Type"
/>
<FormSelectMultiple
name="Series"
options={dataSeries}
label="Series(s)"
/>
<FormSelect name="SeasonId" options={dataSeasons} label="Season" />
<FormNumber
name="Media"
label="Media n°"
step={1}
//defaultValue={0}
min={0}
max={1000}
/>
</>
)}
</DialogBody> </DialogBody>
<DialogFooter> <DialogFooter>
<Button <Button onClick={onClose} marginRight="auto">
onClick={() => setAdmin((value) => !value)} Cancel
marginRight="auto"
>
{admin ? (
<>
<MdEdit />
Edit
</>
) : (
<>
<MdAdminPanelSettings />
Admin
</>
)}
</Button> </Button>
{!admin && form.isFormModified && ( {form.isFormModified && (
<Button colorScheme="blue" mr={3} type="submit"> <Button colorPalette="blue" type="submit">
Save Save
</Button> </Button>
)} )}
<Button onClick={onClose}>Cancel</Button>
</DialogFooter> </DialogFooter>
</Formidable.From> </Formidable.From>
</DialogContent> </DialogContent>

View File

@ -57,7 +57,8 @@ export const SelectMultiple = ({
return []; return [];
} }
return transformedOption.filter((element) => { return transformedOption.filter((element) => {
return values.includes(element[keyKey]); console.log(`plop ${JSON.stringify(values, null, 2)}`);
return values?.includes(element[keyKey]);
}); });
}, [values, transformedOption]); }, [values, transformedOption]);

View File

@ -1,4 +1,4 @@
import { useCallback, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Box, Button, Flex, Input, Table, Text } from '@chakra-ui/react'; import { Box, Button, Flex, Input, Table, Text } from '@chakra-ui/react';
import { LuTrash } from 'react-icons/lu'; import { LuTrash } from 'react-icons/lu';
@ -12,9 +12,10 @@ import {
Series, Series,
SeriesResource, SeriesResource,
Type, Type,
TypeResource TypeResource,
} from '@/back-api'; } from '@/back-api';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { ParameterLayout } from '@/components/ParameterLayout';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { FormSelect } from '@/components/form/FormSelect'; import { FormSelect } from '@/components/form/FormSelect';
import { Formidable, useFormidable } from '@/components/formidable'; import { Formidable, useFormidable } from '@/components/formidable';
@ -23,10 +24,20 @@ import {
NumberInputField, NumberInputField,
NumberInputRoot, NumberInputRoot,
} from '@/components/ui/number-input'; } from '@/components/ui/number-input';
import { useMediaService } from '@/service/Media'; import {
import { useOrderedSeries, useOrderedSeriesWithType, useSeriesService } from '@/service/Series'; MediaExpandSeason,
useFilteredMedia,
useMediaExpandSeason,
useMediaService,
} from '@/service/Media';
import {
useOrderedSeries,
useOrderedSeriesWithType,
useSeriesService,
} from '@/service/Series';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useOrderedTypes, useTypeService } from '@/service/Type'; import { useOrderedTypes, useTypeService } from '@/service/Type';
import { useDebounce, useDebouncedCallback } from '@/utils/debouncedCallback';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
export class ElementList { export class ElementList {
@ -40,7 +51,7 @@ export class ElementList {
export class FileParsedElement { export class FileParsedElement {
public isSended: boolean = false; public isSended: boolean = false;
public nameDetected: boolean = false; public nameDetected: boolean = false;
public mediaIdDetected: boolean = false; public episodeDetected: boolean = false;
public seasonId?: Season['id'] = undefined; public seasonId?: Season['id'] = undefined;
public seriesId?: Series['id'] = undefined; public seriesId?: Series['id'] = undefined;
constructor( constructor(
@ -50,7 +61,7 @@ export class FileParsedElement {
public universe?: string, public universe?: string,
public series?: string, public series?: string,
public season?: number, public season?: number,
public mediaId?: number public episode?: number
) { ) {
console.log(`Unique element: ${uniqueId}`); console.log(`Unique element: ${uniqueId}`);
// nothing to do. // nothing to do.
@ -66,7 +77,11 @@ export class FileFailParsedElement {
// nothing to do. // nothing to do.
} }
} }
type InspectionType = {
episodeDetected: boolean;
nameDetected: boolean;
media: MediaExpandSeason;
};
type FormInsertData = { type FormInsertData = {
typeId?: number; typeId?: number;
seriesId?: number; seriesId?: number;
@ -81,26 +96,95 @@ export const AddPage = () => {
const [needSend, setNeedSend] = useState<boolean>(false); 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 { dataTypes } = useOrderedTypes(); const { dataTypes } = useOrderedTypes();
const { dataSeries: dataSeriesFull } = useOrderedSeries(); const { dataSeries: dataSeriesFull } = useOrderedSeries();
const { store: storeType } = useTypeService(); const { store: storeType } = useTypeService();
const { store: storeSeries } = useSeriesService(); const { store: storeSeries } = useSeriesService();
const { store: storeMedia } = useMediaService(); const { store: storeMedia } = useMediaService();
const { session } = useServiceContext(); const { session } = useServiceContext();
const form = useFormidable<FormInsertData>({ const form = useFormidable<FormInsertData>({
configuration: { configuration: {
enableModifyNotification: false, enableModifyNotification: false,
enableReset: false, enableReset: false,
}, },
}); });
// list of all files already registered in the bdd to compare with the current list of files.
const [listFileInBdd, setListFileInBdd] = useState<
InspectionType[] | undefined
>(undefined);
const formValuesSelectForCheck = useDebounce(form.values, 700);
const { medias } = useFilteredMedia(
formValuesSelectForCheck.typeId,
formValuesSelectForCheck.seriesId
);
const { mediasExpand } = useMediaExpandSeason(medias);
useEffect(() => {
handleCheck('');
}, [
formValuesSelectForCheck,
formValuesSelectForCheck.typeId,
mediasExpand,
parsedElement,
]);
const handleCheck = useDebouncedCallback((_newValue) => {
console.log(`values has changes ... ${mediasExpand.length} medias`);
// find the good case: (ignore the universe that have no sense...)
if (formValuesSelectForCheck.typeId === undefined) {
setListFileInBdd(undefined);
return;
}
const out: InspectionType[] = [];
mediasExpand.forEach((media) => {
out.push({
episodeDetected: false,
nameDetected: false,
media,
});
});
// clear previous data:
out.forEach((elem) => {
elem.episodeDetected = false;
elem.nameDetected = false;
});
parsedElement.forEach((elem) => {
elem.episodeDetected = false;
elem.nameDetected = false;
});
// Detect the identical season + identical ID:
console.log(`check:'${JSON.stringify(parsedElement, null, 2)}'`);
out.forEach((checkElem) => {
parsedElement.forEach((parsedElem) => {
if (
`${parsedElem.season}` === `${checkElem.media?.season?.name}` &&
`${parsedElem.episode}` === `${checkElem.media?.episode}`
) {
checkElem.episodeDetected = true;
parsedElem.episodeDetected = true;
}
if (
parsedElem.title?.length !== undefined &&
parsedElem.title?.length !== 0 &&
checkElem.media?.name?.length !== 0 &&
checkElem.media?.name?.length !== 0 &&
(parsedElem.title.startsWith(checkElem.media?.name) ||
checkElem.media?.name.startsWith(parsedElem.title))
) {
checkElem.nameDetected = true;
parsedElem.nameDetected = true;
}
});
});
setListFileInBdd(
out.sort((a, b) => {
const aPriority = a.episodeDetected || a.nameDetected ? 1 : 0;
const bPriority = b.episodeDetected || b.nameDetected ? 1 : 0;
return bPriority - aPriority;
})
);
}, 1000);
// I think this does not work ... // I think this does not work ...
const { dataSeries } = useOrderedSeriesWithType(form.values["typeId"]); const { dataSeries } = useOrderedSeriesWithType(form.values.typeId);
const updateNeedSend = () => { const updateNeedSend = () => {
if (parsedElement.length === 0) { if (parsedElement.length === 0) {
@ -145,8 +229,8 @@ export const AddPage = () => {
updateNeedSend(); updateNeedSend();
}; };
const onMediaId = (data: FileParsedElement, value: any): void => { const onEpisode = (data: FileParsedElement, value: any): void => {
data.mediaId = value; data.episode = value;
setParsedElement([...parsedElement]); setParsedElement([...parsedElement]);
updateNeedSend(); updateNeedSend();
}; };
@ -164,14 +248,15 @@ export const AddPage = () => {
//setSuggestedSeason(undefined); //setSuggestedSeason(undefined);
}; };
const regex = /^(?:(?<universe>[\w. -]+):)?((?<series>[\w. -]+?)((-s| S)(?<season>\d{1,5}))?(?:(-e|E)(?<episode>\d{1,5}))[- ])?\s*(?<title>.+?)\.(webm|WEBM|Webm|mkv|MKV|Mkv)$/; const regex =
/^(?:(?<universe>[\w. -]+):)?((?<series>[\w. -]+?)((-s| S)(?<season>\d{1,5}))?(?:(-e|E)(?<episode>\d{1,5}))[- ])?\s*(?<title>.+?)\.(webm|WEBM|Webm|mkv|MKV|Mkv)$/;
const addFileWithMetaData = (file: File, id: number) => { const addFileWithMetaData = (file: File, id: number) => {
// parsedElement: FileParsedElement[] = []; // parsedElement: FileParsedElement[] = [];
let universe: string | undefined = undefined; let universe: string | undefined = undefined;
let series: string | undefined = undefined; let series: string | undefined = undefined;
let season: number | undefined = undefined; let season: number | undefined = undefined;
let mediaIdNumber: number | undefined = undefined; let episodeNumber: number | undefined = undefined;
let title: string = ''; let title: string = '';
form.restoreValues(); form.restoreValues();
@ -180,21 +265,25 @@ export const AddPage = () => {
const match = file.name.match(regex); const match = file.name.match(regex);
if (match?.groups) { if (match?.groups) {
universe = match.groups.universe || undefined; universe = match.groups.universe || undefined;
series = match.groups.series ? match.groups.series.trim() : undefined; series = match.groups.series ? match.groups.series.trim() : undefined;
season = match.groups.season? parseInt(match.groups.season, 10) : undefined; season = match.groups.season
mediaIdNumber = match.groups.episode ? parseInt(match.groups.episode, 10) : undefined; ? parseInt(match.groups.season, 10)
title = match.groups.title.trim(); : undefined;
episodeNumber = match.groups.episode
? parseInt(match.groups.episode, 10)
: undefined;
title = match.groups.title.trim();
} else { } else {
console.log("❌ not match :", file.name); console.log('❌ not match :', file.name);
title = file.name.trim(); title = file.name.trim();
} }
if (season && isNaN(season)) { if (season && isNaN(season)) {
season = undefined; season = undefined;
} }
if (mediaIdNumber && isNaN(mediaIdNumber)) { if (episodeNumber && isNaN(episodeNumber)) {
mediaIdNumber = undefined; episodeNumber = undefined;
} }
// remove extension // remove extension
title = title.replace(new RegExp('\\.(webm|WEBM|Webm|mkv|MKV|Mkv)'), ''); title = title.replace(new RegExp('\\.(webm|WEBM|Webm|mkv|MKV|Mkv)'), '');
@ -205,7 +294,7 @@ export const AddPage = () => {
universe, universe,
series, series,
season, season,
mediaIdNumber episodeNumber
); );
console.log(`==>${JSON.stringify(tmp, null, 2)}`); console.log(`==>${JSON.stringify(tmp, null, 2)}`);
@ -316,10 +405,10 @@ export const AddPage = () => {
typeId: `${form.values['typeId']}`, typeId: `${form.values['typeId']}`,
seriesId: `${form.values['seriesId']}`, seriesId: `${form.values['seriesId']}`,
season: `${parsedElement[index].season}`, season: `${parsedElement[index].season}`,
episode: `${parsedElement[index].mediaId}`, episode: `${parsedElement[index].episode}`,
}; };
console.log(`data= ${JSON.stringify(data, null, 2)}`); console.log(`data= ${JSON.stringify(data, null, 2)}`);
console.error("Not_ implemented"); console.error('Not_ implemented');
storeMedia storeMedia
.update( .update(
MediaResource.uploadMedia({ MediaResource.uploadMedia({
@ -384,7 +473,7 @@ export const AddPage = () => {
restConfig: session.getRestConfig(), restConfig: session.getRestConfig(),
data: { data: {
name: data, name: data,
parentId: form.values["typeId"] parentId: form.values['typeId'],
}, },
}) })
); );
@ -394,140 +483,160 @@ export const AddPage = () => {
<> <>
<TopBar title="Add new media" /> <TopBar title="Add new media" />
<PageLayout> <PageLayout>
<Flex <ParameterLayout.Root>
direction="column" <ParameterLayout.HeaderBase
width="80%" title="New Media"
marginX="auto" description="Add a new media in the library."
padding="10px" />
gap="10px" <ParameterLayout.Content>
> <Flex direction="column" width="full">
<Flex direction="column" width="full"> <Flex>
<Flex> <Text flex={1}>format:</Text>
<Text flex={1}>format:</Text> <Text flex={4}>
<Text flex={4}> The format of the media permit to automatic find meta-data:
The format of the media permit to automatic find meta-data:<br /> <br />
<li>Universe:Series name-s05-e22-Title.webm/mkv</li> <li>Universe:Series name-s05-e22-Title.webm/mkv</li>
<li>Universe:Series name S05E22 Title.webm/mkv</li> <li>Universe:Series name S05E22 Title.webm/mkv</li>
<b>example:</b> Stargate:SG1-s05-e22-Tolans.webm <b>example:</b> Stargate:SG1-s05-e22-Tolans.webm
</Text> </Text>
</Flex>
<Flex>
<Text flex={1}>Media:</Text>
<Input
flex={4}
type="file"
placeholder="Select a media file"
accept=".webm,.mkv"
multiple
onChange={onChangeFile}
/>
</Flex>
</Flex> </Flex>
<Flex> </ParameterLayout.Content>
<Text flex={1}>Media:</Text> <ParameterLayout.Footer />
<Input </ParameterLayout.Root>
flex={4}
type="file"
placeholder="Select a media file"
accept=".webm,.mkv"
multiple
onChange={onChangeFile}
/>
</Flex>
</Flex>
{parsedElement && parsedElement.length !== 0 && (
<Formidable.From form={form} onSubmit={sendFile}>
<Text fontSize="30px">Meta-data:</Text>
<FormSelect
label="Type"
name="typeId"
options={dataTypes}
addNewItem={addNewType}
isRequired
/>
<FormSelect
label="Series"
name="seriesId"
options={dataSeries}
addNewItem={addNewSeries}
suggestion={suggestedSeries}
disabled={form.values["typeId"] === undefined}
/>
<Table.Root {parsedElement && parsedElement.length !== 0 && (
colorPalette="striped" <Formidable.From form={form} onSubmit={sendFile}>
colorScheme="teal" <ParameterLayout.Root>
background="gray.700" <ParameterLayout.HeaderBase
> title="Meta-data:"
<Table.Header> description="Edit the data that might be upload."
<Table.Row> />
<Table.ColumnHeader width="10%"> <ParameterLayout.Content>
Season ID <FormSelect
</Table.ColumnHeader> label="Type"
<Table.ColumnHeader width="10%"> name="typeId"
Media ID options={dataTypes}
</Table.ColumnHeader> addNewItem={addNewType}
<Table.ColumnHeader width="full">Title</Table.ColumnHeader> isRequired
<Table.ColumnHeader>actions</Table.ColumnHeader> />
</Table.Row> <FormSelect
</Table.Header> label="Series"
<Table.Body> name="seriesId"
{parsedElement.map((data) => ( options={dataSeries}
<Table.Row key={data.uniqueId}> addNewItem={addNewSeries}
<Table.Cell> suggestion={suggestedSeries}
{form.values["seriesId"] && disabled={form.values['typeId'] === undefined}
<NumberInputRoot />
value={data.season ? `${data.season}` : undefined}
onValueChange={(e) => onSeasonId(data, e.value)} <Table.Root
min={0} colorPalette="striped"
max={5000} colorScheme="teal"
backgroundColor={ background="gray.700"
data.mediaIdDetected === true >
? 'darkred' <Table.Header>
: undefined <Table.Row>
} <Table.ColumnHeader width="10%">
> Season ID
<NumberInputField /> </Table.ColumnHeader>
</NumberInputRoot> <Table.ColumnHeader width="10%">
} Media ID
</Table.Cell> </Table.ColumnHeader>
<Table.Cell> <Table.ColumnHeader width="full">
{form.values["seriesId"] && Title
<NumberInputRoot </Table.ColumnHeader>
value={data.mediaId ? `${data.mediaId}` : undefined} <Table.ColumnHeader>actions</Table.ColumnHeader>
onValueChange={(e) => onMediaId(data, e.value)}
min={0}
max={5000}
backgroundColor={
data.mediaIdDetected === true
? 'darkred'
: undefined
}
>
<NumberInputField />
</NumberInputRoot>
}
</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
colorPalette="@danger"
onClick={(e) => removeElementFromList(data, e.target)}
>
<LuTrash /> Remove
</Button>
</Table.Cell>
</Table.Row> </Table.Row>
))} </Table.Header>
</Table.Body> <Table.Body>
</Table.Root> {parsedElement.map((data) => (
<Flex marginY="15px"> <Table.Row key={data.uniqueId}>
<Table.Cell>
{form.values['seriesId'] && (
<NumberInputRoot
value={data.season ? `${data.season}` : undefined}
onValueChange={(e) => onSeasonId(data, e.value)}
min={0}
max={5000}
backgroundColor={
data.episodeDetected === true
? 'darkred'
: undefined
}
>
<NumberInputField />
</NumberInputRoot>
)}
</Table.Cell>
<Table.Cell>
{form.values['seriesId'] && (
<NumberInputRoot
value={
data.episode ? `${data.episode}` : undefined
}
onValueChange={(e) => onEpisode(data, e.value)}
min={0}
max={5000}
backgroundColor={
data.episodeDetected === true
? 'darkred'
: undefined
}
>
<NumberInputField />
</NumberInputRoot>
)}
</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 === ''
? 'purple'
: data.nameDetected === true
? 'darkred'
: undefined
}
/>
{data.nameDetected === true && (
<>
<br />
<Text as="span" color="red">
^^^This title already exist !!!
</Text>
</>
)}
</Table.Cell>
<Table.Cell>
<Button
colorPalette="@danger"
onClick={(e) =>
removeElementFromList(data, e.target)
}
>
<LuTrash /> Remove
</Button>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</ParameterLayout.Content>
<ParameterLayout.Footer>
<Button <Button
colorPalette="brand" colorPalette="brand"
type="submit" type="submit"
@ -537,51 +646,51 @@ export const AddPage = () => {
> >
<MdCloudUpload /> Upload <MdCloudUpload /> Upload
</Button> </Button>
</Flex> </ParameterLayout.Footer>
</Formidable.From> </ParameterLayout.Root>
)} </Formidable.From>
)}
{listFileInBdd && ( {listFileInBdd && (
<Table.Root <ParameterLayout.Root>
fontPalette="striped" <ParameterLayout.HeaderBase
colorScheme="teal" title="In base"
background="gray.700" description="List of file in the base for the Type/series ... (2 seconds before update)"
> />
<Table.Header> <ParameterLayout.Content>
<Table.Row> <Table.Root
<Table.ColumnHeader>Media ID</Table.ColumnHeader> fontPalette="striped"
<Table.ColumnHeader width="full">Title</Table.ColumnHeader> colorScheme="teal"
<Table.ColumnHeader>actions</Table.ColumnHeader> background="gray.700"
</Table.Row> >
</Table.Header> <Table.Header>
<Table.Body>
{listFileInBdd.map((data) => (
<Table.Row> <Table.Row>
<Table.Cell> <Table.ColumnHeader>Media ID</Table.ColumnHeader>
<Text <Table.ColumnHeader>Data ID</Table.ColumnHeader>
color={ <Table.ColumnHeader>Season</Table.ColumnHeader>
data.episodeDetected === true ? 'red' : undefined <Table.ColumnHeader>Episode</Table.ColumnHeader>
} <Table.ColumnHeader width="full">Title</Table.ColumnHeader>
> <Table.ColumnHeader>Date</Table.ColumnHeader>
{data.MediaId} <Table.ColumnHeader>Actions</Table.ColumnHeader>
</Text>
</Table.Cell>
<Table.Cell>
<Text
color={data.nameDetected === true ? 'red' : undefined}
>
{data.title}
</Text>
</Table.Cell>
<Table.Cell></Table.Cell>
</Table.Row> </Table.Row>
))} </Table.Header>
</Table.Body> <Table.Body>
</Table.Root> {listFileInBdd.map((data) => (
)} <MediaDetectionDetail data={data} />
))}
{parsedFailedElement && ( </Table.Body>
<> </Table.Root>
</ParameterLayout.Content>
<ParameterLayout.Footer />
</ParameterLayout.Root>
)}
{parsedFailedElement && (
<ParameterLayout.Root>
<ParameterLayout.HeaderBase
title="Rejected:"
description="List of files that has been removed due to parsing error"
/>
<ParameterLayout.Content>
<Text fontSize="30px">Rejected:</Text> <Text fontSize="30px">Rejected:</Text>
<Table.Root <Table.Root
colorPalette="striped" colorPalette="striped"
@ -603,10 +712,12 @@ export const AddPage = () => {
))} ))}
</Table.Body> </Table.Body>
</Table.Root> </Table.Root>
</> </ParameterLayout.Content>
)} <ParameterLayout.Footer />
<Box height="250px" width="full" /> </ParameterLayout.Root>
</Flex> )}
<Box height="50%" minHeight="50%" width="99%" />
{/* upload pop-in */}
{indexUpload !== undefined && ( {indexUpload !== undefined && (
<PopUpUploadProgress <PopUpUploadProgress
title="Upload File(s)" title="Upload File(s)"
@ -624,3 +735,37 @@ export const AddPage = () => {
</> </>
); );
}; };
export const MediaDetectionDetail = ({ data }: { data: InspectionType }) => {
return (
<Table.Row>
<Table.Cell>
<Text>{data.media.id}</Text>
</Table.Cell>
<Table.Cell>
<Text>{data.media.dataId.toUpperCase()}</Text>
</Table.Cell>
<Table.Cell>
<Text color={data.episodeDetected === true ? 'red' : undefined}>
{data.media.season?.name}
</Text>
</Table.Cell>
<Table.Cell>
<Text color={data.episodeDetected === true ? 'red' : undefined}>
{data.media.episode}
</Text>
</Table.Cell>
<Table.Cell>
<Text
userSelect="text"
color={data.nameDetected === true ? 'red' : undefined}
>
{data.media.name}
</Text>
</Table.Cell>
<Table.Cell>
<Text>{data.media.date}</Text>
</Table.Cell>
<Table.Cell></Table.Cell>
</Table.Row>
);
};

View File

@ -11,10 +11,10 @@ import { Route, Routes, useNavigate } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { DisplayMediaFullId } from '@/components/media/DisplayMediaFullId'; import { DisplayMediaFullId } from '@/components/media/DisplayMediaFullId';
import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp'; import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp'; import { MediaEditPopUp } from '@/components/popup/MediaEditPopUp';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorModeValue } from '@/components/ui/color-mode';
import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; import { BASE_WRAP_SPACING } from '@/constants/genericSpacing';

View File

@ -7,12 +7,16 @@ import { Covers } from '@/components/Cover';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { MediaEditPopUp } from '@/components/popup/MediaEditPopUp';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorModeValue } from '@/components/ui/color-mode';
import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; import { BASE_WRAP_SPACING } from '@/constants/genericSpacing';
import { useActivePlaylistService, useSeasonVideo, useSpecificSeason } from '@/service'; import {
useActivePlaylistService,
useSeasonVideo,
useSpecificSeason,
} from '@/service';
export const SeasonDetailPage = () => { export const SeasonDetailPage = () => {
const { SeasonId } = useParams(); const { SeasonId } = useParams();

View File

@ -7,9 +7,9 @@ import { Covers } from '@/components/Cover';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { MediaEditPopUp } from '@/components/popup/MediaEditPopUp';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSeasonVideo, useSpecificSeason } from '@/service/Season'; import { useSeasonVideo, useSpecificSeason } from '@/service/Season';
import { useSpecificSeries } from '@/service/Series'; import { useSpecificSeries } from '@/service/Series';

View File

@ -11,7 +11,7 @@ import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
//import { useMediasOfAType } from '@/service/Media'; //import { useMediasOfAType } from '@/service/Media';
import { DisplayMediaFull } from '@/components/media/DisplayMediaFull'; import { DisplayMediaFull } from '@/components/media/DisplayMediaFull';
import { TypeEditPopUp } from '@/components/popup/GenderEditPopUp'; import { TypeEditPopUp } from '@/components/popup/GenderEditPopUp';
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp'; import { MediaEditPopUp } from '@/components/popup/MediaEditPopUp';
import { DisplaySeries } from '@/components/series/DisplaySeries'; import { DisplaySeries } from '@/components/series/DisplaySeries';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorModeValue } from '@/components/ui/color-mode';
import { import {

View File

@ -11,7 +11,7 @@ import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
//import { useMediasOfAType } from '@/service/Media'; //import { useMediasOfAType } from '@/service/Media';
import { DisplayMediaFull } from '@/components/media/DisplayMediaFull'; import { DisplayMediaFull } from '@/components/media/DisplayMediaFull';
import { TypeEditPopUp } from '@/components/popup/GenderEditPopUp'; import { TypeEditPopUp } from '@/components/popup/GenderEditPopUp';
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp'; import { MediaEditPopUp } from '@/components/popup/MediaEditPopUp';
import { DisplaySeason } from '@/components/season/DisplaySeason'; import { DisplaySeason } from '@/components/season/DisplaySeason';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorModeValue } from '@/components/ui/color-mode';
import { import {
@ -34,11 +34,11 @@ export const TypesSeriesDetailPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const onSelectSeasonItem = (mediaId: number) => { const onSelectSeasonItem = (mediaId: number) => {
navigate(`/type/${typeId}/series/${seriesId}/season/${mediaId}`); navigate(`/type/${typeId}/series/${seriesId}/season/${mediaId}`);
} };
const onSelectItem = (mediaId: number) => { const onSelectItem = (mediaId: number) => {
let currentPlay = 0; let currentPlay = 0;
const listMediaId: number[] = []; const listMediaId: number[] = [];
console.log(`select item:${mediaId}`); console.log(`select item:${mediaId}`);
if (!videoWithType) { if (!videoWithType) {
console.log('Fail to get Type...'); console.log('Fail to get Type...');
return; return;
@ -108,8 +108,8 @@ export const TypesSeriesDetailPage = () => {
> >
{seasonOfSeriesId?.map((data) => ( {seasonOfSeriesId?.map((data) => (
<Box <Box
key={data.id} key={data.id}
width="full" width="full"
maxWidth="calc(min(450px,80%))" maxWidth="calc(min(450px,80%))"
height="150px" height="150px"
border="1px" border="1px"
@ -137,8 +137,8 @@ export const TypesSeriesDetailPage = () => {
/> />
</Box> </Box>
))} ))}
</HStack> </HStack>
<Box width="full" height="10px" background="red"/> <Box width="full" height="10px" background="red" />
<HStack <HStack
wrap="wrap" wrap="wrap"
gap="20px" gap="20px"
@ -148,7 +148,7 @@ export const TypesSeriesDetailPage = () => {
> >
{videoWithType?.map((data) => ( {videoWithType?.map((data) => (
<Box <Box
key={data.id} key={data.id}
width="200px" width="200px"
height="300px" height="300px"
border="1px" border="1px"

View File

@ -10,7 +10,7 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { DisplayMediaListFull } from '@/components/media/DisplayMediaListFull'; import { DisplayMediaListFull } from '@/components/media/DisplayMediaListFull';
import { TypeEditPopUp } from '@/components/popup/GenderEditPopUp'; import { TypeEditPopUp } from '@/components/popup/GenderEditPopUp';
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp'; import { MediaEditPopUp } from '@/components/popup/MediaEditPopUp';
import { useColorModeValue } from '@/components/ui/color-mode'; import { useColorModeValue } from '@/components/ui/color-mode';
import { import {
useActivePlaylistService, useActivePlaylistService,
@ -29,7 +29,11 @@ export const TypesSeriesSeasonDetailPage = () => {
const { dataType } = useSpecificType(typeIdInt); const { dataType } = useSpecificType(typeIdInt);
const { dataSeries } = useSpecificSeries(seriesIdInt); const { dataSeries } = useSpecificSeries(seriesIdInt);
const { dataSeason } = useSpecificSeason(seasonIdInt); const { dataSeason } = useSpecificSeason(seasonIdInt);
const { videoWithType: videos } = useTypeSeriesSeasonGetVideo(typeIdInt, seriesIdInt, seasonIdInt); const { videoWithType: videos } = useTypeSeriesSeasonGetVideo(
typeIdInt,
seriesIdInt,
seasonIdInt
);
const navigate = useNavigate(); const navigate = useNavigate();
const onSelectItem = (mediaId: number) => { const onSelectItem = (mediaId: number) => {
let currentPlay = 0; let currentPlay = 0;
@ -107,7 +111,7 @@ export const TypesSeriesSeasonDetailPage = () => {
{videos?.map((data) => ( {videos?.map((data) => (
<Box <Box
key={data.id} key={data.id}
width={{sm:"95%", base:"calc(max(300px,50%))"}} width={{ sm: '95%', base: 'calc(max(300px,50%))' }}
height="60px" height="60px"
border="1px" border="1px"
borderColor="brand.900" borderColor="brand.900"

View File

@ -1,9 +1,12 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Media, MediaResource } from '@/back-api'; import { Media, MediaResource, Season } from '@/back-api';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { SessionServiceProps } from '@/service/session'; import { SessionServiceProps } from '@/service/session';
import { DataStoreType, useDataStore } from '@/utils/data-store'; import { DataStoreType, useDataStore } from '@/utils/data-store';
import { DataTools, TypeCheck } from '@/utils/data-tools';
import { useSeasonService } from './Season';
export type MediaServiceProps = { export type MediaServiceProps = {
store: DataStoreType<Media>; store: DataStoreType<Media>;
@ -42,3 +45,46 @@ export const useSpecificMedia = (id: number | undefined) => {
return { isLoading: store.isLoading, dataMedia }; return { isLoading: store.isLoading, dataMedia };
}; };
export const useFilteredMedia = (typeId?: number, seriesId?: number) => {
const { store } = useMediaService();
const medias = useMemo(() => {
if (typeId === undefined) {
return [];
}
return DataTools.getsWhere(
store.data,
[
{
check: TypeCheck.EQUAL,
key: 'typeId',
value: typeId,
},
{
check: TypeCheck.EQUAL,
key: 'seriesId',
value: seriesId,
},
],
['name']
);
}, [store.data, typeId, seriesId]);
return { isLoading: store.isLoading, medias };
};
export type MediaExpandSeason = Media & {
season?: Season;
};
export const useMediaExpandSeason = (medias: Media[]) => {
const { store } = useSeasonService();
const mediasExpand = useMemo(() => {
const out: MediaExpandSeason[] = [];
medias.forEach((media) => {
if (media.seasonId === undefined) {
out.push(media);
}
const tmp = DataTools.get(store.data, media.seasonId, 'id');
out.push({ ...media, season: tmp });
});
return out;
}, [store.data, medias]);
return { isLoading: store.isLoading, mediasExpand };
};

View File

@ -43,6 +43,9 @@ export const useOrderedSeries = (nameFilter?: string) => {
const { store } = useSeriesService(); const { store } = useSeriesService();
const dataSeries = useMemo(() => { const dataSeries = useMemo(() => {
let tmpData = store.data; let tmpData = store.data;
if (tmpData == undefined) {
return [];
}
if (!isNullOrUndefined(nameFilter)) { if (!isNullOrUndefined(nameFilter)) {
tmpData = DataTools.getNameLike(tmpData, nameFilter); tmpData = DataTools.getNameLike(tmpData, nameFilter);
} }
@ -60,7 +63,10 @@ export const useOrderedSeries = (nameFilter?: string) => {
}, [store.data, nameFilter]); }, [store.data, nameFilter]);
return { isLoading: store.isLoading, dataSeries }; return { isLoading: store.isLoading, dataSeries };
}; };
export const useOrderedSeriesWithType = (typeId?: Type["id"], nameFilter?: string) => { export const useOrderedSeriesWithType = (
typeId?: Type['id'],
nameFilter?: string
) => {
const { store } = useSeriesService(); const { store } = useSeriesService();
const dataSeries = useMemo(() => { const dataSeries = useMemo(() => {
let tmpData = store.data; let tmpData = store.data;
@ -70,6 +76,7 @@ export const useOrderedSeriesWithType = (typeId?: Type["id"], nameFilter?: strin
if (typeId === undefined) { if (typeId === undefined) {
return []; return [];
} }
console.log('RRRRRRRRRRRRRRRRRRRRRRRegenarate ');
return DataTools.getsWhere( return DataTools.getsWhere(
tmpData, tmpData,
[ [

View File

@ -84,10 +84,10 @@ export const useTypeCountVideo = (id?: number) => {
return { isLoading: store.isLoading, countVideoWithType }; return { isLoading: store.isLoading, countVideoWithType };
}; };
export const useTypeGetVideo = (id?: number) => { export const useTypeGetVideo = (typeId?: number) => {
const { store } = useMediaService(); const { store } = useMediaService();
const videoWithType = useMemo(() => { const videoWithType = useMemo(() => {
if (id === undefined) { if (typeId === undefined) {
return []; return [];
} }
return DataTools.getsWhere( return DataTools.getsWhere(
@ -96,7 +96,7 @@ export const useTypeGetVideo = (id?: number) => {
{ {
check: TypeCheck.EQUAL, check: TypeCheck.EQUAL,
key: 'typeId', key: 'typeId',
value: id, value: typeId,
}, },
{ {
check: TypeCheck.EQUAL, check: TypeCheck.EQUAL,
@ -111,7 +111,7 @@ export const useTypeGetVideo = (id?: number) => {
], ],
['name'] ['name']
); );
}, [store.data, id]); }, [store.data, typeId]);
return { isLoading: store.isLoading, videoWithType }; return { isLoading: store.isLoading, videoWithType };
}; };
@ -154,7 +154,11 @@ export const useTypeSeriesGetVideo = (idType?: number, idSeries?: number) => {
return { isLoading: store.isLoading, videoWithType }; return { isLoading: store.isLoading, videoWithType };
}; };
export const useTypeSeriesSeasonGetVideo = (idType?: number, idSeries?: number, idSeason?: number) => { export const useTypeSeriesSeasonGetVideo = (
idType?: number,
idSeries?: number,
idSeason?: number
) => {
const { store } = useMediaService(); const { store } = useMediaService();
const videoWithType = useMemo(() => { const videoWithType = useMemo(() => {
if (idType === undefined) { if (idType === undefined) {

View File

@ -0,0 +1,39 @@
import { useCallback, useEffect, useRef, useState } from 'react';
export const useDebouncedCallback = (
callback: (...args: any[]) => void,
delay: number = 2000
) => {
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const debouncedFunction = useCallback(
(...args: any[]) => {
// Cancel timeout if it previously exist
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// start a new timer
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
},
[callback, delay]
);
return debouncedFunction;
};
export const useDebounce = <T>(value: T, delay: number = 2000): T => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
};