[FEAT] basic working karusic
This commit is contained in:
parent
1d4c547d89
commit
30bc82edb3
@ -36,4 +36,4 @@
|
||||
"defer"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
# URL for database connection
|
||||
VITE_API_BASE_URL=api/
|
@ -1,103 +0,0 @@
|
||||
###############################################################
|
||||
## Install dependency:
|
||||
###############################################################
|
||||
FROM node:latest AS dependency
|
||||
|
||||
# For pnpm
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# copy the credential
|
||||
COPY npmrc /root/.npmrc
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
COPY src/theme ./src/theme
|
||||
# TODO: install only the production environment:
|
||||
RUN pnpm install --prod=false
|
||||
|
||||
###############################################################
|
||||
## Install sources
|
||||
###############################################################
|
||||
FROM dependency AS load_sources
|
||||
|
||||
# JUST to get the vertion of the application and his sha...
|
||||
COPY build.js \
|
||||
version.txt \
|
||||
tsconfig.json \
|
||||
tsconfig.node.json \
|
||||
vite.config.mts \
|
||||
.env.validator.js \
|
||||
index.html \
|
||||
./
|
||||
COPY public public
|
||||
COPY src src
|
||||
|
||||
#We are not in prod mode ==> we need to overwrite the production env.
|
||||
ARG env=docker/.env.production
|
||||
COPY ${env} .env
|
||||
|
||||
###############################################################
|
||||
## Run the linter
|
||||
###############################################################
|
||||
FROM load_sources AS check
|
||||
COPY .eslintrc.json app-build.json ./
|
||||
# Run linter
|
||||
RUN pnpm lint .
|
||||
RUN pnpm tsc --noEmit
|
||||
|
||||
###############################################################
|
||||
## Run the Unit test
|
||||
###############################################################
|
||||
FROM load_sources AS unittest
|
||||
COPY vitest.config.mts app-build.json ./
|
||||
|
||||
# Run unit test
|
||||
RUN pnpm test
|
||||
|
||||
###############################################################
|
||||
## Build the story-book
|
||||
###############################################################
|
||||
FROM load_sources AS builder_storybook
|
||||
COPY app-build.json ./app-build.json
|
||||
COPY .storybook ./.storybook/
|
||||
# build the storybook in static
|
||||
RUN SKIP_ENV_VALIDATIONS=1 pnpm storybook:build
|
||||
|
||||
###############################################################
|
||||
## Build the sources
|
||||
###############################################################
|
||||
FROM load_sources AS builder
|
||||
# build in bundle mode all the application
|
||||
RUN pnpm static:build
|
||||
|
||||
|
||||
###############################################################
|
||||
## Runner environment:
|
||||
###############################################################
|
||||
FROM httpd:latest AS runner
|
||||
WORKDIR /app
|
||||
# configure HTTP server (add a redirection on the index.html to manage new app model to re-find the generic page):
|
||||
RUN sed -e '/DocumentRoot/,/Directory>/d' -i /usr/local/apache2/conf/httpd.conf
|
||||
RUN sed -r 's|#LoadModule rewrite_module|LoadModule rewrite_module|' -i /usr/local/apache2/conf/httpd.conf
|
||||
RUN echo '<VirtualHost *:80> \n\
|
||||
ServerName my-app \n\
|
||||
DocumentRoot "/usr/local/apache2/htdocs" \n\
|
||||
<Directory "/usr/local/apache2/htdocs"> \n\
|
||||
Options Indexes FollowSymLinks \n\
|
||||
AllowOverride None \n\
|
||||
Require all granted \n\
|
||||
RewriteEngine on \n\
|
||||
# Do not rewrite files or directories \n\
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR] \n\
|
||||
RewriteCond %{REQUEST_FILENAME} -d \n\
|
||||
RewriteRule ^ - [L] \n\
|
||||
# Rewrite everything else to index.html to allow HTML5 state links \n\
|
||||
RewriteRule ^ app/index.html [L] \n\
|
||||
</Directory> \n\
|
||||
</VirtualHost> \n\
|
||||
' >> /usr/local/apache2/conf/httpd.conf
|
||||
|
||||
# copy artifact build from the 'build environment'
|
||||
COPY --from=builder /app/dist /usr/local/apache2/htdocs/app
|
@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 67.733333 67.733333"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="ikon.svg"
|
||||
inkscape:export-filename="/home/heero/dev/perso/appl_pro/NoKomment/plugin/chrome/ikon.png"
|
||||
inkscape:export-xdpi="7.1250005"
|
||||
inkscape:export-ydpi="7.1250005">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="52.480467"
|
||||
inkscape:cy="138.73493"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:snap-text-baseline="false"
|
||||
inkscape:window-width="1918"
|
||||
inkscape:window-height="1038"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4504" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-229.26668)">
|
||||
<g
|
||||
aria-label="K"
|
||||
transform="scale(1.0347881,0.96638145)"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;line-height:1.25;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.11376619"
|
||||
id="text821">
|
||||
<path
|
||||
d="m 12.784421,241.62303 h 8.949095 v 27.37877 l 25.568842,-27.37877 6.39221,6.84469 -20.455074,21.90302 20.455074,27.37876 -6.39221,5.47576 -19.176632,-27.37877 -6.39221,6.84469 0,20.53408 h -8.949095 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:2.11376619;fill:#ff0000;fill-opacity:1"
|
||||
id="path823"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.2 KiB |
@ -6,8 +6,8 @@
|
||||
<title>Karusic</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body className="flex flex-col">
|
||||
<div id="root"></div>
|
||||
<body style="width:100vw;height:100vh;min-width:100%;min-height:100%;">
|
||||
<div id="root" style="width:100%;height:100%;min-width:100%;min-height:100%;"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -38,9 +38,6 @@ importers:
|
||||
'@emotion/styled':
|
||||
specifier: 11.13.0
|
||||
version: 11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)
|
||||
'@mui/icons-material':
|
||||
specifier: 5.16.7
|
||||
version: 5.16.7(@mui/material@5.16.7(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)
|
||||
allotment:
|
||||
specifier: 1.20.2
|
||||
version: 1.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@ -1940,94 +1937,6 @@ packages:
|
||||
'@types/react': '>=16'
|
||||
react: '>=16'
|
||||
|
||||
'@mui/core-downloads-tracker@5.16.7':
|
||||
resolution: {integrity: sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==}
|
||||
|
||||
'@mui/icons-material@5.16.7':
|
||||
resolution: {integrity: sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@mui/material': ^5.0.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/material@5.16.7':
|
||||
resolution: {integrity: sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/private-theming@5.16.6':
|
||||
resolution: {integrity: sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/styled-engine@5.16.6':
|
||||
resolution: {integrity: sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.4.1
|
||||
'@emotion/styled': ^11.3.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
|
||||
'@mui/system@5.16.7':
|
||||
resolution: {integrity: sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/types@7.2.15':
|
||||
resolution: {integrity: sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/utils@5.16.6':
|
||||
resolution: {integrity: sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -3113,10 +3022,6 @@ packages:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
co@4.6.0:
|
||||
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||
@ -8677,89 +8582,6 @@ snapshots:
|
||||
'@types/react': 18.3.3
|
||||
react: 18.3.1
|
||||
|
||||
'@mui/core-downloads-tracker@5.16.7': {}
|
||||
|
||||
'@mui/icons-material@5.16.7(@mui/material@5.16.7(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
'@mui/material': 5.16.7(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@mui/material@5.16.7(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
'@mui/core-downloads-tracker': 5.16.7
|
||||
'@mui/system': 5.16.7(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)
|
||||
'@mui/types': 7.2.15(@types/react@18.3.3)
|
||||
'@mui/utils': 5.16.6(@types/react@18.3.3)(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
'@types/react-transition-group': 4.4.10
|
||||
clsx: 2.1.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-is: 18.3.1
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@mui/private-theming@5.16.6(@types/react@18.3.3)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
'@mui/utils': 5.16.6(@types/react@18.3.3)(react@18.3.1)
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@mui/styled-engine@5.16.6(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
'@emotion/cache': 11.13.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)
|
||||
|
||||
'@mui/system@5.16.7(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
'@mui/private-theming': 5.16.6(@types/react@18.3.3)(react@18.3.1)
|
||||
'@mui/styled-engine': 5.16.6(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.15(@types/react@18.3.3)
|
||||
'@mui/utils': 5.16.6(@types/react@18.3.3)(react@18.3.1)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@mui/types@7.2.15(@types/react@18.3.3)':
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@mui/utils@5.16.6(@types/react@18.3.3)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
'@mui/types': 7.2.15(@types/react@18.3.3)
|
||||
'@types/prop-types': 15.7.12
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-is: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@ -10092,8 +9914,6 @@ snapshots:
|
||||
|
||||
clone@1.0.4: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
co@4.6.0: {}
|
||||
|
||||
collect-v8-coverage@1.0.2: {}
|
||||
|
65
front2/src/assets/images/ikon.svg
Normal file
65
front2/src/assets/images/ikon.svg
Normal file
@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg width="256" height="256" viewBox="0 0 67.733333 67.733333" version="1.1" id="svg8"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)" sodipodi:docname="ikon_gray.svg"
|
||||
inkscape:export-filename="/home/heero/dev/perso/appl_pro/NoKomment/plugin/chrome/ikon.png"
|
||||
inkscape:export-xdpi="7.1250005" inkscape:export-ydpi="7.1250005"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs id="defs2">
|
||||
<filter style="color-interpolation-filters:sRGB;" inkscape:label="Drop Shadow" id="filter5338" x="-0.12319682"
|
||||
y="-0.081815216" width="1.2463936" height="1.1636304">
|
||||
<feFlood flood-opacity="1" flood-color="rgb(0,255,0)" result="flood" id="feFlood5328" />
|
||||
<feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1" id="feComposite5330" />
|
||||
<feGaussianBlur in="composite1" stdDeviation="2.1" result="blur" id="feGaussianBlur5332" />
|
||||
<feOffset dx="0" dy="0" result="offset" id="feOffset5334" />
|
||||
<feComposite in="SourceGraphic" in2="offset" operator="over" result="composite2" id="feComposite5336" />
|
||||
</filter>
|
||||
<filter inkscape:collect="always" style="color-interpolation-filters:sRGB" id="filter1159" x="-0.11802406"
|
||||
width="1.2360481" y="-0.078379973" height="1.1567599">
|
||||
<feGaussianBlur inkscape:collect="always" stdDeviation="2.0118255" id="feGaussianBlur1161" />
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" inkscape:label="Drop Shadow" id="filter5338-3" x="-0.12319682"
|
||||
y="-0.081815216" width="1.2463936" height="1.1636304">
|
||||
<feFlood flood-opacity="1" flood-color="rgb(0,255,0)" result="flood" id="feFlood5328-6" />
|
||||
<feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1" id="feComposite5330-7" />
|
||||
<feGaussianBlur in="composite1" stdDeviation="2.1" result="blur" id="feGaussianBlur5332-5" />
|
||||
<feOffset dx="0" dy="0" result="offset" id="feOffset5334-3" />
|
||||
<feComposite in="SourceGraphic" in2="offset" operator="over" result="composite2" id="feComposite5336-5" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="4" inkscape:cx="27.125" inkscape:cy="217.5"
|
||||
inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="true" units="px"
|
||||
inkscape:snap-text-baseline="false" inkscape:window-width="3838" inkscape:window-height="2118"
|
||||
inkscape:window-x="0" inkscape:window-y="20" inkscape:window-maximized="1" inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid type="xygrid" id="grid4504" originx="0" originy="0" spacingy="1" spacingx="1" units="px"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(0,-229.26668)"
|
||||
style="display:inline">
|
||||
<g id="text821-7"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;line-height:1.25;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#2b3137;fill-opacity:1;stroke:none;stroke-width:2.11376619;stroke-opacity:1"
|
||||
transform="matrix(0.8407653,0,0,0.83753055,-37.28971,3.4402954)" aria-label="K">
|
||||
<path id="path823-5"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.5502px;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;opacity:0.775;fill:#2b3137;fill-opacity:1;stroke-width:2.11377;filter:url(#filter5338)"
|
||||
d="m 65.200545,279.95309 v 61.60223 h 8.949095 v -20.53407 l 6.392211,-6.84449 19.176632,27.37856 6.392207,-5.47534 -20.455071,-27.37918 20.455071,-21.9026 -6.392207,-6.84511 -25.568843,27.37918 v -27.37918 z m 34.623041,3.89642 0.08113,0.91996 c -0.319958,0.0205 -0.600028,0.12056 -0.843281,0.38008 -0.481327,0.51353 -0.393613,1.29347 0.321455,1.96887 0.73708,0.69622 1.51285,0.73176 2.02523,0.18511 0.24321,-0.25953 0.31335,-0.58883 0.29195,-0.94218 l 0.90904,0.0154 c 0.0722,0.61998 -0.12994,1.18923 -0.58021,1.66963 -0.83844,0.89456 -2.190053,1.07514 -3.400168,-0.0679 -1.188114,-1.12225 -1.171861,-2.52288 -0.266137,-3.48919 0.424395,-0.45279 0.991241,-0.62554 1.460989,-0.63984 z m -6.777588,6.44528 0.676714,0.6386 -0.786733,0.83975 2.228054,2.10401 0.786119,-0.83914 0.676714,0.63861 -2.333772,2.49087 -0.676714,-0.63923 0.786734,-0.83913 -2.228054,-2.10462 -0.786734,0.83913 -0.676099,-0.6386 z m -5.778189,6.97221 0.129073,0.89158 c -0.419593,0.0825 -0.731113,0.21546 -1.057173,0.56333 -0.253606,0.27057 -0.314123,0.55655 -0.105103,0.75398 0.220021,0.20783 0.524373,0.0375 0.977271,-0.18017 l 0.595582,-0.27025 c 0.615941,-0.3031 1.187271,-0.32558 1.693321,0.15241 0.599556,0.56632 0.616629,1.36433 -0.19361,2.44089 -0.677315,0.60577 -1.102122,0.82218 -1.800268,0.88108 l -0.121083,-0.98844 c 0.484299,-0.0631 0.943518,-0.25438 1.274754,-0.60776 0.320886,-0.34235 0.344427,-0.63278 0.16841,-0.79903 -0.258525,-0.24419 -0.521361,-0.0857 -0.985261,0.12155 l -0.637377,0.28198 c -0.526655,0.25209 -1.170772,0.33129 -1.693321,-0.16228 -0.594058,-0.56111 -0.565292,-1.54388 0.169639,-2.32797 0.403694,-0.4307 0.971757,-0.716 1.585146,-0.7509 z m -6.585821,6.21884 2.205312,2.08364 c 0.929589,0.87805 1.047872,1.78072 0.224957,2.65869 -0.81774,0.8725 -1.743461,0.83116 -2.67305,-0.0469 l -2.205313,-2.08364 0.765836,-0.81692 2.288288,2.16138 c 0.429042,0.40526 0.810303,0.46332 1.126013,0.12649 0.320885,-0.34235 0.244649,-0.72634 -0.184391,-1.1316 l -2.288288,-2.16138 z m -4.57965,9.20516 2.197937,0.53865 -0.853729,0.91071 -1.930571,-0.5294 -0.407503,0.43499 1.287047,1.21551 -0.760919,0.81199 -3.580867,-3.38245 1.200998,-1.28091 c 0.446394,-0.47625 0.945677,-0.80165 1.465291,-0.78175 0.311768,0.0119 0.630508,0.14843 0.950227,0.45042 0.523732,0.4947 0.617235,1.06543 0.432089,1.61224 m 4e-6,5e-5 v 0 m -1.574086,-1.01133 c -0.219842,-0.009 -0.443011,0.13842 -0.69208,0.40414 l -0.378001,0.40291 1.006773,0.95081 0.378001,-0.4029 c 0.39852,-0.42517 0.434395,-0.8287 0.08236,-1.16122 -0.134075,-0.12664 -0.265149,-0.18828 -0.397054,-0.19374 z m -7.033891,5.87578 4.63128,2.26134 -0.807017,0.86135 -1.059017,-0.58493 -1.015378,1.08286 0.645982,1.02608 -0.781816,0.8342 -2.529841,-4.50355 z m 1.278214,2.86141 0.707674,-0.7537 -1.841448,-1.03411 -0.02028,0.0222 z m 0.707674,-0.7537 0.779358,0.43005 z"
|
||||
sodipodi:nodetypes="cccccccccccccccsccccscsccccccccccccccccssccssccssccssccscccccscccsccccccccscccccccccssccccccccccccccccccc" />
|
||||
</g>
|
||||
<g id="text821"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;line-height:1.25;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2b3137;fill-opacity:1;stroke:none;stroke-width:2.11376619;stroke-opacity:1;filter:url(#filter1159)"
|
||||
transform="matrix(1.0347881,0,0,0.96638144,-54.239583,-37.041665)" aria-label="K" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.2 KiB |
300
front2/src/components/AudioPlayer.tsx
Normal file
300
front2/src/components/AudioPlayer.tsx
Normal file
@ -0,0 +1,300 @@
|
||||
import { SyntheticEvent, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
IconButton,
|
||||
Slider,
|
||||
SliderFilledTrack,
|
||||
SliderThumb,
|
||||
SliderTrack,
|
||||
Text,
|
||||
position,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
MdFastForward,
|
||||
MdFastRewind,
|
||||
MdGraphicEq,
|
||||
MdNavigateBefore,
|
||||
MdNavigateNext,
|
||||
MdOutlinePlayArrow,
|
||||
MdPause,
|
||||
MdPlayArrow,
|
||||
MdStop,
|
||||
MdTrendingFlat,
|
||||
} from 'react-icons/md';
|
||||
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSpecificTrack } from '@/service/Track';
|
||||
import { DataUrlAccess } from '@/utils/data-url-access';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export type AudioPlayerProps = {};
|
||||
|
||||
const formatTime = (time) => {
|
||||
if (time && !isNaN(time)) {
|
||||
const minutes = Math.floor(time / 60);
|
||||
const formatMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
|
||||
const seconds = Math.floor(time % 60);
|
||||
const formatSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
|
||||
return `${formatMinutes}:${formatSeconds}`;
|
||||
}
|
||||
return '00:00';
|
||||
};
|
||||
|
||||
export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
const { mode } = useThemeMode();
|
||||
const { playTrackList, trackOffset, previous, next } =
|
||||
useActivePlaylistService();
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||
const [timeProgress, setTimeProgress] = useState<number>(0);
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const { dataTrack, updateTrackId } = useSpecificTrack(
|
||||
trackOffset !== undefined ? playTrackList[trackOffset] : undefined
|
||||
);
|
||||
useEffect(() => {
|
||||
console.log(`detect change of playlist ...`);
|
||||
updateTrackId(
|
||||
trackOffset !== undefined ? playTrackList[trackOffset] : undefined
|
||||
);
|
||||
}, [playTrackList, trackOffset, updateTrackId]);
|
||||
const [mediaSource, setMediaSource] = useState<string>('');
|
||||
useEffect(() => {
|
||||
setMediaSource(
|
||||
dataTrack && dataTrack?.dataId
|
||||
? DataUrlAccess.getUrl(dataTrack?.dataId)
|
||||
: ''
|
||||
);
|
||||
}, [dataTrack, setMediaSource]);
|
||||
const backColor = mode('back.100', 'back.800');
|
||||
const configButton = {
|
||||
borderRadius: 'full',
|
||||
backgroundColor: '#00000000',
|
||||
_hover: {
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: 'brand.500',
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
if (isPlaying) {
|
||||
audioRef.current.play();
|
||||
} else {
|
||||
audioRef.current.pause();
|
||||
}
|
||||
}, [isPlaying, audioRef]);
|
||||
|
||||
const onAudioEnded = () => {
|
||||
// TODO...
|
||||
};
|
||||
const onSeek = (newValue) => {
|
||||
console.log(`onSeek: ${newValue}`);
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
audioRef.current.currentTime = newValue;
|
||||
};
|
||||
const onPlay = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
if (isPlaying) {
|
||||
audioRef.current.pause();
|
||||
} else {
|
||||
audioRef.current.play();
|
||||
}
|
||||
};
|
||||
const onStop = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
if (audioRef.current.currentTime == 0 && audioRef.current.paused) {
|
||||
// TODO remove curent playing value
|
||||
} else {
|
||||
audioRef.current.pause();
|
||||
audioRef.current.currentTime = 0;
|
||||
}
|
||||
};
|
||||
const onNavigatePrevious = () => {
|
||||
previous();
|
||||
};
|
||||
const onFastRewind = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
audioRef.current.currentTime -= 10;
|
||||
};
|
||||
const onFastForward = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
audioRef.current.currentTime += 10;
|
||||
};
|
||||
const onNavigateNext = () => {
|
||||
next();
|
||||
};
|
||||
const onTypePlay = () => {};
|
||||
/**
|
||||
* Call when meta-data is updated
|
||||
*/
|
||||
function onChangeMetadata(): void {
|
||||
const seconds = audioRef.current?.duration;
|
||||
if (seconds !== undefined) {
|
||||
setDuration(seconds);
|
||||
}
|
||||
}
|
||||
const onTimeUpdate = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
console.log(`onTimeUpdate ${audioRef.current.currentTime}`);
|
||||
setTimeProgress(audioRef.current.currentTime);
|
||||
};
|
||||
const onDurationChange = (event) => {};
|
||||
const onChangeStateToPlay = () => {
|
||||
setIsPlaying(true);
|
||||
};
|
||||
const onChangeStateToPause = () => {
|
||||
setIsPlaying(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
position="absolute"
|
||||
height="150px"
|
||||
minHeight="150px"
|
||||
paddingY="5px"
|
||||
paddingX="10px"
|
||||
marginX="15px"
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={1000}
|
||||
borderWidth="1px"
|
||||
borderColor="brand.900"
|
||||
bgColor={backColor}
|
||||
borderTopRadius="10px"
|
||||
direction="column"
|
||||
>
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
>
|
||||
{dataTrack?.name ?? '???'}
|
||||
</Text>
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="16px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={1}
|
||||
>
|
||||
artist / title album
|
||||
</Text>
|
||||
<Box width="full" paddingX="15px">
|
||||
<Slider
|
||||
aria-label="slider-ex-4"
|
||||
defaultValue={0}
|
||||
value={timeProgress}
|
||||
min={0}
|
||||
max={duration}
|
||||
step={0.1}
|
||||
onChange={onSeek}
|
||||
>
|
||||
<SliderTrack bg="gray.200" height="10px" borderRadius="full">
|
||||
<SliderFilledTrack bg="brand.600" />
|
||||
</SliderTrack>
|
||||
<SliderThumb boxSize={6}>
|
||||
<Box color="brand.600" as={MdGraphicEq} />
|
||||
</SliderThumb>
|
||||
</Slider>
|
||||
</Box>
|
||||
<Flex>
|
||||
<Text
|
||||
align="left"
|
||||
fontSize="16px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
>
|
||||
{formatTime(timeProgress)}
|
||||
</Text>
|
||||
<Text align="left" fontSize="16px" userSelect="none">
|
||||
{formatTime(duration)}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap="5px">
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Play'}
|
||||
icon={
|
||||
isPlaying ? <MdPause size="30px" /> : <MdPlayArrow size="30px" />
|
||||
}
|
||||
onClick={onPlay}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Stop'}
|
||||
icon={<MdStop size="30px" />}
|
||||
onClick={onStop}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Previous track'}
|
||||
icon={<MdNavigateBefore size="30px" />}
|
||||
onClick={onNavigatePrevious}
|
||||
marginLeft="auto"
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'jump 15sec in past'}
|
||||
icon={<MdFastRewind size="30px" />}
|
||||
onClick={onFastRewind}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'jump 15sec in future'}
|
||||
icon={<MdFastForward size="30px" />}
|
||||
onClick={onFastForward}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Next track'}
|
||||
icon={<MdNavigateNext size="30px" />}
|
||||
marginRight="auto"
|
||||
onClick={onNavigateNext}
|
||||
/>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'continue to the end'}
|
||||
icon={<MdTrendingFlat size="30px" />}
|
||||
onClick={onTypePlay}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<audio
|
||||
src={mediaSource}
|
||||
ref={audioRef}
|
||||
//preload={true}
|
||||
onPlay={onChangeStateToPlay}
|
||||
onPause={onChangeStateToPause}
|
||||
onTimeUpdate={onTimeUpdate}
|
||||
onDurationChange={onDurationChange}
|
||||
onLoadedMetadata={onChangeMetadata}
|
||||
autoPlay={true}
|
||||
onEnded={onAudioEnded}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
40
front2/src/components/Cover.tsx
Normal file
40
front2/src/components/Cover.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Image } from '@chakra-ui/react';
|
||||
|
||||
import { DataUrlAccess } from '@/utils/data-url-access';
|
||||
|
||||
export type CoversProps = BoxProps & {
|
||||
data?: string[];
|
||||
size?: string;
|
||||
iconEmpty?: ReactElement;
|
||||
};
|
||||
|
||||
export const Covers = ({
|
||||
data,
|
||||
iconEmpty,
|
||||
size = '100px',
|
||||
...rest
|
||||
}: CoversProps) => {
|
||||
if (!data || data.length < 1) {
|
||||
if (iconEmpty) {
|
||||
return iconEmpty;
|
||||
} else {
|
||||
return (
|
||||
<Box
|
||||
width={size}
|
||||
height={size}
|
||||
minHeight={size}
|
||||
minWidth={size}
|
||||
borderColor="blue"
|
||||
borderWidth="1px"
|
||||
margin="auto"
|
||||
{...rest}
|
||||
></Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
const url = DataUrlAccess.getThumbnailUrl(data[0]);
|
||||
return <Image src={url} boxSize={size} {...rest} />;
|
||||
};
|
@ -1,8 +1,9 @@
|
||||
import React, { ReactNode, useEffect } from 'react';
|
||||
|
||||
import { Flex } from '@chakra-ui/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';
|
||||
|
||||
export type LayoutProps = React.PropsWithChildren<unknown> & {
|
||||
@ -17,19 +18,33 @@ export const PageLayout = ({ children }: LayoutProps) => {
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
<>
|
||||
<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}
|
||||
zIndex={-1}
|
||||
>
|
||||
<Image src={background} boxSize="90%" 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}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { colors } from '@/theme/foundations/colors';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export type LayoutProps = FlexProps & {
|
||||
children: ReactNode;
|
||||
@ -21,6 +22,7 @@ export const PageLayoutInfoCenter = ({
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
|
||||
const { mode } = useThemeMode();
|
||||
return (
|
||||
<PageLayout>
|
||||
<Flex
|
||||
@ -32,6 +34,7 @@ export const PageLayoutInfoCenter = ({
|
||||
borderRadius="8px"
|
||||
padding="10px"
|
||||
boxShadow={'0px 0px 16px ' + colors.back[900]}
|
||||
backgroundColor={mode('#FFFFFF', '#000000')}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
@ -93,6 +93,15 @@ export const TopBar = ({ children }: TopBarProps) => {
|
||||
</Text>
|
||||
</Button>
|
||||
{children}
|
||||
<Text
|
||||
fontSize="25px"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
marginRight="auto"
|
||||
userSelect="none"
|
||||
>
|
||||
Karusic
|
||||
</Text>
|
||||
{session?.state !== SessionState.CONNECTED && (
|
||||
<>
|
||||
<Button {...buttonProperty} onClick={onSignIn}>
|
||||
@ -149,6 +158,7 @@ export const TopBar = ({ children }: TopBarProps) => {
|
||||
<DrawerContent>
|
||||
<DrawerHeader
|
||||
paddingY="auto"
|
||||
as="button"
|
||||
onClick={drawerDisclose.onClose}
|
||||
boxShadow={'0px 2px 4px ' + colors.back[900]}
|
||||
backgroundColor={backColor}
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
useTheme,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { environment } from '@/environment';
|
||||
|
||||
const Illustration = ({ colorScheme = 'gray', ...rest }) => {
|
||||
@ -111,12 +113,9 @@ const Illustration = ({ colorScheme = 'gray', ...rest }) => {
|
||||
|
||||
export const Error404 = () => {
|
||||
return (
|
||||
<Center flex="1" p="8">
|
||||
<Stack
|
||||
direction={{ base: 'column', md: 'row' }}
|
||||
align="center"
|
||||
spacing="0"
|
||||
>
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayoutInfoCenter>
|
||||
<Illustration />
|
||||
<Box textAlign={{ base: 'center', md: 'left' }}>
|
||||
<Heading>Erreur 404</Heading>
|
||||
@ -127,7 +126,7 @@ export const Error404 = () => {
|
||||
Retour à l'accueil
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Center>
|
||||
</PageLayoutInfoCenter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -5,12 +5,13 @@ import {
|
||||
Routes,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { AudioPlayer } from '@/components/AudioPlayer';
|
||||
import { Error404 } from '@/errors';
|
||||
import { ErrorBoundary } from '@/errors/ErrorBoundary';
|
||||
import { ServiceContextProvider } from '@/service/ServiceContext';
|
||||
import { ArtistRoutes } from '@/scene/artist/ArtistRoutes';
|
||||
import { HomePage } from '@/scene/home/HomePage';
|
||||
import { SSORoutes } from '@/scene/sso/SSORoutes';
|
||||
import { ServiceContextProvider } from '@/service/ServiceContext';
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
@ -28,6 +29,7 @@ export const App = () => {
|
||||
</Routes>
|
||||
</HistoryRouter>
|
||||
</ErrorBoundary>
|
||||
<AudioPlayer />
|
||||
</ServiceContextProvider>
|
||||
);
|
||||
};
|
||||
|
180
front2/src/scene/artist/ArtistAlbumDetailPage.tsx
Normal file
180
front2/src/scene/artist/ArtistAlbumDetailPage.tsx
Normal file
@ -0,0 +1,180 @@
|
||||
import { Box, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { LuDisc3, LuFileAudio, LuMusic2, LuUser } from 'react-icons/lu';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { Album, Artist, Track } from '@/back-api';
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSpecificAlbum } from '@/service/Album';
|
||||
import { useArtistService, useSpecificArtist } from '@/service/Artist';
|
||||
import { useAlbumIdsOfAnArtist, useTracksOfAnAlbum } from '@/service/Track';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export type DisplayTrackProps = {
|
||||
track: Track;
|
||||
};
|
||||
export const DisplayTrack = ({ track }: DisplayTrackProps) => {
|
||||
return (
|
||||
<Flex direction="row" width="full" height="full">
|
||||
<Covers
|
||||
data={track?.covers}
|
||||
size="50"
|
||||
height="full"
|
||||
iconEmpty={<LuMusic2 size="50" height="full" />}
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
width="full"
|
||||
height="full"
|
||||
paddingLeft="5px"
|
||||
overflowX="hidden"
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={[1, 2]}
|
||||
marginY="auto"
|
||||
>
|
||||
[{track.track}] {track.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
export const EmptyEnd = () => {
|
||||
return (
|
||||
<Box
|
||||
width="full"
|
||||
height="25%"
|
||||
minHeight="25%"
|
||||
borderWidth="1px"
|
||||
borderColor="red"
|
||||
></Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const ArtistAlbumDetailPage = () => {
|
||||
const { artistId, albumId } = useParams();
|
||||
const artistIdInt = artistId ? parseInt(artistId, 10) : undefined;
|
||||
const albumIdInt = albumId ? parseInt(albumId, 10) : undefined;
|
||||
const { mode } = useThemeMode();
|
||||
const { playInList } = useActivePlaylistService();
|
||||
const { dataArtist } = useSpecificArtist(artistIdInt);
|
||||
const { dataAlbum } = useSpecificAlbum(albumIdInt);
|
||||
const { tracksOnAnAlbum } = useTracksOfAnAlbum(albumIdInt);
|
||||
const onSelectItem = (trackId: number) => {
|
||||
//navigate(`/artist/${artistIdInt}/album/${albumId}`);
|
||||
let currentPlay = 0;
|
||||
const listTrackId: number[] = [];
|
||||
for (let iii = 0; iii < tracksOnAnAlbum.length; iii++) {
|
||||
listTrackId.push(tracksOnAnAlbum[iii].id);
|
||||
if (tracksOnAnAlbum[iii].id === trackId) {
|
||||
currentPlay = iii;
|
||||
}
|
||||
}
|
||||
playInList(currentPlay, listTrackId);
|
||||
};
|
||||
|
||||
console.log(`dataAlbum = ${JSON.stringify(dataAlbum, null, 2)}`);
|
||||
if (!dataAlbum) {
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayoutInfoCenter>
|
||||
Fail to load artist id: {artistId}
|
||||
</PageLayoutInfoCenter>
|
||||
</>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayout>
|
||||
<Flex
|
||||
direction="row"
|
||||
width="80%"
|
||||
marginX="auto"
|
||||
padding="10px"
|
||||
gap="10px"
|
||||
>
|
||||
<Covers
|
||||
data={dataArtist?.covers}
|
||||
iconEmpty={<LuUser size="100" height="full" />}
|
||||
/>
|
||||
<Flex direction="column" width="80%" marginRight="auto">
|
||||
<Text fontSize="24px" fontWeight="bold">
|
||||
{dataArtist?.name}
|
||||
</Text>
|
||||
{dataArtist?.description && (
|
||||
<Text>Description: {dataArtist?.description}</Text>
|
||||
)}
|
||||
{dataArtist?.firstName && (
|
||||
<Text>first name: {dataArtist?.firstName}</Text>
|
||||
)}
|
||||
{dataArtist?.surname && <Text>surname: {dataArtist?.surname}</Text>}
|
||||
{dataArtist?.birth && <Text>birth: {dataArtist?.birth}</Text>}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
direction="row"
|
||||
width="80%"
|
||||
marginX="auto"
|
||||
padding="10px"
|
||||
gap="10px"
|
||||
>
|
||||
<Covers
|
||||
data={dataAlbum?.covers}
|
||||
iconEmpty={<LuDisc3 size="100" height="full" />}
|
||||
/>
|
||||
<Flex direction="column" width="80%" marginRight="auto">
|
||||
<Text fontSize="24px" fontWeight="bold">
|
||||
{dataAlbum?.name}
|
||||
</Text>
|
||||
{dataAlbum?.description && (
|
||||
<Text>Description: {dataAlbum?.description}</Text>
|
||||
)}
|
||||
{dataAlbum?.publication && (
|
||||
<Text>first name: {dataAlbum?.publication}</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="20px"
|
||||
marginX="auto"
|
||||
padding="20px"
|
||||
width="80%"
|
||||
>
|
||||
{tracksOnAnAlbum?.map((data) => (
|
||||
<Box
|
||||
minWidth="100%"
|
||||
height="60px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data.id)}
|
||||
>
|
||||
<DisplayTrack track={data} />
|
||||
</Box>
|
||||
))}
|
||||
<EmptyEnd />
|
||||
</Flex>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
126
front2/src/scene/artist/ArtistDetailPage.tsx
Normal file
126
front2/src/scene/artist/ArtistDetailPage.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { LuDisc3, LuUser } from 'react-icons/lu';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { Album, Artist } from '@/back-api';
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { useSpecificAlbum } from '@/service/Album';
|
||||
import { useArtistService, useSpecificArtist } from '@/service/Artist';
|
||||
import { useAlbumIdsOfAnArtist } from '@/service/Track';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export type DisplayAlbumProps = {
|
||||
id: number;
|
||||
};
|
||||
export const DisplayAlbum = ({ id }: DisplayAlbumProps) => {
|
||||
const { dataAlbum } = useSpecificAlbum(id);
|
||||
return (
|
||||
<Flex direction="row" width="full" height="full">
|
||||
<Covers
|
||||
data={dataAlbum?.covers}
|
||||
size="100"
|
||||
height="full"
|
||||
iconEmpty={<LuDisc3 size="100" height="full" />}
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
width="150px"
|
||||
maxWidth="150px"
|
||||
height="full"
|
||||
paddingLeft="5px"
|
||||
overflowX="hidden"
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={[1, 2]}
|
||||
>
|
||||
{dataAlbum?.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const ArtistDetailPage = () => {
|
||||
const { artistId } = useParams();
|
||||
const artistIdInt = artistId ? parseInt(artistId, 10) : undefined;
|
||||
const { mode } = useThemeMode();
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (albumId: number) => {
|
||||
navigate(`/artist/${artistIdInt}/album/${albumId}`);
|
||||
};
|
||||
const { dataArtist } = useSpecificArtist(artistIdInt);
|
||||
const { albumIdsOfAnArtist } = useAlbumIdsOfAnArtist(artistIdInt);
|
||||
|
||||
if (!dataArtist) {
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayoutInfoCenter>
|
||||
Fail to load artist id: {artistId}
|
||||
</PageLayoutInfoCenter>
|
||||
</>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayout>
|
||||
<Flex
|
||||
direction="row"
|
||||
width="80%"
|
||||
marginX="auto"
|
||||
padding="10px"
|
||||
gap="10px"
|
||||
>
|
||||
<Covers
|
||||
data={dataArtist?.covers}
|
||||
iconEmpty={<LuUser size="100" height="full" />}
|
||||
/>
|
||||
<Flex direction="column" width="80%" marginRight="auto">
|
||||
<Text fontSize="24px" fontWeight="bold">
|
||||
{dataArtist?.name}
|
||||
</Text>
|
||||
{dataArtist?.description && (
|
||||
<Text>Description: {dataArtist?.description}</Text>
|
||||
)}
|
||||
{dataArtist?.firstName && (
|
||||
<Text>first name: {dataArtist?.firstName}</Text>
|
||||
)}
|
||||
{dataArtist?.surname && <Text>surname: {dataArtist?.surname}</Text>}
|
||||
{dataArtist?.birth && <Text>birth: {dataArtist?.birth}</Text>}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center">
|
||||
{albumIdsOfAnArtist?.map((data) => (
|
||||
<WrapItem
|
||||
width="270px"
|
||||
height="120px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data)}
|
||||
>
|
||||
<DisplayAlbum id={data} key={data} />
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,15 +1,21 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { Error404 } from '@/errors';
|
||||
import { ArtistAlbumDetailPage } from '@/scene/artist/ArtistAlbumDetailPage';
|
||||
import { ArtistDetailPage } from '@/scene/artist/ArtistDetailPage';
|
||||
import { ArtistsPage } from '@/scene/artist/ArtistsPage';
|
||||
|
||||
export const ArtistRoutes = () => {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayout>
|
||||
<Text>artist Page</Text>;
|
||||
</PageLayout>
|
||||
</>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="all" replace />} />
|
||||
<Route path="all" element={<ArtistsPage />} />
|
||||
<Route path=":artistId" element={<ArtistDetailPage />} />
|
||||
<Route
|
||||
path=":artistId/album/:albumId"
|
||||
element={<ArtistAlbumDetailPage />}
|
||||
/>
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
76
front2/src/scene/artist/ArtistsPage.tsx
Normal file
76
front2/src/scene/artist/ArtistsPage.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { LuUser } from 'react-icons/lu';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Artist } from '@/back-api';
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { useArtistService } from '@/service/Artist';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
export const ArtistsPage = () => {
|
||||
const { mode } = useThemeMode();
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (data: Artist) => {
|
||||
navigate(`/artist/${data.id}/`);
|
||||
};
|
||||
const { store } = useArtistService();
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayout>
|
||||
<Text>All Artists:</Text>
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px">
|
||||
{store.data?.map((data) => (
|
||||
<WrapItem
|
||||
width="270px"
|
||||
height="120px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data)}
|
||||
>
|
||||
<Flex direction="row" width="full" height="full">
|
||||
<Covers
|
||||
data={data.covers}
|
||||
size="100"
|
||||
height="full"
|
||||
iconEmpty={<LuUser size="100" height="full" />}
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
width="150px"
|
||||
maxWidth="150px"
|
||||
height="full"
|
||||
paddingLeft="5px"
|
||||
overflowX="hidden"
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
align="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
noOfLines={[1, 2]}
|
||||
>
|
||||
{data.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,104 +1,95 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { Center, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
|
||||
import { LuCrown, LuDisc3, LuEar, LuFileAudio, LuUser } from 'react-icons/lu';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
import { useThemeMode } from '@/utils/theme-tools';
|
||||
|
||||
type HomeListType = {
|
||||
id: number;
|
||||
name: string;
|
||||
icon: ReactElement;
|
||||
to: string;
|
||||
};
|
||||
const homeList: HomeListType[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Genders',
|
||||
icon: <LuCrown size="60%" height="full" />,
|
||||
to: 'gender',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Artists',
|
||||
icon: <LuUser size="60%" height="full" />,
|
||||
to: 'artist',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Albums',
|
||||
icon: <LuDisc3 size="60%" height="full" />,
|
||||
to: 'album',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Tracks',
|
||||
icon: <LuFileAudio size="60%" height="full" />,
|
||||
to: 'track',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Playlists',
|
||||
icon: <LuEar size="60%" height="full" />,
|
||||
to: 'playlists',
|
||||
},
|
||||
];
|
||||
|
||||
export const HomePage = () => {
|
||||
const { mode } = useThemeMode();
|
||||
const navigate = useNavigate();
|
||||
const onSelectItem = (data: HomeListType) => {
|
||||
navigate(data.to);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayout>
|
||||
<Text>Home Page 1</Text>
|
||||
<br />
|
||||
<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>
|
||||
Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home
|
||||
Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
|
||||
3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
|
||||
3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
|
||||
3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
|
||||
3Home Page 3Home Page 3
|
||||
</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;<Text>Home Page 2</Text>
|
||||
<br />
|
||||
<Text>Home Page 3</Text>
|
||||
<br />
|
||||
<Text>Home Page 4</Text>
|
||||
<br />
|
||||
<Text>Home Page 5</Text>
|
||||
<br />
|
||||
<Text>Home Page 6</Text>
|
||||
<br />;
|
||||
<Wrap spacing="20px" marginX="auto" padding="20px">
|
||||
{homeList.map((data) => (
|
||||
<WrapItem
|
||||
width="200px"
|
||||
height="190px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={mode('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: mode('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data)}
|
||||
>
|
||||
<Flex direction="column" width="full" height="full">
|
||||
<Center height="full">{data.icon}</Center>
|
||||
<Center>
|
||||
<Text
|
||||
fontSize="25px"
|
||||
fontWeight="bold"
|
||||
textTransform="uppercase"
|
||||
userSelect="none"
|
||||
>
|
||||
{data.name}
|
||||
</Text>
|
||||
</Center>
|
||||
</Flex>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { TopBar } from '@/components/TopBar/TopBar';
|
||||
|
||||
export const SSOEmpty = () => {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
<PageLayout>
|
||||
<Text>SSO empty page</Text>;
|
||||
</PageLayout>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,14 +1,13 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { ArtistRoutes } from '@/scene/artist/ArtistRoutes';
|
||||
import { SSOEmpty } from '@/scene/sso/SSOEmpty';
|
||||
import { Error404 } from '@/errors';
|
||||
import { SSOPage } from '@/scene/sso/SSOPage';
|
||||
|
||||
export const SSORoutes = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path=":data/:keepConnected/:token" element={<SSOPage />} />
|
||||
<Route path="*" element={<SSOEmpty />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
125
front2/src/service/ActivePlaylist.ts
Normal file
125
front2/src/service/ActivePlaylist.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useServiceContext } from '@/service/ServiceContext';
|
||||
|
||||
export type PlaylistElement = {
|
||||
trackId: number;
|
||||
};
|
||||
|
||||
export type ActivePlaylistServiceProps = {
|
||||
playTrackList: number[];
|
||||
trackOffset?: number;
|
||||
setNewPlaylist: (listIds: number[]) => void;
|
||||
setNewPlaylistShuffle: (listIds: number[]) => void;
|
||||
playInList: (id: number, listIds: number[]) => void;
|
||||
play: (id: number) => void;
|
||||
previous: () => void;
|
||||
next: () => void;
|
||||
};
|
||||
|
||||
export const useActivePlaylistService = (): ActivePlaylistServiceProps => {
|
||||
const { activePlaylist } = useServiceContext();
|
||||
return activePlaylist;
|
||||
};
|
||||
|
||||
export function localShuffle<T>(array: T[]): T[] {
|
||||
let currentIndex = array.length,
|
||||
randomIndex;
|
||||
// While there remain elements to shuffle.
|
||||
while (currentIndex != 0) {
|
||||
// Pick a remaining element.
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex--;
|
||||
// And swap it with the current element.
|
||||
[array[currentIndex], array[randomIndex]] = [
|
||||
array[randomIndex],
|
||||
array[currentIndex],
|
||||
];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
export const useActivePlaylistServiceWrapped =
|
||||
(): ActivePlaylistServiceProps => {
|
||||
const [playTrackList, setPlayTrackList] = useState<number[]>([]);
|
||||
const [trackOffset, setTrackOffset] = useState<number | undefined>();
|
||||
|
||||
const clear = useCallback(() => {
|
||||
setPlayTrackList([]);
|
||||
setTrackOffset(undefined);
|
||||
}, [setPlayTrackList, setTrackOffset]);
|
||||
|
||||
const play = useCallback(
|
||||
(id: number) => {
|
||||
setPlayTrackList([id]);
|
||||
setTrackOffset(0);
|
||||
},
|
||||
[setPlayTrackList, setTrackOffset]
|
||||
);
|
||||
const playInList = useCallback(
|
||||
(id: number, listIds: number[]) => {
|
||||
console.log(`Request paly in list: ${id} in ${listIds}`);
|
||||
setPlayTrackList(listIds);
|
||||
setTrackOffset(id);
|
||||
},
|
||||
[setPlayTrackList, setTrackOffset]
|
||||
);
|
||||
|
||||
const setNewPlaylist = useCallback(
|
||||
(listIds: number[]) => {
|
||||
if (listIds.length == 0) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
setPlayTrackList(listIds);
|
||||
setTrackOffset(0);
|
||||
},
|
||||
[setPlayTrackList, setTrackOffset, clear]
|
||||
);
|
||||
|
||||
const setNewPlaylistShuffle = useCallback(
|
||||
(listIds: number[]) => {
|
||||
if (listIds.length == 0) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
const newList = localShuffle(listIds);
|
||||
setPlayTrackList(newList);
|
||||
setTrackOffset(0);
|
||||
},
|
||||
[setPlayTrackList, setTrackOffset, clear]
|
||||
);
|
||||
const previous = useCallback(() => {
|
||||
setTrackOffset((previous) => {
|
||||
if (previous === undefined) {
|
||||
return previous;
|
||||
}
|
||||
if (previous === 0) {
|
||||
return previous;
|
||||
}
|
||||
return previous - 1;
|
||||
});
|
||||
}, [setTrackOffset]);
|
||||
const next = useCallback(() => {
|
||||
setTrackOffset((previous) => {
|
||||
if (previous === undefined || playTrackList.length === 0) {
|
||||
return previous;
|
||||
}
|
||||
if (previous >= playTrackList.length - 1) {
|
||||
return previous;
|
||||
}
|
||||
return previous + 1;
|
||||
});
|
||||
}, [playTrackList, setTrackOffset]);
|
||||
|
||||
return {
|
||||
playTrackList,
|
||||
trackOffset,
|
||||
setNewPlaylist,
|
||||
setNewPlaylistShuffle,
|
||||
playInList,
|
||||
play,
|
||||
previous,
|
||||
next,
|
||||
};
|
||||
};
|
49
front2/src/service/Album.ts
Normal file
49
front2/src/service/Album.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Album, AlbumResource } from '@/back-api';
|
||||
import { useServiceContext } from '@/service/ServiceContext';
|
||||
import { SessionServiceProps } from '@/service/session';
|
||||
import { DataStoreType, useDataStore } from '@/utils/data-store';
|
||||
|
||||
export type AlbumServiceProps = {
|
||||
store: DataStoreType<Album>;
|
||||
};
|
||||
|
||||
export const useAlbumService = (): AlbumServiceProps => {
|
||||
const { album } = useServiceContext();
|
||||
return album;
|
||||
};
|
||||
|
||||
export const useAlbumServiceWrapped = (
|
||||
session: SessionServiceProps
|
||||
): AlbumServiceProps => {
|
||||
const store = useDataStore<Album>({
|
||||
restApiName: 'ALBUM',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return AlbumResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { store };
|
||||
};
|
||||
|
||||
export const useSpecificAlbum = (id: number | undefined) => {
|
||||
const [dataAlbum, setDataAlbum] = useState<Album | undefined>(undefined);
|
||||
const { store } = useAlbumService();
|
||||
useEffect(() => {
|
||||
setDataAlbum(store.get(id));
|
||||
}, [store.data]);
|
||||
return { dataAlbum };
|
||||
};
|
||||
|
||||
export const useAlbumOfAnArtist = (idArtist: number | undefined) => {
|
||||
const [dataAlbum, setDataAlbum] = useState<Album | undefined>(undefined);
|
||||
const { store } = useAlbumService();
|
||||
useEffect(() => {
|
||||
setDataAlbum(store.get(idArtist));
|
||||
}, [store.data]);
|
||||
return { dataAlbum };
|
||||
};
|
40
front2/src/service/Artist.ts
Normal file
40
front2/src/service/Artist.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Artist, ArtistResource } from '@/back-api';
|
||||
import { useServiceContext } from '@/service/ServiceContext';
|
||||
import { SessionServiceProps } from '@/service/session';
|
||||
import { DataStoreType, useDataStore } from '@/utils/data-store';
|
||||
|
||||
export type ArtistServiceProps = {
|
||||
store: DataStoreType<Artist>;
|
||||
};
|
||||
|
||||
export const useArtistService = (): ArtistServiceProps => {
|
||||
const { artist } = useServiceContext();
|
||||
return artist;
|
||||
};
|
||||
|
||||
export const useArtistServiceWrapped = (
|
||||
session: SessionServiceProps
|
||||
): ArtistServiceProps => {
|
||||
const store = useDataStore<Artist>({
|
||||
restApiName: 'ARTIST',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return ArtistResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { store };
|
||||
};
|
||||
|
||||
export const useSpecificArtist = (id: number | undefined) => {
|
||||
const [dataArtist, setDataArtist] = useState<Artist | undefined>(undefined);
|
||||
const { store } = useArtistService();
|
||||
useEffect(() => {
|
||||
setDataArtist(store.get(id));
|
||||
}, [store.data]);
|
||||
return { dataArtist };
|
||||
};
|
101
front2/src/service/GenericDataService.ts
Normal file
101
front2/src/service/GenericDataService.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { useDataStore } from '@/utils/data-store';
|
||||
import { DataTools, TypeCheck } from '@/utils/data-tools';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
|
||||
export class GenericDataService<TYPE> {
|
||||
constructor(protected dataStore: DataStore<TYPE>) {}
|
||||
|
||||
gets(): Promise<TYPE[]> {
|
||||
return this.dataStore.getData();
|
||||
}
|
||||
|
||||
get(id: number): Promise<TYPE> {
|
||||
let self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
.gets()
|
||||
.then((response: TYPE[]) => {
|
||||
let data = DataTools.get(response, id);
|
||||
if (isNullOrUndefined(data)) {
|
||||
reject('Data does not exist in the local BDD');
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
return;
|
||||
})
|
||||
.catch((response) => {
|
||||
reject(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getAll(ids: number[]): Promise<TYPE[]> {
|
||||
let self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
.gets()
|
||||
.then((response: TYPE[]) => {
|
||||
let data = DataTools.getsWhere(response, [
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'id',
|
||||
value: ids,
|
||||
},
|
||||
]);
|
||||
resolve(data);
|
||||
return;
|
||||
})
|
||||
.catch((response) => {
|
||||
reject(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getLike(value: string): Promise<TYPE[]> {
|
||||
let self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
.gets()
|
||||
.then((response: TYPE[]) => {
|
||||
let data = DataTools.getNameLike(response, value);
|
||||
if (isNullOrUndefined(data) || data.length === 0) {
|
||||
reject('Data does not exist in the local BDD');
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
return;
|
||||
})
|
||||
.catch((response) => {
|
||||
reject(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getOrder(): Promise<TYPE[]> {
|
||||
let self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
.gets()
|
||||
.then((response: TYPE[]) => {
|
||||
let data = DataTools.getsWhere(
|
||||
response,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.NOT_EQUAL,
|
||||
key: 'id',
|
||||
value: [undefined, null],
|
||||
},
|
||||
],
|
||||
['name', 'id']
|
||||
);
|
||||
resolve(data);
|
||||
})
|
||||
.catch((response) => {
|
||||
console.log(
|
||||
`[E] ${self.constructor.name}: can not retrieve BDD values`
|
||||
);
|
||||
reject(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,15 +1,26 @@
|
||||
import { ReactNode, createContext, useContext, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
ActivePlaylistServiceProps,
|
||||
useActivePlaylistServiceWrapped,
|
||||
} from '@/service/ActivePlaylist';
|
||||
import { AlbumServiceProps, useAlbumServiceWrapped } from '@/service/Album';
|
||||
import { ArtistServiceProps, useArtistServiceWrapped } from '@/service/Artist';
|
||||
import { SessionState } from '@/service/SessionState';
|
||||
import { TrackServiceProps, useTrackServiceWrapped } from '@/service/Track';
|
||||
import {
|
||||
RightPart,
|
||||
SessionServiceProps,
|
||||
useSessionService,
|
||||
getRestConfig,
|
||||
useSessionServiceWrapped,
|
||||
} from '@/service/session';
|
||||
|
||||
export type ServiceContextType = {
|
||||
session: SessionServiceProps;
|
||||
track: TrackServiceProps;
|
||||
artist: ArtistServiceProps;
|
||||
album: AlbumServiceProps;
|
||||
activePlaylist: ActivePlaylistServiceProps;
|
||||
};
|
||||
|
||||
export const ServiceContext = createContext<ServiceContextType>({
|
||||
@ -19,6 +30,39 @@ export const ServiceContext = createContext<ServiceContextType>({
|
||||
hasReadRight: (part: RightPart) => false,
|
||||
hasWriteRight: (part: RightPart) => false,
|
||||
state: SessionState.NO_USER,
|
||||
getRestConfig: getRestConfig,
|
||||
},
|
||||
track: {
|
||||
store: {
|
||||
data: [],
|
||||
isLoading: true,
|
||||
get: (value, key) => undefined,
|
||||
error: undefined,
|
||||
},
|
||||
},
|
||||
artist: {
|
||||
store: {
|
||||
data: [],
|
||||
isLoading: true,
|
||||
get: (value, key) => undefined,
|
||||
error: undefined,
|
||||
},
|
||||
},
|
||||
album: {
|
||||
store: {
|
||||
data: [],
|
||||
isLoading: true,
|
||||
get: (value, key) => undefined,
|
||||
error: undefined,
|
||||
},
|
||||
},
|
||||
activePlaylist: {
|
||||
playTrackList: [],
|
||||
trackOffset: undefined,
|
||||
setNewPlaylist: (listIds: number[]) => {},
|
||||
setNewPlaylistShuffle: (listIds: number[]) => {},
|
||||
playInList: (id: number, listIds: number[]) => {},
|
||||
play: (id: number) => {},
|
||||
},
|
||||
});
|
||||
|
||||
@ -30,11 +74,19 @@ export const ServiceContextProvider = ({
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const session = useSessionServiceWrapped();
|
||||
const track = useTrackServiceWrapped(session);
|
||||
const artist = useArtistServiceWrapped(session);
|
||||
const album = useAlbumServiceWrapped(session);
|
||||
const activePlaylist = useActivePlaylistServiceWrapped();
|
||||
const contextObjectData = useMemo(
|
||||
() => ({
|
||||
session,
|
||||
track,
|
||||
artist,
|
||||
album,
|
||||
activePlaylist,
|
||||
}),
|
||||
[session]
|
||||
[session, track, artist, album]
|
||||
);
|
||||
return (
|
||||
<ServiceContext.Provider value={contextObjectData}>
|
||||
|
180
front2/src/service/Track.ts
Normal file
180
front2/src/service/Track.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Track, TrackResource } from '@/back-api';
|
||||
import { useServiceContext } from '@/service/ServiceContext';
|
||||
import { SessionServiceProps } from '@/service/session';
|
||||
import { DataStoreType, useDataStore } from '@/utils/data-store';
|
||||
import { DataTools, TypeCheck } from '@/utils/data-tools';
|
||||
import { isArrayOf, isNumber } from '@/utils/validator';
|
||||
|
||||
export type TrackServiceProps = {
|
||||
store: DataStoreType<Track>;
|
||||
};
|
||||
|
||||
export const useTrackService = (): TrackServiceProps => {
|
||||
const { track } = useServiceContext();
|
||||
return track;
|
||||
};
|
||||
|
||||
export const useTrackServiceWrapped = (
|
||||
session: SessionServiceProps
|
||||
): TrackServiceProps => {
|
||||
const store = useDataStore<Track>({
|
||||
restApiName: 'TRACK',
|
||||
primaryKey: 'id',
|
||||
getsCall: () => {
|
||||
return TrackResource.gets({
|
||||
restConfig: session.getRestConfig(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { store };
|
||||
};
|
||||
|
||||
export const useSpecificTrack = (id: number | undefined) => {
|
||||
const [dataTrack, setDataTrack] = useState<Track | undefined>(undefined);
|
||||
const { store } = useTrackService();
|
||||
useEffect(() => {
|
||||
console.log(`retrieve specific track: ${id}`);
|
||||
setDataTrack(store.get(id));
|
||||
}, [store.data]);
|
||||
const updateTrackId = useCallback(
|
||||
(id: number | undefined) => {
|
||||
console.log(`retrieve specific track (update): ${id}`);
|
||||
setDataTrack(store.get(id));
|
||||
},
|
||||
[setDataTrack, store]
|
||||
);
|
||||
return { dataTrack, updateTrackId };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all the track for a specific artist
|
||||
* @param idArtist - Id of the artist.
|
||||
* @returns a promise on the list of track elements
|
||||
*/
|
||||
export const useTracksOfAnArtist = (idArtist?: number) => {
|
||||
const [tracksOnAnArtist, setTracksOnAnArtist] = useState<Track[]>([]);
|
||||
const { store } = useTrackService();
|
||||
useEffect(() => {
|
||||
if (idArtist) {
|
||||
setTracksOnAnArtist(
|
||||
DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.CONTAINS,
|
||||
key: 'artists',
|
||||
value: idArtist,
|
||||
},
|
||||
],
|
||||
['track', 'name']
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [store.data]);
|
||||
return { tracksOnAnArtist };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of track in this artist ID
|
||||
* @param id - Id of the artist.
|
||||
* @returns The number of track present in this artist
|
||||
*/
|
||||
export const useCountTracksOfAnArtist = (idArtist: number) => {
|
||||
const { tracksOnAnArtist } = useTracksOfAnArtist(idArtist);
|
||||
const countTracksOnAnArtist = tracksOnAnArtist.length;
|
||||
return { countTracksOnAnArtist };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all the album of a specific artist
|
||||
* @param artistId - ID of the artist
|
||||
* @returns the required List.
|
||||
*/
|
||||
export const useAlbumIdsOfAnArtist = (idArtist?: number) => {
|
||||
const [albumIdsOfAnArtist, setAlbumIdsOfAnArtist] = useState<number[]>([]);
|
||||
const { tracksOnAnArtist } = useTracksOfAnArtist(idArtist);
|
||||
useEffect(() => {
|
||||
// extract a single time all value "id" in an array
|
||||
const listAlbumId = DataTools.extractLimitOne(tracksOnAnArtist, 'albumId');
|
||||
if (isArrayOf(listAlbumId, isNumber)) {
|
||||
setAlbumIdsOfAnArtist(listAlbumId);
|
||||
} else {
|
||||
console.log(
|
||||
'Fail to parse the result of the list value (impossible case)'
|
||||
);
|
||||
setAlbumIdsOfAnArtist([]);
|
||||
}
|
||||
}, [tracksOnAnArtist]);
|
||||
return { albumIdsOfAnArtist };
|
||||
};
|
||||
|
||||
export const useTracksOfAnAlbum = (idAlbum?: number) => {
|
||||
const [tracksOnAnAlbum, setTracksOnAnAlbum] = useState<Track[]>([]);
|
||||
const { store } = useTrackService();
|
||||
useEffect(() => {
|
||||
if (idAlbum) {
|
||||
setTracksOnAnAlbum(
|
||||
DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'albumId',
|
||||
value: idAlbum,
|
||||
},
|
||||
],
|
||||
['track', 'name', 'id']
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [store.data]);
|
||||
return { tracksOnAnAlbum };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of track in this season ID
|
||||
* @param AlbumId - Id of the album.
|
||||
* @returns The number of element present in this season
|
||||
*/
|
||||
export const useCountTracksWithAlbumId = (albumId: number) => {
|
||||
const { tracksOnAnAlbum } = useTracksOfAnAlbum(albumId);
|
||||
const countTracksOnAnArtist = tracksOnAnAlbum.length;
|
||||
return { countTracksOnAnArtist };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all the track for a specific artist
|
||||
* @param idArtist - Id of the artist.
|
||||
* @returns a promise on the list of track elements
|
||||
*/
|
||||
export const useTracksOfArtistWithNoAlbum = (idArtist: number) => {
|
||||
const [tracksOnAnArtistWithNoAlbum, setTracksOnAnArtistWithNoAlbum] =
|
||||
useState<Track[]>([]);
|
||||
const { store } = useTrackService();
|
||||
useEffect(() => {
|
||||
if (idArtist) {
|
||||
setTracksOnAnArtistWithNoAlbum(
|
||||
DataTools.getsWhere(
|
||||
store.data,
|
||||
[
|
||||
{
|
||||
check: TypeCheck.CONTAINS,
|
||||
key: 'artists',
|
||||
value: idArtist,
|
||||
},
|
||||
{
|
||||
check: TypeCheck.EQUAL,
|
||||
key: 'albumId',
|
||||
value: undefined,
|
||||
},
|
||||
],
|
||||
['track', 'name']
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [store.data]);
|
||||
return { tracksOnAnArtistWithNoAlbum };
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { PartRight, RESTConfig, UserMe, UserResource } from '@/back-api';
|
||||
import { environment, getApiUrl } from '@/environment';
|
||||
@ -35,12 +35,14 @@ export type SessionServiceProps = {
|
||||
hasReadRight: (part: RightPart) => boolean;
|
||||
hasWriteRight: (part: RightPart) => boolean;
|
||||
state: SessionState;
|
||||
getRestConfig: () => RESTConfig;
|
||||
};
|
||||
|
||||
export const useSessionService = (): SessionServiceProps => {
|
||||
const { session } = useServiceContext();
|
||||
return session;
|
||||
};
|
||||
|
||||
export const useSessionServiceWrapped = (): SessionServiceProps => {
|
||||
const [token, setToken] = useState<string | undefined>(
|
||||
isBrowser ? (localStorage.getItem(TOKEN_KEY) ?? undefined) : undefined
|
||||
@ -113,6 +115,17 @@ export const useSessionServiceWrapped = (): SessionServiceProps => {
|
||||
[config]
|
||||
);
|
||||
|
||||
const getRestConfig = useCallback((): RESTConfig => {
|
||||
return {
|
||||
server: getApiUrl(),
|
||||
token: token ?? '',
|
||||
};
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
updateRight();
|
||||
}, [updateRight]);
|
||||
|
||||
return {
|
||||
token,
|
||||
setToken: setTokenLocal,
|
||||
@ -121,5 +134,6 @@ export const useSessionServiceWrapped = (): SessionServiceProps => {
|
||||
hasReadRight,
|
||||
hasWriteRight,
|
||||
state,
|
||||
getRestConfig,
|
||||
};
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ export const styles: Styles = {
|
||||
overflowY: 'none',
|
||||
bg: mode('back.50', 'back.700')(props),
|
||||
color: mode('text.900', 'text.50')(props),
|
||||
fontFamily: 'Roboto, Helvetica, Arial, "sans-serif"',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
@ -3,102 +3,78 @@
|
||||
* @copyright 2024, Edouard DUPIN, all right reserved
|
||||
* @license PROPRIETARY (see license file)
|
||||
*/
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { RestErrorResponse } from '@/back-api';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
|
||||
export class DataStore<TYPE> {
|
||||
private data?: TYPE[];
|
||||
private dataPromise?: {
|
||||
resolve: (response: TYPE[]) => void;
|
||||
reject: (error: Error) => void;
|
||||
}[];
|
||||
export type DataStoreType<TYPE> = {
|
||||
isLoading: boolean;
|
||||
error: RestErrorResponse | undefined;
|
||||
data: TYPE[];
|
||||
get: <MODEL>(value: MODEL, key?: string) => TYPE | undefined;
|
||||
};
|
||||
|
||||
constructor(
|
||||
private _gets: () => Promise<TYPE[]>,
|
||||
private primaryKey: string = 'id'
|
||||
) {}
|
||||
export const useDataStore = <TYPE>({
|
||||
primaryKey = 'id',
|
||||
restApiName,
|
||||
getsCall,
|
||||
}: {
|
||||
restApiName?: string;
|
||||
primaryKey?: string;
|
||||
getsCall: () => Promise<TYPE[]>;
|
||||
}): DataStoreType<TYPE> => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<RestErrorResponse | undefined>(undefined);
|
||||
const [data, setData] = useState<TYPE[]>([]);
|
||||
|
||||
public getData(): Promise<TYPE[]> {
|
||||
let self = this;
|
||||
if (!isNullOrUndefined(this.data)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(self.data ?? []);
|
||||
// on instantiation ==> call the request of the data...
|
||||
useEffect(() => {
|
||||
console.log(`[${restApiName}] call data ...`);
|
||||
setError(undefined);
|
||||
setIsLoading(true);
|
||||
getsCall()
|
||||
.then((response: TYPE[]) => {
|
||||
console.log(
|
||||
`[${restApiName}] getData Response: ${JSON.stringify(response, null, 2)}`
|
||||
);
|
||||
setData(response);
|
||||
setError(undefined);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error: RestErrorResponse) => {
|
||||
console.log(
|
||||
`[${restApiName}] catch error: ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
setError(error);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
if (isNullOrUndefined(this.dataPromise)) {
|
||||
this.dataPromise = [];
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
._gets()
|
||||
.then((response: TYPE[]) => {
|
||||
self.data = response;
|
||||
if (self.dataPromise) {
|
||||
for (let iii = 0; iii < self.dataPromise.length; iii++) {
|
||||
self.dataPromise[iii].resolve(self.data);
|
||||
}
|
||||
}
|
||||
resolve(self.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(
|
||||
`[E] ${self.constructor.name}: can not get data from remote server: ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
if (self.dataPromise) {
|
||||
for (let iii = 0; iii < self.dataPromise.length; iii++) {
|
||||
self.dataPromise[iii].reject(error);
|
||||
}
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!isNullOrUndefined(self.data)) {
|
||||
resolve(self.data);
|
||||
return;
|
||||
}
|
||||
if (self.dataPromise) {
|
||||
self.dataPromise.push({ resolve: resolve, reject: reject });
|
||||
}
|
||||
});
|
||||
}
|
||||
public get(id: any): Promise<TYPE> {
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
self
|
||||
.getData()
|
||||
.then((value: TYPE[]) => {
|
||||
for (let iii = 0; iii < value.length; iii++) {
|
||||
if (value[iii][this.primaryKey] == id) {
|
||||
resolve(value[iii]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
reject('Not FOUND');
|
||||
})
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
}
|
||||
}, [setIsLoading, setData]);
|
||||
|
||||
public updateValue(value: TYPE) {
|
||||
console.log(`[E] Not implemented Updater`);
|
||||
if (this.data) {
|
||||
for (let iii = 0; iii < this.data.length; iii++) {
|
||||
if (this.data[iii][this.primaryKey] == value[this.primaryKey]) {
|
||||
this.data[iii] = value;
|
||||
return;
|
||||
const get = useCallback(
|
||||
<MODEL>(value: MODEL, key?: string): TYPE | undefined => {
|
||||
const keyValue = key ?? primaryKey;
|
||||
for (let iii = 0; iii < data.length; iii++) {
|
||||
if (data[iii][keyValue] === value) {
|
||||
return data[iii];
|
||||
}
|
||||
}
|
||||
this.data.push(value);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
public delete(value: any): void {
|
||||
if (this.data) {
|
||||
for (let iii = 0; iii < this.data.length; iii++) {
|
||||
if (this.data[iii][this.primaryKey] == value) {
|
||||
this.data.splice(iii, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const update = useCallback(
|
||||
(value: TYPE, key?: string) => {
|
||||
const keyValue = key ?? primaryKey;
|
||||
const filterData = data.filter(
|
||||
(localData: TYPE) => localData[keyValue] === value[keyValue]
|
||||
);
|
||||
filterData.push(value);
|
||||
setData(filterData);
|
||||
},
|
||||
[data, setData]
|
||||
);
|
||||
|
||||
return { isLoading, error, data, get }; //, update};
|
||||
};
|
||||
|
@ -18,7 +18,17 @@ export enum TypeCheck {
|
||||
export interface SelectModel {
|
||||
check: TypeCheck;
|
||||
key: string;
|
||||
value?: bigint | number | string | boolean | bigint[] | number[] | string[];
|
||||
value:
|
||||
| null
|
||||
| undefined
|
||||
| bigint
|
||||
| number
|
||||
| string
|
||||
| boolean
|
||||
| bigint[]
|
||||
| number[]
|
||||
| string[]
|
||||
| (bigint | number | string | boolean | undefined | null)[];
|
||||
}
|
||||
/*
|
||||
{ check: TypeCheck.EQUAL, key: sss, value: null}
|
||||
@ -68,7 +78,7 @@ export namespace DataTools {
|
||||
bdd: TYPE[],
|
||||
select: SelectModel[],
|
||||
orderByData?: string[]
|
||||
): TYPE[] | undefined {
|
||||
): TYPE[] {
|
||||
// console.log("[I] gets_where " + this.name + " select " + _select);
|
||||
let tmpList = getSubList(bdd, select);
|
||||
if (tmpList && orderByData) {
|
||||
@ -180,7 +190,7 @@ export namespace DataTools {
|
||||
export function getSubList<TYPE>(
|
||||
values: TYPE[],
|
||||
select?: SelectModel[]
|
||||
): undefined | TYPE[] {
|
||||
): TYPE[] {
|
||||
let out = [] as TYPE[];
|
||||
for (let iiiElem = 0; iiiElem < values.length; iiiElem++) {
|
||||
let find = true;
|
||||
@ -216,7 +226,7 @@ export namespace DataTools {
|
||||
console.log(
|
||||
'[ERROR] Internal Server Error{ unknown comparing type ...'
|
||||
);
|
||||
return undefined;
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
//console.log(" [" + control.key + "] = " + valueElement);
|
||||
@ -262,7 +272,7 @@ export namespace DataTools {
|
||||
console.log(
|
||||
'[ERROR] Internal Server Error{ unknown comparing type ...'
|
||||
);
|
||||
return undefined;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
106
front2/src/utils/data-url-access.ts
Normal file
106
front2/src/utils/data-url-access.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/** @file
|
||||
* @author Edouard DUPIN
|
||||
* @copyright 2018, Edouard DUPIN, all right reserved
|
||||
* @license PROPRIETARY (see license file)
|
||||
*/
|
||||
import { string } from 'zod';
|
||||
|
||||
import { RESTUrl } from '@/back-api';
|
||||
import { environment } from '@/environment';
|
||||
import { getRestConfig } from '@/service/session';
|
||||
import { isArrayOf, isString } from '@/utils/validator';
|
||||
|
||||
export namespace DataUrlAccess {
|
||||
/**
|
||||
* Retrieve the Cover URL of a specific data id
|
||||
* @param coverId Id of te cover
|
||||
* @returns the url of the cover
|
||||
*/
|
||||
export const getUrl = (dataId: string, optionalName?: string): string => {
|
||||
if (!optionalName) {
|
||||
const url = RESTUrl({
|
||||
restModel: {
|
||||
endPoint: 'data/{dataId}',
|
||||
tokenInUrl: true,
|
||||
},
|
||||
restConfig: getRestConfig(),
|
||||
params: {
|
||||
dataId,
|
||||
},
|
||||
});
|
||||
console.log(`get URL = ${url}`);
|
||||
if (environment?.replaceDataToRealServer === true) {
|
||||
return url.replace('http://localhost:19080', 'https://atria-soft.org');
|
||||
}
|
||||
return url;
|
||||
}
|
||||
const url = RESTUrl({
|
||||
restModel: {
|
||||
endPoint: 'data/{dataId}/{optionalName}',
|
||||
tokenInUrl: true,
|
||||
},
|
||||
restConfig: getRestConfig(),
|
||||
params: {
|
||||
dataId,
|
||||
optionalName,
|
||||
},
|
||||
});
|
||||
if (environment?.replaceDataToRealServer === true) {
|
||||
return url.replace('http://localhost:19080', 'https://atria-soft.org');
|
||||
}
|
||||
return url;
|
||||
};
|
||||
/**
|
||||
* Retrieve the list Cover URL of a specific data id
|
||||
* @param coverId Id of te cover
|
||||
* @returns the url of the cover
|
||||
*/
|
||||
export const getListUrl = (dataIds?: string[]): string[] | undefined => {
|
||||
if (!isArrayOf(dataIds, isString) || dataIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
let covers: string[] = [];
|
||||
for (let iii = 0; iii < dataIds.length; iii++) {
|
||||
covers.push(getUrl(dataIds[iii]));
|
||||
}
|
||||
return covers;
|
||||
};
|
||||
/**
|
||||
* Retrieve the thumbnail cover URL of a specific data id
|
||||
* @param coverId Id of te cover
|
||||
* @returns the url of the cover
|
||||
*/
|
||||
export const getThumbnailUrl = (coverId: string): string => {
|
||||
const url = RESTUrl({
|
||||
restModel: {
|
||||
endPoint: 'data/thumbnail/{coverId}',
|
||||
tokenInUrl: true,
|
||||
},
|
||||
restConfig: getRestConfig(),
|
||||
params: {
|
||||
coverId,
|
||||
},
|
||||
});
|
||||
if (environment?.replaceDataToRealServer === true) {
|
||||
return url.replace('http://localhost:19080', 'https://atria-soft.org');
|
||||
}
|
||||
return url;
|
||||
};
|
||||
/**
|
||||
* Retrieve the list thumbnail cover URL of a specific data id
|
||||
* @param coverId Id of te cover
|
||||
* @returns the url of the cover
|
||||
*/
|
||||
export const getListThumbnailUrl = (
|
||||
dataIds?: string[]
|
||||
): string[] | undefined => {
|
||||
if (!isArrayOf(dataIds, isString) || dataIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
let covers: string[] = [];
|
||||
for (let iii = 0; iii < dataIds.length; iii++) {
|
||||
covers.push(getThumbnailUrl(dataIds[iii]));
|
||||
}
|
||||
return covers;
|
||||
};
|
||||
}
|
10
front2/tsconfig.node.json
Normal file
10
front2/tsconfig.node.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.mts"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user