Compare commits
	
		
			3 Commits
		
	
	
		
			develop
			...
			feat_raw_r
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 979ec4b576 | |||
| 12223347d3 | |||
| f9019ec508 | 
| @@ -29,16 +29,7 @@ | ||||
|     "*.{ts,tsx,js,jsx,json}": "prettier --write" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@chakra-ui/anatomy": "2.3.4", | ||||
|     "@chakra-ui/cli": "3.3.1", | ||||
|     "@chakra-ui/react": "3.3.1", | ||||
|     "@emotion/react": "11.14.0", | ||||
|     "@emotion/styled": "11.14.0", | ||||
|     "allotment": "1.20.2", | ||||
|     "css-mediaquery": "0.1.2", | ||||
|     "dayjs": "1.11.13", | ||||
|     "history": "5.3.0", | ||||
|     "next-themes": "^0.4.4", | ||||
|     "react": "18.3.1", | ||||
|     "react-dom": "18.3.1", | ||||
|     "react-error-boundary": "5.0.0", | ||||
| @@ -50,7 +41,6 @@ | ||||
|     "zustand": "5.0.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@chakra-ui/styled-system": "^2.12.0", | ||||
|     "@playwright/test": "1.49.1", | ||||
|     "@storybook/addon-actions": "8.4.7", | ||||
|     "@storybook/addon-essentials": "8.4.7", | ||||
| @@ -82,7 +72,6 @@ | ||||
|     "lint-staged": "15.3.0", | ||||
|     "npm-check-updates": "^17.1.13", | ||||
|     "prettier": "3.4.2", | ||||
|     "puppeteer": "24.0.0", | ||||
|     "react-is": "19.0.0", | ||||
|     "storybook": "8.4.7", | ||||
|     "ts-node": "10.9.2", | ||||
|   | ||||
							
								
								
									
										2104
									
								
								front/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2104
									
								
								front/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,31 +1,10 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Box, | ||||
