[FEAT] so many things ....
This commit is contained in:
parent
53c3cfefe3
commit
d49e1d06fe
72
front2/src/assets/images/404.svg
Normal file
72
front2/src/assets/images/404.svg
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 24 24"
|
||||
height="250px"
|
||||
width="250px"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="404.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="true"
|
||||
inkscape:zoom="3.448"
|
||||
inkscape:cx="134.28074"
|
||||
inkscape:cy="125"
|
||||
inkscape:window-width="1918"
|
||||
inkscape:window-height="1044"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="17"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2">
|
||||
<inkscape:grid
|
||||
id="grid2"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.096"
|
||||
spacingy="0.096"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
fill="none"
|
||||
d="M0 0h24v24H0z"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M13 10h5l3-3-3-3h-5V2h-2v2H4v6h7v2H6l-3 3 3 3h5v4h2v-4h7v-6h-7z"
|
||||
id="path2" />
|
||||
<path
|
||||
id="rect2"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.384;stroke-linecap:square"
|
||||
d="M 17.394219,5.0400499 19.459554,6.9903325 17.438107,9.0722051 4.9259029,9.0569946 4.9284452,5.0338374 Z"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
id="rect2-3"
|
||||
style="fill:#f8fefb;fill-opacity:1;stroke:none;stroke-width:0.384;stroke-linecap:square"
|
||||
d="m 6.5757719,13.021525 -2.065335,1.950283 2.021447,2.081873 12.5122061,-0.01521 -0.0025,-4.023157 z"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -1,61 +1,62 @@
|
||||
/**
|
||||
* Interface of the server (auto-generated code)
|
||||
*/
|
||||
import { z as zod } from "zod";
|
||||
import { z as zod } from 'zod';
|
||||
|
||||
import {ZodUUID} from "./uuid";
|
||||
import {ZodLong} from "./long";
|
||||
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||
import {
|
||||
ZodGenericDataSoftDelete,
|
||||
ZodGenericDataSoftDeleteWrite,
|
||||
} from './generic-data-soft-delete';
|
||||
import { ZodLong } from './long';
|
||||
import { ZodUUID } from './uuid';
|
||||
|
||||
export const ZodTrack = ZodGenericDataSoftDelete.extend({
|
||||
name: zod.string().max(256).optional(),
|
||||
description: zod.string().optional(),
|
||||
/**
|
||||
* List of Id of the specific covers
|
||||
*/
|
||||
covers: zod.array(ZodUUID).optional(),
|
||||
genderId: ZodLong.optional(),
|
||||
albumId: ZodLong.optional(),
|
||||
track: ZodLong.optional(),
|
||||
dataId: ZodUUID.optional(),
|
||||
artists: zod.array(ZodLong),
|
||||
|
||||
name: zod.string().max(256).optional(),
|
||||
description: zod.string().optional(),
|
||||
/**
|
||||
* List of Id of the specific covers
|
||||
*/
|
||||
covers: zod.array(ZodUUID).optional(),
|
||||
genderId: ZodLong.optional(),
|
||||
albumId: ZodLong.optional(),
|
||||
track: ZodLong.optional(),
|
||||
dataId: ZodUUID.optional(),
|
||||
artists: zod.array(ZodLong),
|
||||
});
|
||||
|
||||
export type Track = zod.infer<typeof ZodTrack>;
|
||||
|
||||
export function isTrack(data: any): data is Track {
|
||||
try {
|
||||
ZodTrack.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodTrack' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ZodTrack.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodTrack' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export const ZodTrackWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||
name: zod.string().max(256).nullable().optional(),
|
||||
description: zod.string().nullable().optional(),
|
||||
/**
|
||||
* List of Id of the specific covers
|
||||
*/
|
||||
covers: zod.array(ZodUUID).nullable().optional(),
|
||||
genderId: ZodLong.nullable().optional(),
|
||||
albumId: ZodLong.nullable().optional(),
|
||||
track: ZodLong.nullable().optional(),
|
||||
dataId: ZodUUID.nullable().optional(),
|
||||
artists: zod.array(ZodLong).optional(),
|
||||
|
||||
name: zod.string().max(256).nullable().optional(),
|
||||
description: zod.string().nullable().optional(),
|
||||
/**
|
||||
* List of Id of the specific covers
|
||||
*/
|
||||
covers: zod.array(ZodUUID).nullable().optional(),
|
||||
genderId: ZodLong.nullable().optional(),
|
||||
albumId: ZodLong.nullable().optional(),
|
||||
track: ZodLong.nullable().optional(),
|
||||
dataId: ZodUUID.nullable().optional(),
|
||||
artists: zod.array(ZodLong).optional(),
|
||||
});
|
||||
|
||||
export type TrackWrite = zod.infer<typeof ZodTrackWrite>;
|
||||
|
||||
export function isTrackWrite(data: any): data is TrackWrite {
|
||||
try {
|
||||
ZodTrackWrite.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodTrackWrite' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ZodTrackWrite.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodTrackWrite' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -30,20 +30,20 @@ import {
|
||||
} from 'react-icons/md';
|
||||
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSpecificAlbum } from '@/service/Album';
|
||||
import { useSpecificArtists } from '@/service/Artist';
|
||||
import { useSpecificGender } from '@/service/Gender';
|
||||
import { useSpecificTrack } from '@/service/Track';
|
||||
import { DataUrlAccess } from '@/utils/data-url-access';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
|
||||
|
||||
|
||||
|
||||
export enum PlayMode {
|
||||
PLAY_ONE,
|
||||
PLAY_ALL,
|
||||
PLAY_ONE_LOOP,
|
||||
PLAY_ALL_LOOP,
|
||||
};
|
||||
}
|
||||
|
||||
const playModeIcon = {
|
||||
[PlayMode.PLAY_ONE]: <MdLooksOne size="30px" />,
|
||||
@ -65,7 +65,7 @@ const formatTime = (time) => {
|
||||
return '00:00';
|
||||
};
|
||||
|
||||
export const AudioPlayer = ({ }: AudioPlayerProps) => {
|
||||
export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
const { mode } = useThemeMode();
|
||||
const { playTrackList, trackOffset, previous, next, first } =
|
||||
useActivePlaylistService();
|
||||
@ -77,6 +77,10 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
|
||||
const { dataTrack } = useSpecificTrack(
|
||||
trackOffset !== undefined ? playTrackList[trackOffset] : undefined
|
||||
);
|
||||
const { dataAlbum } = useSpecificAlbum(dataTrack?.albumId);
|
||||
const { dataGender } = useSpecificGender(dataTrack?.genderId);
|
||||
const { dataArtists } = useSpecificArtists(dataTrack?.artists);
|
||||
|
||||
const [mediaSource, setMediaSource] = useState<string>('');
|
||||
useEffect(() => {
|
||||
setMediaSource(
|
||||
@ -180,7 +184,7 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
|
||||
} else {
|
||||
return PlayMode.PLAY_ONE;
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Call when meta-data is updated
|
||||
@ -198,7 +202,7 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
|
||||
console.log(`onTimeUpdate ${audioRef.current.currentTime}`);
|
||||
setTimeProgress(audioRef.current.currentTime);
|
||||
};
|
||||
const onDurationChange = (event) => { };
|
||||
const onDurationChange = (event) => {};
|
||||
const onChangeStateToPlay = () => {
|
||||
setIsPlaying(true);
|
||||
};
|
||||
@ -207,124 +211,134 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
position="absolute"
|
||||
height="150px"
|
||||
minHeight="150px"
|
||||
paddingY="5px"
|
||||
paddingX="10px"
|
||||
marginX="15px"
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={1000}
|
||||
borderWidth="1px"
|
||||
borderColor="brand.900"
|
||||
bgColor={backColor}
|
||||
borderTopRadius="10px"
|
||||
direction="column"
|
||||
>
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
{!isNullOrUndefined(trackOffset) && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
height="150px"
|
||||
minHeight="150px"
|
||||
paddingY="5px"
|
||||
paddingX="10px"
|
||||
marginX="15px"
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={1000}
|
||||
borderWidth="1px"
|
||||
borderColor="brand.900"
|
||||
bgColor={backColor}
|
||||
borderTopRadius="10px"
|
||||
direction="column"
|
||||
>
|
||||
{dataTrack?.name ?? '???'}
|
||||
</Text>
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="16px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
>
|
||||
artist / title album
|
||||
</Text>
|
||||
<Box width="full" paddingX="15px">
|
||||
<Slider
|
||||
aria-label="slider-ex-4"
|
||||
defaultValue={0}
|
||||
value={timeProgress}
|
||||
min={0}
|
||||
max={duration}
|
||||
step={0.1}
|
||||
onChange={onSeek}
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
>
|
||||
<SliderTrack bg="gray.200" height="10px" borderRadius="full">
|
||||
<SliderFilledTrack bg="brand.600" />
|
||||
</SliderTrack>
|
||||
<SliderThumb boxSize={6}>
|
||||
<Box color="brand.600" as={MdGraphicEq} />
|
||||
</SliderThumb>
|
||||
</Slider>
|
||||
</Box>
|
||||
<Flex>
|
||||
{dataTrack?.name ?? '???'}
|
||||
</Text>
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="16px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
>
|
||||
{formatTime(timeProgress)}
|
||||
</Text>
|
||||
<Text align="left" fontSize="16px" userSelect="none">
|
||||
{formatTime(duration)}
|
||||
{dataArtists.map((data) => data.name).join(', ')} /{' '}
|
||||
{dataAlbum && dataAlbum?.name}
|
||||
{dataGender && ` / ${dataGender.name}`}
|
||||
</Text>
|
||||
<Box width="full" paddingX="15px">
|
||||
<Slider
|
||||
aria-label="slider-ex-4"
|
||||
defaultValue={0}
|
||||
value={timeProgress}
|
||||
min={0}
|
||||
max={duration}
|
||||
step={0.1}
|
||||
onChange={onSeek}
|
||||
>
|
||||
<SliderTrack bg="gray.200" height="10px" borderRadius="full">
|
||||
<SliderFilledTrack bg="brand.600" />
|
||||
</SliderTrack>
|
||||
<SliderThumb boxSize={6}>
|
||||
<Box color="brand.600" as={MdGraphicEq} />
|
||||
</SliderThumb>
|
||||
</Slider>
|
||||
</Box>
|
||||
<Flex>
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="16px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
>
|
||||
{formatTime(timeProgress)}
|
||||
</Text>
|
||||
<Text align="left" fontSize="16px" userSelect="none">
|
||||
{formatTime(duration)}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap="5px">
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Play'}
|
||||
icon={
|
||||
isPlaying ? (
|
||||
<MdPause size="30px" />
|
||||
) : (
|
||||
<MdPlayArrow size="30px" />
|
||||
)
|
||||
}
|
||||
onClick={onPlay}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Stop'}
|
||||
icon={<MdStop size="30px" />}
|
||||
onClick={onStop}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Previous track'}
|
||||
icon={<MdNavigateBefore size="30px" />}
|
||||
onClick={onNavigatePrevious}
|
||||
marginLeft="auto"
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'jump 15sec in past'}
|
||||
icon={<MdFastRewind size="30px" />}
|
||||
onClick={onFastRewind}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'jump 15sec in future'}
|
||||
icon={<MdFastForward size="30px" />}
|
||||
onClick={onFastForward}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Next track'}
|
||||
icon={<MdNavigateNext size="30px" />}
|
||||
marginRight="auto"
|
||||
onClick={onNavigateNext}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'continue to the end'}
|
||||
icon={playModeIcon[playingMode]}
|
||||
onClick={onTypePlay}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap="5px">
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Play'}
|
||||
icon={
|
||||
isPlaying ? <MdPause size="30px" /> : <MdPlayArrow size="30px" />
|
||||
}
|
||||
onClick={onPlay}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Stop'}
|
||||
icon={<MdStop size="30px" />}
|
||||
onClick={onStop}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Previous track'}
|
||||
icon={<MdNavigateBefore size="30px" />}
|
||||
onClick={onNavigatePrevious}
|
||||
marginLeft="auto"
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'jump 15sec in past'}
|
||||
icon={<MdFastRewind size="30px" />}
|
||||
onClick={onFastRewind}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'jump 15sec in future'}
|
||||
icon={<MdFastForward size="30px" />}
|
||||
onClick={onFastForward}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Next track'}
|
||||
icon={<MdNavigateNext size="30px" />}
|
||||
marginRight="auto"
|
||||
onClick={onNavigateNext}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'continue to the end'}
|
||||
icon={playModeIcon[playingMode]}
|
||||
onClick={onTypePlay}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<audio
|
||||
src={mediaSource}
|
||||
|
@ -36,5 +36,5 @@ export const Covers = ({
|
||||
}
|
||||
}
|
||||
const url = DataUrlAccess.getThumbnailUrl(data[0]);
|
||||
return <Image src={url} boxSize={size} {...rest} />;
|
||||
return <Image loading="lazy" src={url} boxSize={size} {...rest} />;
|
||||
};
|
||||
|
67
front2/src/components/SearchInput.tsx
Normal file
67
front2/src/components/SearchInput.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
useOutsideClick,
|
||||
} from '@chakra-ui/react';
|
||||
import { MdSearch } from 'react-icons/md';
|
||||
|
||||
export type SearchInputProps = {
|
||||
onChange?: (data?: string) => void;
|
||||
onSubmit?: (data?: string) => void;
|
||||
};
|
||||
|
||||
export const SearchInput = ({
|
||||
onChange: onChangeValue,
|
||||
onSubmit: onSubmitValue,
|
||||
}: SearchInputProps) => {
|
||||
const [inputData, setInputData] = useState<string | undefined>(undefined);
|
||||
const [searchInputProperty, setSearchInputProperty] =
|
||||
useState<any>(undefined);
|
||||
function onFocusKeep(): void {
|
||||
setSearchInputProperty({
|
||||
width: '70%',
|
||||
maxWidth: '70%',
|
||||
});
|
||||
}
|
||||
function onFocusLost(): void {
|
||||
setSearchInputProperty({
|
||||
width: '250px',
|
||||
});
|
||||
}
|
||||
const ref = React.useRef(null);
|
||||
// TODO: find a better way...
|
||||
useOutsideClick({
|
||||
ref: ref,
|
||||
handler: onFocusLost,
|
||||
});
|
||||
function onChange(event): void {
|
||||
const data =
|
||||
event.target.value.length === 0 ? undefined : event.target.value;
|
||||
setInputData(data);
|
||||
if (onChangeValue) {
|
||||
onChangeValue(data);
|
||||
}
|
||||
}
|
||||
function onSubmit(): void {
|
||||
if (onSubmitValue) {
|
||||
onSubmitValue(inputData);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<InputGroup maxWidth="200px" marginLeft="auto" {...searchInputProperty}>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<MdSearch color="gray.300" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
ref={ref}
|
||||
onFocus={onFocusKeep}
|
||||
onChange={onChange}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</InputGroup>
|
||||
);
|
||||
};
|
@ -20,7 +20,6 @@ import {
|
||||
import {
|
||||
LuAlignJustify,
|
||||
LuArrowBigLeft,
|
||||
LuArrowRightSquare,
|
||||
LuArrowUpSquare,
|
||||
LuHelpCircle,
|
||||
LuHome,
|
||||
@ -44,9 +43,10 @@ export const TOP_BAR_HEIGHT = '50px';
|
||||
|
||||
export type TopBarProps = {
|
||||
children?: ReactNode;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export const TopBar = ({ children }: TopBarProps) => {
|
||||
export const TopBar = ({ title, children }: TopBarProps) => {
|
||||
const { mode, colorMode, toggleColorMode } = useThemeMode();
|
||||
const buttonProperty = {
|
||||
variant: '@menu',
|
||||
@ -86,69 +86,73 @@ export const TopBar = ({ children }: TopBarProps) => {
|
||||
boxShadow={'0px 2px 4px ' + colors.back[900]}
|
||||
zIndex={200}
|
||||
>
|
||||
<Button {...buttonProperty} onClick={onChangeTheme} marginRight="auto">
|
||||
<Button {...buttonProperty} onClick={onChangeTheme}>
|
||||
<LuAlignJustify />
|
||||
<Text paddingLeft="3px" fontWeight="bold">
|
||||
Menu
|
||||
Karusic
|
||||
</Text>
|
||||
</Button>
|
||||
{title && (
|
||||
<Text
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
marginRight="auto"
|
||||
userSelect="none"
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
{children}
|
||||
<Text
|
||||
fontSize="25px"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
marginRight="auto"
|
||||
userSelect="none"
|
||||
>
|
||||
Karusic
|
||||
</Text>
|
||||
{session?.state !== SessionState.CONNECTED && (
|
||||
<>
|
||||
<Button {...buttonProperty} onClick={onSignIn}>
|
||||
<LuLogIn />
|
||||
<Text paddingLeft="3px" fontWeight="bold">
|
||||
Sign-in
|
||||
</Text>
|
||||
</Button>
|
||||
<Button {...buttonProperty} onClick={onSignUp} disabled={true}>
|
||||
<LuPlusCircle />
|
||||
<Text paddingLeft="3px" fontWeight="bold">
|
||||
Sign-up
|
||||
</Text>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{session?.state === SessionState.CONNECTED && (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label="Options"
|
||||
icon={<LuUserCircle />}
|
||||
{...buttonProperty}
|
||||
width={TOP_BAR_HEIGHT}
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}>
|
||||
Sign in as {session?.login ?? 'Fail'}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<LuArrowUpSquare />}>Add Media</MenuItem>
|
||||
<MenuItem icon={<LuSettings />}>Settings</MenuItem>
|
||||
<MenuItem icon={<LuHelpCircle />}>Help</MenuItem>
|
||||
<MenuItem icon={<LuLogOut onClick={onSignOut} />}>
|
||||
Sign-out
|
||||
</MenuItem>
|
||||
{colorMode === 'light' ? (
|
||||
<MenuItem icon={<LuMoon />} onClick={toggleColorMode}>
|
||||
Set dark mode
|
||||
<Flex right="0">
|
||||
{session?.state !== SessionState.CONNECTED && (
|
||||
<>
|
||||
<Button {...buttonProperty} onClick={onSignIn}>
|
||||
<LuLogIn />
|
||||
<Text paddingLeft="3px" fontWeight="bold">
|
||||
Sign-in
|
||||
</Text>
|
||||
</Button>
|
||||
<Button {...buttonProperty} onClick={onSignUp} disabled={true}>
|
||||
<LuPlusCircle />
|
||||
<Text paddingLeft="3px" fontWeight="bold">
|
||||
Sign-up
|
||||
</Text>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{session?.state === SessionState.CONNECTED && (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label="Options"
|
||||
icon={<LuUserCircle />}
|
||||
{...buttonProperty}
|
||||
width={TOP_BAR_HEIGHT}
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}>
|
||||
Sign in as {session?.login ?? 'Fail'}
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem icon={<LuSun />} onClick={toggleColorMode}>
|
||||
Set light mode
|
||||
<MenuItem icon={<LuArrowUpSquare />}>Add Media</MenuItem>
|
||||
<MenuItem icon={<LuSettings />}>Settings</MenuItem>
|
||||
<MenuItem icon={<LuHelpCircle />}>Help</MenuItem>
|
||||
<MenuItem icon={<LuLogOut onClick={onSignOut} />}>
|
||||
Sign-out
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
{colorMode === 'light' ? (
|
||||
<MenuItem icon={<LuMoon />} onClick={toggleColorMode}>
|
||||
Set dark mode
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem icon={<LuSun />} onClick={toggleColorMode}>
|
||||
Set light mode
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</Flex>
|
||||
<Drawer
|
||||
placement="left"
|
||||
onClose={drawerDisclose.onClose}
|
||||
|
62
front2/src/components/gender/DisplayGender.tsx
Normal file
62
front2/src/components/gender/DisplayGender.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { LuDisc3 } from 'react-icons/lu';
|
||||
|
||||
import { Gender } from '@/back-api';
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { useCountTracksOfAGender } from '@/service/Track';
|
||||
|
||||
export type DisplayGenderProps = {
|
||||
dataGender?: Gender;
|
||||
};
|
||||
export const DisplayGender = ({ dataGender }: DisplayGenderProps) => {
|
||||
const { countTracksOnAGender } = useCountTracksOfAGender(dataGender?.id);
|
||||
if (!dataGender) {
|
||||
return (
|
||||
<Flex direction="row" width="full" height="full">
|
||||
Fail to retrieve Gender Data.
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Flex direction="row" width="full" height="full">
|
||||
<Covers
|
||||
data={dataGender?.covers}
|
||||
size="100"
|
||||
height="full"
|
||||
iconEmpty={<LuDisc3 size="100" height="full" />}
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
width="150px"
|
||||
maxWidth="150px"
|
||||
height="full"
|
||||
paddingLeft="5px"
|
||||
overflowX="hidden"
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={[1, 2]}
|
||||
>
|
||||
{dataGender?.name}
|
||||
</Text>
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="15px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
>
|
||||
{countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
10
front2/src/components/gender/DisplayGenderId.tsx
Normal file
10
front2/src/components/gender/DisplayGenderId.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { DisplayGender } from '@/components/gender/DisplayGender';
|
||||
import { useSpecificGender } from '@/service/Gender';
|
||||
|
||||
export type DisplayGenderIdProps = {
|
||||
id: number;
|
||||
};
|
||||
export const DisplayGenderId = ({ id }: DisplayGenderIdProps) => {
|
||||
const { dataGender } = useSpecificGender(id);
|
||||
return <DisplayGender dataGender={dataGender} />;
|
||||
};
|
@ -1,8 +1,11 @@
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { LuMusic2, LuPlay } from 'react-icons/lu';
|
||||
|
||||
import { Track } from '@/back-api';
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
|
||||
export type DisplayTrackProps = {
|
||||
@ -10,14 +13,19 @@ export type DisplayTrackProps = {
|
||||
};
|
||||
export const DisplayTrack = ({ track }: DisplayTrackProps) => {
|
||||
const { trackActive } = useActivePlaylistService();
|
||||
console.log(`Chnage : ${trackActive?.id} == ${track.id}`);
|
||||
return (
|
||||
<Flex direction="row" width="full" height="full">
|
||||
<Covers
|
||||
data={track?.covers}
|
||||
size="50"
|
||||
height="full"
|
||||
iconEmpty={trackActive?.id === track.id ? <LuPlay size="50" height="full" /> : <LuMusic2 size="50" height="full" />}
|
||||
iconEmpty={
|
||||
trackActive?.id === track.id ? (
|
||||
<LuPlay size="50" height="full" />
|
||||
) : (
|
||||
<LuMusic2 size="50" height="full" />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
@ -36,7 +44,7 @@ export const DisplayTrack = ({ track }: DisplayTrackProps) => {
|
||||
overflow="hidden"
|
||||
noOfLines={[1, 2]}
|
||||
marginY="auto"
|
||||
color={trackActive?.id === track.id ? "green.700" : undefined}
|
||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||
>
|
||||
[{track.track}] {track.name}
|
||||
</Text>
|
110
front2/src/components/track/DisplayTrackFull.tsx
Normal file
110
front2/src/components/track/DisplayTrackFull.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { LuMusic2, LuPlay } from 'react-icons/lu';
|
||||
|
||||
import { Track } from '@/back-api';
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSpecificAlbum } from '@/service/Album';
|
||||
import { useSpecificArtists } from '@/service/Artist';
|
||||
import { useSpecificGender } from '@/service/Gender';
|
||||
|
||||
export type DisplayTrackProps = {
|
||||
track: Track;
|
||||
};
|
||||
export const DisplayTrackFull = ({ track }: DisplayTrackProps) => {
|
||||
const { trackActive } = useActivePlaylistService();
|
||||
const { dataAlbum } = useSpecificAlbum(track?.albumId);
|
||||
const { dataGender } = useSpecificGender(track?.genderId);
|
||||
const { dataArtists } = useSpecificArtists(track?.artists);
|
||||
return (
|
||||
<Suspense fallback={<DisplayTrackSkeleton />}>
|
||||
<Flex direction="row" width="full" height="full">
|
||||
<Covers
|
||||
data={track?.covers}
|
||||
size="50"
|
||||
//height="full"
|
||||
marginY="auto"
|
||||
iconEmpty={
|
||||
trackActive?.id === track.id ? (
|
||||
<LuPlay size="50" />
|
||||
) : (
|
||||
<LuMusic2 size="50" />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
width="full"
|
||||
height="full"
|
||||
paddingLeft="5px"
|
||||
overflowX="hidden"
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||
>
|
||||
{track.name} {track.track && ` [${track.track}]`}
|
||||
</Text>
|
||||
{dataAlbum && (
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="15px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
marginY="auto"
|
||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||
>
|
||||
Album {dataAlbum.name}
|
||||
</Text>
|
||||
)}
|
||||
{dataArtists && (
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="15px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
marginY="auto"
|
||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||
>
|
||||
Artist(s): {dataArtists.map((data) => data.name).join(', ')}
|
||||
</Text>
|
||||
)}
|
||||
{dataGender && (
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="15px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
marginY="auto"
|
||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||
>
|
||||
Gender: {dataGender.name}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
30
front2/src/components/track/DisplayTrackSkeleton.tsx
Normal file
30
front2/src/components/track/DisplayTrackSkeleton.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { Flex, Skeleton, SkeletonText } from '@chakra-ui/react';
|
||||
|
||||
export const DisplayTrackSkeleton = () => {
|
||||
return (
|
||||
<Flex direction="row" width="full" height="full">
|
||||
<Skeleton
|
||||
borderRadius="0px"
|
||||
height="50"
|
||||
width="50"
|
||||
minWidth="50"
|
||||
minHeight="50"
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
width="full"
|
||||
height="full"
|
||||
paddingLeft="5px"
|
||||
overflowX="hidden"
|
||||
>
|
||||
<SkeletonText
|
||||
skeletonHeight="20px"
|
||||
noOfLines={1}
|
||||
spacing={0}
|
||||
width="50%"
|
||||
marginY="auto"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
27
front2/src/errors/Error401.tsx
Normal file
27
front2/src/errors/Error401.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { Box, Button, Center, Heading, Text } from '@chakra-ui/react';
|
||||
import { MdControlCamera } from 'react-icons/md';
|
||||
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
|
||||
export const Error401 = () => {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayoutInfoCenter padding="25px">
|
||||
<Center>
|
||||
<MdControlCamera size="250px" color="red.600" />
|
||||
</Center>
|
||||
<Box textAlign="center">
|
||||
<Heading>Erreur 401</Heading>
|
||||
<Text color="red.600">
|
||||
Vous n'êtes pas autorisé a accéder a ce contenu.
|
||||
</Text>
|
||||
<Button as="a" variant="link" href="/">
|
||||
Retour à l'accueil
|
||||
</Button>
|
||||
</Box>
|
||||
</PageLayoutInfoCenter>
|
||||
</>
|
||||
);
|
||||
};
|
25
front2/src/errors/Error403.tsx
Normal file
25
front2/src/errors/Error403.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { Box, Button, Center, Heading, Text } from '@chakra-ui/react';
|
||||
import { MdDangerous } from 'react-icons/md';
|
||||
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
|
||||
export const Error403 = () => {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayoutInfoCenter padding="25px">
|
||||
<Center>
|
||||
<MdDangerous size="250px" color="orange.600" />
|
||||
</Center>
|
||||
<Box textAlign="center">
|
||||
<Heading>Erreur 401</Heading>
|
||||
<Text color="orange.600">Cette page vous est interdite</Text>
|
||||
<Button as="a" variant="link" href="/">
|
||||
Retour à l'accueil
|
||||
</Button>
|
||||
</Box>
|
||||
</PageLayoutInfoCenter>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,128 +1,23 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Heading,
|
||||
Stack,
|
||||
Text,
|
||||
useTheme,
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Button, Center, Heading, Text } from '@chakra-ui/react';
|
||||
import { MdSignpost } from 'react-icons/md';
|
||||
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { environment } from '@/environment';
|
||||
|
||||
const Illustration = ({ colorScheme = 'gray', ...rest }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const color = theme?.colors?.[colorScheme] ?? {};
|
||||
return (
|
||||
<Box
|
||||
as="svg"
|
||||
width={400}
|
||||
height={300}
|
||||
maxW="full"
|
||||
viewBox="0 0 400 300"
|
||||
fill="none"
|
||||
{...rest}
|
||||
>
|
||||
<path
|
||||
// Left Hand
|
||||
d="M65.013 104.416s-12.773-.562-13.719 11.938c-.946 12.5 16.13 8.397 13.719-11.938z"
|
||||
fill={color['300']}
|
||||
/>
|
||||
<path
|
||||
// Left Arm
|
||||
d="M182.326 67.705s-35.463-20.529-67.804-13.535c-32.342 6.993-60.624 52.94-60.624 52.94l11.499 6.837s49.74-51.775 83.275-21.444c33.535 30.331 33.654-24.798 33.654-24.798z"
|
||||
fill={color['800']}
|
||||
/>
|
||||
<path
|
||||
// Search Zone
|
||||
d="M334.098 220.092a14.333 14.333 0 01-9.838-7.37v-.106l-50.465-96.17-27.774 19.677 19.642 61.796a10.575 10.575 0 01-5.617 12.799 10.563 10.563 0 01-4.945.97 1037.507 1037.507 0 00-47.278-1.067c-85.178 0-154.23 9.93-154.23 22.184 0 12.255 69.052 22.195 154.23 22.195C293.001 255 362 245.07 362 232.837c0-4.756-10.328-9.14-27.902-12.745z"
|
||||
fill={color['200']}
|
||||
/>
|
||||
<path
|
||||
// Foots
|
||||
d="M173.611 225.563s1.578 5.333 6.256 5.962c4.679.63 5.66 5.333 1.365 6.293-4.296.96-14.921-5.066-14.921-5.066l.671-6.773 6.629-.416zM82.518 224.657s-5.414 1.173-6.395 5.791c-.98 4.618-5.734 5.237-6.395.875-.66-4.362 6.193-14.484 6.193-14.484l6.693 1.205-.096 6.613z"
|
||||
fill={color['900']}
|
||||
/>
|
||||
<path
|
||||
// Left Leg
|
||||
d="M83.5 143s-5.245 25.322 12.713 35.305c17.959 9.983 74.606-7.988 65.856 48.592h12.64s16.338-46.928-26.048-63.609l-12.864-14.601L83.5 143z"
|
||||
fill={color['600']}
|
||||
/>
|
||||
<path
|
||||
// Magnifying Glass Shadow
|
||||
d="M257.632 128.216l-4.299-4.112-26.891 28.16 4.299 4.111 26.891-28.159z"
|
||||
fill={color['700']}
|
||||
/>
|
||||
<path
|
||||
// Magnifying Glass Handle
|
||||
d="M255.537 126.2l-4.299-4.112-26.891 28.16 4.299 4.111 26.891-28.159z"
|
||||
fill={color['500']}
|
||||
/>
|
||||
<path
|
||||
// Magnifying Glass Shadow 2
|
||||
d="M267.233 131.381c6.913-7.239 8.606-16.849 3.78-21.464-4.826-4.615-14.342-2.487-21.256 4.752-6.913 7.24-8.605 16.85-3.779 21.465 4.825 4.615 14.342 2.487 21.255-4.753z"
|
||||
fill={color['700']}
|
||||
/>
|
||||
<path
|
||||
// Magnifying Glass Ring
|
||||
d="M265.133 129.382c6.914-7.24 8.606-16.849 3.78-21.464-4.825-4.615-14.342-2.487-21.255 4.752-6.913 7.24-8.606 16.849-3.78 21.464 4.826 4.615 14.342 2.487 21.255-4.752z"
|
||||
fill={color['500']}
|
||||
/>
|
||||
<path
|
||||
// Magnifying Glass
|
||||
d="M262.167 126.545c4.566-4.782 5.685-11.13 2.497-14.178-3.187-3.048-9.473-1.642-14.04 3.14-4.567 4.782-5.685 11.13-2.498 14.178 3.188 3.048 9.474 1.642 14.041-3.14z"
|
||||
fill={color['50']}
|
||||
/>
|
||||
<path
|
||||
// Head
|
||||
d="M217.261 74.257c1.932-2.325 3.256-4.248 3.256-4.248 3.133-4.106-.267-11.743-6.096-12.084a7.606 7.606 0 00-7.759 4.095l-.966 2.572-19.039 7.678 2.664 15.443 14.063-11.483c.418 1.245 1.052 2.35 1.7 3.26a4.269 4.269 0 004.448 1.638 4.267 4.267 0 001.51-.7 25.197 25.197 0 002.341-1.98l1.613.893a1.364 1.364 0 002.014-1.067l.256-4.02-.005.003z"
|
||||
fill={color['300']}
|
||||
/>
|
||||
<path
|
||||
// Body
|
||||
d="M192.199 93.056l-1.791-10.42a25.45 25.45 0 00-15.251-19.325 45.122 45.122 0 00-10.754-2.827c-4.732-.66-11.361 0-18.779 1.952-31.953 8.394-55.4 35.484-60.25 68.141L83 145l52.797 3.687s-2.664-14.931 15.987-14.931 42.269.192 40.415-40.7z"
|
||||
fill={color['700']}
|
||||
/>
|
||||
<path
|
||||
// Right Arm
|
||||
d="M169.945 95.488c4.977-16.617-14.324-30.055-28.084-19.496-11.51 8.82-24.513 24.701-25.024 51.503-.885 48.368 45.413 39.718 108.071 21.192l-1.993-14.089s-75.032 14.196-64.843-10.207c4.263-10.206 9.23-20.061 11.873-28.903z"
|
||||
fill={color['800']}
|
||||
/>
|
||||
<path
|
||||
// Right Leg
|
||||
d="M143.609 163.406s1.545 53.487-60.995 63.491l-2.42-11.338s50.092-12.425 17.735-45.712l45.68-6.441z"
|
||||
fill={color['600']}
|
||||
/>
|
||||
307s17
|
||||
<path
|
||||
// Right Hand
|
||||
d="M223.298 137.307s17.244-1.824 18.758 4.917c1.513 6.741-1.791 10.313-17.309 5.333l-1.449-10.25z"
|
||||
fill={color['300']}
|
||||
/>
|
||||
<path
|
||||
// Hair
|
||||
d="M218.673 68.942a34.11 34.11 0 01-5.649-5.44 7.094 7.094 0 01-4.934 5.994 5.752 5.752 0 01-6.821-2.655l7.034-8.319a8.644 8.644 0 018.27-3.2c1.33.23 2.643.543 3.933.939 3.197 1.066 5.425 5.567 8.942 5.717a2.044 2.044 0 011.946 2.1c-.01.354-.111.7-.294 1.003-1.727 2.87-5.499 6.517-10.114 5.056a7.933 7.933 0 01-2.313-1.195z"
|
||||
fill={color['800']}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Error404 = () => {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayoutInfoCenter>
|
||||
<Illustration />
|
||||
<Box textAlign={{ base: 'center', md: 'left' }}>
|
||||
<PageLayoutInfoCenter padding="25px">
|
||||
<Center>
|
||||
<MdSignpost size="250px" />
|
||||
</Center>
|
||||
<Box textAlign="center">
|
||||
<Heading>Erreur 404</Heading>
|
||||
<Text color="gray.600">
|
||||
Cette page n'existe plus ou l'URL a changé
|
||||
</Text>
|
||||
<Button as="a" variant="link" href={`/${environment.applName}`}>
|
||||
<Button as="a" variant="link" href="/">
|
||||
Retour à l'accueil
|
||||
</Button>
|
||||
</Box>
|
||||
|
@ -1 +1,4 @@
|
||||
export * from './Error401';
|
||||
export * from './Error403';
|
||||
export * from './Error404';
|
||||
export * from './ErrorBoundary';
|
||||
|
@ -1,35 +1,13 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
import {
|
||||
unstable_HistoryRouter as HistoryRouter,
|
||||
Route,
|
||||
Routes,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { AudioPlayer } from '@/components/AudioPlayer';
|
||||
import { Error404 } from '@/errors';
|
||||
import { ErrorBoundary } from '@/errors/ErrorBoundary';
|
||||
import { AlbumRoutes } from '@/scene/album/AlbumRoutes';
|
||||
import { ArtistRoutes } from '@/scene/artist/ArtistRoutes';
|
||||
import { HomePage } from '@/scene/home/HomePage';
|
||||
import { SSORoutes } from '@/scene/sso/SSORoutes';
|
||||
import { AppRoutes } from '@/scene/AppRoutes';
|
||||
import { ServiceContextProvider } from '@/service/ServiceContext';
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<ServiceContextProvider>
|
||||
<ErrorBoundary>
|
||||
<HistoryRouter
|
||||
history={createBrowserHistory({ window })}
|
||||
basename="/karusic"
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="artist/*" element={<ArtistRoutes />} />
|
||||
<Route path="album/*" element={<AlbumRoutes />} />
|
||||
<Route path="sso/*" element={<SSORoutes />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</Routes>
|
||||
</HistoryRouter>
|
||||
<AppRoutes />
|
||||
</ErrorBoundary>
|
||||
<AudioPlayer />
|
||||
</ServiceContextProvider>
|
||||
|
45
front2/src/scene/AppRoutes.tsx
Normal file
45
front2/src/scene/AppRoutes.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
import {
|
||||
unstable_HistoryRouter as HistoryRouter,
|
||||
Route,
|
||||
Routes,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { Error401, Error404 } from '@/errors';
|
||||
import { AlbumRoutes } from '@/scene/album/AlbumRoutes';
|
||||
import { ArtistRoutes } from '@/scene/artist/ArtistRoutes';
|
||||
import { GenderRoutes } from '@/scene/gender/GenderRoutes';
|
||||
import { HelpPage } from '@/scene/home/Help';
|
||||
import { HomePage } from '@/scene/home/HomePage';
|
||||
import { SSORoutes } from '@/scene/sso/SSORoutes';
|
||||
import { TrackRoutes } from '@/scene/track/TrackRoutes';
|
||||
import { useHasRight } from '@/service/session';
|
||||
|
||||
export const AppRoutes = () => {
|
||||
const { isReadable } = useHasRight('user');
|
||||
return (
|
||||
<HistoryRouter
|
||||
// @ts-expect-error
|
||||
history={createBrowserHistory({ window })}
|
||||
basename="/karusic"
|
||||
>
|
||||
<Routes>
|
||||
{/* Need to keep it in all case, it is the only way to log-in */}
|
||||
<Route path="sso/*" element={<SSORoutes />} />
|
||||
{isReadable ? (
|
||||
<>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/help" element={<HelpPage />} />
|
||||
<Route path="artist/*" element={<ArtistRoutes />} />
|
||||
<Route path="album/*" element={<AlbumRoutes />} />
|
||||
<Route path="gender/*" element={<GenderRoutes />} />
|
||||
<Route path="track/*" element={<TrackRoutes />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</>
|
||||
) : (
|
||||
<Route path="*" element={<Error401 />} />
|
||||
)}
|
||||
</Routes>
|
||||
</HistoryRouter>
|
||||
);
|
||||
};
|
@ -3,11 +3,11 @@ import { LuDisc3 } from 'react-icons/lu';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { DisplayTrack } from '@/components/DisplayTrack';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DisplayTrack } from '@/components/track/DisplayTrack';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSpecificAlbum } from '@/service/Album';
|
||||
import { useTracksOfAnAlbum } from '@/service/Track';
|
||||
@ -25,7 +25,7 @@ export const AlbumDetailPage = () => {
|
||||
let currentPlay = 0;
|
||||
const listTrackId: number[] = [];
|
||||
if (!tracksOnAnAlbum) {
|
||||
console.log("Fail to get album...");
|
||||
console.log('Fail to get album...');
|
||||
return;
|
||||
}
|
||||
for (let iii = 0; iii < tracksOnAnAlbum.length; iii++) {
|
||||
@ -41,7 +41,7 @@ export const AlbumDetailPage = () => {
|
||||
if (!dataAlbum) {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="Album detail" />
|
||||
<PageLayoutInfoCenter>
|
||||
Fail to load artist id: {albumId}
|
||||
</PageLayoutInfoCenter>
|
||||
@ -50,7 +50,7 @@ export const AlbumDetailPage = () => {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="Album detail" />
|
||||
<PageLayout>
|
||||
<Flex
|
||||
direction="row"
|
||||
|
@ -1,38 +1,54 @@
|
||||
import { Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
InputRightElement,
|
||||
Text,
|
||||
Wrap,
|
||||
WrapItem,
|
||||
useOutsideClick,
|
||||
} from '@chakra-ui/react';
|
||||
import { MdSearch } from 'react-icons/md';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { SearchInput } from '@/components/SearchInput';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DisplayAlbum } from '@/components/album/DisplayAlbum';
|
||||
import { useAlbumService } from '@/service/Album';
|
||||
import { useAlbumService, useOrderedAlbums } from '@/service/Album';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
|
||||
export const AlbumsPage = () => {
|
||||
const { store } = useAlbumService();
|
||||
const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined);
|
||||
const { isLoading, dataAlbums } = useOrderedAlbums(filterTitle);
|
||||
const { mode } = useThemeMode();
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (albumId: number) => {
|
||||
navigate(`/album/${albumId}`);
|
||||
};
|
||||
|
||||
if (store.isLoading) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="All Albums" />
|
||||
<PageLayoutInfoCenter>No Album available</PageLayoutInfoCenter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="All Albums">
|
||||
<SearchInput onChange={setFilterTitle} />
|
||||
</TopBar>
|
||||
<PageLayout>
|
||||
<Text>All Albums:</Text>
|
||||
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center">
|
||||
{store.data?.map((data) => (
|
||||
{dataAlbums.map((data) => (
|
||||
<WrapItem
|
||||
width="270px"
|
||||
height="120px"
|
||||
|
@ -3,11 +3,11 @@ import { LuDisc3, LuUser } from 'react-icons/lu';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { DisplayTrack } from '@/components/DisplayTrack';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DisplayTrack } from '@/components/track/DisplayTrack';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSpecificAlbum } from '@/service/Album';
|
||||
import { useSpecificArtist } from '@/service/Artist';
|
||||
@ -28,7 +28,7 @@ export const ArtistAlbumDetailPage = () => {
|
||||
let currentPlay = 0;
|
||||
const listTrackId: number[] = [];
|
||||
if (!tracksOnAnAlbum) {
|
||||
console.error("Fail to get the album ...");
|
||||
console.error('Fail to get the album ...');
|
||||
return;
|
||||
}
|
||||
for (let iii = 0; iii < tracksOnAnAlbum.length; iii++) {
|
||||
@ -44,7 +44,7 @@ export const ArtistAlbumDetailPage = () => {
|
||||
if (!dataAlbum) {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="Album detail" />
|
||||
<PageLayoutInfoCenter>
|
||||
Fail to load artist id: {artistId}
|
||||
</PageLayoutInfoCenter>
|
||||
@ -53,7 +53,7 @@ export const ArtistAlbumDetailPage = () => {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="Album detail" />
|
||||
<PageLayout>
|
||||
<Flex
|
||||
direction="row"
|
||||
|
@ -3,6 +3,7 @@ import { LuUser } from 'react-icons/lu';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
@ -10,7 +11,6 @@ import { DisplayAlbumId } from '@/components/album/DisplayAlbumId';
|
||||
import { useSpecificArtist } from '@/service/Artist';
|
||||
import { useAlbumIdsOfAnArtist } from '@/service/Track';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
|
||||
export const ArtistDetailPage = () => {
|
||||
const { artistId } = useParams();
|
||||
@ -26,7 +26,7 @@ export const ArtistDetailPage = () => {
|
||||
if (!dataArtist) {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="Artist detail" />
|
||||
<PageLayoutInfoCenter>
|
||||
Fail to load artist id: {artistId}
|
||||
</PageLayoutInfoCenter>
|
||||
@ -35,7 +35,7 @@ export const ArtistDetailPage = () => {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="Artist detail" />
|
||||
<PageLayout>
|
||||
<Flex
|
||||
direction="row"
|
||||
|
@ -1,29 +1,34 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { LuUser } from 'react-icons/lu';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Artist } from '@/back-api';
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { useArtistService } from '@/service/Artist';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { SearchInput } from '@/components/SearchInput';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { useArtistService, useOrderedArtists } from '@/service/Artist';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export const ArtistsPage = () => {
|
||||
const { mode } = useThemeMode();
|
||||
const [filterName, setFilterName] = useState<string | undefined>(undefined);
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (data: Artist) => {
|
||||
navigate(`/artist/${data.id}/`);
|
||||
};
|
||||
const { store } = useArtistService();
|
||||
const { dataArtist } = useOrderedArtists(filterName);
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="All artists">
|
||||
<SearchInput onChange={setFilterName} />
|
||||
</TopBar>
|
||||
<PageLayout>
|
||||
<Text>All Artists:</Text>
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px">
|
||||
{store.data?.map((data) => (
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center">
|
||||
{dataArtist?.map((data) => (
|
||||
<WrapItem
|
||||
width="270px"
|
||||
height="120px"
|
||||
|
108
front2/src/scene/gender/GenderDetailPage.tsx
Normal file
108
front2/src/scene/gender/GenderDetailPage.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { Box, Flex, Text } from '@chakra-ui/react';
|
||||
import { LuDisc3 } from 'react-icons/lu';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DisplayTrack } from '@/components/track/DisplayTrack';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSpecificGender } from '@/service/Gender';
|
||||
import { useTracksOfAGender } from '@/service/Track';
|
||||
//import { useTracksOfAGender } from '@/service/Track';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export const GenderDetailPage = () => {
|
||||
const { genderId } = useParams();
|
||||
const genderIdInt = genderId ? parseInt(genderId, 10) : undefined;
|
||||
const { mode } = useThemeMode();
|
||||
const { playInList } = useActivePlaylistService();
|
||||
const { dataGender } = useSpecificGender(genderIdInt);
|
||||
const { tracksOnAGender } = useTracksOfAGender(genderIdInt);
|
||||
const onSelectItem = (trackId: number) => {
|
||||
//navigate(`/artist/${artistIdInt}/gender/${genderId}`);
|
||||
let currentPlay = 0;
|
||||
const listTrackId: number[] = [];
|
||||
if (!tracksOnAGender) {
|
||||
console.log('Fail to get gender...');
|
||||
return;
|
||||
}
|
||||
for (let iii = 0; iii < tracksOnAGender.length; iii++) {
|
||||
listTrackId.push(tracksOnAGender[iii].id);
|
||||
if (tracksOnAGender[iii].id === trackId) {
|
||||
currentPlay = iii;
|
||||
}
|
||||
}
|
||||
playInList(currentPlay, listTrackId);
|
||||
};
|
||||
|
||||
console.log(`dataGender = ${JSON.stringify(dataGender, null, 2)}`);
|
||||
if (!dataGender) {
|
||||
return (
|
||||
<>
|
||||
<TopBar title="Gender detail" />
|
||||
<PageLayoutInfoCenter>
|
||||
Fail to load artist id: {genderId}
|
||||
</PageLayoutInfoCenter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TopBar title="Gender detail" />
|
||||
<PageLayout>
|
||||
<Flex
|
||||
direction="row"
|
||||
width="80%"
|
||||
marginX="auto"
|
||||
padding="10px"
|
||||
gap="10px"
|
||||
>
|
||||
<Covers
|
||||
data={dataGender?.covers}
|
||||
iconEmpty={<LuDisc3 size="100" height="full" />}
|
||||
/>
|
||||
<Flex direction="column" width="80%" marginRight="auto">
|
||||
<Text fontSize="24px" fontWeight="bold">
|
||||
{dataGender?.name}
|
||||
</Text>
|
||||
{dataGender?.description && (
|
||||
<Text>Description: {dataGender?.description}</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="20px"
|
||||
marginX="auto"
|
||||
padding="20px"
|
||||
width="80%"
|
||||
>
|
||||
{tracksOnAGender?.map((data) => (
|
||||
<Box
|
||||
minWidth="100%"
|
||||
height="60px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data.id)}
|
||||
>
|
||||
<DisplayTrack track={data} />
|
||||
</Box>
|
||||
))}
|
||||
<EmptyEnd />
|
||||
</Flex>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
16
front2/src/scene/gender/GenderRoutes.tsx
Normal file
16
front2/src/scene/gender/GenderRoutes.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { Error404 } from '@/errors';
|
||||
import { GenderDetailPage } from '@/scene/gender/GenderDetailPage';
|
||||
import { GendersPage } from '@/scene/gender/GendersPage';
|
||||
|
||||
export const GenderRoutes = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="all" replace />} />
|
||||
<Route path="all" element={<GendersPage />} />
|
||||
<Route path=":genderId" element={<GenderDetailPage />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
64
front2/src/scene/gender/GendersPage.tsx
Normal file
64
front2/src/scene/gender/GendersPage.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { SearchInput } from '@/components/SearchInput';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DisplayGender } from '@/components/gender/DisplayGender';
|
||||
import { useOrderedGenders } from '@/service/Gender';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export const GendersPage = () => {
|
||||
const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined);
|
||||
const { isLoading, dataGenders } = useOrderedGenders(filterTitle);
|
||||
const { mode } = useThemeMode();
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (genderId: number) => {
|
||||
navigate(`/gender/${genderId}`);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<TopBar title="All Genders" />
|
||||
<PageLayoutInfoCenter>No Gender available</PageLayoutInfoCenter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBar title="All Genders">
|
||||
<SearchInput onChange={setFilterTitle} />
|
||||
</TopBar>
|
||||
<PageLayout>
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center">
|
||||
{dataGenders.map((data) => (
|
||||
<WrapItem
|
||||
width="270px"
|
||||
height="120px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data.id)}
|
||||
>
|
||||
<DisplayGender dataGender={data} />
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
<EmptyEnd />
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
91
front2/src/scene/home/Help.tsx
Normal file
91
front2/src/scene/home/Help.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { Center, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { LuCrown, LuDisc3, LuEar, LuFileAudio, LuUser } from 'react-icons/lu';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DataTools, TypeCheck } from '@/utils/data-tools';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
type HelpListType = {
|
||||
id: number;
|
||||
name: string;
|
||||
icon: ReactElement;
|
||||
to: string;
|
||||
};
|
||||
const helpList: HelpListType[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'plouf',
|
||||
icon: <LuCrown size="60%" height="full" />,
|
||||
to: 'gender',
|
||||
},
|
||||
];
|
||||
|
||||
export const HelpPage = async () => {
|
||||
const { mode } = useThemeMode();
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (data: HelpListType) => {
|
||||
navigate(data.to);
|
||||
};
|
||||
const testData = [
|
||||
{
|
||||
name: 'lkjlkj',
|
||||
},
|
||||
];
|
||||
const result = await DataTools.getsWhere(
|
||||
testData,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.STARTS_WITH,
|
||||
key: 'name',
|
||||
value: ['ll', 'k'],
|
||||
},
|
||||
],
|
||||
['track', 'name']
|
||||
);
|
||||
console.log(`startsWith : ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBar title="Help" />
|
||||
<PageLayout>
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center">
|
||||
{helpList.map((data) => (
|
||||
<WrapItem
|
||||
width="200px"
|
||||
height="190px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data)}
|
||||
>
|
||||
<Flex direction="column" width="full" height="full">
|
||||
<Center height="full">{data.icon}</Center>
|
||||
<Center>
|
||||
<Text
|
||||
fontSize="25px"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
userSelect="none"
|
||||
>
|
||||
{data.name}
|
||||
</Text>
|
||||
</Center>
|
||||
</Flex>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
@ -55,9 +55,9 @@ export const HomePage = () => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<TopBar title="Home" />
|
||||
<PageLayout>
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px">
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center">
|
||||
{homeList.map((data) => (
|
||||
<WrapItem
|
||||
width="200px"
|
||||
|
@ -25,7 +25,7 @@ export const SSOPage = () => {
|
||||
clearToken();
|
||||
}
|
||||
}, [token, setToken, clearToken]);
|
||||
const delay = isDevelopmentEnvironment() ? 20000 : 2000;
|
||||
const delay = isDevelopmentEnvironment() ? 6000 : 2000;
|
||||
useEffect(() => {
|
||||
if (state === SessionState.CONNECTED) {
|
||||
const destination = data ? b64_to_utf8(data) : '/';
|
||||
|
@ -2,19 +2,15 @@ import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { Error404 } from '@/errors';
|
||||
import { ArtistAlbumDetailPage } from '@/scene/artist/ArtistAlbumDetailPage';
|
||||
import { ArtistDetailPage } from '@/scene/artist/ArtistDetailPage';
|
||||
import { ArtistsPage } from '@/scene/artist/ArtistsPage';
|
||||
import { TrackSelectionPage } from '@/scene/track/TrackSelectionPage';
|
||||
import { TracksStartLetterDetailPage } from '@/scene/track/TracksStartLetterDetailPage';
|
||||
|
||||
export const ArtistRoutes = () => {
|
||||
export const TrackRoutes = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="all" replace />} />
|
||||
<Route path="all" element={<ArtistsPage />} />
|
||||
<Route path=":artistId" element={<ArtistDetailPage />} />
|
||||
<Route
|
||||
path=":artistId/album/:albumId"
|
||||
element={<ArtistAlbumDetailPage />}
|
||||
/>
|
||||
<Route path="all" element={<TrackSelectionPage />} />
|
||||
<Route path=":startLetter" element={<TracksStartLetterDetailPage />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</Routes>
|
||||
);
|
||||
|
85
front2/src/scene/track/TrackSelectionPage.tsx
Normal file
85
front2/src/scene/track/TrackSelectionPage.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DataTools, TypeCheck } from '@/utils/data-tools';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export const alphabet = [
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
];
|
||||
const possibilities = [...alphabet, '^^'];
|
||||
|
||||
export const TrackSelectionPage = () => {
|
||||
const { mode } = useThemeMode();
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (data: string) => {
|
||||
navigate(`/track/${data}`);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TopBar title="All Tracks" />
|
||||
<PageLayout>
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center">
|
||||
{possibilities.map((data) => (
|
||||
<WrapItem
|
||||
width="75px"
|
||||
height="75px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data)}
|
||||
>
|
||||
<Flex direction="column" width="full" height="full">
|
||||
<Text
|
||||
margin="auto"
|
||||
fontSize="25px"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
userSelect="none"
|
||||
>
|
||||
{data.toUpperCase()}
|
||||
</Text>
|
||||
</Flex>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
103
front2/src/scene/track/TracksStartLetterDetailPage.tsx
Normal file
103
front2/src/scene/track/TracksStartLetterDetailPage.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { Box, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DisplayTrack } from '@/components/track/DisplayTrack';
|
||||
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
|
||||
import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton';
|
||||
import { alphabet } from '@/scene/track/TrackSelectionPage';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useTrackService, useTracksWithStartName } from '@/service/Track';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export const TracksStartLetterDetailPage = () => {
|
||||
const { startLetter } = useParams();
|
||||
const { mode } = useThemeMode();
|
||||
const { isLoading, tracksStartsWith } = useTracksWithStartName(
|
||||
startLetter !== '^^'
|
||||
? startLetter
|
||||
? [startLetter.toLowerCase(), startLetter.toUpperCase()]
|
||||
: 'ZZZZ'
|
||||
: [...alphabet, ...alphabet.map((str) => str.toUpperCase())],
|
||||
startLetter === '^^'
|
||||
);
|
||||
const { playInList } = useActivePlaylistService();
|
||||
const onSelectItem = (trackId: number) => {
|
||||
//navigate(`/artist/${artistIdInt}/gender/${genderId}`);
|
||||
let currentPlay = 0;
|
||||
const listTrackId: number[] = [];
|
||||
if (!tracksStartsWith) {
|
||||
console.log('Fail to get tracks...');
|
||||
return;
|
||||
}
|
||||
for (let iii = 0; iii < tracksStartsWith.length; iii++) {
|
||||
listTrackId.push(tracksStartsWith[iii].id);
|
||||
if (tracksStartsWith[iii].id === trackId) {
|
||||
currentPlay = iii;
|
||||
}
|
||||
}
|
||||
playInList(currentPlay, listTrackId);
|
||||
};
|
||||
console.log(
|
||||
`Redraw ... isLoading=${isLoading} data=${tracksStartsWith?.length} ... ${Date()}`
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<TopBar title="All Tracks" />
|
||||
<PageLayout>
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="20px"
|
||||
marginX="auto"
|
||||
padding="20px"
|
||||
width="80%"
|
||||
>
|
||||
{isLoading &&
|
||||
[1, 2, 3, 4]?.map((data) => (
|
||||
<Box
|
||||
minWidth="100%"
|
||||
//height="60px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
>
|
||||
<DisplayTrackSkeleton />
|
||||
</Box>
|
||||
))}
|
||||
{!isLoading &&
|
||||
tracksStartsWith?.map((data) => (
|
||||
<Box
|
||||
minWidth="100%"
|
||||
//height="60px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data.id)}
|
||||
>
|
||||
<DisplayTrackFull track={data} />
|
||||
</Box>
|
||||
))}
|
||||
<EmptyEnd />
|
||||
</Flex>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
@ -4,6 +4,8 @@ import { Album, AlbumResource } from '@/back-api';
|
||||
import { useServiceContext } from '@/service/ServiceContext';
|
||||
import { SessionServiceProps } from '@/service/session';
|
||||
import { DataStoreType, useDataStore } from '@/utils/data-store';
|
||||
import { DataTools, TypeCheck } from '@/utils/data-tools';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
|
||||
export type AlbumServiceProps = {
|
||||
store: DataStoreType<Album>;
|
||||
@ -17,19 +19,43 @@ export const useAlbumService = (): AlbumServiceProps => {
|
||||
export const useAlbumServiceWrapped = (
|
||||
session: SessionServiceProps
|
||||
): AlbumServiceProps => {
|
||||
const store = useDataStore<Album>({
|
||||
restApiName: 'ALBUM',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return AlbumResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
const store = useDataStore<Album>(
|
||||
{
|
||||
restApiName: 'ALBUM',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return AlbumResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
[session.token]
|
||||
);
|
||||
|
||||
return { store };
|
||||
};
|
||||
|
||||
export const useOrderedAlbums = (titleFilter: string | undefined) => {
|
||||
const { store } = useAlbumService();
|
||||
const dataAlbums = useMemo(async () => {
|
||||
let tmpData = store.data;
|
||||
if (!isNullOrUndefined(titleFilter)) {
|
||||
tmpData = DataTools.getNameLike(tmpData, titleFilter);
|
||||
}
|
||||
return await DataTools.getsWhere(
|
||||
tmpData,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.NOT_EQUAL,
|
||||
key: 'id',
|
||||
value: [undefined, null],
|
||||
},
|
||||
],
|
||||
['name', 'id']
|
||||
);
|
||||
}, [store.data, titleFilter]);
|
||||
return { isLoading: store.isLoading, dataAlbums };
|
||||
};
|
||||
export const useSpecificAlbum = (id: number | undefined) => {
|
||||
const { store } = useAlbumService();
|
||||
const dataAlbum = useMemo(() => {
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { Artist, ArtistResource } from '@/back-api';
|
||||
import { useServiceContext } from '@/service/ServiceContext';
|
||||
import { SessionServiceProps } from '@/service/session';
|
||||
import { DataStoreType, useDataStore } from '@/utils/data-store';
|
||||
import { DataTools, TypeCheck } from '@/utils/data-tools';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
|
||||
export type ArtistServiceProps = {
|
||||
store: DataStoreType<Artist>;
|
||||
@ -17,19 +19,44 @@ export const useArtistService = (): ArtistServiceProps => {
|
||||
export const useArtistServiceWrapped = (
|
||||
session: SessionServiceProps
|
||||
): ArtistServiceProps => {
|
||||
const store = useDataStore<Artist>({
|
||||
restApiName: 'ARTIST',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return ArtistResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
const store = useDataStore<Artist>(
|
||||
{
|
||||
restApiName: 'ARTIST',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return ArtistResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
[session.token]
|
||||
);
|
||||
|
||||
return { store };
|
||||
};
|
||||
|
||||
export const useOrderedArtists = (nameFilter: string | undefined) => {
|
||||
const { store } = useArtistService();
|
||||
const dataArtist = useMemo(async () => {
|
||||
let tmpData = store.data;
|
||||
if (!isNullOrUndefined(nameFilter)) {
|
||||
tmpData = DataTools.getNameLike(tmpData, nameFilter);
|
||||
}
|
||||
return await DataTools.getsWhere(
|
||||
tmpData,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.NOT_EQUAL,
|
||||
key: 'id',
|
||||
value: [undefined, null],
|
||||
},
|
||||
],
|
||||
['name', 'id']
|
||||
);
|
||||
}, [store.data, nameFilter]);
|
||||
return { isLoading: store.isLoading, dataArtist };
|
||||
};
|
||||
|
||||
export const useSpecificArtist = (id: number | undefined) => {
|
||||
const { store } = useArtistService();
|
||||
const dataArtist = useMemo(() => {
|
||||
@ -37,3 +64,11 @@ export const useSpecificArtist = (id: number | undefined) => {
|
||||
}, [store.data, id]);
|
||||
return { dataArtist };
|
||||
};
|
||||
|
||||
export const useSpecificArtists = (ids: number[] | undefined) => {
|
||||
const { store } = useArtistService();
|
||||
const dataArtists = useMemo(() => {
|
||||
return store.gets(ids);
|
||||
}, [store.data, ids]);
|
||||
return { dataArtists };
|
||||
};
|
||||
|
73
front2/src/service/Gender.ts
Normal file
73
front2/src/service/Gender.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { Gender, GenderResource } from '@/back-api';
|
||||
import { useServiceContext } from '@/service/ServiceContext';
|
||||
import { SessionServiceProps } from '@/service/session';
|
||||
import { DataStoreType, useDataStore } from '@/utils/data-store';
|
||||
import { DataTools, TypeCheck } from '@/utils/data-tools';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
|
||||
export type GenderServiceProps = {
|
||||
store: DataStoreType<Gender>;
|
||||
};
|
||||
|
||||
export const useGenderService = (): GenderServiceProps => {
|
||||
const { gender } = useServiceContext();
|
||||
return gender;
|
||||
};
|
||||
|
||||
export const useGenderServiceWrapped = (
|
||||
session: SessionServiceProps
|
||||
): GenderServiceProps => {
|
||||
const store = useDataStore<Gender>(
|
||||
{
|
||||
restApiName: 'GENDER',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return GenderResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
},
|
||||
},
|
||||
[session.token]
|
||||
);
|
||||
|
||||
return { store };
|
||||
};
|
||||
|
||||
export const useOrderedGenders = (titleFilter: string | undefined) => {
|
||||
const { store } = useGenderService();
|
||||
const dataGenders = useMemo(async () => {
|
||||
let tmpData = store.data;
|
||||
if (!isNullOrUndefined(titleFilter)) {
|
||||
tmpData = DataTools.getNameLike(tmpData, titleFilter);
|
||||
}
|
||||
return await DataTools.getsWhere(
|
||||
tmpData,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.NOT_EQUAL,
|
||||
key: 'id',
|
||||
value: [undefined, null],
|
||||
},
|
||||
],
|
||||
['name', 'id']
|
||||
);
|
||||
}, [store.data, titleFilter]);
|
||||
return { isLoading: store.isLoading, dataGenders };
|
||||
};
|
||||
export const useSpecificGender = (id: number | undefined) => {
|
||||
const { store } = useGenderService();
|
||||
const dataGender = useMemo(() => {
|
||||
return store.get(id);
|
||||
}, [store.data, id]);
|
||||
return { dataGender };
|
||||
};
|
||||
|
||||
export const useGenderOfAnArtist = (idArtist: number | undefined) => {
|
||||
const { store } = useGenderService();
|
||||
const dataGender = useMemo(() => {
|
||||
return store.get(idArtist);
|
||||
}, [store.data, idArtist]);
|
||||
return { dataGender };
|
||||
};
|
@ -34,8 +34,8 @@ export class GenericDataService<TYPE> {
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
.gets()
|
||||
.then((response: TYPE[]) => {
|
||||
let data = DataTools.getsWhere(response, [
|
||||
.then(async (response: TYPE[]) => {
|
||||
let data = await DataTools.getsWhere(response, [
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'id',
|
||||
@ -76,8 +76,8 @@ export class GenericDataService<TYPE> {
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
.gets()
|
||||
.then((response: TYPE[]) => {
|
||||
let data = DataTools.getsWhere(
|
||||
.then(async (response: TYPE[]) => {
|
||||
let data = await DataTools.getsWhere(
|
||||
response,
|
||||
[
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from '@/service/ActivePlaylist';
|
||||
import { AlbumServiceProps, useAlbumServiceWrapped } from '@/service/Album';
|
||||
import { ArtistServiceProps, useArtistServiceWrapped } from '@/service/Artist';
|
||||
import { useGenderServiceWrapped } from '@/service/Gender';
|
||||
import { SessionState } from '@/service/SessionState';
|
||||
import { TrackServiceProps, useTrackServiceWrapped } from '@/service/Track';
|
||||
import {
|
||||
@ -20,13 +21,14 @@ export type ServiceContextType = {
|
||||
track: TrackServiceProps;
|
||||
artist: ArtistServiceProps;
|
||||
album: AlbumServiceProps;
|
||||
gender: AlbumServiceProps;
|
||||
activePlaylist: ActivePlaylistServiceProps;
|
||||
};
|
||||
|
||||
export const ServiceContext = createContext<ServiceContextType>({
|
||||
session: {
|
||||
setToken: (token: string) => { },
|
||||
clearToken: () => { },
|
||||
setToken: (token: string) => {},
|
||||
clearToken: () => {},
|
||||
hasReadRight: (part: RightPart) => false,
|
||||
hasWriteRight: (part: RightPart) => false,
|
||||
state: SessionState.NO_USER,
|
||||
@ -36,7 +38,14 @@ export const ServiceContext = createContext<ServiceContextType>({
|
||||
store: {
|
||||
data: [],
|
||||
isLoading: true,
|
||||
get: (value, key) => { console.error("!!! WTF !!!"); return undefined; },
|
||||
get: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return undefined;
|
||||
},
|
||||
gets: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return [];
|
||||
},
|
||||
error: undefined,
|
||||
},
|
||||
},
|
||||
@ -44,7 +53,14 @@ export const ServiceContext = createContext<ServiceContextType>({
|
||||
store: {
|
||||
data: [],
|
||||
isLoading: true,
|
||||
get: (value, key) => { console.error("!!! WTF !!!"); return undefined; },
|
||||
get: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return undefined;
|
||||
},
|
||||
gets: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return [];
|
||||
},
|
||||
error: undefined,
|
||||
},
|
||||
},
|
||||
@ -52,7 +68,29 @@ export const ServiceContext = createContext<ServiceContextType>({
|
||||
store: {
|
||||
data: [],
|
||||
isLoading: true,
|
||||
get: (value, key) => { console.error("!!! WTF !!!"); return undefined; },
|
||||
get: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return undefined;
|
||||
},
|
||||
gets: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return [];
|
||||
},
|
||||
error: undefined,
|
||||
},
|
||||
},
|
||||
gender: {
|
||||
store: {
|
||||
data: [],
|
||||
isLoading: true,
|
||||
get: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return undefined;
|
||||
},
|
||||
gets: (_value, _key) => {
|
||||
console.error('!!! WTF !!!');
|
||||
return [];
|
||||
},
|
||||
error: undefined,
|
||||
},
|
||||
},
|
||||
@ -60,13 +98,27 @@ export const ServiceContext = createContext<ServiceContextType>({
|
||||
playTrackList: [],
|
||||
trackOffset: undefined,
|
||||
trackActive: undefined,
|
||||
setNewPlaylist: (_listIds: number[]) => { console.error("!!! WTF !!!"); },
|
||||
setNewPlaylistShuffle: (_listIds: number[]) => { console.error("!!! WTF !!!"); },
|
||||
playInList: (_id: number, _listIds: number[]) => { console.error("!!! WTF !!!"); },
|
||||
play: (_id: number) => { console.error("!!! WTF !!!"); },
|
||||
previous: () => { console.error("!!! WTF !!!"); },
|
||||
next: () => { console.error("!!! WTF !!!"); },
|
||||
first: () => { console.error("!!! WTF !!!"); },
|
||||
setNewPlaylist: (_listIds: number[]) => {
|
||||
console.error('!!! WTF !!!');
|
||||
},
|
||||
setNewPlaylistShuffle: (_listIds: number[]) => {
|
||||
console.error('!!! WTF !!!');
|
||||
},
|
||||
playInList: (_id: number, _listIds: number[]) => {
|
||||
console.error('!!! WTF !!!');
|
||||
},
|
||||
play: (_id: number) => {
|
||||
console.error('!!! WTF !!!');
|
||||
},
|
||||
previous: () => {
|
||||
console.error('!!! WTF !!!');
|
||||
},
|
||||
next: () => {
|
||||
console.error('!!! WTF !!!');
|
||||
},
|
||||
first: () => {
|
||||
console.error('!!! WTF !!!');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -81,6 +133,7 @@ export const ServiceContextProvider = ({
|
||||
const track = useTrackServiceWrapped(session);
|
||||
const artist = useArtistServiceWrapped(session);
|
||||
const album = useAlbumServiceWrapped(session);
|
||||
const gender = useGenderServiceWrapped(session);
|
||||
const activePlaylist = useActivePlaylistServiceWrapped(track);
|
||||
const contextObjectData = useMemo(
|
||||
() => ({
|
||||
@ -89,6 +142,7 @@ export const ServiceContextProvider = ({
|
||||
artist,
|
||||
album,
|
||||
activePlaylist,
|
||||
gender,
|
||||
}),
|
||||
[session, track, artist, album]
|
||||
);
|
||||
|
@ -19,15 +19,18 @@ export const useTrackService = (): TrackServiceProps => {
|
||||
export const useTrackServiceWrapped = (
|
||||
session: SessionServiceProps
|
||||
): TrackServiceProps => {
|
||||
const store = useDataStore<Track>({
|
||||
restApiName: 'TRACK',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return TrackResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
const store = useDataStore<Track>(
|
||||
{
|
||||
restApiName: 'TRACK',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return TrackResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
[session.token]
|
||||
);
|
||||
|
||||
return { store };
|
||||
};
|
||||
@ -46,23 +49,29 @@ export const useSpecificTrack = (id: number | undefined) => {
|
||||
*/
|
||||
export const useTracksOfAnArtist = (idArtist?: number) => {
|
||||
const { store } = useTrackService();
|
||||
const tracksOnAnArtist = useMemo(() => {
|
||||
const [tracksOnAnArtist, setTracksOnAnArtist] = useState<Track[]>([]);
|
||||
useEffect(() => {
|
||||
if (idArtist) {
|
||||
return DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.CONTAINS,
|
||||
key: 'artists',
|
||||
value: idArtist,
|
||||
},
|
||||
],
|
||||
['track', 'name']
|
||||
)
|
||||
const tmpFunction = async () => {
|
||||
const data = await DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.CONTAINS,
|
||||
key: 'artists',
|
||||
value: idArtist,
|
||||
},
|
||||
],
|
||||
['track', 'name']
|
||||
);
|
||||
setTracksOnAnArtist(data);
|
||||
};
|
||||
tmpFunction();
|
||||
} else {
|
||||
setTracksOnAnArtist([]);
|
||||
}
|
||||
return [];
|
||||
}, [store.data, idArtist]);
|
||||
return { tracksOnAnArtist };
|
||||
}, [store.data, idArtist, setTracksOnAnArtist]);
|
||||
return { isLoading: store.isLoading, tracksOnAnArtist };
|
||||
};
|
||||
|
||||
/**
|
||||
@ -72,10 +81,59 @@ export const useTracksOfAnArtist = (idArtist?: number) => {
|
||||
*/
|
||||
export const useCountTracksOfAnArtist = (idArtist: number) => {
|
||||
const { tracksOnAnAlbum } = useTracksOfAnAlbum(idArtist);
|
||||
const countTracksOnAnArtist = useMemo(() => tracksOnAnAlbum?.length ?? 0, [tracksOnAnAlbum]);
|
||||
const countTracksOnAnArtist = useMemo(
|
||||
() => tracksOnAnAlbum?.length ?? 0,
|
||||
[tracksOnAnAlbum]
|
||||
);
|
||||
return { countTracksOnAnArtist };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all the track for a specific artist
|
||||
* @param idGender - Id of the artist.
|
||||
* @returns a promise on the list of track elements
|
||||
*/
|
||||
export const useTracksOfAGender = (idGender?: number) => {
|
||||
const { store } = useTrackService();
|
||||
const [tracksOnAGender, setTracksOnAGender] = useState<Track[]>([]);
|
||||
useEffect(() => {
|
||||
if (idGender) {
|
||||
const tmpFunction = async () => {
|
||||
const ret = await DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.CONTAINS,
|
||||
key: 'genderId',
|
||||
value: idGender,
|
||||
},
|
||||
],
|
||||
['name', 'track']
|
||||
);
|
||||
setTracksOnAGender(ret);
|
||||
};
|
||||
tmpFunction();
|
||||
} else {
|
||||
setTracksOnAGender([]);
|
||||
}
|
||||
}, [store.data, idGender]);
|
||||
return { tracksOnAGender };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of track in this artist ID
|
||||
* @param id - Id of the gender.
|
||||
* @returns The number of track present in this artist
|
||||
*/
|
||||
export const useCountTracksOfAGender = (idGender?: number) => {
|
||||
const { tracksOnAGender } = useTracksOfAGender(idGender);
|
||||
const countTracksOnAGender = useMemo(
|
||||
() => tracksOnAGender?.length ?? 0,
|
||||
[tracksOnAGender]
|
||||
);
|
||||
return { countTracksOnAGender };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all the album of a specific artist
|
||||
* @param artistId - ID of the artist
|
||||
@ -100,19 +158,26 @@ export const useAlbumIdsOfAnArtist = (idArtist?: number) => {
|
||||
|
||||
export const useTracksOfAnAlbum = (idAlbum?: number) => {
|
||||
const { store } = useTrackService();
|
||||
const tracksOnAnAlbum = useMemo(() => {
|
||||
const [tracksOnAnAlbum, setTracksOnAnAlbum] = useState<Track[]>();
|
||||
useEffect(() => {
|
||||
if (idAlbum) {
|
||||
return DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'albumId',
|
||||
value: idAlbum,
|
||||
},
|
||||
],
|
||||
['track', 'name', 'id']
|
||||
);
|
||||
const tmpFunction = async () => {
|
||||
const ret = await DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'albumId',
|
||||
value: idAlbum,
|
||||
},
|
||||
],
|
||||
['track', 'name', 'id']
|
||||
);
|
||||
setTracksOnAnAlbum(ret);
|
||||
};
|
||||
tmpFunction();
|
||||
} else {
|
||||
setTracksOnAnAlbum([]);
|
||||
}
|
||||
}, [store.data, idAlbum]);
|
||||
return { tracksOnAnAlbum };
|
||||
@ -125,7 +190,10 @@ export const useTracksOfAnAlbum = (idAlbum?: number) => {
|
||||
*/
|
||||
export const useCountTracksWithAlbumId = (albumId?: number) => {
|
||||
const { tracksOnAnAlbum } = useTracksOfAnAlbum(albumId);
|
||||
const countTracksOfAnAlbum = useMemo(() => tracksOnAnAlbum?.length ?? 0, [tracksOnAnAlbum]);
|
||||
const countTracksOfAnAlbum = useMemo(
|
||||
() => tracksOnAnAlbum?.length ?? 0,
|
||||
[tracksOnAnAlbum]
|
||||
);
|
||||
return { countTracksOfAnAlbum };
|
||||
};
|
||||
|
||||
@ -136,26 +204,64 @@ export const useCountTracksWithAlbumId = (albumId?: number) => {
|
||||
*/
|
||||
export const useTracksOfArtistWithNoAlbum = (idArtist: number) => {
|
||||
const { store } = useTrackService();
|
||||
const tracksOnAnArtistWithNoAlbum = useMemo(() => {
|
||||
const [tracksOnAnArtistWithNoAlbum, setTracksOnAnArtistWithNoAlbum] =
|
||||
useState<Track[]>([]);
|
||||
useEffect(() => {
|
||||
if (idArtist) {
|
||||
return DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.CONTAINS,
|
||||
key: 'artists',
|
||||
value: idArtist,
|
||||
},
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'albumId',
|
||||
value: undefined,
|
||||
},
|
||||
],
|
||||
['track', 'name']
|
||||
);
|
||||
const tmpFunction = async () => {
|
||||
const ret = await DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.CONTAINS,
|
||||
key: 'artists',
|
||||
value: idArtist,
|
||||
},
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'albumId',
|
||||
value: undefined,
|
||||
},
|
||||
],
|
||||
['track', 'name']
|
||||
);
|
||||
setTracksOnAnArtistWithNoAlbum(ret);
|
||||
};
|
||||
tmpFunction();
|
||||
} else {
|
||||
setTracksOnAnArtistWithNoAlbum([]);
|
||||
}
|
||||
return [];
|
||||
}, [store.data, idArtist]);
|
||||
return { tracksOnAnArtistWithNoAlbum };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all the track for a specific artist
|
||||
* @param startName - basic starting name.
|
||||
* @returns a promise on the list of track elements
|
||||
*/
|
||||
export const useTracksWithStartName = (
|
||||
startName: string | string[],
|
||||
invert: boolean
|
||||
) => {
|
||||
const { store } = useTrackService();
|
||||
const [tracksStartsWith, setTracksStartsWith] = useState<Track[]>([]);
|
||||
useEffect(() => {
|
||||
const tmpFunction = async () => {
|
||||
const ret = await DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: invert ? TypeCheck.STARTS_NOT_WITH : TypeCheck.STARTS_WITH,
|
||||
key: 'name',
|
||||
value: startName,
|
||||
},
|
||||
],
|
||||
['name', 'track']
|
||||
);
|
||||
setTracksStartsWith(ret);
|
||||
};
|
||||
tmpFunction();
|
||||
}, [store.data, startName, invert]);
|
||||
return { isLoading: store.isLoading, tracksStartsWith };
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { PartRight, RESTConfig, UserMe, UserResource } from '@/back-api';
|
||||
import { environment, getApiUrl } from '@/environment';
|
||||
@ -10,7 +10,7 @@ import { parseToken } from '@/utils/sso';
|
||||
const TOKEN_KEY = 'karusic-token-key-storage';
|
||||
|
||||
export const USERS = {
|
||||
ADMIN:
|
||||
admin:
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E',
|
||||
NO_USER: '',
|
||||
} as const;
|
||||
@ -18,7 +18,7 @@ export const USERS = {
|
||||
export const getUserToken = () => {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
};
|
||||
export type RightPart = 'ADMIN' | 'USER';
|
||||
export type RightPart = 'admin' | 'user';
|
||||
|
||||
export function getRestConfig(): RESTConfig {
|
||||
return {
|
||||
@ -71,13 +71,13 @@ export const useSessionServiceWrapped = (): SessionServiceProps => {
|
||||
restConfig: getRestConfig(),
|
||||
})
|
||||
.then((response: UserMe) => {
|
||||
console.log(` ==> New right arrived to '${response.login}'`);
|
||||
//console.log(` ==> New right arrived to '${response.login}'`);
|
||||
setState(SessionState.CONNECTED);
|
||||
setConfig(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
setState(SessionState.CONNECTION_FAIL);
|
||||
console.log(` ==> Fail to get right: '${error}'`);
|
||||
//console.log(` ==> Fail to get right: '${error}'`);
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
});
|
||||
}
|
||||
@ -96,6 +96,7 @@ export const useSessionServiceWrapped = (): SessionServiceProps => {
|
||||
}, [updateRight, setToken]);
|
||||
const hasReadRight = useCallback(
|
||||
(part: RightPart) => {
|
||||
//console.log(`config = ${JSON.stringify(config, null, 2)}`);
|
||||
const right = config?.rights[environment.applName];
|
||||
if (right === undefined) {
|
||||
return false;
|
||||
@ -137,3 +138,15 @@ export const useSessionServiceWrapped = (): SessionServiceProps => {
|
||||
getRestConfig,
|
||||
};
|
||||
};
|
||||
|
||||
export const useHasRight = (part: RightPart) => {
|
||||
const { token, hasReadRight, hasWriteRight } = useSessionService();
|
||||
const isReadable = useMemo(() => {
|
||||
console.log(`get is read for: ${part} ==> ${hasReadRight(part)}`);
|
||||
return hasReadRight(part);
|
||||
}, [token, hasReadRight, part]);
|
||||
const isWritable = useMemo(() => {
|
||||
return hasWriteRight(part);
|
||||
}, [token, hasWriteRight, part]);
|
||||
return { isReadable, isWritable };
|
||||
};
|
||||
|
@ -3,7 +3,7 @@
|
||||
* @copyright 2024, Edouard DUPIN, all right reserved
|
||||
* @license PROPRIETARY (see license file)
|
||||
*/
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { DependencyList, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { RestErrorResponse } from '@/back-api';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
@ -13,17 +13,25 @@ export type DataStoreType<TYPE> = {
|
||||
error: RestErrorResponse | undefined;
|
||||
data: TYPE[];
|
||||
get: <MODEL>(value: MODEL, key?: string) => TYPE | undefined;
|
||||
gets: <MODEL>(value: MODEL[] | undefined, key?: string) => TYPE[];
|
||||
};
|
||||
|
||||
export const useDataStore = <TYPE>({
|
||||
primaryKey = 'id',
|
||||
restApiName,
|
||||
getsCall,
|
||||
}: {
|
||||
restApiName?: string;
|
||||
primaryKey?: string;
|
||||
getsCall: () => Promise<TYPE[]>;
|
||||
}): DataStoreType<TYPE> => {
|
||||
function delay(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const useDataStore = <TYPE>(
|
||||
{
|
||||
primaryKey = 'id',
|
||||
restApiName,
|
||||
getsCall,
|
||||
}: {
|
||||
restApiName?: string;
|
||||
primaryKey?: string;
|
||||
getsCall: () => Promise<TYPE[]>;
|
||||
},
|
||||
deps: DependencyList = []
|
||||
): DataStoreType<TYPE> => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<RestErrorResponse | undefined>(undefined);
|
||||
const [data, setData] = useState<TYPE[]>([]);
|
||||
@ -34,10 +42,13 @@ export const useDataStore = <TYPE>({
|
||||
setError(undefined);
|
||||
setIsLoading(true);
|
||||
getsCall()
|
||||
.then((response: TYPE[]) => {
|
||||
console.log(
|
||||
.then(async (response: TYPE[]) => {
|
||||
/*console.log(
|
||||
`[${restApiName}] getData Response: ${JSON.stringify(response, null, 2)}`
|
||||
);
|
||||
);*/
|
||||
console.log(`[${restApiName}] ${Date()} Receive data done (wait 10s)`);
|
||||
await delay(10000);
|
||||
console.log(`[${restApiName}] ${Date()} Ready`);
|
||||
setData(response);
|
||||
setError(undefined);
|
||||
setIsLoading(false);
|
||||
@ -49,7 +60,7 @@ export const useDataStore = <TYPE>({
|
||||
setError(error);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [setIsLoading, setData]);
|
||||
}, [setIsLoading, setData, ...deps]);
|
||||
|
||||
const get = useCallback(
|
||||
<MODEL>(value: MODEL, key?: string): TYPE | undefined => {
|
||||
@ -63,6 +74,22 @@ export const useDataStore = <TYPE>({
|
||||
},
|
||||
[data]
|
||||
);
|
||||
const gets = useCallback(
|
||||
<MODEL>(value: MODEL[] | undefined, key?: string): TYPE[] => {
|
||||
const keyValue = key ?? primaryKey;
|
||||
const out: TYPE[] = [];
|
||||
if (isNullOrUndefined(value)) {
|
||||
return out;
|
||||
}
|
||||
for (let iii = 0; iii < data.length; iii++) {
|
||||
if (value.includes(data[iii][keyValue])) {
|
||||
out.push(data[iii]);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
(value: TYPE, key?: string) => {
|
||||
@ -76,5 +103,5 @@ export const useDataStore = <TYPE>({
|
||||
[data, setData]
|
||||
);
|
||||
|
||||
return { isLoading, error, data, get }; //, update};
|
||||
return { isLoading, error, data, get, gets }; //, update};
|
||||
};
|
||||
|
@ -3,7 +3,12 @@
|
||||
* @copyright 2018, Edouard DUPIN, all right reserved
|
||||
* @license PROPRIETARY (see license file)
|
||||
*/
|
||||
import { isArray, isNullOrUndefined } from '@/utils/validator';
|
||||
import {
|
||||
isArray,
|
||||
isArrayOf,
|
||||
isNullOrUndefined,
|
||||
isString,
|
||||
} from '@/utils/validator';
|
||||
|
||||
export enum TypeCheck {
|
||||
CONTAINS = 'C',
|
||||
@ -13,6 +18,8 @@ export enum TypeCheck {
|
||||
LESS_EQUAL = '<=',
|
||||
GREATER = '>',
|
||||
GREATER_EQUAL = '>=',
|
||||
STARTS_WITH = 'START',
|
||||
STARTS_NOT_WITH = '!START',
|
||||
}
|
||||
|
||||
export interface SelectModel {
|
||||
@ -78,13 +85,15 @@ export namespace DataTools {
|
||||
bdd: TYPE[],
|
||||
select: SelectModel[],
|
||||
orderByData?: string[]
|
||||
): TYPE[] {
|
||||
// console.log("[I] gets_where " + this.name + " select " + _select);
|
||||
let tmpList = getSubList(bdd, select);
|
||||
if (tmpList && orderByData) {
|
||||
tmpList = orderBy(tmpList, orderByData);
|
||||
}
|
||||
return tmpList;
|
||||
): Promise<TYPE[]> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
// console.log("[I] gets_where " + this.name + " select " + _select);
|
||||
let tmpList = getSubList(bdd, select);
|
||||
if (tmpList && orderByData) {
|
||||
tmpList = orderBy(tmpList, orderByData);
|
||||
}
|
||||
resolve(tmpList);
|
||||
});
|
||||
}
|
||||
|
||||
export function get<TYPE>(
|
||||
@ -123,7 +132,7 @@ export namespace DataTools {
|
||||
let nameLower = name.toLowerCase();
|
||||
for (const item of bdd) {
|
||||
// console.log("compare '" + _name + "' ??? '" + v['name'] + "'");
|
||||
if (item['name'].toLowerCase() === nameLower) {
|
||||
if (item['name'].toLowerCase().includes(nameLower)) {
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
@ -157,7 +166,26 @@ export namespace DataTools {
|
||||
}
|
||||
return tmp.length;
|
||||
}
|
||||
|
||||
export function startsWith<TYPE>(
|
||||
value: TYPE,
|
||||
listValues: TYPE | TYPE[]
|
||||
): boolean {
|
||||
if (!isString(value)) {
|
||||
return false;
|
||||
}
|
||||
if (isArrayOf(listValues, isString)) {
|
||||
for (const item of listValues) {
|
||||
if (value.startsWith(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (isString(listValues)) {
|
||||
return value.startsWith(listValues);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function existIn<TYPE>(
|
||||
value: TYPE,
|
||||
listValues: TYPE | TYPE[]
|
||||
@ -203,8 +231,8 @@ export namespace DataTools {
|
||||
let valueElement = values[iiiElem][control.key];
|
||||
//console.log(" valueElement : " + JSON.stringify(valueElement, null, 2));
|
||||
if (isArray(control.value)) {
|
||||
//console.log(`check ${control.check} ... ${valueElement} in ${control.value}`);
|
||||
if (control.check === TypeCheck.CONTAINS) {
|
||||
//console.log("check contains ... " + valueElement + " contains one element in " + control.value);
|
||||
if (
|
||||
DataTools.containsOneIn(valueElement, control.value) === false
|
||||
) {
|
||||
@ -222,6 +250,16 @@ export namespace DataTools {
|
||||
find = true;
|
||||
break;
|
||||
}
|
||||
} else if (control.check === TypeCheck.STARTS_WITH) {
|
||||
if (DataTools.startsWith(valueElement, control.value) === false) {
|
||||
find = false;
|
||||
break;
|
||||
}
|
||||
} else if (control.check === TypeCheck.STARTS_NOT_WITH) {
|
||||
if (DataTools.startsWith(valueElement, control.value) === true) {
|
||||
find = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
'[ERROR] Internal Server Error{ unknown comparing type ...'
|
||||
@ -268,6 +306,19 @@ export namespace DataTools {
|
||||
find = false;
|
||||
break;
|
||||
}
|
||||
} else if (control.check === TypeCheck.STARTS_WITH) {
|
||||
console.log(
|
||||
`AA start with : ${valueElement} with ${control.value}`
|
||||
);
|
||||
if (DataTools.startsWith(valueElement, control.value) === false) {
|
||||
find = false;
|
||||
break;
|
||||
}
|
||||
} else if (control.check === TypeCheck.STARTS_NOT_WITH) {
|
||||
if (DataTools.startsWith(valueElement, control.value) === true) {
|
||||
find = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
'[ERROR] Internal Server Error{ unknown comparing type ...'
|
||||
|
Loading…
Reference in New Issue
Block a user