|   Button, | ||||
|   ChakraProvider, | ||||
|   DialogBody, | ||||
|   DialogContent, | ||||
|   DialogFooter, | ||||
|   DialogHeader, | ||||
|   DialogRoot, | ||||
|   DialogTrigger, | ||||
|   SelectContent, | ||||
|   SelectItem, | ||||
|   SelectRoot, | ||||
|   SelectTrigger, | ||||
|   SelectValueText, | ||||
|   Stack, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { environment } from '@/environment'; | ||||
| import { App as SpaApp } from '@/scene/App'; | ||||
| import { USERS, USERS_COLLECTION } from '@/service/session'; | ||||
| import { hashLocalData } from '@/utils/sso'; | ||||
| import { Toaster } from './components/ui/toaster'; | ||||
| import { systemTheme } from './theme/theme'; | ||||
| import { Div, FullPage } from './ui'; | ||||
| import { useDisclosure } from './utils/disclosure'; | ||||
| import { useState } from 'react'; | ||||
| import { environment } from './environment'; | ||||
| import { hashLocalData } from './utils/sso'; | ||||
| import { USERS } from './service/session'; | ||||
|  | ||||
| const AppEnvHint = () => { | ||||
|   const dialog = useDisclosure(); | ||||
| @@ -55,66 +34,70 @@ const AppEnvHint = () => { | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <Box | ||||
|         zIndex="100000" | ||||
|         position="fixed" | ||||
|         top="0" | ||||
|         insetStart="0" | ||||
|         insetEnd="0" | ||||
|         h="2px" | ||||
|         bg="warning.400" | ||||
|         as="button" | ||||
|         cursor="pointer" | ||||
|       <Div style={{ | ||||
|         zIndex: "100000", | ||||
|         position: "fixed", | ||||
|         top: "0", | ||||
|         height: "2px", | ||||
|         width: "100%", | ||||
|         background: "warning.400", | ||||
|         cursor: "pointer" | ||||
|       }} | ||||
|         data-test-id="devtools" | ||||
|         onClick={dialog.onOpen} | ||||
|       > | ||||
|         <Text | ||||
|           position="fixed" | ||||
|           top="0" | ||||
|           insetStart="4" | ||||
|           bg="warning.400" | ||||
|           color="warning.900" | ||||
|           fontSize="0.6rem" | ||||
|           fontWeight="bold" | ||||
|           px="10px" | ||||
|           marginLeft="25%" | ||||
|           borderBottomStartRadius="sm" | ||||
|           borderBottomEndRadius="sm" | ||||
|           textTransform="uppercase" | ||||
|         <Div style={{ | ||||
|           position: "fixed", | ||||
|           top: "0", | ||||
|           background: "warning.400", | ||||
|           color: "warning.900", | ||||
|           fontSize: "0.6rem", | ||||
|           fontWeight: "bold", | ||||
|           paddingLeft: "10px", | ||||
|           paddingRight: "10px", | ||||
|           marginLeft: "25%", | ||||
|           borderRadius: "0 0 10px 10px", | ||||
|           textTransform: "uppercase", | ||||
|         }} | ||||
|         > | ||||
|           {envName.join(' : ')} | ||||
|         </Text> | ||||
|       </Box> | ||||
|       <DialogRoot open={dialog.open} onOpenChange={dialog.onClose}> | ||||
|         <DialogContent> | ||||
|           <DialogHeader>Outils développeurs</DialogHeader> | ||||
|           <DialogTrigger asChild> | ||||
|             <Button variant="outline" size="sm"> | ||||
|               {dialog.open ? "Close" : "Open"} Dialog | ||||
|             </Button> | ||||
|           </DialogTrigger> | ||||
|           <DialogBody> | ||||
|             <Stack> | ||||
|               <Text>User</Text> | ||||
|               <SelectRoot onChange={handleChange} collection={USERS_COLLECTION}> | ||||
|                 <SelectTrigger> | ||||
|                   <SelectValueText placeholder="Select test user" /> | ||||
|                 </SelectTrigger> | ||||
|                 <SelectContent> | ||||
|                   {USERS_COLLECTION.items.map((value) => ( | ||||
|                     <SelectItem item={value} key={value.value}> | ||||
|                       {value.label} | ||||
|                     </SelectItem> | ||||
|                   ))} | ||||
|                 </SelectContent> | ||||
|               </SelectRoot> | ||||
|             </Stack> | ||||
|           </DialogBody> | ||||
|           <DialogFooter> | ||||
|             <Button onClick={onClose}>Apply</Button> | ||||
|           </DialogFooter> | ||||
|         </DialogContent> | ||||
|       </DialogRoot> | ||||
|         </Div> | ||||
|       </Div> | ||||
|       {/* <Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}> | ||||
|         <Dialog.Trigger asChild> | ||||
|           <Button> | ||||
|             {dialog.open ? "Close" : "Open"} Dialog | ||||
|           </Button> | ||||
|         </Dialog.Trigger> | ||||
|         <Portal> | ||||
|           <Dialog.Backdrop /> | ||||
|           <Dialog.Positioner> | ||||
|             <Dialog.Content> | ||||
|               <Dialog.Title>Outils développeurs</Dialog.Title> | ||||
|               <Dialog.Description> | ||||
|                 <HStack> | ||||
|                   <Text>User</Text> | ||||
|                   <Select.Root onChange={handleChange} collection={USERS_COLLECTION}> | ||||
|                     <Select.Trigger> | ||||
|                       <SelectValueText placeholder="Select test user" /> | ||||
|                     </Select.Trigger> | ||||
|                     <Select.Content> | ||||
|                       {USERS_COLLECTION.items.map((value) => ( | ||||
|                         <Select.Item item={value} key={value.value}> | ||||
|                           {value.label} | ||||
|                         </Select.Item> | ||||
|                       ))} | ||||
|                     </Select.Content> | ||||
|                   </Select.Root> | ||||
|                 </HStack> | ||||
|               </Dialog.Description> | ||||
|               <Dialog.CloseTrigger> | ||||
|                 <Button onClick={onClose}>Apply</Button> | ||||
|               </Dialog.CloseTrigger> | ||||
|             </Dialog.Content> | ||||
|           </Dialog.Positioner> | ||||
|         </Portal> | ||||
|       </Dialog.Root> */} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -122,11 +105,11 @@ const AppEnvHint = () => { | ||||
| const App = () => { | ||||
|  | ||||
|   return ( | ||||
|     <ChakraProvider value={systemTheme}> | ||||
|     <FullPage data-test-id="Full-root-page"> | ||||
|       <AppEnvHint /> | ||||
|       <SpaApp /> | ||||
|       <Toaster /> | ||||
|     </ChakraProvider> | ||||
|       <SpaApp data-test-id="app" /> | ||||
|       {/* <Toaster /> */} | ||||
|     </FullPage> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1,16 +1,6 @@ | ||||
| import { SyntheticEvent, useEffect, useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Box, | ||||
|   Button, | ||||
|   Flex, | ||||
|   IconButton, | ||||
|   Slider, | ||||
|   SliderRoot, | ||||
|   SliderThumb, | ||||
|   SliderTrack, | ||||
|   Text, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   MdFastForward, | ||||
|   MdFastRewind, | ||||
| @@ -32,9 +22,10 @@ import { useSpecificArtists } from '@/service/Artist'; | ||||
| import { useSpecificGender } from '@/service/Gender'; | ||||
| import { useSpecificTrack } from '@/service/Track'; | ||||
| import { DataUrlAccess } from '@/utils/data-url-access'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Icon } from './Icon'; | ||||
| import { Flex, Text } from '@/ui'; | ||||
|  | ||||
| export enum PlayMode { | ||||
|   PLAY_ONE, | ||||
| @@ -86,10 +77,10 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => { | ||||
|         : '' | ||||
|     ); | ||||
|   }, [dataTrack, setMediaSource]); | ||||
|   const backColor = useColorModeValue('back.100', 'back.800'); | ||||
|   const backColor = useColorThemeValue('back.100', 'back.800'); | ||||
|   const configButton = { | ||||
|     borderRadius: 'full', | ||||
|     backgroundColor: '#00000000', | ||||
|     background: '#00000000', | ||||
|     _hover: { | ||||
|       boxShadow: 'outline-over', | ||||
|       bgColor: 'brand.500', | ||||
| @@ -213,47 +204,53 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => { | ||||
|     <> | ||||
|       {!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" | ||||
|           style={{ | ||||
|             position: "absolute", | ||||
|             height: "150px", | ||||
|             minHeight: "150px", | ||||
|             padding: "5px 10px 5px 10px", | ||||
|             margin: "0 15px 0 15px", | ||||
|             bottom: 0, | ||||
|             left: 0, | ||||
|             right: 0, | ||||
|             zIndex: 1000, | ||||
|             borderWidth: "1px", | ||||
|             borderColor: "brand.900", | ||||
|             background: backColor, | ||||
|             borderRadius: "10px 10px 0 0", | ||||
|           }} | ||||
|           direction="column" | ||||
|         > | ||||
|           <Text | ||||
|             alignContent="left" | ||||
|             fontSize="20px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|             }} | ||||
|           // noOfLines={1} | ||||
|           > | ||||
|             {dataTrack?.name ?? '???'} | ||||
|           </Text> | ||||
|           <Text | ||||
|             alignContent="left" | ||||
|             fontSize="16px" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|           // noOfLines={1} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               //noOfLines:1 | ||||
|             }} | ||||
|           > | ||||
|             {dataArtists.map((data) => data.name).join(', ')} /{' '} | ||||
|             {dataAlbum && dataAlbum?.name} | ||||
|             {dataGender && ` / ${dataGender.name}`} | ||||
|           </Text> | ||||
|           <Box width="full" paddingX="15px"> | ||||
|             <Slider.Root | ||||
|           <Flex style={{ width: "100%", padding: "0 15px 0 15px" }}> | ||||
|             <>TODO ... </> | ||||
|             {/* <Slider.Root | ||||
|               defaultValue={[0]} | ||||
|               value={[timeProgress]} | ||||
|               min={0} | ||||
| @@ -264,26 +261,28 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => { | ||||
|             // focusThumbOnChange={false} | ||||
|             > | ||||
|               <SliderTrack bg="gray.200" height="10px" borderRadius="full"> | ||||
|                 {/* <SliderFilledTrack bg="brand.600" /> */} | ||||
|                 {/ * <SliderFilledTrack bg="brand.600" /> * /} | ||||
|               </SliderTrack> | ||||
|             </Slider.Root> | ||||
|           </Box> | ||||
|             </Slider.Root> */} | ||||
|           </Flex> | ||||
|           <Flex> | ||||
|             <Text | ||||
|               alignContent="left" | ||||
|               fontSize="16px" | ||||
|               userSelect="none" | ||||
|               marginRight="auto" | ||||
|               overflow="hidden" | ||||
|             // noOfLines={1} | ||||
|               style={{ | ||||
|                 alignContent: "left", | ||||
|                 userSelect: "none", | ||||
|                 marginRight: "auto", | ||||
|                 overflow: "hidden", | ||||
|                 // noOfLines={1}, | ||||
|               }} | ||||
|             > | ||||
|               {formatTime(timeProgress)} | ||||
|             </Text> | ||||
|             <Text alignContent="left" fontSize="16px" userSelect="none"> | ||||
|             <Text fontSize="16px" style={{ alignContent: "left", userSelect: "none" }}> | ||||
|               {formatTime(duration)} | ||||
|             </Text> | ||||
|           </Flex> | ||||
|           <Flex gap="5px"> | ||||
|           {/* <Flex gap="5px"> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'Play'} | ||||
| @@ -327,7 +326,7 @@ export const AudioPlayer = ({ }: AudioPlayerProps) => { | ||||
|               aria-label={'continue to the end'} | ||||
|               onClick={onTypePlay} | ||||
|             >{playModeIcon[playingMode]}</IconButton> | ||||
|           </Flex> | ||||
|           </Flex> */} | ||||
|         </Flex> | ||||
|       )} | ||||
|  | ||||
|   | ||||
| @@ -1,15 +1,13 @@ | ||||
| import { ReactElement, useEffect, useState } from 'react'; | ||||
|  | ||||
| import { Box, BoxProps, Flex, FlexProps } from '@chakra-ui/react'; | ||||
| import { Image } from '@chakra-ui/react'; | ||||
| import { CSSProperties, ReactElement, useEffect, useState } from 'react'; | ||||
|  | ||||
| import { DataUrlAccess } from '@/utils/data-url-access'; | ||||
| import { Icon } from './Icon'; | ||||
| import { ObjectId } from '@/back-api'; | ||||
| import { DivProps, Flex } from '@/ui'; | ||||
|  | ||||
| export type CoversProps = Omit<BoxProps, "iconEmpty"> & { | ||||
| export type CoversProps = Omit<DivProps, "iconEmpty"> & { | ||||
|   data?: ObjectId[]; | ||||
|   size?: BoxProps["width"]; | ||||
|   size?: CSSProperties["width"]; | ||||
|   iconEmpty?: ReactElement; | ||||
|   slideshow?: boolean; | ||||
| }; | ||||
| @@ -45,26 +43,28 @@ export const Covers = ({ | ||||
|       return <Icon icon={iconEmpty} sizeIcon={size} />; | ||||
|     } else { | ||||
|       return ( | ||||
|         <Box | ||||
|           width={size} | ||||
|           height={size} | ||||
|           minHeight={size} | ||||
|           minWidth={size} | ||||
|           borderColor="blue" | ||||
|           borderWidth="1px" | ||||
|           margin="auto" | ||||
|         <Flex | ||||
|           style={{ | ||||
|             width: size, | ||||
|             height: size, | ||||
|             minHeight: size, | ||||
|             minWidth: size, | ||||
|             borderColor: "blue", | ||||
|             borderWidth: "1px", | ||||
|             margin: "auto", | ||||
|           }} | ||||
|           {...rest} | ||||
|         ></Box> | ||||
|         ></Flex> | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|   if (slideshow === false || data.length === 1) { | ||||
|     const url = DataUrlAccess.getThumbnailUrl(data[0]); | ||||
|     return <Image loading="lazy" src={url} maxWidth={size} boxSize={size} /*{...rest}*/ />; | ||||
|     return <></>;//<image loading="lazy" src={url} maxWidth={size} boxSize={size} /*{...rest}*/ />; | ||||
|   } | ||||
|   const urlCurrent = DataUrlAccess.getThumbnailUrl(data[currentImageIndex]); | ||||
|   const urlPrevious = DataUrlAccess.getThumbnailUrl(data[previousImageIndex]); | ||||
|   return <Flex | ||||
|   return <></>/*<Flex | ||||
|     position="relative" | ||||
|     // {...rest} | ||||
|     maxWidth={size} | ||||
| @@ -95,5 +95,5 @@ export const Covers = ({ | ||||
|       opacity={topOpacity} | ||||
|       zIndex={2} | ||||
|     /> | ||||
|   </Flex> | ||||
|   </Flex>*/ | ||||
| }; | ||||
|   | ||||
| @@ -1,13 +1,15 @@ | ||||
| import { Box } from '@chakra-ui/react'; | ||||
| import { Flex } from "@/ui"; | ||||
|  | ||||
| export const EmptyEnd = () => { | ||||
|   return ( | ||||
|     <Box | ||||
|       width="full" | ||||
|       height="25%" | ||||
|       minHeight="250px" | ||||
|     <Flex | ||||
|       style={{ | ||||
|         width: "100%", | ||||
|         height: "25%", | ||||
|         minHeight: "250px", | ||||
|       }} | ||||
|     // borderWidth="1px" | ||||
|     // borderColor="red" | ||||
|     ></Box> | ||||
|     ></Flex> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,8 +1,5 @@ | ||||
| import { | ||||
|     Box, | ||||
|     Flex, | ||||
|     FlexProps, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { Flex, FlexProps } from '@/ui'; | ||||
| import { forwardRef, ReactNode } from 'react'; | ||||
|  | ||||
| export type IconProps = FlexProps & { | ||||
| @@ -12,26 +9,31 @@ export type IconProps = FlexProps & { | ||||
| }; | ||||
|  | ||||
| export const Icon = forwardRef<HTMLDivElement, IconProps>( | ||||
|     ({ icon: IconEl, color, sizeIcon = '1em', ...rest }, ref) => { | ||||
|     ({ icon: IconEl, color, sizeIcon = '1em', style, ...rest }, ref) => { | ||||
|         return ( | ||||
|             <Flex flex="none" | ||||
|                 minWidth={sizeIcon} | ||||
|                 minHeight={sizeIcon} | ||||
|                 maxWidth={sizeIcon} | ||||
|                 maxHeight={sizeIcon} | ||||
|             <Flex | ||||
|                 align="center" | ||||
|                 padding="1px" | ||||
|                 ref={ref} | ||||
|                 style={{ | ||||
|                     flex: "none", | ||||
|                     minWidth: sizeIcon, | ||||
|                     minHeight: sizeIcon, | ||||
|                     maxWidth: sizeIcon, | ||||
|                     maxHeight: sizeIcon, | ||||
|                     padding: "1px", | ||||
|                     ...style | ||||
|                 }} | ||||
|                 {...rest}> | ||||
|                 <Box | ||||
|                     marginX="auto" | ||||
|                     width="100%" | ||||
|                     minWidth="100%" | ||||
|                     height="100%" | ||||
|                     color={color} | ||||
|                 <Flex | ||||
|                     style={{ | ||||
|                         margin: "0 auto 0 auto", | ||||
|                         width: "100%", | ||||
|                         minWidth: "100%", | ||||
|                         height: "100%", | ||||
|                         color: color, | ||||
|                     }} | ||||
|                 > | ||||
|                     {IconEl} | ||||
|                 </Box> | ||||
|                 </Flex> | ||||
|             </Flex> | ||||
|         ); | ||||
|     } | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import React, { ReactNode, useEffect } from 'react'; | ||||
|  | ||||
| import { Flex, Image } from '@chakra-ui/react'; | ||||
| import { useLocation } from 'react-router-dom'; | ||||
|  | ||||
| import background from '@/assets/images/ikon.svg'; | ||||
| import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Image } from '@/ui'; | ||||
|  | ||||
| export type LayoutProps = React.PropsWithChildren<unknown> & { | ||||
|   topBar?: ReactNode; | ||||
| @@ -20,30 +20,34 @@ export const PageLayout = ({ children }: LayoutProps) => { | ||||
|   return ( | ||||
|     <> | ||||
|       <Flex | ||||
|         minH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         position="absolute" | ||||
|         top={TOP_BAR_HEIGHT} | ||||
|         bottom={0} | ||||
|         left={0} | ||||
|         right={0} | ||||
|         minWidth="300px" | ||||
|         zIndex={-1} | ||||
|         style={{ | ||||
|           position: "absolute", | ||||
|           minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           top: TOP_BAR_HEIGHT, | ||||
|           bottom: 0, | ||||
|           left: 0, | ||||
|           right: 0, | ||||
|           minWidth: "300px", | ||||
|           zIndex: -1, | ||||
|           background: "back.800", | ||||
|         }} | ||||
|       > | ||||
|         <Image src={background} boxSize="90%" margin="auto" opacity="30%" /> | ||||
|         <Image src={background} boxSize="90%" style={{ margin: "auto", opacity: "30%" }} /> | ||||
|       </Flex> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         overflowX="auto" | ||||
|         overflowY="auto" | ||||
|         minH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         position="absolute" | ||||
|         top={TOP_BAR_HEIGHT} | ||||
|         bottom={0} | ||||
|         left={0} | ||||
|         right={0} | ||||
|         minWidth="300px" | ||||
|         style={{ | ||||
|           overflow: "auto", | ||||
|           minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           position: "absolute", | ||||
|           top: TOP_BAR_HEIGHT, | ||||
|           bottom: 0, | ||||
|           left: 0, | ||||
|           right: 0, | ||||
|           minWidth: "300px", | ||||
|         }} | ||||
|       > | ||||
|         {children} | ||||
|       </Flex> | ||||
|   | ||||
| @@ -1,19 +1,20 @@ | ||||
| import React, { ReactNode, useEffect } from 'react'; | ||||
| import { CSSProperties, useEffect } from 'react'; | ||||
|  | ||||
| import { Flex, FlexProps } from '@chakra-ui/react'; | ||||
| import { useLocation } from 'react-router-dom'; | ||||
|  | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { colors } from '@/theme/foundations/colors'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { basicColor } from '@/theme/colors'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Flex, FlexProps } from '@/ui'; | ||||
|  | ||||
| export type LayoutProps = FlexProps & { | ||||
|   children: ReactNode; | ||||
|   width?: CSSProperties['width']; | ||||
| }; | ||||
|  | ||||
| export const PageLayoutInfoCenter = ({ | ||||
|   children, | ||||
|   width = '25%', | ||||
|   width = "75%", | ||||
|   style, | ||||
|   ...rest | ||||
| }: LayoutProps) => { | ||||
|   const { pathname } = useLocation(); | ||||
| @@ -26,14 +27,17 @@ export const PageLayoutInfoCenter = ({ | ||||
|     <PageLayout> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         margin="auto" | ||||
|         minWidth={width} | ||||
|         border="back.900" | ||||
|         borderWidth="1px" | ||||
|         borderRadius="8px" | ||||
|         padding="10px" | ||||
|         boxShadow={'0px 0px 16px ' + colors.back[900]} | ||||
|         backgroundColor={useColorModeValue('#FFFFFF', '#000000')} | ||||
|         style={{ | ||||
|           margin: "auto", | ||||
|           width, | ||||
|           border: "back.900", | ||||
|           borderWidth: "1px", | ||||
|           borderRadius: "8px", | ||||
|           padding: "10px", | ||||
|           boxShadow: '0px 0px 16px ' + basicColor.back[900], | ||||
|           background: useColorThemeValue('#FFFFFF', '#000000'), | ||||
|           ...style | ||||
|         }} | ||||
|         {...rest} | ||||
|       > | ||||
|         {children} | ||||
|   | ||||
| @@ -1,9 +1,5 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Group, | ||||
|   Input, | ||||
| } from '@chakra-ui/react'; | ||||
| import { MdSearch } from 'react-icons/md'; | ||||
|  | ||||
| export type SearchInputProps = { | ||||
| @@ -42,15 +38,15 @@ export const SearchInput = ({ | ||||
|       onSubmitValue(inputData); | ||||
|     } | ||||
|   } | ||||
|   return ( | ||||
|     <Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}> | ||||
|       <MdSearch color="gray.300" /> | ||||
|       <Input | ||||
|         onFocus={onFocusKeep} | ||||
|         onBlur={() => setTimeout(() => onFocusLost(), 200)} | ||||
|         onChange={onChange} | ||||
|         onSubmit={onSubmit} | ||||
|       /> | ||||
|     </Group> | ||||
|   return (<></> | ||||
|     //<Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}> | ||||
|     // <MdSearch color="gray.300" /> | ||||
|     // <Input | ||||
|     //   onFocus={onFocusKeep} | ||||
|     //   onBlur={() => setTimeout(() => onFocusLost(), 200)} | ||||
|     //   onChange={onChange} | ||||
|     //   onSubmit={onSubmit} | ||||
|     // /> | ||||
|     //</Group> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,13 +1,6 @@ | ||||
| import { ReactNode } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Box, | ||||
|   Flex, | ||||
|   HStack, | ||||
|   IconButton, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   LuAlignJustify, | ||||
|   LuArrowBigLeft, | ||||
| @@ -21,19 +14,13 @@ import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { SessionState } from '@/service/SessionState'; | ||||
| import { colors } from '@/theme/foundations/colors'; | ||||
| import { basicColor } from '@/theme/colors'; | ||||
| import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso'; | ||||
| import { useSessionService } from '@/service/session'; | ||||
| import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md'; | ||||
| import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '@/components/ui/menu'; | ||||
| import { useColorMode, useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { | ||||
|   DrawerBody, | ||||
|   DrawerContent, | ||||
|   DrawerHeader, | ||||
|   DrawerRoot, | ||||
| } from '@/components/ui/drawer'; | ||||
| import { Button } from '../ui/themed'; | ||||
| import { useColorThemeValue, useTheme } from '@/theme/ThemeContext'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const TOP_BAR_HEIGHT = '50px'; | ||||
|  | ||||
| @@ -48,11 +35,11 @@ export type TopBarProps = { | ||||
| }; | ||||
|  | ||||
| export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|   const { colorMode, toggleColorMode } = useColorMode(); | ||||
|   const { theme, toggleTheme } = useTheme(); | ||||
|   const { clearToken } = useSessionService(); | ||||
|  | ||||
|   const { session } = useServiceContext(); | ||||
|   const backColor = useColorModeValue('back.100', 'back.800'); | ||||
|   const backColor = useColorThemeValue('back.100', 'back.800'); | ||||
|   const drawerDisclose = useDisclosure(); | ||||
|   const onChangeTheme = () => { | ||||
|     drawerDisclose.onOpen(); | ||||
| @@ -87,22 +74,24 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|   }; | ||||
|   return ( | ||||
|     <Flex | ||||
|       position="absolute" | ||||
|       top={0} | ||||
|       left={0} | ||||
|       right={0} | ||||
|       height={TOP_BAR_HEIGHT} | ||||
|       alignItems="center" | ||||
|       justifyContent="space-between" | ||||
|       backgroundColor={backColor} | ||||
|       gap="2" | ||||
|       px="2" | ||||
|       boxShadow={'0px 2px 4px ' + colors.back[900]} | ||||
|       zIndex={200} | ||||
|       align="center" | ||||
|       justify="space-between" | ||||
|       style={{ | ||||
|         position: "absolute", | ||||
|         top: 0, | ||||
|         left: 0, | ||||
|         right: 0, | ||||
|         height: TOP_BAR_HEIGHT, | ||||
|         background: backColor, | ||||
|         padding: "0 2 0 2", | ||||
|         boxShadow: `0px 2px 4px ${basicColor.back[900]}`, | ||||
|         zIndex: 200, | ||||
|       }} | ||||
|     > | ||||
|       <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}> | ||||
|         <LuAlignJustify /> | ||||
|         <Text paddingLeft="3px" fontWeight="bold"> | ||||
|         <Text style={{ padding: "0 0 0 3px" }} fontWeight="bold"> | ||||
|           Karusic | ||||
|         </Text> | ||||
|       </Button> | ||||
| @@ -110,82 +99,84 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|         <Text | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           textTransform="uppercase" | ||||
|           marginRight="auto" | ||||
|           userSelect="none" | ||||
|           style={{ | ||||
|             textTransform: "uppercase", | ||||
|             marginRight: "auto", | ||||
|             userSelect: "none", | ||||
|           }} | ||||
|         > | ||||
|           {title} | ||||
|         </Text> | ||||
|       )} | ||||
|       {children} | ||||
|       <Flex right="0"> | ||||
|       <Flex style={{ right: 0 }}> | ||||
|         {session?.state !== SessionState.CONNECTED && ( | ||||
|           <> | ||||
|             <Button {...BUTTON_TOP_BAR_PROPERTY} theme="@primary" onClick={onSignIn}> | ||||
|             <Button {...BUTTON_TOP_BAR_PROPERTY}  /*{...THEME.Button.primary}*/ onClick={onSignIn}> | ||||
|               <LuLogIn /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold"> | ||||
|               <Text style={{ paddingLeft: "0 0 0 3px" }} fontWeight="bold"> | ||||
|                 Sign-in | ||||
|               </Text> | ||||
|             </Button> | ||||
|             <Button | ||||
|               {...BUTTON_TOP_BAR_PROPERTY} | ||||
|               onClick={onSignUp} | ||||
|               disabled={true} | ||||
|             // disabled={true} | ||||
|             > | ||||
|               <MdMore /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold"> | ||||
|               <Text style={{ padding: "0 0 0 3px" }} fontWeight="bold"> | ||||
|                 Sign-up | ||||
|               </Text> | ||||
|             </Button> | ||||
|           </> | ||||
|         )} | ||||
|         {session?.state === SessionState.CONNECTED && ( | ||||
|           <MenuRoot> | ||||
|             <MenuTrigger asChild> | ||||
|         {/* {session?.state === SessionState.CONNECTED && ( | ||||
|           <Menu.Root> | ||||
|             <Menu.Trigger asChild> | ||||
|               <IconButton | ||||
|                 as={IconButton} | ||||
|                 aria-label="Options" | ||||
|                 {...BUTTON_TOP_BAR_PROPERTY} | ||||
|                 width={TOP_BAR_HEIGHT} | ||||
|               ><MdSupervisedUserCircle /></IconButton> | ||||
|             </MenuTrigger> | ||||
|             <MenuContent> | ||||
|               <MenuItem value="user" valueText="user" _hover={{}} color={useColorModeValue('brand.800', 'brand.200')}> | ||||
|             </Menu.Trigger> | ||||
|             <Menu.Content> | ||||
|               <Menu.Item value="user" valueText="user" _hover={{}} color={useColorModeValue('brand.800', 'brand.200')}> | ||||
|                 <MdSupervisedUserCircle /> | ||||
|                 <Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box> | ||||
|               </MenuItem> | ||||
|               <MenuItem value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</MenuItem> | ||||
|               <MenuItem value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</MenuItem> | ||||
|               <MenuItem value="Sign-out" valueText="Sign-out" onClick={onSignOut}> | ||||
|               </Menu.Item> | ||||
|               <Menu.Item value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</Menu.Item> | ||||
|               <Menu.Item value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</Menu.Item> | ||||
|               <Menu.Item value="Sign-out" valueText="Sign-out" onClick={onSignOut}> | ||||
|                 <LuLogOut /> Sign-out | ||||
|               </MenuItem> | ||||
|               </Menu.Item> | ||||
|               {colorMode === 'light' ? ( | ||||
|                 <MenuItem value="set-dark" valueText="set-dark" onClick={toggleColorMode}> | ||||
|                 <Menu.Item value="set-dark" valueText="set-dark" onClick={toggleColorMode}> | ||||
|                   <LuMoon /> Set dark mode | ||||
|                 </MenuItem> | ||||
|                 </Menu.Item> | ||||
|               ) : ( | ||||
|                 <MenuItem value="set-light" valueText="set-light" onClick={toggleColorMode}> | ||||
|                 <Menu.Item value="set-light" valueText="set-light" onClick={toggleColorMode}> | ||||
|                   <LuSun /> Set light mode | ||||
|                 </MenuItem> | ||||
|                 </Menu.Item> | ||||
|               )} | ||||
|             </MenuContent> | ||||
|           </MenuRoot> | ||||
|         )} | ||||
|             </Menu.Content> | ||||
|           </Menu.Root> | ||||
|         )} */} | ||||
|       </Flex> | ||||
|       <DrawerRoot | ||||
|       {/* <Drawer.Root | ||||
|         placement="start" | ||||
|         onOpenChange={drawerDisclose.onClose} | ||||
|         open={drawerDisclose.open} | ||||
|         data-testid="top-bar_drawer-root" | ||||
|       > | ||||
|         <DrawerContent | ||||
|           data-testid="top-bar_drawer-content"> | ||||
|           <DrawerHeader | ||||
|         <Drawer.Content | ||||
|           data-test-id="top-bar_drawer-content"> | ||||
|           <Drawer.Header | ||||
|             paddingY="auto" | ||||
|             as="button" | ||||
|             onClick={drawerDisclose.onClose} | ||||
|             boxShadow={'0px 2px 4px ' + colors.back[900]} | ||||
|             backgroundColor={backColor} | ||||
|             background={backColor} | ||||
|             color={useColorModeValue('brand.900', 'brand.50')} | ||||
|             textTransform="uppercase" | ||||
|           > | ||||
| @@ -195,13 +186,13 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|                 Karusic | ||||
|               </Text> | ||||
|             </HStack> | ||||
|           </DrawerHeader> | ||||
|           <DrawerBody paddingX="0px"> | ||||
|           </Drawer.Header> | ||||
|           <Drawer.Body paddingX="0px"> | ||||
|             <Button | ||||
|               background="#00000000" | ||||
|               borderRadius="0px" | ||||
|               onClick={onSelectHome} | ||||
|               width="full" | ||||
|               width="100%" | ||||
|             > | ||||
|               <MdHome /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> | ||||
| @@ -213,7 +204,7 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|               background="#00000000" | ||||
|               borderRadius="0px" | ||||
|               onClick={onSelectOnAir} | ||||
|               width="full" | ||||
|               width="100%" | ||||
|             > | ||||
|               <MdOutlinePlaylistPlay /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> | ||||
| @@ -225,16 +216,16 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|               background="#00000000" | ||||
|               borderRadius="0px" | ||||
|               onClick={onSelectAdd} | ||||
|               width="full" | ||||
|               width="100%" | ||||
|             > | ||||
|               <MdOutlineUploadFile /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> | ||||
|                 Add Media | ||||
|               </Text> | ||||
|             </Button> | ||||
|           </DrawerBody> | ||||
|         </DrawerContent> | ||||
|       </DrawerRoot> | ||||
|           </Drawer.Body> | ||||
|         </Drawer.Content> | ||||
|       </Drawer.Root> */} | ||||
|     </Flex> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| import { Flex, Text } from '@chakra-ui/react'; | ||||
|  | ||||
| import { LuDisc3 } from 'react-icons/lu'; | ||||
|  | ||||
| import { Album } from '@/back-api'; | ||||
| import { Covers } from '@/components/Cover'; | ||||
| import { useCountTracksWithAlbumId } from '@/service/Track'; | ||||
| import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing'; | ||||
| import { Flex, Text } from '@/ui'; | ||||
| import { Span } from '@/ui/Span'; | ||||
|  | ||||
| export type DisplayAlbumProps = { | ||||
|   dataAlbum?: Album; | ||||
| @@ -13,13 +15,13 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => { | ||||
|   const { countTracksOfAnAlbum } = useCountTracksWithAlbumId(dataAlbum?.id); | ||||
|   if (!dataAlbum) { | ||||
|     return ( | ||||
|       <Flex direction="row" width="full" height="full"> | ||||
|       <Flex direction="row" width="100%" height="full"> | ||||
|         Fail to retrieve Album Data. | ||||
|       </Flex> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full" | ||||
|     <Flex direction="row" width="100%" height="full" | ||||
|       data-testid="display-album_flex"> | ||||
|       <Covers | ||||
|         data={dataAlbum?.covers} | ||||
| @@ -33,32 +35,37 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => { | ||||
|         //maxWidth="150px" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         flex={1} | ||||
|         style={{ | ||||
|           marginRight: "auto", | ||||
|           overflow: "hidden", | ||||
|           flex: 1, | ||||
|         }} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|         <Span | ||||
|           // align="left" | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           style={{ | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|           }} | ||||
|         // noOfLines={[1, 2]} | ||||
|         > | ||||
|           {dataAlbum?.name} | ||||
|         </Text> | ||||
|         <Text | ||||
|           as="span" | ||||
|         </Span> | ||||
|         <Span | ||||
|           // align="left" | ||||
|           fontSize="15px" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           style={{ | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|           }} | ||||
|         // noOfLines={1} | ||||
|         > | ||||
|           {countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'} | ||||
|         </Text> | ||||
|         </Span> | ||||
|       </Flex> | ||||
|     </Flex> | ||||
|   ); | ||||
|   | ||||
| @@ -1,12 +1,6 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   IconButton, | ||||
| } from '@chakra-ui/react'; | ||||
| import { LuMenu } from 'react-icons/lu'; | ||||
| import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '../ui/menu'; | ||||
| import { Button } from '../ui/themed'; | ||||
| import { customVariant } from '@/theme/recipes/button'; | ||||
|  | ||||
|  | ||||
| export type MenuElement = { | ||||
|   name: string; | ||||
| @@ -17,44 +11,29 @@ export type ContextMenuProps = { | ||||
|   elements?: MenuElement[]; | ||||
| }; | ||||
|  | ||||
| const theme = { | ||||
|   plop: { | ||||
|     bg: { _light: 'red', _dark: 'brand.300', _pink: "orange" }, | ||||
|     _hover: { bg: { _light: 'green', _dark: 'brand.400', _pink: "black" } }, | ||||
|     // bg: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|     // _hover: { bg: { _light: 'brand.700', _dark: 'brand.400' } }, | ||||
|     /* | ||||
|     bgActive: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|     color: { _light: 'white', _dark: 'brand.900' }, | ||||
|     colorHover: { _light: 'brand.800', _dark: 'brand.100' }, | ||||
|     boxShadowHover: 'outline-over' | ||||
|     */ | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export const ContextMenu = ({ elements }: ContextMenuProps) => { | ||||
|   if (!elements) { | ||||
|     return <></>; | ||||
|   } | ||||
|   return ( | ||||
|     <MenuRoot | ||||
|       data-testid="context-menu"> | ||||
|       <MenuTrigger asChild | ||||
|         data-testid="context-menu_trigger"> | ||||
|         {/* This is very stupid, we need to set as span to prevent a button in button... WTF */} | ||||
|         <Button {...theme.plop} /*theme="@primary"*/> | ||||
|           <LuMenu /> | ||||
|         </Button> | ||||
|       </MenuTrigger> | ||||
|       <MenuContent | ||||
|         data-testid="context-menu_content"> | ||||
|         {elements?.map((data) => ( | ||||
|           <MenuItem key={data.name} value={data.name} onClick={data.onClick} | ||||
|             data-testid="context-menu_item"> | ||||
|             {data.name} | ||||
|           </MenuItem> | ||||
|         ))} | ||||
|       </MenuContent> | ||||
|     </MenuRoot > | ||||
|   ); | ||||
|   // if (!elements) { | ||||
|   return <></>; | ||||
|   // } | ||||
|   // return ( | ||||
|   //   <Menu.Root | ||||
|   //     data-testid="context-menu"> | ||||
|   //     <Menu.Trigger asChild | ||||
|   //       data-testid="context-menu_trigger"> | ||||
|   //       {/* This is very stupid, we need to set as span to prevent a button in button... WTF */} | ||||
|   //       <Button {...THEME.Button.primary} > | ||||
|   //         <LuMenu /> | ||||
|   //       </Button> | ||||
|   //     </Menu.Trigger> | ||||
|   //     <Menu.Content | ||||
|   //       data-testid="context-menu_content"> | ||||
|   //       {elements?.map((data) => ( | ||||
|   //         <Menu.Item key={data.name} value={data.name} onClick={data.onClick} | ||||
|   //           data-testid="context-menu_item"> | ||||
|   //           {data.name} | ||||
|   //         </Menu.Item> | ||||
|   //       ))} | ||||
|   //     </Menu.Content> | ||||
|   //   </Menu.Root > | ||||
|   //); | ||||
| }; | ||||
|   | ||||
| @@ -3,14 +3,7 @@ import { | ||||
|   RefObject, | ||||
| } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Box, | ||||
|   BoxProps, | ||||
|   Center, | ||||
|   Flex, | ||||
|   HStack, | ||||
|   Image, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   MdHighlightOff, | ||||
|   MdUploadFile, | ||||
| @@ -19,6 +12,7 @@ import { | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
| import { DataUrlAccess } from '@/utils/data-url-access'; | ||||
| import { Flex, FlexProps } from '@/ui'; | ||||
|  | ||||
| export type DragNdropProps = { | ||||
|   onFilesSelected?: (file: File[]) => void; | ||||
| @@ -58,40 +52,42 @@ export const DragNdrop = ({ | ||||
|       onUriSelected(listUri); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <Box | ||||
|       width={width} | ||||
|       height={height} | ||||
|       border="2px" | ||||
|       borderRadius="5px" | ||||
|       borderStyle="dashed" | ||||
|       onDrop={handleDrop} | ||||
|       onDragOver={(event) => event.preventDefault()} | ||||
|     > | ||||
|       <label htmlFor="browse"> | ||||
|         <Box paddingY="15%" height="100%" cursor="pointer"> | ||||
|           <Center> | ||||
|             <MdUploadFile size="50%" /> | ||||
|           </Center> | ||||
|           <Center> | ||||
|             <input | ||||
|               type="file" | ||||
|               hidden | ||||
|               id="browse" | ||||
|               onChange={handleFileChange} | ||||
|               //accept=".pdf,.docx,.pptx,.txt,.xlsx" | ||||
|               multiple | ||||
|             /> | ||||
|             Browse files | ||||
|           </Center> | ||||
|         </Box> | ||||
|       </label> | ||||
|     </Box> | ||||
|   ); | ||||
|   return <></>; | ||||
|   /* | ||||
|     return ( | ||||
|       <Box | ||||
|         width={width} | ||||
|         height={height} | ||||
|         border="2px" | ||||
|         borderRadius="5px" | ||||
|         borderStyle="dashed" | ||||
|         onDrop={handleDrop} | ||||
|         onDragOver={(event) => event.preventDefault()} | ||||
|       > | ||||
|         <label htmlFor="browse"> | ||||
|           <Box paddingY="15%" height="100%" cursor="pointer"> | ||||
|             <Center> | ||||
|               <MdUploadFile size="50%" /> | ||||
|             </Center> | ||||
|             <Center> | ||||
|               <input | ||||
|                 type="file" | ||||
|                 hidden | ||||
|                 id="browse" | ||||
|                 onChange={handleFileChange} | ||||
|                 //accept=".pdf,.docx,.pptx,.txt,.xlsx" | ||||
|                 multiple | ||||
|               /> | ||||
|               Browse files | ||||
|             </Center> | ||||
|           </Box> | ||||
|         </label> | ||||
|       </Box> | ||||
|     ); | ||||
|     */ | ||||
| }; | ||||
|  | ||||
| export type CenterIconProps = BoxProps & { | ||||
| export type CenterIconProps = FlexProps & { | ||||
|   icon: any; | ||||
|   sizeIcon?: string; | ||||
| }; | ||||
| @@ -99,11 +95,13 @@ export type CenterIconProps = BoxProps & { | ||||
| export const CenterIcon = ({ | ||||
|   icon: IconEl, | ||||
|   sizeIcon = '15px', | ||||
|   style, | ||||
|   ...rest | ||||
| }: CenterIconProps) => { | ||||
|   return ( | ||||
|     <Box position="relative" w={sizeIcon} h={sizeIcon} flex="none" {...rest}> | ||||
|       <Box | ||||
|     <Flex style={{ position: "relative", width: sizeIcon, height: sizeIcon, flex: "none", ...style }} | ||||
|       {...rest}> | ||||
|       {/*<Flex | ||||
|         as={IconEl} | ||||
|         w={sizeIcon} | ||||
|         h={sizeIcon} | ||||
| @@ -111,8 +109,8 @@ export const CenterIcon = ({ | ||||
|         top="50%" | ||||
|         left="50%" | ||||
|         transform="translate(-50%, -50%)" | ||||
|       /> | ||||
|     </Box> | ||||
|       />*/} | ||||
|     </Flex> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| @@ -143,8 +141,8 @@ export const FormCovers = ({ | ||||
|       isModify={form.isModify[variableName]} | ||||
|       onRestore={() => form.restoreValue({ [variableName]: true })} | ||||
|       {...rest} | ||||
|     > | ||||
|       <HStack wrap="wrap" width="full"> | ||||
|     > <></> | ||||
|       {/* <HStack wrap="wrap" width="100%"> | ||||
|         {urls.map((data, index) => ( | ||||
|           <Flex align="flex-start" key={data}> | ||||
|             <Box width="125px" height="125px" position="relative"> | ||||
| @@ -171,7 +169,7 @@ export const FormCovers = ({ | ||||
|             onUriSelected={onUriSelected} | ||||
|           /> | ||||
|         </Flex> | ||||
|       </HStack> | ||||
|       </HStack> */} | ||||
|     </FormGroup> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { Flex, Text } from '@/ui'; | ||||
| import { Span } from '@/ui/Span'; | ||||
| import { ReactNode } from 'react'; | ||||
|  | ||||
| import { Flex, Text } from '@chakra-ui/react'; | ||||
| import { MdErrorOutline, MdHelpOutline, MdRefresh } from 'react-icons/md'; | ||||
|  | ||||
| export type FormGroupProps = { | ||||
| @@ -23,20 +24,22 @@ export const FormGroup = ({ | ||||
|   onRestore, | ||||
| }: FormGroupProps) => ( | ||||
|   <Flex | ||||
|     borderLeftWidth="3px" | ||||
|     borderLeftColor={error ? 'red' : isModify ? 'blue' : '#00000000'} | ||||
|     style={{ | ||||
|       borderLeftWidth: "3px", | ||||
|       borderLeftColor: error ? 'red' : isModify ? 'blue' : '#00000000', | ||||
|     }} | ||||
|     paddingLeft="7px" | ||||
|     paddingY="4px" | ||||
|     padding="0 4px" | ||||
|     direction="column" | ||||
|   > | ||||
|     <Flex direction="row" width="full" gap="52px"> | ||||
|     <Flex direction="row" width="100%" gap="52px"> | ||||
|       {!!label && ( | ||||
|         <Text marginRight="auto" fontWeight="bold"> | ||||
|         <Text style={{ marginRight: "auto" }} fontWeight="bold"> | ||||
|           {label}{' '} | ||||
|           {isRequired && ( | ||||
|             <Text as="span" color="red.600"> | ||||
|             <Span color="red.600"> | ||||
|               * | ||||
|             </Text> | ||||
|             </Span> | ||||
|           )} | ||||
|         </Text> | ||||
|       )} | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { Input } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
| import { Input } from '@/ui'; | ||||
|  | ||||
| export type FormInputProps = { | ||||
|   form: UseFormidableReturn; | ||||
|   | ||||
| @@ -1,8 +1,5 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { | ||||
|   NumberInput, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
| @@ -36,7 +33,8 @@ export const FormNumber = ({ | ||||
|       onRestore={() => form.restoreValue({ [variableName]: true })} | ||||
|       {...rest} | ||||
|     > | ||||
|       <NumberInput.Root | ||||
|       <></> | ||||
|       {/* <NumberInput.Root | ||||
|         ref={ref} | ||||
|         value={form.values[variableName]} | ||||
|         onValueChange={(value) => form.setValues({ [variableName]: value })} | ||||
| @@ -46,7 +44,7 @@ export const FormNumber = ({ | ||||
|         max={max} | ||||
|       > | ||||
|         <NumberInput.Input /> | ||||
|       </NumberInput.Root> | ||||
|       </NumberInput.Root> */} | ||||
|     </FormGroup> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Box } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormSelect } from '@/components/form/FormSelect'; | ||||
| import { useFormidable } from '@/components/form/Formidable'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export default { | ||||
|   title: 'Components/FormSelect', | ||||
| @@ -94,7 +94,7 @@ export const DarkBackground = { | ||||
|   render: () => { | ||||
|     const form = useFormidable<BasicFormData>({}); | ||||
|     return ( | ||||
|       <Box p="4" color="white" bg="gray.800"> | ||||
|       <Flex style={{ padding: "4", color: "white", background: "gray.800" }}> | ||||
|         <FormSelect | ||||
|           label="Simple Title for (DarkBackground)" | ||||
|           form={form} | ||||
| @@ -105,7 +105,7 @@ export const DarkBackground = { | ||||
|             { id: 333, name: 'third item' }, | ||||
|           ]} | ||||
|         /> | ||||
|       </Box> | ||||
|       </Flex> | ||||
|     ); | ||||
|   }, | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { Text } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Box } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormSelectMultiple } from '@/components/form/FormSelectMultiple'; | ||||
| import { useFormidable } from '@/components/form/Formidable'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export default { | ||||
|   title: 'Components/FormSelectMultipleMultiple', | ||||
| @@ -94,7 +94,7 @@ export const DarkBackground = { | ||||
|   render: () => { | ||||
|     const form = useFormidable<BasicFormData>({}); | ||||
|     return ( | ||||
|       <Box p="4" color="white" bg="gray.800"> | ||||
|       <Flex style={{ padding: "4", color: "white", background: "gray.800" }}> | ||||
|         <FormSelectMultiple | ||||
|           label="Simple Title for (DarkBackground)" | ||||
|           form={form} | ||||
| @@ -105,7 +105,7 @@ export const DarkBackground = { | ||||
|             { id: 333, name: 'third item' }, | ||||
|           ]} | ||||
|         /> | ||||
|       </Box> | ||||
|       </Flex> | ||||
|     ); | ||||
|   }, | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { Textarea } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| 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'; | ||||
| import { Flex, Span, Text } from '@/ui'; | ||||
|  | ||||
| export type DisplayGenderProps = { | ||||
|   dataGender?: Gender; | ||||
| @@ -12,13 +13,13 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => { | ||||
|   const { countTracksOnAGender } = useCountTracksOfAGender(dataGender?.id); | ||||
|   if (!dataGender) { | ||||
|     return ( | ||||
|       <Flex direction="row" width="full" height="full"> | ||||
|       <Flex direction="row" width="100%" height="full"> | ||||
|         Fail to retrieve Gender Data. | ||||
|       </Flex> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|     <Flex direction="row" width="100%" height="full"> | ||||
|       <Covers | ||||
|         data={dataGender?.covers} | ||||
|         size="100" | ||||
| @@ -31,31 +32,36 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => { | ||||
|         maxWidth="150px" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         style={{ | ||||
|           overflowX: "hidden" | ||||
|         }} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|           alignContent="left" | ||||
|         <Span | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           style={{ | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             alignContent: "left", | ||||
|           }} | ||||
|  | ||||
|         //TODO: noOfLines={[1, 2]} | ||||
|         > | ||||
|           {dataGender?.name} | ||||
|         </Text> | ||||
|         <Text | ||||
|           as="span" | ||||
|           alignContent="left" | ||||
|         </Span> | ||||
|         <Span | ||||
|           fontSize="15px" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           style={{ | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             alignContent: "left", | ||||
|           }} | ||||
|         //TODO: noOfLines={1} | ||||
|         > | ||||
|           {countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'} | ||||
|         </Text> | ||||
|         </Span> | ||||
|       </Flex> | ||||
|     </Flex> | ||||
|   ); | ||||
|   | ||||
| @@ -1,10 +1,5 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Flex, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
| import { | ||||
|   MdAdminPanelSettings, | ||||
|   MdDeleteForever, | ||||
| @@ -24,8 +19,7 @@ import { useAlbumService, useSpecificAlbum } from '@/service/Album'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useCountTracksWithAlbumId } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog'; | ||||
| import { Button } from '../ui/themed'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type AlbumEditPopUpProps = {}; | ||||
|  | ||||
| @@ -134,109 +128,110 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => { | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <DialogRoot | ||||
|       //initialFocusRef={initialRef} | ||||
|       //finalFocusRef={finalRef} | ||||
|       //closeOnOverlayClick={false} | ||||
|       onOpenChange={onClose} | ||||
|       open={true} | ||||
|       data-testid="album-edit-pop-up" | ||||
|     > | ||||
|       {/* <DialogOverlay /> */} | ||||
|       {/* <DialogCloseTrigger /> */} | ||||
|       <DialogContent> | ||||
|         <DialogHeader>Edit Album</DialogHeader> | ||||
|         {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="album-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     {/* <DialogCloseTrigger /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>Edit Album</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <DialogBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataAlbum?.id}</Text> | ||||
|               </FormGroup> | ||||
|               {countTracksOfAnAlbum !== 0 && ( | ||||
|                 <Flex paddingLeft="14px"> | ||||
|                   <MdWarning color="red.600" /> | ||||
|                   <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|                     Can not remove album {countTracksOfAnAlbum} track(s) depend | ||||
|                     on it. | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               )} | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   theme="@danger" | ||||
|                   disabled={countTracksOfAnAlbum !== 0} | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove Media | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove album" | ||||
|                 body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Title" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="publication" | ||||
|                 label="Publication" | ||||
|               /> | ||||
|               <FormCovers | ||||
|                 form={form} | ||||
|                 variableName="covers" | ||||
|                 onFilesSelected={onFilesSelected} | ||||
|                 onUriSelected={onUriSelected} | ||||
|                 onRemove={onRemoveCover} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </DialogBody> | ||||
|         <DialogFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </DialogFooter> | ||||
|       </DialogContent> | ||||
|     </DialogRoot> | ||||
|   ); | ||||
|   //       <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|   //         {admin && ( | ||||
|   //           <> | ||||
|   //             <FormGroup isRequired label="Id"> | ||||
|   //               <Text>{dataAlbum?.id}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             {countTracksOfAnAlbum !== 0 && ( | ||||
|   //               <Flex paddingLeft="14px"> | ||||
|   //                 <MdWarning color="red.600" /> | ||||
|   //                 <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|   //                   Can not remove album {countTracksOfAnAlbum} track(s) depend | ||||
|   //                   on it. | ||||
|   //                 </Text> | ||||
|   //               </Flex> | ||||
|   //             )} | ||||
|   //             <FormGroup label="Action(s):"> | ||||
|   //               <Button | ||||
|   //                 onClick={disclosure.onOpen} | ||||
|   //                 marginRight="auto" | ||||
|   //                 theme="@danger" | ||||
|   //                 disabled={countTracksOfAnAlbum !== 0} | ||||
|   //               > | ||||
|   //                 <MdDeleteForever /> Remove Media | ||||
|   //               </Button> | ||||
|   //             </FormGroup> | ||||
|   //             <ConfirmPopUp | ||||
|   //               disclosure={disclosure} | ||||
|   //               title="Remove album" | ||||
|   //               body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`} | ||||
|   //               confirmTitle="Remove" | ||||
|   //               onConfirm={onRemove} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //         {!admin && ( | ||||
|   //           <> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="name" | ||||
|   //               isRequired | ||||
|   //               label="Title" | ||||
|   //               ref={initialRef} | ||||
|   //             /> | ||||
|   //             <FormTextarea | ||||
|   //               form={form} | ||||
|   //               variableName="description" | ||||
|   //               label="Description" | ||||
|   //             /> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="publication" | ||||
|   //               label="Publication" | ||||
|   //             /> | ||||
|   //             <FormCovers | ||||
|   //               form={form} | ||||
|   //               variableName="covers" | ||||
|   //               onFilesSelected={onFilesSelected} | ||||
|   //               onUriSelected={onUriSelected} | ||||
|   //               onRemove={onRemoveCover} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button | ||||
|   //           onClick={() => setAdmin((value) => !value)} | ||||
|   //           marginRight="auto" | ||||
|   //         > | ||||
|   //           {admin ? ( | ||||
|   //             <> | ||||
|   //               <MdEdit /> | ||||
|   //               Edit | ||||
|   //             </> | ||||
|   //           ) : ( | ||||
|   //             <> | ||||
|   //               <MdAdminPanelSettings /> | ||||
|   //               Admin | ||||
|   //             </> | ||||
|   //           )} | ||||
|   //         </Button> | ||||
|   //         {!admin && form.isFormModified && ( | ||||
|   //           <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|   //             Save | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //         <Button onClick={onClose}>Cancel</Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Flex, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
| import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog'; | ||||
|  | ||||
| import { | ||||
|   MdAdminPanelSettings, | ||||
|   MdDeleteForever, | ||||
| @@ -25,7 +20,7 @@ import { useArtistService, useSpecificArtist } from '@/service/Artist'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useCountTracksOfAnArtist } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button } from '../ui/themed'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type ArtistEditPopUpProps = {}; | ||||
|  | ||||
| @@ -134,110 +129,110 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => { | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <DialogRoot | ||||
|       //initialFocusRef={initialRef} | ||||
|       //finalFocusRef={finalRef} | ||||
|       //closeOnOverlayClick={false} | ||||
|       onOpenChange={onClose} | ||||
|       open={true} | ||||
|       data-testid="artist-edit-pop-up" | ||||
|     > | ||||
|       {/* <DialogOverlay /> */} | ||||
|       <DialogContent> | ||||
|         <DialogHeader>Edit Artist</DialogHeader> | ||||
|         {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|     // <Dialog.Root | ||||
|     //   //initialFocusRef={initialRef} | ||||
|     //   //finalFocusRef={finalRef} | ||||
|     //   //closeOnOverlayClick={false} | ||||
|     //   onOpenChange={onClose} | ||||
|     //   open={true} | ||||
|     //   data-testid="artist-edit-pop-up" | ||||
|     // > | ||||
|     //   {/* <DialogOverlay /> */} | ||||
|     //   <Dialog.Content> | ||||
|     //     <Dialog.Header>Edit Artist</Dialog.Header> | ||||
|     //     {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <DialogBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataArtist?.id}</Text> | ||||
|               </FormGroup> | ||||
|               {countTracksOnAnArtist !== 0 && ( | ||||
|                 <Flex paddingLeft="14px"> | ||||
|                   <MdWarning color="red.600" /> | ||||
|                   <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|                     Can not remove artist {countTracksOnAnArtist} track(s) | ||||
|                     depend on it. | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               )} | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   theme="@danger" | ||||
|                   disabled={countTracksOnAnArtist !== 0} | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove Media | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove artist" | ||||
|                 body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Artist name" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="firstName" | ||||
|                 label="First Name" | ||||
|               /> | ||||
|               <FormInput form={form} variableName="surname" label="SurName" /> | ||||
|               <FormInput form={form} variableName="birth" label="Birth date" /> | ||||
|               <FormInput form={form} variableName="death" label="Death date" /> | ||||
|               <FormCovers | ||||
|                 form={form} | ||||
|                 variableName="covers" | ||||
|                 onFilesSelected={onFilesSelected} | ||||
|                 onUriSelected={onUriSelected} | ||||
|                 onRemove={onRemoveCover} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </DialogBody> | ||||
|         <DialogFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </DialogFooter> | ||||
|       </DialogContent> | ||||
|     </DialogRoot> | ||||
|   ); | ||||
|     //     <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|     //       {admin && ( | ||||
|     //         <> | ||||
|     //           <FormGroup isRequired label="Id"> | ||||
|     //             <Text>{dataArtist?.id}</Text> | ||||
|     //           </FormGroup> | ||||
|     //           {countTracksOnAnArtist !== 0 && ( | ||||
|     //             <Flex paddingLeft="14px"> | ||||
|     //               <MdWarning color="red.600" /> | ||||
|     //               <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|     //                 Can not remove artist {countTracksOnAnArtist} track(s) | ||||
|     //                 depend on it. | ||||
|     //               </Text> | ||||
|     //             </Flex> | ||||
|     //           )} | ||||
|     //           <FormGroup label="Action(s):"> | ||||
|     //             <Button | ||||
|     //               onClick={disclosure.onOpen} | ||||
|     //               marginRight="auto" | ||||
|     //               theme="@danger" | ||||
|     //               disabled={countTracksOnAnArtist !== 0} | ||||
|     //             > | ||||
|     //               <MdDeleteForever /> Remove Media | ||||
|     //             </Button> | ||||
|     //           </FormGroup> | ||||
|     //           <ConfirmPopUp | ||||
|     //             disclosure={disclosure} | ||||
|     //             title="Remove artist" | ||||
|     //             body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`} | ||||
|     //             confirmTitle="Remove" | ||||
|     //             onConfirm={onRemove} | ||||
|     //           /> | ||||
|     //         </> | ||||
|     //       )} | ||||
|     //       {!admin && ( | ||||
|     //         <> | ||||
|     //           <FormInput | ||||
|     //             form={form} | ||||
|     //             variableName="name" | ||||
|     //             isRequired | ||||
|     //             label="Artist name" | ||||
|     //             ref={initialRef} | ||||
|     //           /> | ||||
|     //           <FormTextarea | ||||
|     //             form={form} | ||||
|     //             variableName="description" | ||||
|     //             label="Description" | ||||
|     //           /> | ||||
|     //           <FormInput | ||||
|     //             form={form} | ||||
|     //             variableName="firstName" | ||||
|     //             label="First Name" | ||||
|     //           /> | ||||
|     //           <FormInput form={form} variableName="surname" label="SurName" /> | ||||
|     //           <FormInput form={form} variableName="birth" label="Birth date" /> | ||||
|     //           <FormInput form={form} variableName="death" label="Death date" /> | ||||
|     //           <FormCovers | ||||
|     //             form={form} | ||||
|     //             variableName="covers" | ||||
|     //             onFilesSelected={onFilesSelected} | ||||
|     //             onUriSelected={onUriSelected} | ||||
|     //             onRemove={onRemoveCover} | ||||
|     //           /> | ||||
|     //         </> | ||||
|     //       )} | ||||
|     //     </Dialog.Body> | ||||
|     //     <Dialog.Footer> | ||||
|     //       <Button | ||||
|     //         onClick={() => setAdmin((value) => !value)} | ||||
|     //         marginRight="auto" | ||||
|     //       > | ||||
|     //         {admin ? ( | ||||
|     //           <> | ||||
|     //             <MdEdit /> | ||||
|     //             Edit | ||||
|     //           </> | ||||
|     //         ) : ( | ||||
|     //           <> | ||||
|     //             <MdAdminPanelSettings /> | ||||
|     //             Admin | ||||
|     //           </> | ||||
|     //         )} | ||||
|     //       </Button> | ||||
|     //       {!admin && form.isFormModified && ( | ||||
|     //         <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|     //           Save | ||||
|     //         </Button> | ||||
|     //       )} | ||||
|     //       <Button onClick={onClose}>Cancel</Button> | ||||
|     //     </Dialog.Footer> | ||||
|     //   </Dialog.Content> | ||||
|     // </Dialog.Root> | ||||
|     <></>); | ||||
| }; | ||||
|   | ||||
| @@ -1,11 +1,8 @@ | ||||
| import { UseDisclosureReturn } from '@/utils/disclosure'; | ||||
| import { useRef } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   UseDisclosureReturn, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog'; | ||||
|  | ||||
| export type ConfirmPopUpProps = { | ||||
|   title: string; | ||||
|   body: string; | ||||
| @@ -26,29 +23,28 @@ export const ConfirmPopUp = ({ | ||||
|     disclosure.onClose(); | ||||
|   }; | ||||
|   const cancelRef = useRef<HTMLButtonElement>(null); | ||||
|   return ( | ||||
|     <DialogRoot role="alertdialog" | ||||
|       open={disclosure.open} | ||||
|       //leastDestructiveRef={cancelRef} | ||||
|       onOpenChange={disclosure.onClose} | ||||
|       data-testid="confirm-pop-up" | ||||
|     > | ||||
|       <DialogContent> | ||||
|         <DialogHeader fontSize="lg" fontWeight="bold"> | ||||
|           {title} | ||||
|         </DialogHeader> | ||||
|  | ||||
|         <DialogBody>{body}</DialogBody> | ||||
|  | ||||
|         <DialogFooter> | ||||
|           <Button onClick={disclosure.onClose} ref={cancelRef}> | ||||
|             Cancel | ||||
|           </Button> | ||||
|           <Button colorScheme="red" onClick={onClickConfirm} ml={3}> | ||||
|             {confirmTitle} | ||||
|           </Button> | ||||
|         </DialogFooter> | ||||
|       </DialogContent> | ||||
|     </DialogRoot> | ||||
|   ); | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root role="alertdialog" | ||||
|   //     open={disclosure.open} | ||||
|   //     //leastDestructiveRef={cancelRef} | ||||
|   //     onOpenChange={disclosure.onClose} | ||||
|   //     data-testid="confirm-pop-up" | ||||
|   //   > | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header fontSize="lg" fontWeight="bold"> | ||||
|   //         {title} | ||||
|   //       </Dialog.Header> | ||||
|   //       <Dialog.Body>{body}</Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button onClick={disclosure.onClose} ref={cancelRef}> | ||||
|   //           Cancel | ||||
|   //         </Button> | ||||
|   //         <Button colorScheme="red" onClick={onClickConfirm} ml={3}> | ||||
|   //           {confirmTitle} | ||||
|   //         </Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,10 +1,6 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Flex, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   MdAdminPanelSettings, | ||||
|   MdDeleteForever, | ||||
| @@ -12,7 +8,6 @@ import { | ||||
|   MdWarning, | ||||
| } from 'react-icons/md'; | ||||
|  | ||||
| import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| import { Gender, GenderResource } from '@/back-api'; | ||||
| @@ -26,7 +21,7 @@ import { useGenderService, useSpecificGender } from '@/service/Gender'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useCountTracksOfAGender } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button } from '../ui/themed'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type GenderEditPopUpProps = {}; | ||||
|  | ||||
| @@ -133,103 +128,104 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => { | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <DialogRoot | ||||
|       //initialFocusRef={initialRef} | ||||
|       //finalFocusRef={finalRef} | ||||
|       //closeOnOverlayClick={false} | ||||
|       onOpenChange={onClose} | ||||
|       open={true} | ||||
|       data-testid="gender-edit-pop-up" | ||||
|     > | ||||
|       {/* <DialogOverlay /> */} | ||||
|       <DialogContent> | ||||
|         <DialogHeader>Edit Gender</DialogHeader> | ||||
|         {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="gender-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>Edit Gender</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <DialogBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataGender?.id}</Text> | ||||
|               </FormGroup> | ||||
|               {countTracksOnAGender !== 0 && ( | ||||
|                 <Flex paddingLeft="14px"> | ||||
|                   <MdWarning color="red.600" /> | ||||
|                   <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|                     Can not remove gender {countTracksOnAGender} track(s) depend | ||||
|                     on it. | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               )} | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   theme="@danger" | ||||
|                   disabled={countTracksOnAGender !== 0} | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove gender | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove gender" | ||||
|                 body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Gender name" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormCovers | ||||
|                 form={form} | ||||
|                 variableName="covers" | ||||
|                 onFilesSelected={onFilesSelected} | ||||
|                 onUriSelected={onUriSelected} | ||||
|                 onRemove={onRemoveCover} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </DialogBody> | ||||
|         <DialogFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </DialogFooter> | ||||
|       </DialogContent> | ||||
|     </DialogRoot> | ||||
|   ); | ||||
|   //       <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|   //         {admin && ( | ||||
|   //           <> | ||||
|   //             <FormGroup isRequired label="Id"> | ||||
|   //               <Text>{dataGender?.id}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             {countTracksOnAGender !== 0 && ( | ||||
|   //               <Flex paddingLeft="14px"> | ||||
|   //                 <MdWarning color="red.600" /> | ||||
|   //                 <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|   //                   Can not remove gender {countTracksOnAGender} track(s) depend | ||||
|   //                   on it. | ||||
|   //                 </Text> | ||||
|   //               </Flex> | ||||
|   //             )} | ||||
|   //             <FormGroup label="Action(s):"> | ||||
|   //               <Button | ||||
|   //                 onClick={disclosure.onOpen} | ||||
|   //                 marginRight="auto" | ||||
|   //                 theme="@danger" | ||||
|   //                 disabled={countTracksOnAGender !== 0} | ||||
|   //               > | ||||
|   //                 <MdDeleteForever /> Remove gender | ||||
|   //               </Button> | ||||
|   //             </FormGroup> | ||||
|   //             <ConfirmPopUp | ||||
|   //               disclosure={disclosure} | ||||
|   //               title="Remove gender" | ||||
|   //               body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`} | ||||
|   //               confirmTitle="Remove" | ||||
|   //               onConfirm={onRemove} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //         {!admin && ( | ||||
|   //           <> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="name" | ||||
|   //               isRequired | ||||
|   //               label="Gender name" | ||||
|   //               ref={initialRef} | ||||
|   //             /> | ||||
|   //             <FormTextarea | ||||
|   //               form={form} | ||||
|   //               variableName="description" | ||||
|   //               label="Description" | ||||
|   //             /> | ||||
|   //             <FormCovers | ||||
|   //               form={form} | ||||
|   //               variableName="covers" | ||||
|   //               onFilesSelected={onFilesSelected} | ||||
|   //               onUriSelected={onUriSelected} | ||||
|   //               onRemove={onRemoveCover} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button | ||||
|   //           onClick={() => setAdmin((value) => !value)} | ||||
|   //           marginRight="auto" | ||||
|   //         > | ||||
|   //           {admin ? ( | ||||
|   //             <> | ||||
|   //               <MdEdit /> | ||||
|   //               Edit | ||||
|   //             </> | ||||
|   //           ) : ( | ||||
|   //             <> | ||||
|   //               <MdAdminPanelSettings /> | ||||
|   //               Admin | ||||
|   //             </> | ||||
|   //           )} | ||||
|   //         </Button> | ||||
|   //         {!admin && form.isFormModified && ( | ||||
|   //           <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|   //             Save | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //         <Button onClick={onClose}>Cancel</Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,13 +1,5 @@ | ||||
| import { useRef } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Flex, | ||||
|   Progress, | ||||
|   Text, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog'; | ||||
| import { Button } from '../ui/themed'; | ||||
|  | ||||
| export type PopUpUploadProgressProps = { | ||||
|   title: string; | ||||
| @@ -40,64 +32,66 @@ export const PopUpUploadProgress = ({ | ||||
| }: PopUpUploadProgressProps) => { | ||||
|   const initialRef = useRef<HTMLButtonElement>(null); | ||||
|   const finalRef = useRef<HTMLButtonElement>(null); | ||||
|   return ( | ||||
|     <DialogRoot | ||||
|       //initialFocusRef={initialRef} | ||||
|       //finalFocusRef={finalRef} | ||||
|       //closeOnOverlayClick={false} | ||||
|       onOpenChange={onClose} | ||||
|       open={true} | ||||
|       data-testid="upload-progress-edit-pop-up" | ||||
|     > | ||||
|       {/* <DialogOverlay /> */} | ||||
|       <DialogContent> | ||||
|         <DialogHeader>{title}</DialogHeader> | ||||
|         {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|   return <></>; | ||||
|  | ||||
|         <DialogBody pb={6} paddingLeft="18px"> | ||||
|           <Flex direction="column" gap="10px"> | ||||
|             {isFinished ? ( | ||||
|               <Text fontSize="20px" fontWeight="bold"> | ||||
|                 All {elements.length} element have been sent | ||||
|               </Text> | ||||
|             ) : ( | ||||
|               <Text fontSize="20px" fontWeight="bold"> | ||||
|                 [{index + 1}/{elements.length}] {elements[index]} | ||||
|               </Text> | ||||
|             )} | ||||
|             <Progress.Root | ||||
|               colorScheme="green" | ||||
|               striped | ||||
|               value={currentSize} | ||||
|               animated | ||||
|               max={totalSize} | ||||
|               height="24px" | ||||
|             /> | ||||
|             <Flex> | ||||
|               <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text> | ||||
|               <Text marginLeft="auto"> | ||||
|                 {totalSize.toLocaleString('fr-FR')} Bytes | ||||
|               </Text> | ||||
|             </Flex> | ||||
|             {error && ( | ||||
|               <Text fontWeight="bold" color="darkred"> | ||||
|                 {error} | ||||
|               </Text> | ||||
|             )} | ||||
|           </Flex> | ||||
|         </DialogBody> | ||||
|         <DialogFooter> | ||||
|           {isFinished ? ( | ||||
|             <Button onClick={onClose} theme="@success"> | ||||
|               Ok | ||||
|             </Button> | ||||
|           ) : ( | ||||
|             <Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}> | ||||
|               Abort | ||||
|             </Button> | ||||
|           )} | ||||
|         </DialogFooter> | ||||
|       </DialogContent> | ||||
|     </DialogRoot> | ||||
|   ); | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="upload-progress-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>{title}</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|   //       <Dialog.Body pb={6} paddingLeft="18px"> | ||||
|   //         <Flex direction="column" gap="10px"> | ||||
|   //           {isFinished ? ( | ||||
|   //             <Text fontSize="20px" fontWeight="bold"> | ||||
|   //               All {elements.length} element have been sent | ||||
|   //             </Text> | ||||
|   //           ) : ( | ||||
|   //             <Text fontSize="20px" fontWeight="bold"> | ||||
|   //               [{index + 1}/{elements.length}] {elements[index]} | ||||
|   //             </Text> | ||||
|   //           )} | ||||
|   //           <Progress.Root | ||||
|   //             colorScheme="green" | ||||
|   //             striped | ||||
|   //             value={currentSize} | ||||
|   //             animated | ||||
|   //             max={totalSize} | ||||
|   //             height="24px" | ||||
|   //           /> | ||||
|   //           <Flex> | ||||
|   //             <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text> | ||||
|   //             <Text marginLeft="auto"> | ||||
|   //               {totalSize.toLocaleString('fr-FR')} Bytes | ||||
|   //             </Text> | ||||
|   //           </Flex> | ||||
|   //           {error && ( | ||||
|   //             <Text fontWeight="bold" color="darkred"> | ||||
|   //               {error} | ||||
|   //             </Text> | ||||
|   //           )} | ||||
|   //         </Flex> | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         {isFinished ? ( | ||||
|   //           <Button onClick={onClose} theme="@success"> | ||||
|   //             Ok | ||||
|   //           </Button> | ||||
|   //         ) : ( | ||||
|   //           <Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}> | ||||
|   //             Abort | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,11 +1,5 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from '@/components/ui/dialog'; | ||||
|  | ||||
| import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
| @@ -25,7 +19,7 @@ import { useOrderedGenders } from '@/service/Gender'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useSpecificTrack, useTrackService } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button } from '../ui/themed'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type TrackEditPopUpProps = {}; | ||||
|  | ||||
| @@ -86,116 +80,117 @@ export const TrackEditPopUp = ({ }: TrackEditPopUpProps) => { | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <DialogRoot | ||||
|       //initialFocusRef={initialRef} | ||||
|       //finalFocusRef={finalRef} | ||||
|       //closeOnOverlayClick={false} | ||||
|       onOpenChange={onClose} | ||||
|       open={true} | ||||
|       data-testid="track-edit-pop-up" | ||||
|     > | ||||
|       {/* <DialogOverlay /> */} | ||||
|       <DialogContent> | ||||
|         <DialogHeader>Edit Track</DialogHeader> | ||||
|         {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="track-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>Edit Track</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <DialogBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataTrack?.id}</Text> | ||||
|               </FormGroup> | ||||
|               <FormGroup label="Data Id"> | ||||
|                 <Text>{dataTrack?.dataId}</Text> | ||||
|               </FormGroup> | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   theme="@danger" | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove Media | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove track" | ||||
|                 body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Title" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormSelect | ||||
|                 form={form} | ||||
|                 variableName="genderId" | ||||
|                 options={dataGenders} | ||||
|                 label="Gender" | ||||
|               /> | ||||
|               <FormSelectMultiple | ||||
|                 form={form} | ||||
|                 variableName="artists" | ||||
|                 options={dataArtist} | ||||
|                 label="Artist(s)" | ||||
|               /> | ||||
|               <FormSelect | ||||
|                 form={form} | ||||
|                 variableName="albumId" | ||||
|                 options={dataAlbums} | ||||
|                 label="Album" | ||||
|               /> | ||||
|               <FormNumber | ||||
|                 form={form} | ||||
|                 variableName="track" | ||||
|                 label="Track n°" | ||||
|                 step={1} | ||||
|                 //defaultValue={0} | ||||
|                 min={0} | ||||
|                 max={1000} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </DialogBody> | ||||
|         <DialogFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </DialogFooter> | ||||
|       </DialogContent> | ||||
|     </DialogRoot> | ||||
|   ); | ||||
|   //       <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|   //         {admin && ( | ||||
|   //           <> | ||||
|   //             <FormGroup isRequired label="Id"> | ||||
|   //               <Text>{dataTrack?.id}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             <FormGroup label="Data Id"> | ||||
|   //               <Text>{dataTrack?.dataId}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             <FormGroup label="Action(s):"> | ||||
|   //               <Button | ||||
|   //                 onClick={disclosure.onOpen} | ||||
|   //                 marginRight="auto" | ||||
|   //                 theme="@danger" | ||||
|   //               > | ||||
|   //                 <MdDeleteForever /> Remove Media | ||||
|   //               </Button> | ||||
|   //             </FormGroup> | ||||
|   //             <ConfirmPopUp | ||||
|   //               disclosure={disclosure} | ||||
|   //               title="Remove track" | ||||
|   //               body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`} | ||||
|   //               confirmTitle="Remove" | ||||
|   //               onConfirm={onRemove} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //         {!admin && ( | ||||
|   //           <> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="name" | ||||
|   //               isRequired | ||||
|   //               label="Title" | ||||
|   //               ref={initialRef} | ||||
|   //             /> | ||||
|   //             <FormTextarea | ||||
|   //               form={form} | ||||
|   //               variableName="description" | ||||
|   //               label="Description" | ||||
|   //             /> | ||||
|   //             <FormSelect | ||||
|   //               form={form} | ||||
|   //               variableName="genderId" | ||||
|   //               options={dataGenders} | ||||
|   //               label="Gender" | ||||
|   //             /> | ||||
|   //             <FormSelectMultiple | ||||
|   //               form={form} | ||||
|   //               variableName="artists" | ||||
|   //               options={dataArtist} | ||||
|   //               label="Artist(s)" | ||||
|   //             /> | ||||
|   //             <FormSelect | ||||
|   //               form={form} | ||||
|   //               variableName="albumId" | ||||
|   //               options={dataAlbums} | ||||
|   //               label="Album" | ||||
|   //             /> | ||||
|   //             <FormNumber | ||||
|   //               form={form} | ||||
|   //               variableName="track" | ||||
|   //               label="Track n°" | ||||
|   //               step={1} | ||||
|   //               //defaultValue={0} | ||||
|   //               min={0} | ||||
|   //               max={1000} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button | ||||
|   //           onClick={() => setAdmin((value) => !value)} | ||||
|   //           marginRight="auto" | ||||
|   //         > | ||||
|   //           {admin ? ( | ||||
|   //             <> | ||||
|   //               <MdEdit /> | ||||
|   //               Edit | ||||
|   //             </> | ||||
|   //           ) : ( | ||||
|   //             <> | ||||
|   //               <MdAdminPanelSettings /> | ||||
|   //               Admin | ||||
|   //             </> | ||||
|   //           )} | ||||
|   //         </Button> | ||||
|   //         {!admin && form.isFormModified && ( | ||||
|   //           <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|   //             Save | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //         <Button onClick={onClose}>Cancel</Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { useEffect, useRef } from 'react'; | ||||
|  | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { MdAdd } from 'react-icons/md'; | ||||
|  | ||||
| import { isNullOrUndefined, isNumber } from '@/utils/validator'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export type SelectListModel = { | ||||
|   id: any; | ||||
| @@ -65,56 +65,65 @@ export const SelectList = ({ | ||||
|     } | ||||
|   }, []); | ||||
|   return ( | ||||
|     <Box position="relative"> | ||||
|     <Flex style={{ position: "relative" }}> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         position="absolute" | ||||
|         border="1px" | ||||
|         borderColor="black" | ||||
|         backgroundColor="gray.700" | ||||
|         overflowY="auto" | ||||
|         overflowX="hidden" | ||||
|         maxHeight="300px" | ||||
|         zIndex={300} | ||||
|         transform="translateY(1px)" | ||||
|         style={{ | ||||
|           width: "100%", | ||||
|           position: "absolute", | ||||
|           border: "1px", | ||||
|           borderColor: "black", | ||||
|           background: "gray.700", | ||||
|           overflowY: "auto", | ||||
|           overflowX: "hidden", | ||||
|           maxHeight: "300px", | ||||
|           zIndex: 300, | ||||
|           transform: "translateY(1px)", | ||||
|         }} | ||||
|       > | ||||
|         {displayedValue.length === 0 && ( | ||||
|           <Text marginX="auto" color="red.500" fontWeight="bold" marginY="10px"> | ||||
|           <Text fontWeight="bold" color="red.500" style={{ margin: "0 auto" }}> | ||||
|             ... No element found... | ||||
|           </Text> | ||||
|         )} | ||||
|         {displayedValue.map((data) => ( | ||||
|           <Button | ||||
|             key={data.id} | ||||
|             marginY="1px" | ||||
|             borderRadius="0px" | ||||
|             autoFocus={false} | ||||
|             backgroundColor={data.isSelected ? 'green.800' : '0x00000000'} | ||||
|             _hover={{ backgroundColor: 'gray.400' }} | ||||
|             style={{ | ||||
|               margin: "1px 0", | ||||
|               borderRadius: "0px", | ||||
|               //autoFocus: false, | ||||
|               background: data.isSelected ? 'green.800' : '0x00000000', | ||||
|               //_hover={ background: 'gray.400' }, | ||||
|             }} | ||||
|             onClick={() => onSelectValue(data)} | ||||
|             ref={data.isSelected ? scrollToRef : undefined} | ||||
|           //ref={data.isSelected ? scrollToRef : undefined} | ||||
|           > | ||||
|             <Text marginRight="auto" autoFocus={false}> | ||||
|             <Text style={{ margin: "0 auto", }} /*autoFocus={false}*/> | ||||
|               {data.name} | ||||
|             </Text> | ||||
|           </Button> | ||||
|         ))} | ||||
|         {onCreate && search && search.length > 0 && ( | ||||
|           <Button | ||||
|             marginY="1px" | ||||
|             borderRadius="0px" | ||||
|             autoFocus={false} | ||||
|             _hover={{ backgroundColor: 'gray.400' }} | ||||
|             style={{ | ||||
|               margin: "1px 0", | ||||
|               borderRadius: "0px", | ||||
|               //autoFocus:false, | ||||
|               //_hover={ background: 'gray.400' } | ||||
|             }} | ||||
|             onClick={() => onCreate(search)} | ||||
|           > | ||||
|             <Flex marginRight="auto"> | ||||
|             <Flex | ||||
|               style={{ | ||||
|                 margin: "0 auto", | ||||
|               }}> | ||||
|               <MdAdd /> | ||||
|               <Text autoFocus={false}>Create '{search}'</Text> | ||||
|               <Text /*autoFocus={false}*/>Create '{search}'</Text> | ||||
|             </Flex> | ||||
|           </Button> | ||||
|         )} | ||||
|       </Flex> | ||||
|     </Box> | ||||
|     </Flex> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,18 +1,10 @@ | ||||
| import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Flex, | ||||
|   HStack, | ||||
|   Input, | ||||
|   Spinner, | ||||
|   Tag, | ||||
|   TagLabel, | ||||
| } from '@chakra-ui/react'; | ||||
| import { MdEdit, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md'; | ||||
|  | ||||
| import { SelectList, SelectListModel } from '@/components/select/SelectList'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button, Flex, HStack, Input } from '@/ui'; | ||||
|  | ||||
| export type SelectMultipleProps = { | ||||
|   options?: object[]; | ||||
| @@ -83,7 +75,7 @@ export const SelectMultiple = ({ | ||||
|     } | ||||
|   }; | ||||
|   if (!options) { | ||||
|     return <Spinner />; | ||||
|     return <></>;// TODO: <Spinner />; | ||||
|   } | ||||
|   const onChangeInput = (value: string): void => { | ||||
|     setHasSuggestion(false); | ||||
| @@ -107,21 +99,21 @@ export const SelectMultiple = ({ | ||||
|     }; | ||||
|  | ||||
|   return ( | ||||
|     <Flex direction="column" width="full" gap="0px"> | ||||
|     <Flex direction="column" width="100%" gap="0px"> | ||||
|       {selectedOptions && ( | ||||
|         <HStack wrap="wrap" /*spacing="5px"*/ justify="left" width="full" marginBottom="2px"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing="5px"*/ justifyContent: "left", width: "100%", marginBottom: "2px" }}> | ||||
|           {selectedOptions.map((data) => ( | ||||
|             <Flex align="flex-start" key={data[keyKey]}> | ||||
|               <Tag.Root | ||||
|               {/* <Tag.Root | ||||
|                 size="md" | ||||
|                 key="md" | ||||
|                 borderRadius="5px" | ||||
|                 variant="solid" | ||||
|                 backgroundColor="green.500" | ||||
|                 background="green.500" | ||||
|               > | ||||
|                 <Tag.Label>{data[keyValue] ?? `id=${data[keyKey]}`}</Tag.Label> | ||||
|                 <Tag.CloseTrigger onClick={() => selectValue(data)} /> | ||||
|               </Tag.Root> | ||||
|               </Tag.Root> */} | ||||
|             </Flex> | ||||
|           ))} | ||||
|         </HStack> | ||||
| @@ -130,19 +122,24 @@ export const SelectMultiple = ({ | ||||
|       <Flex> | ||||
|         <Input | ||||
|           ref={refFocus} | ||||
|           width="full" | ||||
|           style={{ | ||||
|             width: "100%", | ||||
|             borderRadius: "5px 0 0 5px", | ||||
|           }} | ||||
|           onChange={(e) => onChangeInput(e.target.value)} | ||||
|           //onSubmit={onSubmit} | ||||
|           onFocus={() => setShowList(true)} | ||||
|           onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           // TODO: onFocus={() => setShowList(true)} | ||||
|           // TODO: onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''} | ||||
|           borderRadius="5px 0 0 5px" | ||||
|  | ||||
|         /> | ||||
|         <Button | ||||
|           onClick={onOpenClose} | ||||
|           variant="outline" | ||||
|           borderRadius="0 5px 5px 0" | ||||
|           borderWidth="1px 1px 1px 0" | ||||
|           //variant="outline" | ||||
|           style={{ | ||||
|             borderRadius: "0 5px 5px 0", | ||||
|             borderWidth: "1px 1px 1px 0", | ||||
|           }} | ||||
|         > | ||||
|           {showList ? ( | ||||
|             <MdKeyboardArrowUp color="gray.300" /> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; | ||||
|  | ||||
| import { Button, Flex, Input, Spinner } from '@chakra-ui/react'; | ||||
| import { | ||||
|   MdClose, | ||||
|   MdEdit, | ||||
| @@ -10,6 +9,7 @@ import { | ||||
|  | ||||
| import { SelectList, SelectListModel } from '@/components/select/SelectList'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button, Flex, Input } from '@/ui'; | ||||
|  | ||||
| export type SelectSingleProps = { | ||||
|   options?: object[]; | ||||
| @@ -70,7 +70,7 @@ export const SelectSingle = ({ | ||||
|     } | ||||
|   }; | ||||
|   if (!transformedOption) { | ||||
|     return <Spinner />; | ||||
|     return <></>; // TODO: <Spinner />; | ||||
|   } | ||||
|   function onChangeInput(value: string): void { | ||||
|     setHasSuggestion(false); | ||||
| @@ -101,48 +101,53 @@ export const SelectSingle = ({ | ||||
|     }; | ||||
|  | ||||
|   return ( | ||||
|     <Flex direction="column" width="full" gap="0px"> | ||||
|     <Flex direction="column" width="100%" gap="0px"> | ||||
|       <Flex> | ||||
|         <Input | ||||
|           ref={refFocus} | ||||
|           width="full" | ||||
|           style={{ | ||||
|             width: "100%", | ||||
|             background: showList || !selectedOptions ? undefined : 'green.500', | ||||
|             borderRadius: "5px 0 0 5px", | ||||
|           }} | ||||
|           onChange={(e) => onChangeInput(e.target.value)} | ||||
|           onFocus={() => setShowList(true)} | ||||
|           onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           //onFocus={() => setShowList(true)} | ||||
|           //onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           value={ | ||||
|             showList ? (currentSearch ?? '') : (selectedOptions?.name ?? (hasSuggestion ? `suggest: ${currentSearch}` : '')) | ||||
|           } | ||||
|           backgroundColor={ | ||||
|             showList || !selectedOptions ? undefined : 'green.500' | ||||
|           } | ||||
|           borderRadius="5px 0 0 5px" | ||||
|         /> | ||||
|         <Button | ||||
|           onClick={onRemoveItem} | ||||
|           variant="outline" | ||||
|           borderRadius="0 5px 5px 0" | ||||
|           borderWidth="1px 1px 1px 0" | ||||
|           // TODO: variant="outline" | ||||
|           style={{ | ||||
|             borderRadius: "0 5px 5px 0", | ||||
|             borderWidth: "1px 1px 1px 0", | ||||
|           }} | ||||
|         > | ||||
|           {selectedOptions ? ( | ||||
|             <MdClose color="gray.300" /> | ||||
|           ) : showList ? ( | ||||
|             <MdKeyboardArrowUp color="gray.300" /> | ||||
|           ) : hasSuggestion ? ( | ||||
|             <MdEdit color="gray.300" /> | ||||
|           ) : ( | ||||
|             <MdKeyboardArrowDown color="gray.300" /> | ||||
|           )} | ||||
|           { | ||||
|             selectedOptions ? ( | ||||
|               <MdClose color="gray.300" /> | ||||
|             ) : showList ? ( | ||||
|               <MdKeyboardArrowUp color="gray.300" /> | ||||
|             ) : hasSuggestion ? ( | ||||
|               <MdEdit color="gray.300" /> | ||||
|             ) : ( | ||||
|               <MdKeyboardArrowDown color="gray.300" /> | ||||
|             )} | ||||
|         </Button> | ||||
|       </Flex> | ||||
|       {showList && ( | ||||
|         <SelectList | ||||
|           options={transformedOption} | ||||
|           selected={selectedOptions ? [selectedOptions] : []} | ||||
|           search={currentSearch} | ||||
|           onSelectValue={selectValue} | ||||
|           onCreate={createNewItem} | ||||
|         /> | ||||
|       )} | ||||
|     </Flex> | ||||
|       { | ||||
|         showList && ( | ||||
|           <SelectList | ||||
|             options={transformedOption} | ||||
|             selected={selectedOptions ? [selectedOptions] : []} | ||||
|             search={currentSearch} | ||||
|             onSelectValue={selectValue} | ||||
|             onCreate={createNewItem} | ||||
|           /> | ||||
|         ) | ||||
|       } | ||||
|     </Flex > | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| 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 { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { Flex, Span } from '@/ui'; | ||||
|  | ||||
| export type DisplayTrackProps = { | ||||
|   track: Track; | ||||
| @@ -18,7 +18,7 @@ export const DisplayTrack = ({ | ||||
| }: DisplayTrackProps) => { | ||||
|   const { trackActive } = useActivePlaylistService(); | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|     <Flex direction="row" width="100%" height="full"> | ||||
|       <Covers | ||||
|         data={track?.covers} | ||||
|         size="50" | ||||
| @@ -30,26 +30,29 @@ export const DisplayTrack = ({ | ||||
|       /> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         width="100%" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         style={{ | ||||
|           overflowX: "hidden", | ||||
|         }} | ||||
|         onClick={onClick} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|           alignContent="left" | ||||
|         <Span | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           // TODO: noOfLines={[1, 2]} | ||||
|           marginY="auto" | ||||
|           color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|           style={{ | ||||
|             alignContent: "left", | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             // TODO: noOfLines={[1, 2]} | ||||
|             margin: "auto 0", | ||||
|           }} | ||||
|         > | ||||
|           [{track.track}] {track.name} | ||||
|         </Text> | ||||
|         </Span> | ||||
|       </Flex> | ||||
|       <ContextMenu elements={contextMenu} /> | ||||
|     </Flex> | ||||
|   | ||||
| @@ -1,7 +1,4 @@ | ||||
| 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'; | ||||
| @@ -10,6 +7,7 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificAlbum } from '@/service/Album'; | ||||
| import { useSpecificArtists } from '@/service/Artist'; | ||||
| import { useSpecificGender } from '@/service/Gender'; | ||||
| import { Flex, Span } from '@/ui'; | ||||
|  | ||||
| export type DisplayTrackProps = { | ||||
|   track: Track; | ||||
| @@ -26,7 +24,7 @@ export const DisplayTrackFull = ({ | ||||
|   const { dataGender } = useSpecificGender(track?.genderId); | ||||
|   const { dataArtists } = useSpecificArtists(track?.artists); | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full" | ||||
|     <Flex direction="row" width="100%" height="full" | ||||
|       data-testid="display-track-full"> | ||||
|       <Covers | ||||
|         data={track?.covers} | ||||
| @@ -39,72 +37,81 @@ export const DisplayTrackFull = ({ | ||||
|       /> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         width="100%" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         onClick={onClick} | ||||
|         style={{ | ||||
|           overflowX: "hidden", | ||||
|         }} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|           alignContent="left" | ||||
|         <Span | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           // TODO: noOfLines={1} | ||||
|           color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|           style={{ | ||||
|             alignContent: "left", | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             // TODO: noOfLines={1} | ||||
|             margin: "auto 0", | ||||
|           }} | ||||
|         > | ||||
|           {track.name} {track.track && ` [${track.track}]`} | ||||
|         </Text> | ||||
|         </Span> | ||||
|         {dataAlbum && ( | ||||
|           <Text | ||||
|             as="span" | ||||
|             alignContent="left" | ||||
|           <Span | ||||
|             fontSize="15px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             //noOfLines={1} | ||||
|             marginY="auto" | ||||
|             color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               // TODO: noOfLines={[1, 2]} | ||||
|               margin: "auto 0", | ||||
|             }} | ||||
|           > | ||||
|             <Text as="span" fontWeight="normal">Album:</Text> {dataAlbum.name} | ||||
|           </Text> | ||||
|             <Span fontWeight="normal">Album:</Span> {dataAlbum.name} | ||||
|           </Span> | ||||
|         )} | ||||
|         {dataArtists && ( | ||||
|           <Text | ||||
|             as="span" | ||||
|             alignContent="left" | ||||
|           <Span | ||||
|             fontSize="15px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             //noOfLines={1} | ||||
|             marginY="auto" | ||||
|             color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               // TODO: noOfLines={[1, 2]} | ||||
|               margin: "auto 0", | ||||
|             }} | ||||
|           > | ||||
|             <Text as="span" fontWeight="normal">Artist(s):</Text> {dataArtists.map((data) => data.name).join(', ')} | ||||
|           </Text> | ||||
|             <Span fontWeight="normal">Artist(s):</Span> {dataArtists.map((data) => data.name).join(', ')} | ||||
|           </Span> | ||||
|         )} | ||||
|         {dataGender && ( | ||||
|           <Text | ||||
|             as="span" | ||||
|             alignContent="left" | ||||
|           <Span | ||||
|             fontSize="15px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             //noOfLines={1} | ||||
|             marginY="auto" | ||||
|             color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               // TODO: noOfLines={[1, 2]} | ||||
|               margin: "auto 0", | ||||
|             }} | ||||
|           > | ||||
|             <Text as="span" fontWeight="normal">Gender:</Text> {dataGender.name} | ||||
|           </Text> | ||||
|             <Span fontWeight="normal">Gender:</Span> {dataGender.name} | ||||
|           </Span> | ||||
|         )} | ||||
|       </Flex> | ||||
|       <ContextMenu elements={contextMenu} | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Track } from '@/back-api'; | ||||
| import { Covers } from '@/components/Cover'; | ||||
| import { MenuElement } from '@/components/contextMenu/ContextMenu'; | ||||
| import { useSpecificTrack } from '@/service/Track'; | ||||
| import { DisplayTrackFull } from './DisplayTrackFull'; | ||||
|   | ||||
| @@ -1,21 +1,24 @@ | ||||
| import { Flex, Skeleton } from '@chakra-ui/react'; | ||||
| import { Flex } from "@/ui"; | ||||
|  | ||||
|  | ||||
| export const DisplayTrackSkeleton = () => { | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|       <Skeleton | ||||
|     <Flex direction="row" width="100%" height="full"> | ||||
|       {/* <Skeleton | ||||
|         borderRadius="0px" | ||||
|         height="50" | ||||
|         width="50" | ||||
|         minWidth="50" | ||||
|         minHeight="50" | ||||
|       /> | ||||
|       /> */} | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         width="100%" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         style={{ | ||||
|           overflowX: "hidden" | ||||
|         }} | ||||
|       > | ||||
|         {/* <SkeletonText | ||||
|           skeletonHeight="20px" | ||||
|   | ||||
| @@ -1,17 +0,0 @@ | ||||
| import type { ButtonProps } from "@chakra-ui/react" | ||||
| import { IconButton as ChakraIconButton } from "@chakra-ui/react" | ||||
| import * as React from "react" | ||||
| import { LuX } from "react-icons/lu" | ||||
|  | ||||
| export type CloseButtonProps = ButtonProps | ||||
|  | ||||
| export const CloseButton = React.forwardRef< | ||||
|   HTMLButtonElement, | ||||
|   CloseButtonProps | ||||
| >(function CloseButton(props, ref) { | ||||
|   return ( | ||||
|     <ChakraIconButton variant="ghost" aria-label="Close" ref={ref} {...props}> | ||||
|       {props.children ?? <LuX />} | ||||
|     </ChakraIconButton> | ||||
|   ) | ||||
| }) | ||||
| @@ -1,29 +0,0 @@ | ||||
| "use client" | ||||
|  | ||||
| import { ThemeProvider, useTheme, ThemeProviderProps } from "next-themes" | ||||
|  | ||||
| export interface ColorModeProviderProps extends ThemeProviderProps { } | ||||
|  | ||||
| export function ColorModeProvider(props: ColorModeProviderProps) { | ||||
|   return ( | ||||
|     <ThemeProvider attribute="class" themes={['pink', 'dark', 'light']} disableTransitionOnChange {...props} /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export function useColorMode() { | ||||
|   const { resolvedTheme, setTheme } = useTheme() | ||||
|   const toggleColorMode = () => { | ||||
|     console.log(`plop: ${resolvedTheme}`); | ||||
|     setTheme(resolvedTheme === "light" ? "pink" : resolvedTheme === "pink" ? "dark" : "light") | ||||
|   } | ||||
|   return { | ||||
|     colorMode: resolvedTheme, | ||||
|     setColorMode: setTheme, | ||||
|     toggleColorMode, | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function useColorModeValue<T>(light: T, dark: T) { | ||||
|   const { colorMode } = useColorMode() | ||||
|   return colorMode === "light" ? light : dark | ||||
| } | ||||
| @@ -1,62 +0,0 @@ | ||||
| import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react" | ||||
| import { CloseButton } from "./close-button" | ||||
| import * as React from "react" | ||||
|  | ||||
| interface DialogContentProps extends ChakraDialog.ContentProps { | ||||
|   portalled?: boolean | ||||
|   portalRef?: React.RefObject<HTMLElement> | ||||
|   backdrop?: boolean | ||||
| } | ||||
|  | ||||
| export const DialogContent = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   DialogContentProps | ||||
| >(function DialogContent(props, ref) { | ||||
|   const { | ||||
|     children, | ||||
|     portalled = true, | ||||
|     portalRef, | ||||
|     backdrop = true, | ||||
|     ...rest | ||||
|   } = props | ||||
|  | ||||
|   return ( | ||||
|     <Portal disabled={!portalled} container={portalRef}> | ||||
|       {backdrop && <ChakraDialog.Backdrop />} | ||||
|       <ChakraDialog.Positioner> | ||||
|         <ChakraDialog.Content ref={ref} {...rest} asChild={false}> | ||||
|           {children} | ||||
|         </ChakraDialog.Content> | ||||
|       </ChakraDialog.Positioner> | ||||
|     </Portal> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const DialogCloseTrigger = React.forwardRef< | ||||
|   HTMLButtonElement, | ||||
|   ChakraDialog.CloseTriggerProps | ||||
| >(function DialogCloseTrigger(props, ref) { | ||||
|   return ( | ||||
|     <ChakraDialog.CloseTrigger | ||||
|       position="absolute" | ||||
|       top="2" | ||||
|       insetEnd="2" | ||||
|       {...props} | ||||
|       asChild | ||||
|     > | ||||
|       <CloseButton size="sm" ref={ref}> | ||||
|         {props.children} | ||||
|       </CloseButton> | ||||
|     </ChakraDialog.CloseTrigger> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const DialogRoot = ChakraDialog.Root | ||||
| export const DialogFooter = ChakraDialog.Footer | ||||
| export const DialogHeader = ChakraDialog.Header | ||||
| export const DialogBody = ChakraDialog.Body | ||||
| export const DialogBackdrop = ChakraDialog.Backdrop | ||||
| export const DialogTitle = ChakraDialog.Title | ||||
| export const DialogDescription = ChakraDialog.Description | ||||
| export const DialogTrigger = ChakraDialog.Trigger | ||||
| export const DialogActionTrigger = ChakraDialog.ActionTrigger | ||||
| @@ -1,52 +0,0 @@ | ||||
| import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react" | ||||
| import { CloseButton } from "./close-button" | ||||
| import * as React from "react" | ||||
|  | ||||
| interface DrawerContentProps extends ChakraDrawer.ContentProps { | ||||
|   portalled?: boolean | ||||
|   portalRef?: React.RefObject<HTMLElement> | ||||
|   offset?: ChakraDrawer.ContentProps["padding"] | ||||
| } | ||||
|  | ||||
| export const DrawerContent = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   DrawerContentProps | ||||
| >(function DrawerContent(props, ref) { | ||||
|   const { children, portalled = true, portalRef, offset, ...rest } = props | ||||
|   return ( | ||||
|     <Portal disabled={!portalled} container={portalRef}> | ||||
|       <ChakraDrawer.Positioner padding={offset}> | ||||
|         <ChakraDrawer.Content ref={ref} {...rest} asChild={false}> | ||||
|           {children} | ||||
|         </ChakraDrawer.Content> | ||||
|       </ChakraDrawer.Positioner> | ||||
|     </Portal> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const DrawerCloseTrigger = React.forwardRef< | ||||
|   HTMLButtonElement, | ||||
|   ChakraDrawer.CloseTriggerProps | ||||
| >(function DrawerCloseTrigger(props, ref) { | ||||
|   return ( | ||||
|     <ChakraDrawer.CloseTrigger | ||||
|       position="absolute" | ||||
|       top="2" | ||||
|       insetEnd="2" | ||||
|       {...props} | ||||
|       asChild | ||||
|     > | ||||
|       <CloseButton size="sm" ref={ref} /> | ||||
|     </ChakraDrawer.CloseTrigger> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const DrawerTrigger = ChakraDrawer.Trigger | ||||
| export const DrawerRoot = ChakraDrawer.Root | ||||
| export const DrawerFooter = ChakraDrawer.Footer | ||||
| export const DrawerHeader = ChakraDrawer.Header | ||||
| export const DrawerBody = ChakraDrawer.Body | ||||
| export const DrawerBackdrop = ChakraDrawer.Backdrop | ||||
| export const DrawerDescription = ChakraDrawer.Description | ||||
| export const DrawerTitle = ChakraDrawer.Title | ||||
| export const DrawerActionTrigger = ChakraDrawer.ActionTrigger | ||||
| @@ -1,110 +0,0 @@ | ||||
| "use client" | ||||
|  | ||||
| import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react" | ||||
| import * as React from "react" | ||||
| import { LuCheck, LuChevronRight } from "react-icons/lu" | ||||
|  | ||||
| interface MenuContentProps extends ChakraMenu.ContentProps { | ||||
|   portalled?: boolean | ||||
|   portalRef?: React.RefObject<HTMLElement> | ||||
| } | ||||
|  | ||||
| export const MenuContent = React.forwardRef<HTMLDivElement, MenuContentProps>( | ||||
|   function MenuContent(props, ref) { | ||||
|     const { portalled = true, portalRef, ...rest } = props | ||||
|     return ( | ||||
|       <Portal disabled={!portalled} container={portalRef}> | ||||
|         <ChakraMenu.Positioner> | ||||
|           <ChakraMenu.Content ref={ref} {...rest} /> | ||||
|         </ChakraMenu.Positioner> | ||||
|       </Portal> | ||||
|     ) | ||||
|   }, | ||||
| ) | ||||
|  | ||||
| export const MenuArrow = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   ChakraMenu.ArrowProps | ||||
| >(function MenuArrow(props, ref) { | ||||
|   return ( | ||||
|     <ChakraMenu.Arrow ref={ref} {...props}> | ||||
|       <ChakraMenu.ArrowTip /> | ||||
|     </ChakraMenu.Arrow> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const MenuCheckboxItem = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   ChakraMenu.CheckboxItemProps | ||||
| >(function MenuCheckboxItem(props, ref) { | ||||
|   return ( | ||||
|     <ChakraMenu.CheckboxItem ref={ref} {...props}> | ||||
|       <ChakraMenu.ItemIndicator hidden={false}> | ||||
|         <LuCheck /> | ||||
|       </ChakraMenu.ItemIndicator> | ||||
|       {props.children} | ||||
|     </ChakraMenu.CheckboxItem> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const MenuRadioItem = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   ChakraMenu.RadioItemProps | ||||
| >(function MenuRadioItem(props, ref) { | ||||
|   const { children, ...rest } = props | ||||
|   return ( | ||||
|     <ChakraMenu.RadioItem ps="8" ref={ref} {...rest}> | ||||
|       <AbsoluteCenter axis="horizontal" left="4" asChild> | ||||
|         <ChakraMenu.ItemIndicator> | ||||
|           <LuCheck /> | ||||
|         </ChakraMenu.ItemIndicator> | ||||
|       </AbsoluteCenter> | ||||
|       <ChakraMenu.ItemText>{children}</ChakraMenu.ItemText> | ||||
|     </ChakraMenu.RadioItem> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const MenuItemGroup = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   ChakraMenu.ItemGroupProps | ||||
| >(function MenuItemGroup(props, ref) { | ||||
|   const { title, children, ...rest } = props | ||||
|   return ( | ||||
|     <ChakraMenu.ItemGroup ref={ref} {...rest}> | ||||
|       {title && ( | ||||
|         <ChakraMenu.ItemGroupLabel userSelect="none"> | ||||
|           {title} | ||||
|         </ChakraMenu.ItemGroupLabel> | ||||
|       )} | ||||
|       {children} | ||||
|     </ChakraMenu.ItemGroup> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export interface MenuTriggerItemProps extends ChakraMenu.ItemProps { | ||||
|   startIcon?: React.ReactNode | ||||
| } | ||||
|  | ||||
| export const MenuTriggerItem = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   MenuTriggerItemProps | ||||
| >(function MenuTriggerItem(props, ref) { | ||||
|   const { startIcon, children, ...rest } = props | ||||
|   return ( | ||||
|     <ChakraMenu.TriggerItem ref={ref} {...rest}> | ||||
|       {startIcon} | ||||
|       {children} | ||||
|       <LuChevronRight /> | ||||
|     </ChakraMenu.TriggerItem> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup | ||||
| export const MenuContextTrigger = ChakraMenu.ContextTrigger | ||||
| export const MenuRoot = ChakraMenu.Root | ||||
| export const MenuSeparator = ChakraMenu.Separator | ||||
|  | ||||
| export const MenuItem = ChakraMenu.Item | ||||
| export const MenuItemText = ChakraMenu.ItemText | ||||
| export const MenuItemCommand = ChakraMenu.ItemCommand | ||||
| export const MenuTrigger = ChakraMenu.Trigger | ||||
| @@ -1,7 +0,0 @@ | ||||
|  | ||||
| import buttonRecipe from '@/theme/recipes/button'; | ||||
| import { chakra } from '@chakra-ui/react'; | ||||
|  | ||||
| // we export the element with the application recipe theme. | ||||
| // cf doc: https://www.chakra-ui.com/docs/theming/recipes | ||||
| export const Button = chakra("button", buttonRecipe) | ||||
| @@ -1,51 +1,39 @@ | ||||
| "use client" | ||||
|  | ||||
| import { RestErrorResponse } from "@/back-api"; | ||||
| import { | ||||
|   Toaster as ChakraToaster, | ||||
|   Portal, | ||||
|   Spinner, | ||||
|   Stack, | ||||
|   Toast, | ||||
|   createToaster, | ||||
| } from "@chakra-ui/react" | ||||
| import { HStack } from "@/ui"; | ||||
|  | ||||
| export const toaster = createToaster({ | ||||
|   placement: "bottom-end", | ||||
|   pauseOnPageIdle: true, | ||||
| }) | ||||
| // export const toaster = createToaster({ | ||||
| //   placement: "bottom-end", | ||||
| //   pauseOnPageIdle: true, | ||||
| // }) | ||||
|  | ||||
| export const toasterAPIError = (error: RestErrorResponse) => { | ||||
|   toaster.create({ | ||||
|     title: `[${error.status}] ${error.statusMessage}`, | ||||
|     description: error.message, | ||||
|   }); | ||||
| }; | ||||
| // export const toasterAPIError = (error: RestErrorResponse) => { | ||||
| //   toaster.create({ | ||||
| //     title: `[${error.status}] ${error.statusMessage}`, | ||||
| //     description: error.message, | ||||
| //   }); | ||||
| // }; | ||||
|  | ||||
| export const Toaster = () => { | ||||
|   return ( | ||||
|     <Portal> | ||||
|       <ChakraToaster toaster={toaster} insetInline={{ mdDown: "4" }}> | ||||
|         {(toast) => ( | ||||
|           <Toast.Root width={{ md: "sm" }}> | ||||
|             {toast.type === "loading" ? ( | ||||
|               <Spinner size="sm" color="blue.solid" /> | ||||
|             ) : ( | ||||
|               <Toast.Indicator /> | ||||
|             )} | ||||
|             <Stack gap="1" flex="1" maxWidth="100%"> | ||||
|               {toast.title && <Toast.Title>{toast.title}</Toast.Title>} | ||||
|               {toast.description && ( | ||||
|                 <Toast.Description>{toast.description}</Toast.Description> | ||||
|               )} | ||||
|             </Stack> | ||||
|             {toast.action && ( | ||||
|               <Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger> | ||||
|             )} | ||||
|             {toast.meta?.closable && <Toast.CloseTrigger />} | ||||
|           </Toast.Root> | ||||
|         )} | ||||
|       </ChakraToaster> | ||||
|     </Portal> | ||||
|   ) | ||||
| } | ||||
| // export const Toaster = () => { | ||||
| //   return ( | ||||
| //     <Portal> | ||||
| //       <ArkToaster toaster={toaster}> | ||||
| //         {(toast) => ( | ||||
| //           <Toast.Root> | ||||
| //             <HStack spacing="1" style={{ width: "100%" }}> | ||||
| //               {toast.title && <Toast.Title>{toast.title}</Toast.Title>} | ||||
| //               {toast.description && ( | ||||
| //                 <Toast.Description>{toast.description}</Toast.Description> | ||||
| //               )} | ||||
| //             </HStack> | ||||
| //             {toast.action && ( | ||||
| //               <Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger> | ||||
| //             )} | ||||
| //             {toast.meta?.closable && <Toast.CloseTrigger />} | ||||
| //           </Toast.Root> | ||||
| //         )} | ||||
| //       </ArkToaster> | ||||
| //     </Portal> | ||||
| //   ) | ||||
| // } | ||||
|   | ||||
| @@ -1,28 +0,0 @@ | ||||
| import dayjs from 'dayjs'; | ||||
| import 'dayjs/locale/fr'; | ||||
| import advancedFormat from 'dayjs/plugin/advancedFormat'; | ||||
| import customParseFormat from 'dayjs/plugin/customParseFormat'; | ||||
| import dayOfYear from 'dayjs/plugin/dayOfYear'; | ||||
| import duration from 'dayjs/plugin/duration'; | ||||
| import isBetween from 'dayjs/plugin/isBetween'; | ||||
| import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; | ||||
| import isToday from 'dayjs/plugin/isToday'; | ||||
| import isTomorrow from 'dayjs/plugin/isTomorrow'; | ||||
| import isYesterday from 'dayjs/plugin/isYesterday'; | ||||
| import quarterOfYear from 'dayjs/plugin/quarterOfYear'; | ||||
| import relativeTime from 'dayjs/plugin/relativeTime'; | ||||
| import weekOfYear from 'dayjs/plugin/weekOfYear'; | ||||
|  | ||||
| dayjs.locale('fr'); | ||||
| dayjs.extend(relativeTime); | ||||
| dayjs.extend(customParseFormat); | ||||
| dayjs.extend(weekOfYear); | ||||
| dayjs.extend(isSameOrAfter); | ||||
| dayjs.extend(isToday); | ||||
| dayjs.extend(isTomorrow); | ||||
| dayjs.extend(isYesterday); | ||||
| dayjs.extend(dayOfYear); | ||||
| dayjs.extend(isBetween); | ||||
| dayjs.extend(advancedFormat); | ||||
| dayjs.extend(quarterOfYear); | ||||
| dayjs.extend(duration); | ||||
| @@ -1,2 +0,0 @@ | ||||
| import './axios'; | ||||
| import './dayjs'; | ||||
| @@ -1,4 +1,4 @@ | ||||
| export const BASE_WRAP_SPACING = { base: "5px", md: "10px", lg: "20px" }; | ||||
| export const BASE_WRAP_WIDTH = { base: "90%", md: "45%", lg: "270px" }; | ||||
| export const BASE_WRAP_HEIGHT = { base: "75px", lg: "120px" }; | ||||
| export const BASE_WRAP_ICON_SIZE = { base: "50px", lg: "100px" }; | ||||
| export const BASE_WRAP_SPACING = "5px";   // { base: "5px", md: "10px", lg: "20px" }; | ||||
| export const BASE_WRAP_WIDTH = "90%";     // { base: "90%", md: "45%", lg: "270px" }; | ||||
| export const BASE_WRAP_HEIGHT = "75px";   // { base: "75px", lg: "120px" }; | ||||
| export const BASE_WRAP_ICON_SIZE = "50px";// { base: "50px", lg: "100px" }; | ||||
| @@ -1,26 +1,27 @@ | ||||
| import { Box, Button, Center, Heading, Link, Text } from '@chakra-ui/react'; | ||||
|  | ||||
| import { MdControlCamera } from 'react-icons/md'; | ||||
|  | ||||
| import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Link, Text } from '@/ui'; | ||||
|  | ||||
| export const Error401 = () => { | ||||
|   return ( | ||||
|     <> | ||||
|       <TopBar /> | ||||
|       <PageLayoutInfoCenter padding="25px"> | ||||
|         <Center> | ||||
|       <PageLayoutInfoCenter padding="25px" width="75%"> | ||||
|         <Flex align="center"> | ||||
|           <MdControlCamera size="250px" color="red.600" /> | ||||
|         </Center> | ||||
|         <Box textAlign="center"> | ||||
|           <Heading>Erreur 401</Heading> | ||||
|         </Flex> | ||||
|         <Flex style={{ textAlign: "center" }}> | ||||
|           <Text fontSize="px">Erreur 401</Text> | ||||
|           <Text color="red.600"> | ||||
|             Vous n'êtes pas autorisé a accéder a ce contenu. | ||||
|           </Text> | ||||
|           <Link as="a" href="/"> | ||||
|           <Link href="/"> | ||||
|             Retour à l'accueil | ||||
|           </Link> | ||||
|         </Box> | ||||
|         </Flex> | ||||
|       </PageLayoutInfoCenter> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,24 +1,26 @@ | ||||
| import { Box, Button, Center, Heading, Link, Text } from '@chakra-ui/react'; | ||||
| import { MdDangerous } from 'react-icons/md'; | ||||
| import { MdControlCamera } from 'react-icons/md'; | ||||
|  | ||||
| import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Link, Text } from '@/ui'; | ||||
|  | ||||
| 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> | ||||
|       <PageLayoutInfoCenter padding="25px" width="75%"> | ||||
|         <Flex align="center"> | ||||
|           <MdControlCamera size="250px" color="red.600" /> | ||||
|         </Flex> | ||||
|         <Flex style={{ textAlign: "center" }}> | ||||
|           <Text fontSize="px">Erreur 403</Text> | ||||
|           <Text color="red.600"> | ||||
|             Cette page vous est interdite. | ||||
|           </Text> | ||||
|           <Link href="/"> | ||||
|             Retour à l'accueil | ||||
|           </Link> | ||||
|         </Box> | ||||
|         </Flex> | ||||
|       </PageLayoutInfoCenter> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,26 +1,26 @@ | ||||
| import { Box, Button, Center, Heading, Link, Text } from '@chakra-ui/react'; | ||||
| import { MdSignpost } from 'react-icons/md'; | ||||
| import { MdControlCamera } from 'react-icons/md'; | ||||
|  | ||||
| import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Link, Text } from '@/ui'; | ||||
|  | ||||
| export const Error404 = () => { | ||||
|   return ( | ||||
|     <> | ||||
|       <TopBar /> | ||||
|       <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é | ||||
|       <PageLayoutInfoCenter padding="25px" width="75%"> | ||||
|         <Flex align="center"> | ||||
|           <MdControlCamera size="250px" color="red.600" /> | ||||
|         </Flex> | ||||
|         <Flex style={{ textAlign: "center" }}> | ||||
|           <Text fontSize="px">Erreur 404</Text> | ||||
|           <Text color="red.600"> | ||||
|             Cette page n'existe plus ou l'URL a changé. | ||||
|           </Text> | ||||
|           <Link href="/"> | ||||
|             Retour à l'accueil | ||||
|           </Link> | ||||
|         </Box> | ||||
|         </Flex> | ||||
|       </PageLayoutInfoCenter> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,31 +1,27 @@ | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
| import React, { FC } from 'react'; | ||||
|  | ||||
| import { | ||||
|   AlertDescription, | ||||
|   AlertRoot, | ||||
|   AlertTitle, | ||||
|   Box, | ||||
|   Collapsible, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   FallbackProps, | ||||
|   ErrorBoundary as ReactErrorBoundary, | ||||
| } from 'react-error-boundary'; | ||||
| import { Button } from '@/components/ui/themed'; | ||||
| import { LuChevronUp, LuChevronDown } from 'react-icons/lu'; | ||||
|  | ||||
| const ErrorFallback = ({ error }: FallbackProps) => { | ||||
|   const { open, onToggle } = useDisclosure(); | ||||
|   return ( | ||||
|     <Box p="4" m="auto"> | ||||
|     <Flex direction="column" style={{ padding: 4, margin: "auto", background: "red.500" }}> | ||||
|       <Text color='red'>An unexpected error has occurred.</Text> | ||||
|       <Text>Message: {error.message}</Text> | ||||
|       {/* | ||||
|       <AlertRoot status="error" borderRadius="md"> | ||||
|         {/* <AlertIcon /> */} | ||||
|         <Box flex="1"> | ||||
|         <Flex style={{ flex: "1" }}> | ||||
|           <AlertTitle>An unexpected error has occurred.</AlertTitle> | ||||
|           <AlertDescription display="block" lineHeight="1.4"> | ||||
|             <Button | ||||
|               theme="@secondary" | ||||
|               //theme="@secondary" | ||||
|               color="red.800" | ||||
|               //size="sm" | ||||
|               onClick={onToggle} | ||||
| @@ -34,15 +30,15 @@ const ErrorFallback = ({ error }: FallbackProps) => { | ||||
|             </Button> | ||||
|             <Collapsible.Root open={open}> | ||||
|               <Collapsible.Content> | ||||
|                 <Box mt={4} fontFamily="monospace"> | ||||
|                 <Flex mt={4} fontFamily="monospace"> | ||||
|                   {error.message} | ||||
|                 </Box> | ||||
|                 </Flex> | ||||
|               </Collapsible.Content> | ||||
|             </Collapsible.Root> | ||||
|           </AlertDescription> | ||||
|         </Box> | ||||
|       </AlertRoot> | ||||
|     </Box> | ||||
|         </Flex> | ||||
|       </AlertRoot> */} | ||||
|     </Flex > | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,15 @@ | ||||
| import { createIcon } from '@chakra-ui/react'; | ||||
|  | ||||
| export const DoubleArrowIcon = createIcon({ | ||||
|   displayName: 'DoubleArrowIcon', | ||||
|   viewBox: '0 0 24 24', | ||||
|   path: ( | ||||
|     <path | ||||
|       fillRule="evenodd" | ||||
|       clipRule="evenodd" | ||||
|       d="M1.293 12.207a1 1 0 0 1 0-1.414l6.364-6.364A1 1 0 0 1 9.07 5.843L4.414 10.5h15.172l-4.657-4.657a1 1 0 0 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 0 1-1.414-1.414l4.657-4.657H4.414l4.657 4.657a1 1 0 1 1-1.414 1.414l-6.364-6.364Z" | ||||
|       fill="currentColor" | ||||
|     /> | ||||
|   ), | ||||
| }); | ||||
|  | ||||
| export const DoubleArrowIcon = <></>; | ||||
| // createIcon({ | ||||
| //   displayName: 'DoubleArrowIcon', | ||||
| //   viewBox: '0 0 24 24', | ||||
| //   path: ( | ||||
| //     <path | ||||
| //       fillRule="evenodd" | ||||
| //       clipRule="evenodd" | ||||
| //       d="M1.293 12.207a1 1 0 0 1 0-1.414l6.364-6.364A1 1 0 0 1 9.07 5.843L4.414 10.5h15.172l-4.657-4.657a1 1 0 0 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 0 1-1.414-1.414l4.657-4.657H4.414l4.657 4.657a1 1 0 1 1-1.414 1.414l-6.364-6.364Z" | ||||
| //       fill="currentColor" | ||||
| //     /> | ||||
| //   ), | ||||
| // }); | ||||
|   | ||||
| @@ -1,19 +1,16 @@ | ||||
| import { StrictMode } from 'react'; | ||||
|  | ||||
| import ReactDOM from 'react-dom/client'; | ||||
|  | ||||
| import App from '@/App'; | ||||
| import { ColorModeProvider } from './components/ui/color-mode'; | ||||
|  | ||||
| import { ThemeProvider } from './theme/ThemeContext'; | ||||
| import { StrictMode } from 'react'; | ||||
| // Render the app | ||||
| const rootElement = document.getElementById('root'); | ||||
| if (rootElement && !rootElement.innerHTML) { | ||||
|   const root = ReactDOM.createRoot(rootElement); | ||||
|   root.render( | ||||
|     <StrictMode> | ||||
|       <ColorModeProvider> | ||||
|       <ThemeProvider> | ||||
|         <App /> | ||||
|       </ColorModeProvider> | ||||
|       </ThemeProvider> | ||||
|     </StrictMode> | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { AudioPlayer } from '@/components/AudioPlayer'; | ||||
| import { ErrorBoundary } from '@/errors/ErrorBoundary'; | ||||
| import { AppRoutes } from '@/scene/AppRoutes'; | ||||
| import { Text } from '@/ui'; | ||||
| import { ServiceContextProvider } from '@/service/ServiceContext'; | ||||
|  | ||||
| export const App = () => { | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuDisc3 } from 'react-icons/lu'; | ||||
|  | ||||
| import { MdEdit } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| @@ -10,13 +9,13 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp'; | ||||
| import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; | ||||
| import { DisplayTrack } from '@/components/track/DisplayTrack'; | ||||
| import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificAlbum } from '@/service/Album'; | ||||
| import { useTracksOfAnAlbum } from '@/service/Track'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const AlbumDetailPage = () => { | ||||
|   const { albumId } = useParams(); | ||||
| @@ -47,7 +46,7 @@ export const AlbumDetailPage = () => { | ||||
|     return ( | ||||
|       <> | ||||
|         <TopBar title="Album detail" /> | ||||
|         <PageLayoutInfoCenter> | ||||
|         <PageLayoutInfoCenter width="75%"> | ||||
|           Fail to load artist id: {albumId} | ||||
|         </PageLayoutInfoCenter> | ||||
|       </> | ||||
| @@ -69,17 +68,19 @@ export const AlbumDetailPage = () => { | ||||
|         data-testid="Album-detail-page_layout"> | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|         > | ||||
|           <Covers | ||||
|             data={dataAlbum?.covers} | ||||
|             // TODO: iconEmpty={LuDisc3} | ||||
|             slideshow | ||||
|           /> | ||||
|           <Flex direction="column" width="80%" marginRight="auto"> | ||||
|           <Flex direction="column" style={{ width: "80%", marginRight: "auto" }}> | ||||
|             <Text fontSize="24px" fontWeight="bold"> | ||||
|               {dataAlbum?.name} | ||||
|             </Text> | ||||
| @@ -94,25 +95,27 @@ export const AlbumDetailPage = () => { | ||||
|  | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap={BASE_WRAP_SPACING} | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|           data-testid="Album-detail-page_flex-list" | ||||
|           gap={BASE_WRAP_SPACING} style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|           data-test-id="Album-detail-page_flex-list" | ||||
|         > | ||||
|           {tracksOnAnAlbum?.map((data) => ( | ||||
|             <Box | ||||
|               minWidth="100%" | ||||
|               //height="60px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|             <Flex | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 //height="60px" | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrackFull | ||||
| @@ -129,7 +132,7 @@ export const AlbumDetailPage = () => { | ||||
|                 ]} | ||||
|                 data-testid="Album-detail-page_display-detail" | ||||
|               /> | ||||
|             </Box> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -9,9 +9,9 @@ import { SearchInput } from '@/components/SearchInput'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { DisplayAlbum } from '@/components/album/DisplayAlbum'; | ||||
| import { useOrderedAlbums } from '@/service/Album'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { BASE_WRAP_SPACING, BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing'; | ||||
| import { Flex, HStack } from '@chakra-ui/react'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing'; | ||||
| import { Button, Flex, HStack } from '@/ui'; | ||||
|  | ||||
| export const AlbumsPage = () => { | ||||
|   const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); | ||||
| @@ -25,7 +25,7 @@ export const AlbumsPage = () => { | ||||
|     return ( | ||||
|       <> | ||||
|         <TopBar title="All Albums" /> | ||||
|         <PageLayoutInfoCenter>No Album available</PageLayoutInfoCenter> | ||||
|         <PageLayoutInfoCenter width="75%">No Album available</PageLayoutInfoCenter> | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
| @@ -36,29 +36,31 @@ export const AlbumsPage = () => { | ||||
|         <SearchInput onChange={setFilterTitle} /> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|         <HStack wrap="wrap" /*spacing={BASE_WRAP_SPACING}*/ marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}> | ||||
|           {dataAlbums.map((data) => ( | ||||
|             <Flex align="flex-start" | ||||
|               width={BASE_WRAP_WIDTH} | ||||
|               height={BASE_WRAP_HEIGHT} | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|             <Button | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 width: BASE_WRAP_WIDTH, | ||||
|                 height: BASE_WRAP_HEIGHT, | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover={ | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|                 alignContent: "flex-start", | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data.id)} | ||||
|             > | ||||
|               <DisplayAlbum dataAlbum={data} /> | ||||
|             </Flex> | ||||
|             </Button> | ||||
|           ))} | ||||
|         </HStack> | ||||
|         <EmptyEnd /> | ||||
|       </PageLayout> | ||||
|       </PageLayout > | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuDisc3, LuUser } from 'react-icons/lu'; | ||||
| import { MdEdit, MdPerson } from 'react-icons/md'; | ||||
|  | ||||
| import { MdEdit } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| import { Covers } from '@/components/Cover'; | ||||
| @@ -15,7 +14,8 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificAlbum } from '@/service/Album'; | ||||
| import { useSpecificArtist } from '@/service/Artist'; | ||||
| import { useTracksOfAnAlbum } from '@/service/Track'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const ArtistAlbumDetailPage = () => { | ||||
|   const { artistId, albumId } = useParams(); | ||||
| @@ -60,13 +60,18 @@ export const ArtistAlbumDetailPage = () => { | ||||
|           <> | ||||
|             <Button | ||||
|               {...BUTTON_TOP_BAR_PROPERTY} | ||||
|               marginRight="auto" | ||||
|  | ||||
|               style={{ | ||||
|                 marginRight: "auto" | ||||
|               }} | ||||
|               onClick={() => navigate(`/artist/${dataArtist.id}`)} | ||||
|             > | ||||
|               <Covers | ||||
|                 data={dataArtist?.covers} | ||||
|                 size="35px" | ||||
|                 borderRadius="full" | ||||
|                 style={{ | ||||
|                   borderRadius: "full", | ||||
|                 }} | ||||
|               // TODO: iconEmpty={MdPerson} | ||||
|               /> | ||||
|               <Text fontSize="24px" fontWeight="bold"> | ||||
| @@ -87,9 +92,11 @@ export const ArtistAlbumDetailPage = () => { | ||||
|       <PageLayout> | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|           gap="10px" | ||||
|         > | ||||
|           <Covers | ||||
| @@ -97,7 +104,8 @@ export const ArtistAlbumDetailPage = () => { | ||||
|             // TODO: iconEmpty={LuDisc3} | ||||
|             slideshow | ||||
|           /> | ||||
|           <Flex direction="column" width="80%" marginRight="auto"> | ||||
|           <Flex direction="column" | ||||
|             style={{ width: "80%", marginRight: "auto" }}> | ||||
|             <Text fontSize="24px" fontWeight="bold"> | ||||
|               {dataAlbum?.name} | ||||
|             </Text> | ||||
| @@ -113,23 +121,28 @@ export const ArtistAlbumDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap="20px" | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|  | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {tracksOnAnAlbum?.map((data) => ( | ||||
|             <Box | ||||
|               minWidth="100%" | ||||
|               height="60px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|             <Flex | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|  | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 height: "60px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrack | ||||
| @@ -147,7 +160,7 @@ export const ArtistAlbumDetailPage = () => { | ||||
|                   { name: 'Add Playlist', onClick: () => { } }, | ||||
|                 ]} | ||||
|               /> | ||||
|             </Box> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Button, Flex, Text, HStack } from '@chakra-ui/react'; | ||||
| import { LuUser } from 'react-icons/lu'; | ||||
|  | ||||
| import { MdEdit, MdGroup } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| @@ -12,8 +11,9 @@ import { DisplayAlbumId } from '@/components/album/DisplayAlbumId'; | ||||
| import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp'; | ||||
| import { useSpecificArtist } from '@/service/Artist'; | ||||
| import { useAlbumIdsOfAnArtist } from '@/service/Track'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { Button, Flex, HStack, Text } from '@/ui'; | ||||
|  | ||||
| export const ArtistDetailPage = () => { | ||||
|   const { artistId } = useParams(); | ||||
| @@ -41,7 +41,7 @@ export const ArtistDetailPage = () => { | ||||
|         <> | ||||
|           <Button | ||||
|             {...BUTTON_TOP_BAR_PROPERTY} | ||||
|             marginRight="auto" | ||||
|             style={{ marginRight: "auto" }} | ||||
|             onClick={() => navigate(`/artist/all`)} | ||||
|           > | ||||
|             <MdGroup height="full" /> | ||||
| @@ -64,7 +64,7 @@ export const ArtistDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           margin="0 auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|         > | ||||
| @@ -88,25 +88,27 @@ export const ArtistDetailPage = () => { | ||||
|           </Flex> | ||||
|         </Flex> | ||||
|  | ||||
|         <HStack wrap="wrap" /*spacing={BASE_WRAP_SPACING}*/ marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}> | ||||
|           {albumIdsOfAnArtist?.map((data) => ( | ||||
|             <Flex align="flex-start" | ||||
|               width={BASE_WRAP_WIDTH} | ||||
|               height={BASE_WRAP_HEIGHT} | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|             <Button | ||||
|               key={data} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 alignContent: "flex-start", | ||||
|                 width: BASE_WRAP_WIDTH, | ||||
|                 height: BASE_WRAP_HEIGHT, | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover={ | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <DisplayAlbumId id={data} key={data} /> | ||||
|             </Flex> | ||||
|             </Button> | ||||
|           ))} | ||||
|         </HStack> | ||||
|         <EmptyEnd /> | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Button, Flex, Text, Tooltip, HStack, Span } from '@chakra-ui/react'; | ||||
| import { LuUser } from 'react-icons/lu'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { Artist, Track } from '@/back-api'; | ||||
| @@ -11,13 +9,14 @@ import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { SearchInput } from '@/components/SearchInput'; | ||||
| import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { useOrderedArtists } from '@/service/Artist'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { MdOutlineForkRight } from 'react-icons/md'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { shuffleArray } from '@/utils/arrayTools'; | ||||
| import { useTrackService } from '@/service/Track'; | ||||
| import { DataTools, TypeCheck } from '@/utils/data-tools'; | ||||
| import { Button, Flex, HStack, Span, Text } from '@/ui'; | ||||
| const LIMIT_RANDOM_VALUES = 25; | ||||
|  | ||||
| export const ArtistsPage = () => { | ||||
| @@ -57,35 +56,35 @@ export const ArtistsPage = () => { | ||||
|     <> | ||||
|       <TopBar title="All artists"> | ||||
|         <SearchInput onChange={setFilterName} /> | ||||
|         <Tooltip.Root aria-label="Random play"> | ||||
|           <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onRandomPlay}> | ||||
|             <MdOutlineForkRight /> | ||||
|           </Button> | ||||
|         </Tooltip.Root> | ||||
|         <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onRandomPlay}> | ||||
|           <MdOutlineForkRight /> | ||||
|         </Button> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|         <HStack wrap="wrap" /*spacing={BASE_WRAP_SPACING}*/ marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", alignContent: "center" }}> | ||||
|           {dataArtist?.map((data) => ( | ||||
|             <Flex align="flex-start" | ||||
|               width={BASE_WRAP_WIDTH} | ||||
|               height={BASE_WRAP_HEIGHT} | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|             <Button | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 width: BASE_WRAP_WIDTH, | ||||
|                 height: BASE_WRAP_HEIGHT, | ||||
|                 alignContent: "flex-start", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover:{ | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <Flex direction="row" width="full" height="full"> | ||||
|               <Flex direction="row" width="100%" height="full"> | ||||
|                 <Covers | ||||
|                   data={data.covers} | ||||
|                   size={BASE_WRAP_ICON_SIZE} | ||||
|                   height="full" | ||||
|                   style={{ height: "100%" }} | ||||
|                 // iconEmpty={LuUser} | ||||
|                 /> | ||||
|                 <Flex | ||||
| @@ -94,19 +93,20 @@ export const ArtistsPage = () => { | ||||
|                   maxWidth="150px" | ||||
|                   height="full" | ||||
|                   paddingLeft="5px" | ||||
|                   overflowX="hidden" | ||||
|                   style={{ overflowX: "hidden" }} | ||||
|                 > | ||||
|                   <Text | ||||
|                   /*align="left"*/ | ||||
|                   /*noOfLines={[1, 2]}*/ | ||||
|                   > | ||||
|                     <Span | ||||
|                       as="span" | ||||
|                       fontSize="20px" | ||||
|                       fontWeight="bold" | ||||
|                       userSelect="none" | ||||
|                       marginRight="auto" | ||||
|                       overflow="hidden" | ||||
|                       style={{ | ||||
|                         marginRight: "auto", | ||||
|                         overflow: "hidden", | ||||
|                       }} | ||||
|                     > | ||||
|                       {data.name} | ||||
|                     </Span> | ||||
| @@ -114,7 +114,7 @@ export const ArtistsPage = () => { | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               </Flex> | ||||
|             </Flex> | ||||
|             </Button> | ||||
|           ))} | ||||
|         </HStack> | ||||
|         <EmptyEnd /> | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuDisc3 } from 'react-icons/lu'; | ||||
|  | ||||
| import { MdEdit } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| @@ -10,13 +9,13 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { GenderEditPopUp } from '@/components/popup/GenderEditPopUp'; | ||||
| import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; | ||||
| import { DisplayTrack } from '@/components/track/DisplayTrack'; | ||||
| import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificGender } from '@/service/Gender'; | ||||
| import { useTracksOfAGender } from '@/service/Track'; | ||||
| //import { useTracksOfAGender } from '@/service/Track'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const GenderDetailPage = () => { | ||||
|   const { genderId } = useParams(); | ||||
| @@ -68,17 +67,19 @@ export const GenderDetailPage = () => { | ||||
|       <PageLayout> | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|         > | ||||
|           <Covers | ||||
|             data={dataGender?.covers} | ||||
|             // TODO: iconEmpty={LuDisc3} | ||||
|             slideshow | ||||
|           /> | ||||
|           <Flex direction="column" width="80%" marginRight="auto"> | ||||
|           <Flex direction="column" style={{ width: "80%", marginRight: "auto" }}> | ||||
|             <Text fontSize="24px" fontWeight="bold"> | ||||
|               {dataGender?.name} | ||||
|             </Text> | ||||
| @@ -91,23 +92,26 @@ export const GenderDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap="20px" | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {tracksOnAGender?.map((data) => ( | ||||
|             <Box | ||||
|               minWidth="100%" | ||||
|               //height="60px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|             <Flex | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 //height="60px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrackFull | ||||
| @@ -123,7 +127,7 @@ export const GenderDetailPage = () => { | ||||
|                   { name: 'Add Playlist', onClick: () => { } }, | ||||
|                 ]} | ||||
|               /> | ||||
|             </Box> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import { SearchInput } from '@/components/SearchInput'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { DisplayGender } from '@/components/gender/DisplayGender'; | ||||
| import { useOrderedGenders } from '@/service/Gender'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { Flex, HStack } from '@chakra-ui/react'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Flex, HStack } from '@/ui'; | ||||
|  | ||||
| export const GendersPage = () => { | ||||
|   const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); | ||||
| @@ -35,20 +35,24 @@ export const GendersPage = () => { | ||||
|         <SearchInput onChange={setFilterTitle} /> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|         <HStack wrap="wrap" /*spacing="20px"*/ marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ | ||||
|           // wrap: "wrap", | ||||
|           /*spacing="20px"*/ margin: "0 auto 0 auto", padding: "20px", justifyContent: "center" | ||||
|         }}> | ||||
|           {dataGenders.map((data) => ( | ||||
|             <Flex align="flex-start" | ||||
|               width="270px" | ||||
|               height="120px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 width: "270px", | ||||
|                 height: "120px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 //background:{useColorModeValue('#FFFFFF88', '#00000088')}, | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data.id)} | ||||
|             > | ||||
|   | ||||
| @@ -1,11 +1,5 @@ | ||||
| import { useCallback, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Flex, | ||||
|   Input, | ||||
|   Table, | ||||
|   Text, | ||||
| } from '@chakra-ui/react'; | ||||
| import { LuTrash } from 'react-icons/lu'; | ||||
| import { MdCloudUpload } from 'react-icons/md'; | ||||
|  | ||||
| @@ -31,7 +25,7 @@ import { useGenderService, useOrderedGenders } from '@/service/Gender'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useTrackService } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button } from '@/components/ui/themed'; | ||||
| import { Button, Flex, Table, Text } from '@/ui'; | ||||
|  | ||||
| export class ElementList { | ||||
|   constructor( | ||||
| @@ -121,7 +115,7 @@ export const AddPage = () => { | ||||
|     updateNeedSend(); | ||||
|   }; | ||||
|  | ||||
|   const removeElementFromList = (data: FileParsedElement, value: any): void => { | ||||
|   const removeElementFromList = (data: FileParsedElement): void => { | ||||
|     const parsedElementTmp = [...parsedElement]; | ||||
|     for (let iii = 0; iii < parsedElementTmp.length; iii++) { | ||||
|       if (parsedElementTmp[iii] === data) { | ||||
| @@ -420,15 +414,17 @@ export const AddPage = () => { | ||||
|       <PageLayout> | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto 0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|         > | ||||
|           <Flex direction="column" width="full"> | ||||
|           <Flex direction="column" style={{ width: "100%" }}> | ||||
|             <Flex> | ||||
|               <Text flex={1}>format:</Text> | ||||
|               <Text flex={4}> | ||||
|               <Text>format:</Text> | ||||
|               <Text> | ||||
|                 The format of the media permit to automatic find meta-data: | ||||
|                 <br /> | ||||
|                 Artist~album#idTrack-my name of my media.webm | ||||
| @@ -437,15 +433,15 @@ export const AddPage = () => { | ||||
|               </Text> | ||||
|             </Flex> | ||||
|             <Flex> | ||||
|               <Text flex={1}>Media:</Text> | ||||
|               <Input | ||||
|               <Text>Media:</Text> | ||||
|               {/* <Input | ||||
|                 flex={4} | ||||
|                 type="file" | ||||
|                 placeholder="Select a media file" | ||||
|                 accept=".webm" | ||||
|                 multiple | ||||
|                 onChange={onChangeFile} | ||||
|               /> | ||||
|               /> */} | ||||
|             </Flex> | ||||
|           </Flex> | ||||
|           {parsedElement && parsedElement.length !== 0 && ( | ||||
| @@ -478,14 +474,12 @@ export const AddPage = () => { | ||||
|                 suggestion={suggestedAlbum} | ||||
|               /> | ||||
|               <Table.Root | ||||
|                 colorPalette="striped" | ||||
|                 colorScheme="teal" | ||||
|                 background="gray.700" | ||||
|               > | ||||
|                 <Table.Header> | ||||
|                   <Table.Row> | ||||
|                     <Table.ColumnHeader>track ID</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader width="full">Title</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader style={{ width: "100%" }}>Title</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader>actions</Table.ColumnHeader> | ||||
|                   </Table.Row> | ||||
|                 </Table.Header> | ||||
| @@ -493,33 +487,33 @@ export const AddPage = () => { | ||||
|                   {parsedElement.map((data) => ( | ||||
|                     <Table.Row key={data.uniqueId}> | ||||
|                       <Table.Cell> | ||||
|                         <Input | ||||
|                         {/* <Input | ||||
|                           type="number" | ||||
|                           pattern="[0-9]{0-4}" | ||||
|                           placeholder="e?" | ||||
|                           value={data.trackId} | ||||
|                           onChange={(e) => onTrackId(data, e.target.value)} | ||||
|                           backgroundColor={ | ||||
|                           background={ | ||||
|                             data.trackIdDetected === true | ||||
|                               ? 'darkred' | ||||
|                               : undefined | ||||
|                           } | ||||
|                         /> | ||||
|                         /> */} | ||||
|                       </Table.Cell> | ||||
|                       <Table.Cell> | ||||
|                         <Input | ||||
|                         {/* <Input | ||||
|                           type="text" | ||||
|                           placeholder="Name of the Media" | ||||
|                           value={data.title} | ||||
|                           onChange={(e) => onTitle(data, e.target.value)} | ||||
|                           backgroundColor={ | ||||
|                           background={ | ||||
|                             data.title === '' ? 'darkred' : undefined | ||||
|                           } | ||||
|                         /> | ||||
|                         /> */} | ||||
|                         {data.nameDetected === true && ( | ||||
|                           <> | ||||
|                             <br /> | ||||
|                             <Text as="span" color="@danger"> | ||||
|                             <Text style={{ background: "red" }}> | ||||
|                               ^^^This title already exist !!! | ||||
|                             </Text> | ||||
|                           </> | ||||
| @@ -527,8 +521,8 @@ export const AddPage = () => { | ||||
|                       </Table.Cell> | ||||
|                       <Table.Cell> | ||||
|                         <Button | ||||
|                           onClick={(e) => | ||||
|                             removeElementFromList(data, e.target) | ||||
|                           onClick={() => | ||||
|                             removeElementFromList(data) | ||||
|                           } | ||||
|                         > | ||||
|                           <LuTrash /> Remove | ||||
| @@ -538,13 +532,14 @@ export const AddPage = () => { | ||||
|                   ))} | ||||
|                 </Table.Body> | ||||
|               </Table.Root> | ||||
|               <Flex marginY="15px"> | ||||
|               <Flex style={{ margin: "15px 0 15px 0" }}> | ||||
|                 <Button | ||||
|                   theme="@primary" | ||||
|                   //theme="@primary" | ||||
|                   onClick={sendFile} | ||||
|                   disabled={!needSend} | ||||
|                   marginLeft="auto" | ||||
|                   marginRight="30px" | ||||
|                   style={{ | ||||
|                     //disabled:!needSend, | ||||
|                     margin: "0 auto 0 30px" | ||||
|                   }} | ||||
|                 > | ||||
|                   <MdCloudUpload /> Upload | ||||
|                 </Button> | ||||
| @@ -554,14 +549,16 @@ export const AddPage = () => { | ||||
|  | ||||
|           {listFileInBdd && ( | ||||
|             <Table.Root | ||||
|               fontPalette="striped" | ||||
|               colorScheme="teal" | ||||
|               background="gray.700" | ||||
|               //fontPalette="striped" | ||||
|               //colorScheme="teal" | ||||
|               style={{ | ||||
|                 background: "gray.700" | ||||
|               }} | ||||
|             > | ||||
|               <Table.Header> | ||||
|                 <Table.Row> | ||||
|                   <Table.ColumnHeader>track ID</Table.ColumnHeader> | ||||
|                   <Table.ColumnHeader width="full">Title</Table.ColumnHeader> | ||||
|                   <Table.ColumnHeader style={{ width: "100%" }}>Title</Table.ColumnHeader> | ||||
|                   <Table.ColumnHeader>actions</Table.ColumnHeader> | ||||
|                 </Table.Row> | ||||
|               </Table.Header> | ||||
| @@ -597,13 +594,13 @@ export const AddPage = () => { | ||||
|             <> | ||||
|               <Text fontSize="30px">Rejected:</Text> | ||||
|               <Table.Root | ||||
|                 colorPalette="striped" | ||||
|                 colorScheme="teal" | ||||
|                 background="gray.700" | ||||
|                 //colorPalette="striped" | ||||
|                 //colorScheme="teal" | ||||
|                 style={{ background: "gray.700" }} | ||||
|               > | ||||
|                 <Table.Header> | ||||
|                   <Table.Row> | ||||
|                     <Table.ColumnHeader maxWidth="80%">file</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader style={{ maxWidth: "80%" }}>file</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader>Reason</Table.ColumnHeader> | ||||
|                   </Table.Row> | ||||
|                 </Table.Header> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { ReactElement } from 'react'; | ||||
|  | ||||
| import { Center, Flex, HStack, Text } from '@chakra-ui/react'; | ||||
| import { LuCrown, LuDisc3, LuEar, LuFileAudio } from 'react-icons/lu'; | ||||
| import { MdGroup } from 'react-icons/md'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| @@ -8,7 +7,8 @@ import { useNavigate } from 'react-router-dom'; | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
|  | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Div, Flex, HStack, Text } from '@/ui'; | ||||
|  | ||||
| type HomeListType = { | ||||
|   id: number; | ||||
| @@ -58,40 +58,56 @@ export const HomePage = () => { | ||||
|     <> | ||||
|       <TopBar title="Home" /> | ||||
|       <PageLayout> | ||||
|         <HStack wrap="wrap" /*spacing="20px"*/ marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ | ||||
|           flexWrap: "wrap", | ||||
|           /*spacing:"20px",*/ | ||||
|           margin: "0 auto", | ||||
|           padding: "20px", | ||||
|           alignContent: "center" | ||||
|         }}> | ||||
|           {homeList.map((data) => ( | ||||
|             <Flex align="flex-start" | ||||
|               width="200px" | ||||
|               height="190px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|               margin="auto" | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               style={{ | ||||
|                 width: "200px", | ||||
|                 height: "190px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|               }} | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 background: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <Flex direction="column" width="full" height="full"> | ||||
|                 <Center height="full">{data.icon}</Center> | ||||
|                 <Center> | ||||
|               <Flex | ||||
|                 direction="column" | ||||
|                 style={{ | ||||
|                   width: "100%", | ||||
|                   margin: "auto" | ||||
|                 }}> | ||||
|                 <Div style={{ margin: "auto", height: "100%", color: "black.50" }}>{data.icon}</Div> | ||||
|                 <Div style={{ margin: "auto", height: "100%" }}> | ||||
|                   <Text | ||||
|                     fontSize="25px" | ||||
|                     fontWeight="bold" | ||||
|                     textTransform="uppercase" | ||||
|                     userSelect="none" | ||||
|                     color="black.50" | ||||
|                     style={{ | ||||
|                       textTransform: "uppercase", | ||||
|                       userSelect: "none", | ||||
|                     }} | ||||
|                   > | ||||
|                     {data.name} | ||||
|                   </Text> | ||||
|                 </Center> | ||||
|                 </Div> | ||||
|               </Flex> | ||||
|             </Flex> | ||||
|           ))} | ||||
|         </HStack> | ||||
|       </PageLayout> | ||||
|       </PageLayout > | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| import { Box, Flex, Text } from '@chakra-ui/react'; | ||||
| import { Route, Routes, useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { EmptyEnd } from '@/components/EmptyEnd'; | ||||
| @@ -8,9 +7,10 @@ import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp'; | ||||
| import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; | ||||
| import { DisplayTrackFullId } from '@/components/track/DisplayTrackFullId'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export const OnAirPage = () => { | ||||
|   const { playInList } = useActivePlaylistService(); | ||||
| @@ -73,24 +73,27 @@ export const OnAirPage = () => { | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap={BASE_WRAP_SPACING} | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {!playTrackList && <>No playing</>} | ||||
|           {playTrackList && playTrackList?.map((data) => ( | ||||
|             <Box | ||||
|               minWidth="100%" | ||||
|               //height="60px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|             <Flex | ||||
|               key={data} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 //height:"60px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrackFullId | ||||
| @@ -111,7 +114,7 @@ export const OnAirPage = () => { | ||||
|                   }, | ||||
|                 ]} | ||||
|               /> | ||||
|             </Box> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { useEffect } from 'react'; | ||||
|  | ||||
| import { Center, Heading, Image, Text } from '@chakra-ui/react'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| import avatar_generic from '@/assets/images/avatar_generic.svg'; | ||||
| @@ -11,6 +10,8 @@ import { SessionState } from '@/service/SessionState'; | ||||
| import { useSessionService } from '@/service/session'; | ||||
| import { b64_to_utf8 } from '@/utils/sso'; | ||||
|  | ||||
| import { Flex, Text, Image } from '@/ui'; | ||||
|  | ||||
| export const SSOPage = () => { | ||||
|   const { data, keepConnected, token } = useParams(); | ||||
|   console.log(`- data: ${data}`); | ||||
| @@ -47,13 +48,11 @@ export const SSOPage = () => { | ||||
|     <> | ||||
|       <TopBar /> | ||||
|       <PageLayoutInfoCenter width="35%" gap="15px"> | ||||
|         <Center w="full"> | ||||
|           <Heading size="xl">LOGIN (after SSO) </Heading> | ||||
|         </Center> | ||||
|         <Text>LOGIN (after SSO)</Text> | ||||
|  | ||||
|         <Center w="full"> | ||||
|           <Image src={avatar_generic} boxSize="150px" borderRadius="full" /> | ||||
|         </Center> | ||||
|         <Flex width="100%"> | ||||
|           <Image src={avatar_generic} boxSize="150px" style={{ borderRadius: "full" }} /> | ||||
|         </Flex> | ||||
|         {token === '__CANCEL__' && ( | ||||
|           <Text> | ||||
|             <b>ERROR: </b> Request cancel of connection ! | ||||
|   | ||||
| @@ -1,12 +1,8 @@ | ||||
| import { ReactElement } from 'react'; | ||||
|  | ||||
| import { Flex, Text, HStack } 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 { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
|  | ||||
| export const alphabet = [ | ||||
|   'a', | ||||
| @@ -54,17 +50,17 @@ export const TrackSelectionPage = () => { | ||||
|               height="75px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|               background={useColorThemeValue('#FFFFFF88', '#00000088')} | ||||
|               key={data} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <Flex direction="column" width="full" height="full"> | ||||
|               <Flex direction="column" width="100%" height="full"> | ||||
|                 <Text | ||||
|                   margin="auto" | ||||
|                   fontSize="25px" | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| import { ReactElement } from 'react'; | ||||
|  | ||||
| import { Box, Flex } from '@chakra-ui/react'; | ||||
| import { useParams } from 'react-router-dom'; | ||||
|  | ||||
| import { EmptyEnd } from '@/components/EmptyEnd'; | ||||
| @@ -11,7 +8,8 @@ import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton'; | ||||
| import { alphabet } from '@/scene/track/TrackSelectionPage'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useTracksWithStartName } from '@/service/Track'; | ||||
| import { useColorModeValue } from '@/components/ui/color-mode'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export const TracksStartLetterDetailPage = () => { | ||||
|   const { startLetter } = useParams(); | ||||
| @@ -50,50 +48,55 @@ export const TracksStartLetterDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap="20px" | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {isLoading && | ||||
|             [1, 2, 3, 4]?.map((data) => ( | ||||
|               <Box | ||||
|                 minWidth="100%" | ||||
|                 //height="60px" | ||||
|                 border="1px" | ||||
|                 borderColor="brand.900" | ||||
|                 backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|               <Flex | ||||
|                 key={data} | ||||
|                 padding="5px" | ||||
|                 as="button" | ||||
|                 _hover={{ | ||||
|                   boxShadow: 'outline-over', | ||||
|                   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 style={{ | ||||
|                   minWidth: "100%", | ||||
|                   //height:"60px", | ||||
|                   border: "1px", | ||||
|                   borderColor: "brand.900", | ||||
|                   background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                   padding: "5px", | ||||
|                   // _hover: { | ||||
|                   //   boxShadow: 'outline-over', | ||||
|                   //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                   // } | ||||
|                 }} | ||||
|               > | ||||
|                 <DisplayTrackSkeleton /> | ||||
|               </Box> | ||||
|               </Flex> | ||||
|             ))} | ||||
|           {!isLoading && | ||||
|             tracksStartsWith?.map((data) => ( | ||||
|               <Box | ||||
|                 minWidth="100%" | ||||
|                 //height="60px" | ||||
|                 border="1px" | ||||
|                 borderColor="brand.900" | ||||
|                 backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')} | ||||
|               <Flex | ||||
|                 key={data.id} | ||||
|                 padding="5px" | ||||
|                 as="button" | ||||
|                 _hover={{ | ||||
|                   boxShadow: 'outline-over', | ||||
|                   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 style={{ | ||||
|                   minWidth: "100%", | ||||
|                   //height:"60px", | ||||
|                   border: "1px", | ||||
|                   borderColor: "brand.900", | ||||
|                   background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|  | ||||
|                   padding: "5px", | ||||
|                   // _hover: { | ||||
|                   //   boxShadow: 'outline-over', | ||||
|                   //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                   // } | ||||
|                 }} | ||||
|               > | ||||
|                 <DisplayTrackFull | ||||
|                   track={data} | ||||
|                   onClick={() => onSelectItem(data.id)} | ||||
|                 /> | ||||
|               </Box> | ||||
|               </Flex> | ||||
|             ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { useEffect, useMemo, useState } from 'react'; | ||||
| import { useMemo } from 'react'; | ||||
|  | ||||
| import { Album, AlbumResource } from '@/back-api'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | ||||
| import { useMemo } from 'react'; | ||||
|  | ||||
| import { Track, TrackResource } from '@/back-api'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
|   | ||||
| @@ -6,16 +6,13 @@ import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { SessionState } from '@/service/SessionState'; | ||||
| import { isBrowser } from '@/utils/layout'; | ||||
| import { parseToken } from '@/utils/sso'; | ||||
| import { createListCollection } from '@chakra-ui/react'; | ||||
|  | ||||
| const TOKEN_KEY = 'karusic-token-key-storage'; | ||||
|  | ||||
| export const USERS_COLLECTION = createListCollection({ | ||||
|   items: [ | ||||
|     { label: "admin", value: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E" }, | ||||
|     { label: "NO_USER", value: "svelte" }, | ||||
|   ], | ||||
| }) | ||||
| export const USERS_COLLECTION = [ | ||||
|   { label: "admin", value: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E" }, | ||||
|   { label: "NO_USER", value: "svelte" }, | ||||
| ]; | ||||
| export const USERS = { | ||||
|   admin: | ||||
|     'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E', | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { ReactElement, ReactNode } from 'react'; | ||||
|  | ||||
| import { ChakraProvider, defaultSystem } from '@chakra-ui/react'; | ||||
| import { RenderOptions, render } from '@testing-library/react'; | ||||
| import { BrowserRouter } from 'react-router-dom'; | ||||
|  | ||||
|   | ||||
							
								
								
									
										202
									
								
								front/src/theme/ThemeContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								front/src/theme/ThemeContext.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
| import { createContext, useContext, useState, useEffect, CSSProperties, ReactNode, useCallback } from "react"; | ||||
| import { basicColor } from "./colors"; | ||||
|  | ||||
| export type ApplyThemeProperty = { | ||||
|   componentType?: String; | ||||
|   // define the format of the shape | ||||
|   shape?: String; | ||||
|   // Define the color model used | ||||
|   palette?: String; | ||||
| } | ||||
|  | ||||
| export type ThemeContextProps = { | ||||
|   theme: string; | ||||
|   themeList: string[]; | ||||
|   setTheme: (string) => void; | ||||
|   toggleTheme: () => void; | ||||
|   convertStyle: (style: CSSProperties) => CSSProperties, | ||||
|   applyTheme: (style: CSSProperties, theme: ApplyThemeProperty) => CSSProperties, | ||||
| } | ||||
|  | ||||
|  | ||||
| const ThemeContext = createContext<ThemeContextProps>({ | ||||
|   theme: "error", | ||||
|   themeList: ["error"], | ||||
|   setTheme: (_: string) => { | ||||
|     console.error("Request setTheme without context"); | ||||
|   }, | ||||
|   toggleTheme: () => { | ||||
|     console.error("Request toggleTheme without context"); | ||||
|   }, | ||||
|   convertStyle: (style: CSSProperties): CSSProperties => { | ||||
|     console.error("Request convertStyle without context"); | ||||
|     return style; | ||||
|   }, | ||||
|   applyTheme: (style: CSSProperties, _theme: ApplyThemeProperty) => { | ||||
|     console.error("Request applyTheme without context"); | ||||
|     return style; | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| const themes = { | ||||
|   baseColors: { | ||||
|     ...basicColor | ||||
|   }, | ||||
|   themeColors: { | ||||
|     light: { | ||||
|       primary: "#007bff", | ||||
|       secondary: "#6c757d", | ||||
|       background: "#ffffff", | ||||
|       text: "#000000", | ||||
|     }, | ||||
|     dark: { | ||||
|       primary: "#375a7f", | ||||
|       secondary: "#444444", | ||||
|       background: "#181818", | ||||
|       text: "#ffffff", | ||||
|     }, | ||||
|   }, | ||||
|   base: { | ||||
|     Button: { | ||||
|       border: '1px solid red', | ||||
|     }, | ||||
|     Flex: { | ||||
|  | ||||
|     }, | ||||
|   }, | ||||
|   shape: { | ||||
|     outline: { | ||||
|       border: '1px solid transparent', | ||||
|     }, | ||||
|   }, | ||||
|   palette: { | ||||
|     primary: { | ||||
|       borderColor: "green" | ||||
|     }, | ||||
|     secondary: { | ||||
|  | ||||
|     }, | ||||
|     danger: { | ||||
|  | ||||
|     }, | ||||
|     success: { | ||||
|  | ||||
|     } | ||||
|  | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| export function ThemeProvider({ children, themeList = ["light", "dark"] }: { children: ReactNode, themeList?: string[] }) { | ||||
|   const [theme, setTheme] = useState<string>(themeList[1]); | ||||
|   const [basicsColor, setBasicsColor] = useState<{ [key: string]: string }>({}); | ||||
|  | ||||
|   // update the global CSS wen theme is updated: | ||||
|   useEffect(() => { | ||||
|     // generates the colors: | ||||
|     const baseColor = themes.baseColors; | ||||
|     const newBasicsColor: { [key: string]: string } = {} | ||||
|     Object.entries(baseColor).forEach(([tableColorName, colorValues]) => { | ||||
|       Object.entries(colorValues).forEach(([key, value]) => { | ||||
|         const colorName = `${tableColorName}.${key}`; | ||||
|         newBasicsColor[colorName] = value; | ||||
|       }); | ||||
|     }); | ||||
|     setBasicsColor(newBasicsColor); | ||||
|  | ||||
|     const root = document.documentElement; | ||||
|     //const currentColorTheme = themes.baseColors[theme]; | ||||
|     Object.entries(newBasicsColor).forEach(([key, value]) => { | ||||
|       const cssKeyValue = `--${key.replace(".", "-")}`; | ||||
|       console.log(` generate CSS color: ${cssKeyValue}:${value}`); | ||||
|       root.style.setProperty(cssKeyValue, value); | ||||
|     }); | ||||
|   }, [theme, setBasicsColor]); | ||||
|  | ||||
|   const toggleTheme = useCallback(() => { | ||||
|     setTheme((previous) => { | ||||
|       if (themeList.length <= 1) { | ||||
|         return previous; | ||||
|       } | ||||
|       for (let iii = 0; iii < themeList.length - 1; iii++) { | ||||
|         if (themeList[iii] == theme) { | ||||
|           return themeList[iii + 1] | ||||
|         } | ||||
|       } | ||||
|       return themeList[0]; | ||||
|     }) | ||||
|   }, [setTheme]); | ||||
|  | ||||
|   const convertElementStyle = useCallback((style: CSSProperties, key: string) => { | ||||
|     const value = style[key]; | ||||
|     if (typeof value !== "string") { | ||||
|       return; | ||||
|     } | ||||
|     // console.log(`request convert value: ${value}`); | ||||
|     if (basicsColor[value]) { | ||||
|       // console.log(`convert value: ${value} in ${basicsColor[value]}`); | ||||
|       style[key] = basicsColor[value]; | ||||
|     } | ||||
|   }, [basicsColor]); | ||||
|  | ||||
|   const convertStyle = useCallback((style: CSSProperties): CSSProperties => { | ||||
|     //console.log(`plop: ${theme}`); | ||||
|     if (!style) { | ||||
|       return style; | ||||
|     } | ||||
|     const out = { ...style } | ||||
|     convertElementStyle(out, "background"); | ||||
|     convertElementStyle(out, "backgroundColor"); | ||||
|     convertElementStyle(out, "borderColor"); | ||||
|     convertElementStyle(out, "color"); | ||||
|     return out; | ||||
|   }, [convertElementStyle]); | ||||
|  | ||||
|   const applyTheme = useCallback((style: CSSProperties, selectedTheme?: ApplyThemeProperty): CSSProperties => { | ||||
|     let out = style; | ||||
|     console.log(`apply style ... ${JSON.stringify(selectedTheme, null, 2)}`); | ||||
|     // Apply the basic theme of the component: | ||||
|     if (selectedTheme?.componentType) { | ||||
|       console.log(`detect component type theme ... ${selectedTheme?.componentType}`); | ||||
|       const base = themes?.base[selectedTheme.componentType]; | ||||
|       console.log(`    base = ${JSON.stringify(base, null, 2)}`); | ||||
|       if (base) { | ||||
|         out = { ...base, ...out }; | ||||
|       } | ||||
|     } | ||||
|     // Apply the variant selected: | ||||
|     if (selectedTheme?.shape) { | ||||
|       const shape = themes?.shape[selectedTheme.shape]; | ||||
|       if (shape) { | ||||
|         out = { ...shape, ...out }; | ||||
|       } | ||||
|     } | ||||
|     // Apply the palette selected: | ||||
|     if (selectedTheme?.palette) { | ||||
|       const palette = themes?.shape[selectedTheme.palette]; | ||||
|       if (palette) { | ||||
|         const palette2 = palette[theme]; | ||||
|         if (palette2) { | ||||
|           out = { ...palette2, ...out }; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     return convertStyle(out); | ||||
|   }, [convertStyle]); | ||||
|  | ||||
|   return ( | ||||
|     <ThemeContext.Provider value={{ theme, themeList, setTheme, toggleTheme, convertStyle, applyTheme }}> | ||||
|       {children} | ||||
|     </ThemeContext.Provider> | ||||
|   ); | ||||
| }; | ||||
| export const useTheme = () => useContext(ThemeContext); | ||||
|  | ||||
| export function useColorThemeValue<T>(firstTheme: T, secondTheme: T) { | ||||
|   const { theme, themeList } = useTheme() | ||||
|   //console.log(`use theme =${theme} ==> ${theme === themeList[0] ? firstTheme : secondTheme}`); | ||||
|   return theme === themeList[0] ? firstTheme : secondTheme | ||||
| } | ||||
							
								
								
									
										126
									
								
								front/src/theme/colors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								front/src/theme/colors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| // Update me with other Tailwind colors or with https://smart-swatch.netlify.app/ | ||||
| const brand = { | ||||
|   50: '#e3edff', | ||||
|   100: '#b6c9fd', | ||||
|   200: '#88a5f7', | ||||
|   300: '#5a81f2', | ||||
|   400: '#2c5ded', | ||||
|   500: '#1543d4', | ||||
|   600: '#0d34a5', | ||||
|   700: '#062577', | ||||
|   800: '#02164a', | ||||
|   900: '#00071e', | ||||
| }; | ||||
|  | ||||
| const black = { | ||||
|   50: '#f2f2f2', | ||||
|   100: '#d9d9d9', | ||||
|   200: '#bfbfbf', | ||||
|   300: '#a6a6a6', | ||||
|   400: '#8c8c8c', | ||||
|   500: '#737373', | ||||
|   600: '#595959', | ||||
|   700: '#404040', | ||||
|   800: '#262626', | ||||
|   900: '#0d0d0d', | ||||
| }; | ||||
|  | ||||
| const green = { | ||||
|   50: '#f0fdf4', | ||||
|   100: '#dcfce7', | ||||
|   200: '#bbf7d0', | ||||
|   300: '#86efac', | ||||
|   400: '#4ade80', | ||||
|   500: '#22c55e', | ||||
|   600: '#16a34a', | ||||
|   700: '#15803d', | ||||
|   800: '#166534', | ||||
|   900: '#14532d', | ||||
| }; | ||||
| const blue = { | ||||
|   50: '#eff6ff', | ||||
|   100: '#dbeafe', | ||||
|   200: '#bfdbfe', | ||||
|   300: '#93c5fd', | ||||
|   400: '#60a5fa', | ||||
|   500: '#3b82f6', | ||||
|   600: '#2563eb', | ||||
|   700: '#1d4ed8', | ||||
|   800: '#1e40af', | ||||
|   900: '#1e3a8a', | ||||
| }; | ||||
| const orange = { | ||||
|   50: '#fff7ed', | ||||
|   100: '#ffedd5', | ||||
|   200: '#fed7aa', | ||||
|   300: '#fdba74', | ||||
|   400: '#fb923c', | ||||
|   500: '#f97316', | ||||
|   600: '#ea580c', | ||||
|   700: '#c2410c', | ||||
|   800: '#9a3412', | ||||
|   900: '#7c2d12', | ||||
| }; | ||||
| const red = { | ||||
|   50: '#fef2f2', | ||||
|   100: '#fee2e2', | ||||
|   200: '#fecaca', | ||||
|   300: '#fca5a5', | ||||
|   400: '#f87171', | ||||
|   500: '#ef4444', | ||||
|   600: '#dc2626', | ||||
|   700: '#b91c1c', | ||||
|   800: '#991b1b', | ||||
|   900: '#7f1d1d', | ||||
| }; | ||||
| const yellow = { | ||||
|   50: '#ffffda', | ||||
|   100: '#ffffad', | ||||
|   200: '#ffff7d', | ||||
|   300: '#ffff4b', | ||||
|   400: '#ffff1a', | ||||
|   500: '#e5e600', | ||||
|   600: '#b2b300', | ||||
|   700: '#7f8000', | ||||
|   800: '#4c4d00', | ||||
|   900: '#191b00', | ||||
| }; | ||||
| const purple = { | ||||
|   50: '#ffe3ff', | ||||
|   100: '#ffb2ff', | ||||
|   200: '#ff80ff', | ||||
|   300: '#fe4efe', | ||||
|   400: '#fe20fe', | ||||
|   500: '#e50ce4', | ||||
|   600: '#b204b1', | ||||
|   700: '#80007f', | ||||
|   800: '#4e004d', | ||||
|   900: '#1d001c', | ||||
| }; | ||||
|  | ||||
| const cyan = { | ||||
|   50: '#d6ffff', | ||||
|   100: '#aaffff', | ||||
|   200: '#7affff', | ||||
|   300: '#47ffff', | ||||
|   400: '#1affff', | ||||
|   500: '#00e5e6', | ||||
|   600: '#00b2b3', | ||||
|   700: '#008081', | ||||
|   800: '#004d4e', | ||||
|   900: '#001b1d', | ||||
| } | ||||
|  | ||||
| export const basicColor = { | ||||
|   brand, | ||||
|   green, | ||||
|   red, | ||||
|   orange, | ||||
|   black, | ||||
|   blue, | ||||
|   yellow, | ||||
|   purple, | ||||
|   cyan, | ||||
|   back: black, | ||||
|   warning: orange, | ||||
| } | ||||
| @@ -1,110 +0,0 @@ | ||||
| type ThemeModel = { | ||||
|   50: string; | ||||
|   100: string; | ||||
|   200: string; | ||||
|   300: string; | ||||
|   400: string; | ||||
|   500: string; | ||||
|   600: string; | ||||
|   700: string; | ||||
|   800: string; | ||||
|   900: string; | ||||
| }; | ||||
|  | ||||
| const back = { | ||||
|   50: { value: '#ebf4fa' }, | ||||
|   100: { value: '#d1dbe0' }, | ||||
|   200: { value: '#b6c2c9' }, | ||||
|   300: { value: '#99aab4' }, | ||||
|   400: { value: '#7c939e' }, | ||||
|   500: { value: '#637985' }, | ||||
|   600: { value: '#4d5e67' }, | ||||
|   700: { value: '#37444a' }, | ||||
|   800: { value: '#1f292e' }, | ||||
|   900: { value: '#020f12' }, | ||||
| }; | ||||
|  | ||||
| const brand = { | ||||
|   50: { value: '#e3edff' }, | ||||
|   100: { value: '#b6c9fd' }, | ||||
|   200: { value: '#88a5f7' }, | ||||
|   300: { value: '#5a81f2' }, | ||||
|   400: { value: '#2c5ded' }, | ||||
|   500: { value: '#1543d4' }, | ||||
|   600: { value: '#0d34a5' }, | ||||
|   700: { value: '#062577' }, | ||||
|   800: { value: '#02164a' }, | ||||
|   900: { value: '#00071e' }, | ||||
| }; | ||||
| const normalText = { | ||||
|   50: { value: '#f2f2f2' }, | ||||
|   100: { value: '#d9d9d9' }, | ||||
|   200: { value: '#bfbfbf' }, | ||||
|   300: { value: '#a6a6a6' }, | ||||
|   400: { value: '#8c8c8c' }, | ||||
|   500: { value: '#737373' }, | ||||
|   600: { value: '#595959' }, | ||||
|   700: { value: '#404040' }, | ||||
|   800: { value: '#262626' }, | ||||
|   900: { value: '#0d0d0d' }, | ||||
| }; | ||||
|  | ||||
| const green = { | ||||
|   50: { value: '#f0fdf4' }, | ||||
|   100: { value: '#dcfce7' }, | ||||
|   200: { value: '#bbf7d0' }, | ||||
|   300: { value: '#86efac' }, | ||||
|   400: { value: '#4ade80' }, | ||||
|   500: { value: '#22c55e' }, | ||||
|   600: { value: '#16a34a' }, | ||||
|   700: { value: '#15803d' }, | ||||
|   800: { value: '#166534' }, | ||||
|   900: { value: '#14532d' }, | ||||
| }; | ||||
| const blue = { | ||||
|   50: { value: '#eff6ff' }, | ||||
|   100: { value: '#dbeafe' }, | ||||
|   200: { value: '#bfdbfe' }, | ||||
|   300: { value: '#93c5fd' }, | ||||
|   400: { value: '#60a5fa' }, | ||||
|   500: { value: '#3b82f6' }, | ||||
|   600: { value: '#2563eb' }, | ||||
|   700: { value: '#1d4ed8' }, | ||||
|   800: { value: '#1e40af' }, | ||||
|   900: { value: '#1e3a8a' }, | ||||
| }; | ||||
|  | ||||
| const orange = { | ||||
|   50: { value: '#fff7ed' }, | ||||
|   100: { value: '#ffedd5' }, | ||||
|   200: { value: '#fed7aa' }, | ||||
|   300: { value: '#fdba74' }, | ||||
|   400: { value: '#fb923c' }, | ||||
|   500: { value: '#f97316' }, | ||||
|   600: { value: '#ea580c' }, | ||||
|   700: { value: '#c2410c' }, | ||||
|   800: { value: '#9a3412' }, | ||||
|   900: { value: '#7c2d12' }, | ||||
| }; | ||||
| const red = { | ||||
|   50: { value: '#fef2f2' }, | ||||
|   100: { value: '#fee2e2' }, | ||||
|   200: { value: '#fecaca' }, | ||||
|   300: { value: '#fca5a5' }, | ||||
|   400: { value: '#f87171' }, | ||||
|   500: { value: '#ef4444' }, | ||||
|   600: { value: '#dc2626' }, | ||||
|   700: { value: '#b91c1c' }, | ||||
|   800: { value: '#991b1b' }, | ||||
|   900: { value: '#7f1d1d' }, | ||||
| }; | ||||
|  | ||||
| export const colors = { | ||||
|   // Update me with other Tailwind colors or with https://smart-swatch.netlify.app/ | ||||
|   brand: brand, | ||||
|   back: back, | ||||
|   text: normalText, | ||||
|   success: green, | ||||
|   error: red, | ||||
|   warning: orange, | ||||
| } as const; | ||||
| @@ -1,9 +0,0 @@ | ||||
| import { colors } from './colors'; | ||||
| import { shadows } from './shadows'; | ||||
|  | ||||
| const foundations = { | ||||
|   colors, | ||||
|   shadows, | ||||
| }; | ||||
|  | ||||
| export default foundations; | ||||
| @@ -1,21 +0,0 @@ | ||||
| import { colors } from './colors'; | ||||
|  | ||||
| const createOutline = (colorScheme = 'gray') => | ||||
|   `0 0 0 3px ${colorScheme}.500/3`; | ||||
|  | ||||
| export const shadows = { | ||||
|   outline: createOutline('brand'), | ||||
|   'outline-brand': '0 0 0 1px brand.900', | ||||
|   'outline-gray': createOutline('gray'), | ||||
|   'outline-over': `4px 4px 5px #00000088`, | ||||
|   'outline-darkgray': `0 0 0 3px gray.500/8`, | ||||
|   'outline-success': createOutline('success'), | ||||
|   'outline-warning': createOutline('warning'), | ||||
|   'outline-error': createOutline('error'), | ||||
|   'outline-doing': createOutline('doing'), | ||||
|   'outline-paused': createOutline('paused'), | ||||
|   layout: '0 0 24px 1px rgba(0, 0, 0, 0.05)', | ||||
|   smooth: 'inset 0px 0px 16px rgba(0, 0, 0, 0.05)', | ||||
|   // smooth-light is used for dark backgrounds | ||||
|   'smooth-light': 'inset 0px 0px 16px rgba(255, 255, 255, 0.1)', | ||||
| }; | ||||
| @@ -1,4 +1,3 @@ | ||||
| import { defineRecipe, defineStyle, RecipeVariantRecord, SystemStyleObject } from '@chakra-ui/react'; | ||||
|  | ||||
| // https://medium.com/@a.heydari.dev/simplifying-chakra-ui-v3-recipes-vs-chakra-factory-a-developers-perspective-4020b62f1b4d | ||||
|  | ||||
| @@ -9,7 +8,7 @@ import { defineRecipe, defineStyle, RecipeVariantRecord, SystemStyleObject } fro | ||||
| // `; | ||||
|  | ||||
| export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxShadowHover }) => { | ||||
|   return defineStyle({ | ||||
|   return { | ||||
|     bg, | ||||
|     color, | ||||
|     border: '1px solid transparent', | ||||
| @@ -29,81 +28,76 @@ export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxSha | ||||
|     _active: { | ||||
|       bg: bgActive, | ||||
|     }, | ||||
|   }); | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| const buttonRecipe = defineRecipe({ | ||||
|   variants: { | ||||
|     theme: { | ||||
|       "@primary": | ||||
|         customVariant({ | ||||
|           bg: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|           bgHover: { _light: 'brand.700', _dark: 'brand.400' }, | ||||
|           bgActive: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|           color: { _light: 'white', _dark: 'brand.900' }, | ||||
|           colorHover: { _light: 'brand.800', _dark: 'brand.100' }, | ||||
|           boxShadowHover: 'outline-over' | ||||
|         }), | ||||
|       "@secondary": | ||||
|         customVariant({ | ||||
|           bg: { _light: 'brand.100', _dark: 'brand.900' }, | ||||
|           bgHover: { _light: 'brand.200', _dark: 'brand.800' }, | ||||
|           bgActive: { _light: 'brand.300', _dark: 'brand.700' }, | ||||
|           color: { _light: 'brand.700', _dark: 'brand.50' }, | ||||
|           colorHover: { _light: 'brand.800', _dark: 'brand.100' }, | ||||
|           boxShadowHover: 'outline-over', | ||||
|         }), | ||||
|       "@danger": | ||||
|         customVariant({ | ||||
|           bg: { _light: 'error.600', _dark: 'error.600' }, | ||||
|           bgHover: { _light: 'error.700', _dark: 'error.500' }, | ||||
|           bgActive: { _light: 'error.600', _dark: 'error.500' }, | ||||
|           color: { _light: 'white', _dark: 'error.900' }, | ||||
|           colorHover: { _light: 'error.700', _dark: 'error.900' }, | ||||
|           boxShadowHover: 'outline-over', | ||||
|         }), | ||||
|       "@success": | ||||
|         customVariant({ | ||||
|           bg: { _light: 'green.300', _dark: 'green.300' }, | ||||
|           bgHover: { _light: 'green.400', _dark: 'green.400' }, | ||||
|           bgActive: { _light: 'green.500', _dark: 'green.400' }, | ||||
|           color: { _light: 'white', _dark: 'green.900' }, | ||||
|           colorHover: { _light: 'green.500', _dark: 'green.900' }, | ||||
|           boxShadowHover: 'outline-over', | ||||
|         }), | ||||
|  | ||||
|       "@progress": | ||||
|         defineStyle({ | ||||
|           bg: { _light: `brand.500`, _dark: `brand.300` }, | ||||
|           overflow: 'hidden', | ||||
|           /* | ||||
|           _after: !props.isLoading | ||||
|             ? { | ||||
|               content: '""', | ||||
|               position: 'absolute', | ||||
|               top: 0, | ||||
|               right: 0, | ||||
|               bottom: 0, | ||||
|               left: 0, | ||||
|               transform: 'translateX(-100%)', | ||||
|               bgGradient: `linear(90deg, brand.100/0 0%, brand.100/2 20%, brand.100/5 60%, v.100/0`, | ||||
|             } | ||||
|             : undefined, | ||||
|             */ | ||||
|         }), | ||||
|  | ||||
|       "@menu": defineStyle({ | ||||
|         bg: 'back.100', | ||||
|         color: 'brand.900', | ||||
|         borderRadius: 0, | ||||
|         border: 0, | ||||
|         _hover: { background: 'back.300' }, | ||||
|         _focus: { border: 'none' }, | ||||
|         fontSize: '20px', | ||||
|         textTransform: 'uppercase', | ||||
|       }), | ||||
|     }, | ||||
| const buttonTheme = { | ||||
|   primary: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|       bgHover: { _light: 'brand.700', _dark: 'brand.400' }, | ||||
|       bgActive: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|       color: { _light: 'white', _dark: 'brand.900' }, | ||||
|       colorHover: { _light: 'brand.800', _dark: 'brand.100' }, | ||||
|       boxShadowHover: 'outline-over' | ||||
|     }), | ||||
|   secondary: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'brand.100', _dark: 'brand.900' }, | ||||
|       bgHover: { _light: 'brand.200', _dark: 'brand.800' }, | ||||
|       bgActive: { _light: 'brand.300', _dark: 'brand.700' }, | ||||
|       color: { _light: 'brand.700', _dark: 'brand.50' }, | ||||
|       colorHover: { _light: 'brand.800', _dark: 'brand.100' }, | ||||
|       boxShadowHover: 'outline-over', | ||||
|     }), | ||||
|   danger: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'error.600', _dark: 'error.600' }, | ||||
|       bgHover: { _light: 'error.700', _dark: 'error.500' }, | ||||
|       bgActive: { _light: 'error.600', _dark: 'error.500' }, | ||||
|       color: { _light: 'white', _dark: 'error.900' }, | ||||
|       colorHover: { _light: 'error.700', _dark: 'error.900' }, | ||||
|       boxShadowHover: 'outline-over', | ||||
|     }), | ||||
|   success: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'green.300', _dark: 'green.300' }, | ||||
|       bgHover: { _light: 'green.400', _dark: 'green.400' }, | ||||
|       bgActive: { _light: 'green.500', _dark: 'green.400' }, | ||||
|       color: { _light: 'white', _dark: 'green.900' }, | ||||
|       colorHover: { _light: 'green.500', _dark: 'green.900' }, | ||||
|       boxShadowHover: 'outline-over', | ||||
|     }), | ||||
|   progress: | ||||
|   { | ||||
|     bg: { _light: `brand.500`, _dark: `brand.300` }, | ||||
|     overflow: 'hidden', | ||||
|     /* | ||||
|     _after: !props.isLoading | ||||
|       ? { | ||||
|         content: '""', | ||||
|         position: 'absolute', | ||||
|         top: 0, | ||||
|         right: 0, | ||||
|         bottom: 0, | ||||
|         left: 0, | ||||
|         transform: 'translateX(-100%)', | ||||
|         bgGradient: `linear(90deg, brand.100/0 0%, brand.100/2 20%, brand.100/5 60%, v.100/0`, | ||||
|       } | ||||
|       : undefined, | ||||
|       */ | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| export default buttonRecipe; | ||||
|   menu: { | ||||
|     bg: 'back.100', | ||||
|     color: 'brand.900', | ||||
|     borderRadius: 0, | ||||
|     border: 0, | ||||
|     _hover: { background: 'back.300' }, | ||||
|     _focus: { border: 'none' }, | ||||
|     fontSize: '20px', | ||||
|     textTransform: 'uppercase', | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default buttonTheme; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { defineRecipe } from '@chakra-ui/react'; | ||||
|  | ||||
| const drawerRecipe = defineRecipe({ | ||||
|  | ||||
| const drawerRecipe = { | ||||
|     base: { | ||||
|         bg: { _light: 'white', _dark: 'gray.800' }, | ||||
|         color: { _light: 'gray.900', _dark: 'whiteAlpha.900' }, | ||||
| @@ -24,6 +24,6 @@ const drawerRecipe = defineRecipe({ | ||||
|     defaultVariants: { | ||||
|         variant: 'solid', | ||||
|     }, | ||||
| }); | ||||
| }; | ||||
|  | ||||
| export default drawerRecipe; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { defineRecipe } from '@chakra-ui/react'; | ||||
|  | ||||
|  | ||||
| const flexTheme = defineRecipe({ | ||||
|  | ||||
| const flexTheme = { | ||||
|   variants: { | ||||
|     variant: { | ||||
|       '@menu': { | ||||
| @@ -15,6 +15,6 @@ const flexTheme = defineRecipe({ | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| }; | ||||
|  | ||||
| export default flexTheme; | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { defineRecipe } from '@chakra-ui/react'; | ||||
|  | ||||
| const inputTheme = defineRecipe({ | ||||
| const inputTheme = { | ||||
|   variants: { | ||||
|     variant: { | ||||
|       outline: { | ||||
| @@ -16,6 +15,6 @@ const inputTheme = defineRecipe({ | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| }; | ||||
|  | ||||
| export default inputTheme; | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| import { modalAnatomy as parts } from '@chakra-ui/anatomy'; | ||||
| import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; | ||||
|  | ||||
| const { definePartsStyle, defineMultiStyleConfig } = | ||||
|   createMultiStyleConfigHelpers(parts.keys); | ||||
|   | ||||
| @@ -1,27 +0,0 @@ | ||||
| import { numberInputAnatomy } from '@chakra-ui/anatomy'; | ||||
| import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; | ||||
| import { getColor, mode } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| const { definePartsStyle, defineMultiStyleConfig } = | ||||
|   createMultiStyleConfigHelpers(numberInputAnatomy.keys); | ||||
|  | ||||
| const baseStyle = definePartsStyle((props) => { | ||||
|  | ||||
|   return { | ||||
|     field: { | ||||
|       border: 0, | ||||
|       _focusVisible: { | ||||
|         borderColor: {_light:'brand.500', _dark:'brand.300'}, | ||||
|         boxShadow: `0 0 0 1px 'brand.500'`, | ||||
|         ring: '1px', | ||||
|         ringColor: {_light:'brand.500', _dark:'brand.300'}, | ||||
|         ringOffset: '1px', | ||||
|         ringOffsetColor: {_light:'brand.500', _dark:'brand.300'}, | ||||
|       }, | ||||
|     }, | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| export default defineMultiStyleConfig({ | ||||
|   baseStyle, | ||||
| }); | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { defineRecipe } from '@chakra-ui/react'; | ||||
|  | ||||
| const selectTheme = defineRecipe({ | ||||
| const selectTheme = { | ||||
|   variants: { | ||||
|     variant: { | ||||
|       outline: { | ||||
| @@ -15,6 +14,6 @@ const selectTheme = defineRecipe({ | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| }; | ||||
|  | ||||
| export default selectTheme; | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| import { defineRecipe } from '@chakra-ui/react'; | ||||
|  | ||||
|  | ||||
| const textAreaTheme = defineRecipe({ | ||||
| const textAreaTheme = { | ||||
|   variants: { | ||||
|     variant: { | ||||
|       outline: { | ||||
| @@ -14,5 +12,5 @@ const textAreaTheme = defineRecipe({ | ||||
|       }, | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| }; | ||||
| export default textAreaTheme; | ||||
| @@ -1,67 +0,0 @@ | ||||
| import { Group, VStack } from '@chakra-ui/react'; | ||||
| import { HiMinus, HiPlus } from 'react-icons/hi'; | ||||
|  | ||||
| const meta = { | ||||
|   title: 'StyleGuide/Buttons', | ||||
| }; | ||||
|  | ||||
| export default meta; | ||||
|  | ||||
| export const Default = { | ||||
|   render: () => ( | ||||
|     <Group> | ||||
|       <Button>Default Button</Button> | ||||
|       <IconButton aria-label="Add" ><HiPlus /></IconButton> | ||||
|     </Group> | ||||
|   ), | ||||
| }; | ||||
|  | ||||
| export const Primary = { | ||||
|   render: () => ( | ||||
|     <Group> | ||||
|       <Button theme="@primary">Primary Button</Button> | ||||
|       <IconButton theme="@primary" aria-label="Add" ><HiPlus /></IconButton> | ||||
|     </Group> | ||||
|   ), | ||||
| }; | ||||
|  | ||||
| export const Secondary = { | ||||
|   render: () => ( | ||||
|     <Group> | ||||
|       <Button theme="@secondary">Secondary Button</Button> | ||||
|       <IconButton theme="@secondary" aria-label="Add" ><HiPlus /></IconButton> | ||||
|     </Group> | ||||
|   ), | ||||
| }; | ||||
|  | ||||
| export const Danger = { | ||||
|   render: () => ( | ||||
|     <Group> | ||||
|       <Button theme="@danger">Danger Button</Button> | ||||
|       <IconButton theme="@danger" aria-label="Remove"><HiMinus /></IconButton> | ||||
|     </Group> | ||||
|   ), | ||||
| }; | ||||
|  | ||||
| export const Progress = { | ||||
|   render: () => ( | ||||
|     <Group> | ||||
|       <VStack> | ||||
|         <Button variant="solid" colorScheme="brand"> | ||||
|           Progress Button with Brand ColorScheme | ||||
|         </Button> | ||||
|         <Button variant="solid" colorScheme="error"> | ||||
|           Progress Button with Error ColorScheme | ||||
|         </Button> | ||||
|         <Button variant="solid"> | ||||
|           Button | ||||
|         </Button> | ||||
|         <IconButton | ||||
|           variant="solid" | ||||
|           theme="@danger" | ||||
|           aria-label="Remove" | ||||
|         ><HiMinus /></IconButton> | ||||
|       </VStack> | ||||
|     </Group> | ||||
|   ), | ||||
| }; | ||||
| @@ -1,66 +0,0 @@ | ||||
| import { Box, Flex, FlexProps, HStack } from '@chakra-ui/react'; | ||||
|  | ||||
| const Color = ({ children, ...rest }: FlexProps) => ( | ||||
|   <Flex flex="1" h="16" p="2" {...rest}> | ||||
|     <Box | ||||
|       bg="white" | ||||
|       display="inline-block" | ||||
|       px="2" | ||||
|       py="1" | ||||
|       m="auto" | ||||
|       fontSize="xs" | ||||
|       fontWeight="bold" | ||||
|       borderRadius="md" | ||||
|     > | ||||
|       {children} | ||||
|     </Box> | ||||
|   </Flex> | ||||
| ); | ||||
|  | ||||
| const Colors = ({ colorScheme = 'gray', ...rest }) => ( | ||||
|   <HStack | ||||
|     // spacing="0" | ||||
|     overflow="hidden" | ||||
|     boxShadow="lg" | ||||
|     color={`${colorScheme}.700`} | ||||
|     borderRadius="md" | ||||
|     {...rest} | ||||
|   > | ||||
|     <Color bg={`${colorScheme}.50`}>50</Color> | ||||
|     <Color bg={`${colorScheme}.100`}>100</Color> | ||||
|     <Color bg={`${colorScheme}.200`}>200</Color> | ||||
|     <Color bg={`${colorScheme}.300`}>300</Color> | ||||
|     <Color bg={`${colorScheme}.400`}>400</Color> | ||||
|     <Color bg={`${colorScheme}.500`}>500</Color> | ||||
|     <Color bg={`${colorScheme}.600`}>600</Color> | ||||
|     <Color bg={`${colorScheme}.700`}>700</Color> | ||||
|     <Color bg={`${colorScheme}.800`}>800</Color> | ||||
|     <Color bg={`${colorScheme}.900`}>900</Color> | ||||
|   </HStack> | ||||
| ); | ||||
|  | ||||
| const meta = { | ||||
|   title: 'StyleGuide/Colors', | ||||
| }; | ||||
|  | ||||
| export default meta; | ||||
|  | ||||
| export const Brand = { | ||||
|   render: () => <Colors colorScheme="brand" />, | ||||
| }; | ||||
|  | ||||
| export const Gray = { | ||||
|   render: () => <Colors colorScheme="gray" />, | ||||
| }; | ||||
|  | ||||
| export const Error = { | ||||
|   render: () => <Colors colorScheme="error" />, | ||||
| }; | ||||
|  | ||||
| export const Warning = { | ||||
|   render: () => <Colors colorScheme="warning" />, | ||||
| }; | ||||
|  | ||||
| export const Success = { | ||||
|   render: () => <Colors colorScheme="success" />, | ||||
| }; | ||||
| @@ -1,4 +0,0 @@ | ||||
| import { Styles } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| export const styles: Styles = { | ||||
| }; | ||||
| @@ -1,44 +0,0 @@ | ||||
| import * as recipes from './recipes'; | ||||
| import { createSystem, defaultConfig, mergeConfigs, SystemConfig } from "@chakra-ui/react" | ||||
| import { colors } from "./foundations/colors" | ||||
|  | ||||
| const baseTheme: SystemConfig = { | ||||
|   globalCss: { | ||||
|     body: { | ||||
|       overflowY: 'none', | ||||
|       bg: { _light: 'back.50', _dark: 'back.700' }, | ||||
|       color: { _light: 'text.900', _dark: 'text.50' }, | ||||
|       fontFamily: 'Roboto, Helvetica, Arial, "sans-serif"', | ||||
|     }, | ||||
|     svg: { | ||||
|       width: "32px", | ||||
|       height: "32px", | ||||
|       aspectRatio: "square", | ||||
|     } | ||||
|   }, | ||||
|   theme: { | ||||
|     ...recipes, | ||||
|     tokens: { | ||||
|       fonts: { | ||||
|         heading: { value: `Roboto, Helvetica, Arial, "sans-serif"` }, | ||||
|         body: { value: `Roboto, Helvetica, Arial, "sans-serif"` }, | ||||
|       }, | ||||
|       colors, | ||||
|     }, | ||||
|     semanticTokens: { | ||||
|       colors: { | ||||
|         brand: { | ||||
|           solid: { value: "{colors.brand.500}" }, | ||||
|           contrast: { value: "{colors.brand.100}" }, | ||||
|           fg: { value: "{colors.brand.700}" }, | ||||
|           muted: { value: "{colors.brand.100}" }, | ||||
|           subtle: { value: "{colors.brand.200}" }, | ||||
|           emphasized: { value: "{colors.brand.300}" }, | ||||
|           focusRing: { value: "{colors.brand.500}" }, | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| const config = mergeConfigs(defaultConfig, baseTheme); | ||||
| export const systemTheme = createSystem(config); | ||||
							
								
								
									
										5
									
								
								front/src/types/theme.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								front/src/types/theme.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +0,0 @@ | ||||
| import { ThemeTypings } from '@chakra-ui/react'; | ||||
|  | ||||
| import { colors } from '@/theme/foundations/colors'; | ||||
|  | ||||
| export type ColorSchemes = ThemeTypings['colorSchemes'] | keyof typeof colors; | ||||
							
								
								
									
										33
									
								
								front/src/ui/Button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								front/src/ui/Button.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import { ApplyThemeProperty, useTheme } from "@/theme/ThemeContext"; | ||||
| import { ReactNode, CSSProperties } from "react"; | ||||
|  | ||||
| export type ButtonProps = { | ||||
|     children: ReactNode; | ||||
|     onClick?: () => void; | ||||
|     style?: CSSProperties; | ||||
|     componentType?: ApplyThemeProperty['componentType']; | ||||
|     shape?: ApplyThemeProperty['shape']; | ||||
|     palette?: ApplyThemeProperty['palette']; | ||||
| }; | ||||
|  | ||||
| export const Button = ({ children, onClick, style, componentType = "Button", shape, palette }: ButtonProps) => { | ||||
|     const { applyTheme } = useTheme(); | ||||
|     const themedStyle = style ? applyTheme({ | ||||
|         padding: '10px 20px', | ||||
|         background: '#3182CE', | ||||
|         color: '#FFF', | ||||
|         //border: 'none', | ||||
|         //borderRadius: '4px', | ||||
|         cursor: 'pointer', | ||||
|         fontSize: '16px', | ||||
|         ...style, | ||||
|     }, { componentType, shape, palette }) : undefined; | ||||
|     return ( | ||||
|         <button | ||||
|             onClick={onClick} | ||||
|             style={themedStyle} | ||||
|         > | ||||
|             {children} | ||||
|         </button> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										33
									
								
								front/src/ui/Div.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								front/src/ui/Div.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import { ApplyThemeProperty, useTheme } from '@/theme/ThemeContext'; | ||||
| import { CSSProperties, ReactNode, useState } from 'react'; | ||||
|  | ||||
| export type DivProps = { | ||||
|     children?: ReactNode; | ||||
|     onClick?: () => void; | ||||
|     style?: CSSProperties; | ||||
|     _hover?: CSSProperties; | ||||
|     componentType?: ApplyThemeProperty['componentType']; | ||||
|     shape?: ApplyThemeProperty['shape']; | ||||
|     palette?: ApplyThemeProperty['palette']; | ||||
| }; | ||||
|  | ||||
| export const Div = ({ children, onClick, style, _hover, ...themeToApply }: DivProps) => { | ||||
|     const { applyTheme } = useTheme(); | ||||
|     const [hover, setHover] = useState(false); | ||||
|     const hoverTheme = hover ? _hover : {}; | ||||
|     const themedStyle = style ? applyTheme({ ...style, ...hoverTheme }, themeToApply) : undefined; | ||||
|     return ( | ||||
|         <div | ||||
|             onMouseEnter={() => { | ||||
|                 setHover(true); | ||||
|             }} | ||||
|             onMouseLeave={() => { | ||||
|                 setHover(false); | ||||
|             }} | ||||
|             onClick={onClick} | ||||
|             style={themedStyle} | ||||
|         > | ||||
|             {children} | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										9
									
								
								front/src/ui/ErrorManager.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								front/src/ui/ErrorManager.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import { RestErrorResponse } from "@/back-api"; | ||||
|  | ||||
|  | ||||
| export const registerAnError = (error) => { | ||||
|     console.error(`Detect error: ${JSON.stringify(error, null, 2)}`); | ||||
| } | ||||
| export const registerAnApiError = (error: RestErrorResponse) => { | ||||
|     console.error(`Detect error: ${JSON.stringify(error, null, 2)}`); | ||||
| } | ||||
							
								
								
									
										76
									
								
								front/src/ui/Flex.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								front/src/ui/Flex.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| import React, { CSSProperties, ReactNode } from 'react'; | ||||
| import { Div } from './Div'; | ||||
| import { ApplyThemeProperty } from '@/theme/ThemeContext'; | ||||
|  | ||||
| export type FlexProps = { | ||||
|     children?: ReactNode; | ||||
|     componentType?: ApplyThemeProperty['componentType']; | ||||
|     shape?: ApplyThemeProperty['shape']; | ||||
|     palette?: ApplyThemeProperty['palette']; | ||||
|     onClick?: () => void; | ||||
|     direction?: 'row' | 'column'; | ||||
|     gap?: string | number; | ||||
|     justify?: CSSProperties['justifyContent']; | ||||
|     align?: CSSProperties['alignItems']; | ||||
|     width?: CSSProperties['width']; | ||||
|     height?: CSSProperties['height']; | ||||
|     padding?: CSSProperties['padding']; | ||||
|     margin?: CSSProperties['margin']; | ||||
|     maxWidth?: CSSProperties['maxWidth']; | ||||
|     minWidth?: CSSProperties['minWidth']; | ||||
|     maxHeight?: CSSProperties['maxHeight']; | ||||
|     minHeight?: CSSProperties['maxHeight']; | ||||
|     paddingLeft?: CSSProperties['paddingLeft']; | ||||
|     paddingRight?: CSSProperties['paddingRight']; | ||||
|     paddingTop?: CSSProperties['paddingTop']; | ||||
|     paddingBottom?: CSSProperties['paddingBottom']; | ||||
|     marginLeft?: CSSProperties['marginLeft']; | ||||
|     marginRight?: CSSProperties['marginRight']; | ||||
|     marginTop?: CSSProperties['marginTop']; | ||||
|     marginBottom?: CSSProperties['marginBottom']; | ||||
|     style?: Omit<CSSProperties, "flexDirection" | "display" | "justifyContent" | "alignItems" | "alignItems" | "direction"> | ||||
|     _hover?: CSSProperties; | ||||
| }; | ||||
|  | ||||
| export const Flex = ({ | ||||
|     children, | ||||
|     onClick, | ||||
|     direction = 'row', | ||||
|     justify = 'flex-start', | ||||
|     gap, | ||||
|     align = 'stretch', | ||||
|     width, | ||||
|     height, | ||||
|     padding, | ||||
|     margin, | ||||
|     maxWidth, minWidth, maxHeight, minHeight, | ||||
|     paddingLeft, paddingRight, paddingTop, paddingBottom, | ||||
|     marginLeft, marginRight, marginTop, marginBottom, | ||||
|     componentType = "Flex", shape, palette, | ||||
|     style, _hover }: FlexProps) => { | ||||
|  | ||||
|     return ( | ||||
|         <Div | ||||
|             onClick={onClick} | ||||
|             style={{ | ||||
|                 display: 'flex', | ||||
|                 flexDirection: direction, | ||||
|                 justifyContent: justify, | ||||
|                 gap: gap, | ||||
|                 alignItems: align, | ||||
|                 width, | ||||
|                 height, | ||||
|                 padding, | ||||
|                 margin, | ||||
|                 maxWidth, minWidth, maxHeight, minHeight, | ||||
|                 paddingLeft, paddingRight, paddingTop, paddingBottom, | ||||
|                 marginLeft, marginRight, marginTop, marginBottom, | ||||
|                 ...style, | ||||
|             }} | ||||
|             _hover={_hover} | ||||
|             componentType={componentType} shape={shape} palette={palette} | ||||
|         > | ||||
|             {children} | ||||
|         </Div > | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										23
									
								
								front/src/ui/FullPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								front/src/ui/FullPage.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import { ReactNode, CSSProperties } from "react"; | ||||
| import { Div } from "./Div"; | ||||
|  | ||||
| export type FullPageProps = { | ||||
|     children: ReactNode; | ||||
|     style?: CSSProperties; | ||||
| }; | ||||
|  | ||||
| export const FullPage = ({ children, style }: FullPageProps) => { | ||||
|     return ( | ||||
|         <Div | ||||
|             style={{ | ||||
|                 top: '0', | ||||
|                 bottom: '0', | ||||
|                 right: '0', | ||||
|                 left: '0', | ||||
|                 ...style, | ||||
|             }} | ||||
|         > | ||||
|             {children} | ||||
|         </Div> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										25
									
								
								front/src/ui/HStack.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								front/src/ui/HStack.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { ReactNode, CSSProperties } from "react"; | ||||
| import { Div } from "./Div"; | ||||
|  | ||||
| export type HStackProps = { | ||||
|     children: ReactNode; | ||||
|     spacing?: CSSProperties['gap']; | ||||
|     align?: CSSProperties['alignItems']; | ||||
|     style?: Omit<CSSProperties, "gap" | "alignItems">; | ||||
| }; | ||||
|  | ||||
| export const HStack = ({ children, spacing = '8px', align = 'flex-start', style }: HStackProps) => { | ||||
|     return ( | ||||
|         <Div | ||||
|             style={{ | ||||
|                 display: 'flex', | ||||
|                 flexDirection: 'row', | ||||
|                 alignItems: align, | ||||
|                 gap: spacing, | ||||
|                 ...style, | ||||
|             }} | ||||
|         > | ||||
|             {children} | ||||
|         </Div> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										30
									
								
								front/src/ui/Image.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								front/src/ui/Image.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import { CSSProperties, RefObject } from "react"; | ||||
| import { useTheme } from "@/theme/ThemeContext"; | ||||
|  | ||||
| export type ImageProps = { | ||||
|     ref?: RefObject<any>; | ||||
|     src?: string; | ||||
|     boxSize?: string; | ||||
|     onChange?: (e) => void; | ||||
|     style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">; | ||||
| }; | ||||
|  | ||||
| export const Image = ({ | ||||
|     ref, | ||||
|     src, | ||||
|     boxSize, | ||||
|     onChange, | ||||
|     style, | ||||
| }: ImageProps) => { | ||||
|     const { convertStyle } = useTheme(); | ||||
|     const themedStyle = style ? convertStyle({ width: boxSize, height: boxSize, ...style }) : undefined; | ||||
|     return ( | ||||
|         <img | ||||
|             ref={ref} | ||||
|             src={src} | ||||
|             onChange={onChange} | ||||
|             style={themedStyle} | ||||
|         > | ||||
|         </img> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										29
									
								
								front/src/ui/Input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								front/src/ui/Input.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import { ReactNode, CSSProperties, RefObject } from "react"; | ||||
| import { Div } from "./Div"; | ||||
| import { useTheme } from "@/theme/ThemeContext"; | ||||
|  | ||||
| export type InputProps = { | ||||
|     ref?: RefObject<any>; | ||||
|     value?: string; | ||||
|     onChange?: (e) => void; | ||||
|     style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">; | ||||
| }; | ||||
|  | ||||
| export const Input = ({ | ||||
|     ref, | ||||
|     value, | ||||
|     onChange, | ||||
|     style, | ||||
| }: InputProps) => { | ||||
|     const { convertStyle } = useTheme(); | ||||
|     const themedStyle = style ? convertStyle(style) : undefined; | ||||
|     return ( | ||||
|         <input | ||||
|             ref={ref} | ||||
|             value={value} | ||||
|             onChange={onChange} | ||||
|             style={themedStyle} | ||||
|         > | ||||
|         </input> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										16
									
								
								front/src/ui/Link.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								front/src/ui/Link.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import { CSSProperties, ReactNode } from 'react' | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| import { Div } from './Div'; | ||||
|  | ||||
| export type LinkProps = { | ||||
|     href?: string; | ||||
|     style?: CSSProperties; | ||||
|     children?: ReactNode; | ||||
| } | ||||
|  | ||||
| export const Link = ({ href = "/", style, children }: LinkProps) => { | ||||
|     const navigate = useNavigate(); | ||||
|     return (<Div onClick={() => navigate(href)} | ||||
|         style={style} | ||||
|     > {children}</Div >); | ||||
| } | ||||
							
								
								
									
										36
									
								
								front/src/ui/Span.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								front/src/ui/Span.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import { ReactNode, CSSProperties } from "react"; | ||||
| import { Div } from "./Div"; | ||||
| import { useTheme } from "@/theme/ThemeContext"; | ||||
|  | ||||
| export type SpanProps = { | ||||
|     children: ReactNode; | ||||
|     color?: string; | ||||
|     fontSize?: CSSProperties["fontSize"]; | ||||
|     fontWeight?: CSSProperties["fontWeight"]; | ||||
|     userSelect?: CSSProperties["userSelect"]; | ||||
|     style?: Omit<CSSProperties, "fontSize" | "fontWeight" | "color">; | ||||
| }; | ||||
|  | ||||
| export const Span = ({ children, | ||||
|     fontSize = '16px', | ||||
|     fontWeight = "normal", | ||||
|     color = "#000000", | ||||
|     userSelect, | ||||
|     style, | ||||
| }: SpanProps) => { | ||||
|     const { convertStyle } = useTheme(); | ||||
|     const themedStyle = style ? convertStyle({ | ||||
|         fontSize, | ||||
|         color, | ||||
|         fontWeight, | ||||
|         userSelect, | ||||
|         ...style, | ||||
|     }) : undefined; | ||||
|     return ( | ||||
|         <span | ||||
|             style={themedStyle} | ||||
|         > | ||||
|             {children} | ||||
|         </span> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										12
									
								
								front/src/ui/Table/body.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								front/src/ui/Table/body.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import { useTheme } from "@/theme/ThemeContext"; | ||||
| import { CSSProperties, ReactNode } from "react" | ||||
|  | ||||
| export type BodyProps = { | ||||
|     children?: ReactNode; | ||||
|     style?: CSSProperties; | ||||
| }; | ||||
| export const Body = ({ children, style }: BodyProps) => { | ||||
|     const { convertStyle } = useTheme(); | ||||
|     const themedStyle = style ? convertStyle(style) : undefined; | ||||
|     return <tbody style={themedStyle}>{children}</tbody> | ||||
| } | ||||
							
								
								
									
										12
									
								
								front/src/ui/Table/cell.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								front/src/ui/Table/cell.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import { useTheme } from "@/theme/ThemeContext"; | ||||
| import { CSSProperties, ReactNode } from "react" | ||||
|  | ||||
| export type CellProps = { | ||||
|     children?: ReactNode; | ||||
|     style?: CSSProperties; | ||||
| }; | ||||
| export const Cell = ({ children, style }: CellProps) => { | ||||
|     const { convertStyle } = useTheme(); | ||||
|     const themedStyle = style ? convertStyle(style) : undefined; | ||||
|     return <td style={themedStyle}>{children}</td> | ||||
| } | ||||
							
								
								
									
										12
									
								
								front/src/ui/Table/columnHeader.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								front/src/ui/Table/columnHeader.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import { useTheme } from "@/theme/ThemeContext"; | ||||
| import { CSSProperties, ReactNode } from "react" | ||||
|  | ||||
| export type ColumnHeaderProps = { | ||||
|     children?: ReactNode; | ||||
|     style?: CSSProperties; | ||||
| }; | ||||
| export const ColumnHeader = ({ children, style }: ColumnHeaderProps) => { | ||||
|     const { convertStyle } = useTheme(); | ||||
|     const themedStyle = style ? convertStyle(style) : undefined; | ||||
|     return <th style={themedStyle}>{children}</th> | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user