Compare commits
2 Commits
21d53b77f2
...
b49a465afe
Author | SHA1 | Date | |
---|---|---|---|
b49a465afe | |||
1d4c547d89 |
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ public class WebLauncherLocal extends WebLauncher {
|
|||||||
TrackResource.class, DataResource.class);
|
TrackResource.class, DataResource.class);
|
||||||
final AnalyzeApi api = new AnalyzeApi();
|
final AnalyzeApi api = new AnalyzeApi();
|
||||||
api.addAllApi(listOfResources);
|
api.addAllApi(listOfResources);
|
||||||
TsGenerateApi.generateApi(api, "../front/src/app/back-api/");
|
TsGenerateApi.generateApi(api, "../front2/src/back-api/");
|
||||||
LOGGER.info("Generate APIs (DONE)");
|
LOGGER.info("Generate APIs (DONE)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +49,7 @@ public class WebLauncherLocal extends WebLauncher {
|
|||||||
ConfigBaseVariable.apiAdress = "http://0.0.0.0:19080/karusic/api/";
|
ConfigBaseVariable.apiAdress = "http://0.0.0.0:19080/karusic/api/";
|
||||||
//ConfigBaseVariable.ssoAdress = "https://atria-soft.org/karso/api/";
|
//ConfigBaseVariable.ssoAdress = "https://atria-soft.org/karso/api/";
|
||||||
ConfigBaseVariable.dbPort = "3906";
|
ConfigBaseVariable.dbPort = "3906";
|
||||||
|
ConfigBaseVariable.testMode = "true";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
super.migrateDB();
|
super.migrateDB();
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package org.kar.karusic.api;
|
package org.kar.karusic.api;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.kar.archidata.dataAccess.DataAccess;
|
import org.kar.archidata.dataAccess.DataAccess;
|
||||||
import org.kar.archidata.filter.GenericContext;
|
import org.kar.archidata.filter.GenericContext;
|
||||||
|
import org.kar.karusic.api.UserResourceModel.PartRight;
|
||||||
|
import org.kar.karusic.api.UserResourceModel.UserMe;
|
||||||
import org.kar.karusic.model.UserKarusic;
|
import org.kar.karusic.model.UserKarusic;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -20,7 +23,7 @@ import jakarta.ws.rs.core.MediaType;
|
|||||||
import jakarta.ws.rs.core.SecurityContext;
|
import jakarta.ws.rs.core.SecurityContext;
|
||||||
|
|
||||||
@Path("/users")
|
@Path("/users")
|
||||||
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class UserResource {
|
public class UserResource {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
|
||||||
|
|
||||||
@ -74,10 +77,15 @@ public class UserResource {
|
|||||||
@GET
|
@GET
|
||||||
@Path("me")
|
@Path("me")
|
||||||
@RolesAllowed("USER")
|
@RolesAllowed("USER")
|
||||||
public UserOut getMe(@Context final SecurityContext sc) {
|
public UserMe getMe(@Context final SecurityContext sc) {
|
||||||
LOGGER.debug("getMe()");
|
LOGGER.debug("getMe()");
|
||||||
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
|
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
|
||||||
LOGGER.debug("== USER ? {}", gc.userByToken);
|
LOGGER.debug("== USER ? {}", gc.userByToken);
|
||||||
return new UserOut(gc.userByToken.id, gc.userByToken.name);
|
return new UserMe(gc.userByToken.id, gc.userByToken.name, //
|
||||||
|
Map.of(gc.userByToken.name, //
|
||||||
|
Map.of("admin", PartRight.READ_WRITE, //
|
||||||
|
"user", PartRight.READ_WRITE), //
|
||||||
|
"karusic", //
|
||||||
|
Map.of("user", PartRight.READ)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package org.kar.karusic.api.UserResourceModel;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import org.kar.archidata.annotation.NoWriteSpecificMode;
|
||||||
|
|
||||||
|
@NoWriteSpecificMode
|
||||||
|
public class ModuleAuthorizations extends HashMap<String, PartRight> {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ModuleAuthorizations() {}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package org.kar.karusic.api.UserResourceModel;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
public enum PartRight {
|
||||||
|
READ(1), //
|
||||||
|
WRITE(2), //
|
||||||
|
READ_WRITE(3);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
PartRight(final int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public int getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartRight fromValue(final int value) {
|
||||||
|
for (final PartRight species : PartRight.values()) {
|
||||||
|
if (species.getValue() == value) {
|
||||||
|
return species;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("PartRight: Unknown value: " + value);
|
||||||
|
}
|
||||||
|
}
|
24
back/src/org/kar/karusic/api/UserResourceModel/UserMe.java
Normal file
24
back/src/org/kar/karusic/api/UserResourceModel/UserMe.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package org.kar.karusic.api.UserResourceModel;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.kar.archidata.annotation.NoWriteSpecificMode;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@NoWriteSpecificMode
|
||||||
|
public class UserMe {
|
||||||
|
public long id;
|
||||||
|
public String login;
|
||||||
|
@Schema(description = "Map<EntityName, Map<PartName, Right>>")
|
||||||
|
public Map<String, Map<String, PartRight>> rights;
|
||||||
|
|
||||||
|
public UserMe() {}
|
||||||
|
|
||||||
|
public UserMe(final long id, final String login, final Map<String, Map<String, PartRight>> rights) {
|
||||||
|
this.id = id;
|
||||||
|
this.login = login;
|
||||||
|
this.rights = rights;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
env_dev/docker-compose.yaml
Normal file
31
env_dev/docker-compose.yaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
services:
|
||||||
|
kar_db_service:
|
||||||
|
image: mysql:latest
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=base_db_password
|
||||||
|
volumes:
|
||||||
|
- ./data:/var/lib/mysql
|
||||||
|
mem_limit: 300m
|
||||||
|
ports:
|
||||||
|
- 3906:3306
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
# perform a 1 minute grace to let the DB to perform the initialization
|
||||||
|
start_period: 1m
|
||||||
|
start_interval: 1m
|
||||||
|
|
||||||
|
kar_adminer_service:
|
||||||
|
image: adminer:latest
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
kar_db_service:
|
||||||
|
condition: service_healthy
|
||||||
|
links:
|
||||||
|
- kar_db_service:db
|
||||||
|
ports:
|
||||||
|
- 4079:8080
|
||||||
|
mem_limit: 50m
|
||||||
|
|
@ -36,4 +36,4 @@
|
|||||||
"defer"
|
"defer"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
front2/.storybook/main.ts
Normal file
27
front2/.storybook/main.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-vite',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
core: {
|
||||||
|
disableTelemetry: true,
|
||||||
|
builder: '@storybook/builder-vite',
|
||||||
|
},
|
||||||
|
|
||||||
|
stories: ['../src/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
|
||||||
|
|
||||||
|
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
|
||||||
|
|
||||||
|
staticDirs: ['../public'],
|
||||||
|
|
||||||
|
typescript: {
|
||||||
|
reactDocgen: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
docs: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
16
front2/.storybook/preview-head.html
Normal file
16
front2/.storybook/preview-head.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<style>
|
||||||
|
html {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-story > :first-child {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.docs-story > * {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root #start-ui-storybook-wrapper {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
43
front2/.storybook/preview.tsx
Normal file
43
front2/.storybook/preview.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { ChakraProvider } from '@chakra-ui/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import theme from '../src/theme';
|
||||||
|
|
||||||
|
// .storybook/preview.js
|
||||||
|
export const parameters = {
|
||||||
|
options: {
|
||||||
|
storySort: {
|
||||||
|
order: ['StyleGuide', 'Components', 'Fields', 'App Layout'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
layout: 'fullscreen',
|
||||||
|
backgrounds: { disable: true, grid: { disable: true } },
|
||||||
|
chakra: {
|
||||||
|
theme,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocumentationWrapper = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<Box id="start-ui-storybook-wrapper" p="4" pb="8" flex="1">
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decorators = [
|
||||||
|
(Story, context) => (
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
|
{/* Using MemoryRouter to avoid route clashing with Storybook */}
|
||||||
|
<MemoryRouter>
|
||||||
|
<DocumentationWrapper>
|
||||||
|
<Story {...context} />
|
||||||
|
</DocumentationWrapper>
|
||||||
|
</MemoryRouter>
|
||||||
|
</ChakraProvider>
|
||||||
|
),
|
||||||
|
];
|
2
front2/LICENSE
Normal file
2
front2/LICENSE
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Proprietary
|
||||||
|
@copyright Edouard Dupin 2024
|
6
front2/app-build.json
Normal file
6
front2/app-build.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"display": "__DEVELOPMENT__",
|
||||||
|
"version": "__VERSION__",
|
||||||
|
"commit": "__COMMIT__",
|
||||||
|
"date": "__DATE__"
|
||||||
|
}
|
25
front2/build.js
Normal file
25
front2/build.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const dayjs = require('dayjs');
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const generateAppBuild = () => {
|
||||||
|
const getVersion = () => fs.readFileSync('version.txt', 'utf8');
|
||||||
|
|
||||||
|
const commit = process.env.VERCEL_GIT_COMMIT_SHA
|
||||||
|
? process.env.VERCEL_GIT_COMMIT_SHA
|
||||||
|
: getVersion();
|
||||||
|
|
||||||
|
const appBuildContent = {
|
||||||
|
display: `${dayjs().format('YYYY-MM-DD')}`,
|
||||||
|
version: `${commit} - ${dayjs().format()}`,
|
||||||
|
commit,
|
||||||
|
date: dayjs().format(),
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
'./app-build.json',
|
||||||
|
JSON.stringify(appBuildContent, null, 2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
generateAppBuild();
|
0
front2/doc/.keep
Normal file
0
front2/doc/.keep
Normal file
2
front2/docker/.env.production
Normal file
2
front2/docker/.env.production
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# URL for database connection
|
||||||
|
VITE_API_BASE_URL=api/
|
103
front2/docker/prod.Dockerfile
Normal file
103
front2/docker/prod.Dockerfile
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
###############################################################
|
||||||
|
## 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
|
13
front2/index.html
Normal file
13
front2/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Karusic</title>
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<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>
|
18
front2/knip.ts
Normal file
18
front2/knip.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { KnipConfig } from 'knip';
|
||||||
|
|
||||||
|
const config: KnipConfig = {
|
||||||
|
// Ignoring mostly shell binaries
|
||||||
|
ignoreBinaries: ['export', 'sleep'],
|
||||||
|
ignore: [
|
||||||
|
// Related to tests
|
||||||
|
'tests/**',
|
||||||
|
'**.conf.js',
|
||||||
|
'steps.d.ts',
|
||||||
|
'steps_file.js',
|
||||||
|
'env_ci/codecept.conf.js',
|
||||||
|
// Generic components are useful.
|
||||||
|
'src/components/**',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
108
front2/package.json
Normal file
108
front2/package.json
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"name": "karusic",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "KAR web music application",
|
||||||
|
"author": {
|
||||||
|
"name": "Edouard DUPIN",
|
||||||
|
"email": "yui.heero@gmail.farm"
|
||||||
|
},
|
||||||
|
"license": "PROPRIETARY",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"update_packages": "ncu --upgrade",
|
||||||
|
"install_dependency": "pnpm install",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest watch",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"dev": "vite",
|
||||||
|
"pretty": "prettier -w .",
|
||||||
|
"lint": "pnpm tsc --noEmit",
|
||||||
|
"storybook": "storybook dev -p 3001",
|
||||||
|
"storybook:build": "storybook build && mv ./storybook-static ./public/storybook"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{ts,tsx,js,jsx,json}": "prettier --write"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/anatomy": "2.2.2",
|
||||||
|
"@chakra-ui/cli": "2.4.1",
|
||||||
|
"@chakra-ui/react": "2.8.2",
|
||||||
|
"@chakra-ui/theme-tools": "2.1.2",
|
||||||
|
"@dnd-kit/core": "6.1.0",
|
||||||
|
"@dnd-kit/modifiers": "7.0.0",
|
||||||
|
"@dnd-kit/sortable": "8.0.0",
|
||||||
|
"@dnd-kit/utilities": "3.2.2",
|
||||||
|
"@emotion/react": "11.13.0",
|
||||||
|
"@emotion/styled": "11.13.0",
|
||||||
|
"allotment": "1.20.2",
|
||||||
|
"css-mediaquery": "0.1.2",
|
||||||
|
"dayjs": "1.11.12",
|
||||||
|
"history": "5.3.0",
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-color-palette": "7.2.2",
|
||||||
|
"react-currency-input-field": "3.8.0",
|
||||||
|
"react-custom-scrollbars": "4.2.1",
|
||||||
|
"react-day-picker": "9.0.8",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
|
"react-error-boundary": "4.0.13",
|
||||||
|
"react-focus-lock": "2.12.1",
|
||||||
|
"react-icons": "5.3.0",
|
||||||
|
"react-popper": "2.3.0",
|
||||||
|
"react-router-dom": "6.26.1",
|
||||||
|
"react-select": "5.8.0",
|
||||||
|
"react-simple-keyboard": "3.7.144",
|
||||||
|
"react-sticky-el": "2.1.0",
|
||||||
|
"react-use": "17.5.1",
|
||||||
|
"react-use-draggable-scroll": "0.4.7",
|
||||||
|
"react-virtuoso": "4.10.1",
|
||||||
|
"ts-pattern": "5.3.1",
|
||||||
|
"uuid": "10.0.0",
|
||||||
|
"zod": "3.23.8",
|
||||||
|
"zustand": "4.5.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@chakra-ui/styled-system": "2.9.2",
|
||||||
|
"@playwright/test": "1.46.0",
|
||||||
|
"@storybook/addon-actions": "8.2.9",
|
||||||
|
"@storybook/addon-essentials": "8.2.9",
|
||||||
|
"@storybook/addon-links": "8.2.9",
|
||||||
|
"@storybook/addon-mdx-gfm": "8.2.9",
|
||||||
|
"@storybook/react": "8.2.9",
|
||||||
|
"@storybook/react-vite": "8.2.9",
|
||||||
|
"@storybook/theming": "8.2.9",
|
||||||
|
"@testing-library/jest-dom": "6.4.8",
|
||||||
|
"@testing-library/react": "16.0.0",
|
||||||
|
"@testing-library/user-event": "14.5.2",
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "4.3.0",
|
||||||
|
"@types/jest": "29.5.12",
|
||||||
|
"@types/node": "22.3.0",
|
||||||
|
"@types/react": "18.3.3",
|
||||||
|
"@types/react-dom": "18.3.0",
|
||||||
|
"@types/react-sticky-el": "1.0.7",
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.1.0",
|
||||||
|
"@typescript-eslint/parser": "8.1.0",
|
||||||
|
"@vitejs/plugin-react": "4.3.1",
|
||||||
|
"eslint": "9.9.0",
|
||||||
|
"eslint-plugin-codeceptjs": "1.3.0",
|
||||||
|
"eslint-plugin-import": "2.29.1",
|
||||||
|
"eslint-plugin-react": "7.35.0",
|
||||||
|
"eslint-plugin-react-hooks": "4.6.2",
|
||||||
|
"eslint-plugin-storybook": "0.8.0",
|
||||||
|
"jest": "29.7.0",
|
||||||
|
"jest-environment-jsdom": "29.7.0",
|
||||||
|
"knip": "5.27.2",
|
||||||
|
"lint-staged": "15.2.9",
|
||||||
|
"prettier": "3.3.3",
|
||||||
|
"puppeteer": "23.1.0",
|
||||||
|
"react-is": "18.3.1",
|
||||||
|
"storybook": "8.2.9",
|
||||||
|
"ts-node": "10.9.2",
|
||||||
|
"typescript": "5.5.4",
|
||||||
|
"vite": "5.4.1",
|
||||||
|
"vitest": "2.0.5",
|
||||||
|
"npm-check-updates": "^17.0.6"
|
||||||
|
}
|
||||||
|
}
|
0
front2/playwright-report/.keep
Normal file
0
front2/playwright-report/.keep
Normal file
13977
front2/pnpm-lock.yaml
generated
Normal file
13977
front2/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
front2/prettier.config.js
Normal file
16
front2/prettier.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Using a JS file, allowing us to add comments
|
||||||
|
module.exports = {
|
||||||
|
// This plugins line is mandatory for the plugin to work with pnpm.
|
||||||
|
// https://github.com/trivago/prettier-plugin-sort-imports/blob/61d069711008c530f5a41ca4e254781abc5de358/README.md?plain=1#L89-L96
|
||||||
|
plugins: ['@trivago/prettier-plugin-sort-imports'],
|
||||||
|
endOfLine: 'lf',
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
tabWidth: 2,
|
||||||
|
trailingComma: 'es5',
|
||||||
|
arrowParens: 'always',
|
||||||
|
importOrder: ['^react$', '^(?!^react$|^@/|^[./]).*', '^@/(.*)$', '^[./]'],
|
||||||
|
importOrderSeparation: true,
|
||||||
|
importOrderSortSpecifiers: true,
|
||||||
|
importOrderParserPlugins: ['jsx', 'typescript'],
|
||||||
|
};
|
BIN
front2/public/favicon.ico
Normal file
BIN
front2/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
118
front2/src/App.tsx
Normal file
118
front2/src/App.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { ChakraProvider, Select } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { environment } from '@/environment';
|
||||||
|
import { App as SpaApp } from '@/scene/App';
|
||||||
|
import { USERS } from '@/service/session';
|
||||||
|
import theme from '@/theme';
|
||||||
|
import { hashLocalData } from '@/utils/sso';
|
||||||
|
|
||||||
|
const AppEnvHint = () => {
|
||||||
|
const modal = useDisclosure();
|
||||||
|
const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER');
|
||||||
|
//const setUser = useRightsStore((store) => store.setUser);
|
||||||
|
const buildEnv =
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? 'Development'
|
||||||
|
: import.meta.env.VITE_DEV_ENV_NAME;
|
||||||
|
const envName: Array<string> = [];
|
||||||
|
!!buildEnv && envName.push(buildEnv);
|
||||||
|
if (!envName.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const handleChange = (selectedOption) => {
|
||||||
|
console.log(`SELECT: [${selectedOption.target.value}]`);
|
||||||
|
setSelectUserTest(selectedOption.target.value);
|
||||||
|
};
|
||||||
|
const onClose = () => {
|
||||||
|
modal.onClose();
|
||||||
|
if (selectUserTest == 'NO_USER') {
|
||||||
|
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`;
|
||||||
|
} else {
|
||||||
|
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/true/${USERS[selectUserTest]}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
zIndex="100000"
|
||||||
|
position="fixed"
|
||||||
|
top="0"
|
||||||
|
insetStart="0"
|
||||||
|
insetEnd="0"
|
||||||
|
h="2px"
|
||||||
|
bg="warning.400"
|
||||||
|
as="button"
|
||||||
|
cursor="pointer"
|
||||||
|
data-test-id="devtools"
|
||||||
|
onClick={modal.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"
|
||||||
|
>
|
||||||
|
{envName.join(' : ')}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Modal isOpen={modal.isOpen} onClose={modal.onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Outils développeurs</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Stack>
|
||||||
|
<Text>Utilisateur</Text>
|
||||||
|
<Select placeholder="Select test user" onChange={handleChange}>
|
||||||
|
{Object.keys(USERS).map((key) => (
|
||||||
|
<option value={key} key={key}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Stack>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onClick={onClose}>Apply</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
|
<AppEnvHint />
|
||||||
|
<SpaApp />
|
||||||
|
</ChakraProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
66
front2/src/assets/images/avatar_generic.svg
Normal file
66
front2/src/assets/images/avatar_generic.svg
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="499"
|
||||||
|
height="498"
|
||||||
|
viewBox="0 0 499 498"
|
||||||
|
version="1.1"
|
||||||
|
id="svg10"
|
||||||
|
sodipodi:docname="avatar_generic.svg"
|
||||||
|
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs14" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview12"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.5060241"
|
||||||
|
inkscape:cx="344.97766"
|
||||||
|
inkscape:cy="288.78866"
|
||||||
|
inkscape:window-width="3838"
|
||||||
|
inkscape:window-height="2118"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="20"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg10" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c83e5;fill-opacity:1;stroke:none;stroke-width:8.82841587;stroke-opacity:1"
|
||||||
|
width="500"
|
||||||
|
height="500"
|
||||||
|
x="0"
|
||||||
|
y="-1.9999847"
|
||||||
|
id="rect2" />
|
||||||
|
<path
|
||||||
|
style="fill:#4e4e4d;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
|
||||||
|
d="m 198.78572,292.40268 -11.97951,28.10434 -7.03868,-39.62542 -32.77188,20.51375 14.30166,-59.1095 -47.70972,20.8692 30.95257,-58.44261 -40.08325,-11.9709 50.99682,-31.08004 -30.32488,-33.92052 41.38608,6.88643 c 0,0 -27.42157,-58.130582 -26.08007,-58.130582 1.34149,0 54.85161,37.962212 54.85161,37.962212 l 6.10019,-52.959427 22.55992,46.026947 33.20732,-51.718401 8.75817,54.113371 53.78031,-55.134502 -14.88381,76.635492 112.00146,-17.67965 -84.26404,54.10353 65.61018,10.26713 -53.91421,37.51917 40.05564,55.00796 -51.48529,-23.57551 7.49544,56.99322 -27.79947,-24.64556 -3.80452,36.62241 -23.37869,-22.14564 -5.89389,23.82189 -20.64297,-29.48769 -15.46209,46.92367 -7.2608,-54.46889 -21.32424,51.07849 z"
|
||||||
|
id="path3162"
|
||||||
|
sodipodi:nodetypes="cccccccccccsccccccccccccccccccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#fbd0a6;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 126.90172,351.92657 55.90379,145.23242 168.77912,0.48055 8.10502,-147.83696 -56.19339,-10.73582 -9.91368,3.09728 -8.25753,-48.27446 -8.77147,-41.82541 -73.97306,-0.86753 -4.65072,84.85557 z"
|
||||||
|
id="path3467"
|
||||||
|
sodipodi:nodetypes="ccccccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 45.319305,524.24985 50.878125,-168.64351 91.02866,-21.85078 124.81002,189.43887 15.4976,-12.33208 -130.63444,-191.36234 -9.67318,14.25555 124.81002,189.43887"
|
||||||
|
id="path275"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 443.88287,524.02559 393.00474,355.38208 301.97608,333.5313 177.16606,522.97017 161.66846,510.63809 292.3029,319.27575 301.97608,333.5313 177.16606,522.97017"
|
||||||
|
id="path275-6"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#fbd0a6;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 187.52213,139.10279 14.34593,25.22547 9.57434,-32.06638 13.85516,33.79118 18.44245,-38.89028 4.92331,44.11511 20.28515,-38.50102 -2.21466,39.35905 31.27764,-28.90273 -5.47875,45.83312 23.27252,-10.25342 -8.67174,30.59353 c 24.86464,-33.77835 23.21015,12.27629 3.94365,35.3922 l -12.95127,26.98572 -12.80079,22.10524 -26.61623,18.28625 -53.44338,-20.79546 c -21.13665,-20.42844 -23.443,-25.48798 -31.95313,-51.61993 -19.36867,-32.18928 -17.20493,-59.66994 4.27858,-38.00437 l -2.30548,-27.96686 11.07502,10.22035 -14.60569,-33.15484 22.1057,18.70679 z"
|
||||||
|
id="path9531"
|
||||||
|
sodipodi:nodetypes="cccccccccccccccccccccccc" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
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 |
250
front2/src/back-api/api/album-resource.ts
Normal file
250
front2/src/back-api/api/album-resource.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTCallbacks,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
RESTRequestVoid,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import { z as zod } from "zod"
|
||||||
|
import {
|
||||||
|
Album,
|
||||||
|
AlbumWrite,
|
||||||
|
Long,
|
||||||
|
UUID,
|
||||||
|
ZodAlbum,
|
||||||
|
isAlbum,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace AlbumResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Track on a specific album
|
||||||
|
*/
|
||||||
|
export function addTrack({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
trackId: Long,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Album> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/{id}/track/{trackId}",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isAlbum);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Get a specific Album with his ID
|
||||||
|
*/
|
||||||
|
export function get({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Album> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/{id}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isAlbum);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZodGetsTypeReturn = zod.array(ZodAlbum);
|
||||||
|
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
|
||||||
|
|
||||||
|
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
|
||||||
|
try {
|
||||||
|
ZodGetsTypeReturn.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the available Albums
|
||||||
|
*/
|
||||||
|
export function gets({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<GetsTypeReturn> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isGetsTypeReturn);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Update a specific album
|
||||||
|
*/
|
||||||
|
export function patch({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: AlbumWrite,
|
||||||
|
}): Promise<Album> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/{id}",
|
||||||
|
requestType: HTTPRequestModel.PATCH,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}, isAlbum);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Add an album (when all the data already exist)
|
||||||
|
*/
|
||||||
|
export function post({
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
data: AlbumWrite,
|
||||||
|
}): Promise<Album> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}, isAlbum);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Remove a specific album
|
||||||
|
*/
|
||||||
|
export function remove({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<void> {
|
||||||
|
return RESTRequestVoid({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/{id}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Remove a cover on a specific album
|
||||||
|
*/
|
||||||
|
export function removeCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
coverId: UUID,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Album> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/{id}/cover/{coverId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isAlbum);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Remove a Track on a specific album
|
||||||
|
*/
|
||||||
|
export function removeTrack({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
trackId: Long,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Album> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/{id}/track/{trackId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isAlbum);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Add a cover on a specific album
|
||||||
|
*/
|
||||||
|
export function uploadCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
file: File,
|
||||||
|
},
|
||||||
|
callbacks?: RESTCallbacks,
|
||||||
|
}): Promise<Album> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/album/{id}/cover",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}, isAlbum);
|
||||||
|
};
|
||||||
|
}
|
181
front2/src/back-api/api/artist-resource.ts
Normal file
181
front2/src/back-api/api/artist-resource.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTCallbacks,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
RESTRequestVoid,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import { z as zod } from "zod"
|
||||||
|
import {
|
||||||
|
Artist,
|
||||||
|
ArtistWrite,
|
||||||
|
Long,
|
||||||
|
UUID,
|
||||||
|
ZodArtist,
|
||||||
|
isArtist,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace ArtistResource {
|
||||||
|
|
||||||
|
export function get({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Artist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/artist/{id}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isArtist);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZodGetsTypeReturn = zod.array(ZodArtist);
|
||||||
|
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
|
||||||
|
|
||||||
|
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
|
||||||
|
try {
|
||||||
|
ZodGetsTypeReturn.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gets({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<GetsTypeReturn> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/artist/",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isGetsTypeReturn);
|
||||||
|
};
|
||||||
|
export function patch({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: ArtistWrite,
|
||||||
|
}): Promise<Artist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/artist/{id}",
|
||||||
|
requestType: HTTPRequestModel.PATCH,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}, isArtist);
|
||||||
|
};
|
||||||
|
export function post({
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
data: ArtistWrite,
|
||||||
|
}): Promise<Artist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/artist/",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}, isArtist);
|
||||||
|
};
|
||||||
|
export function remove({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<void> {
|
||||||
|
return RESTRequestVoid({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/artist/{id}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export function removeCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
coverId: UUID,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Artist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/artist/{id}/cover/{coverId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isArtist);
|
||||||
|
};
|
||||||
|
export function uploadCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
file: File,
|
||||||
|
},
|
||||||
|
callbacks?: RESTCallbacks,
|
||||||
|
}): Promise<Artist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/artist/{id}/cover",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}, isArtist);
|
||||||
|
};
|
||||||
|
}
|
128
front2/src/back-api/api/data-resource.ts
Normal file
128
front2/src/back-api/api/data-resource.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
RESTRequestVoid,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import {
|
||||||
|
UUID,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace DataResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get back some data from the data environment (with a beautiful name (permit download with basic name)
|
||||||
|
*/
|
||||||
|
export function retrieveDataFull({
|
||||||
|
restConfig,
|
||||||
|
queries,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
queries: {
|
||||||
|
Authorization?: string,
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
name: string,
|
||||||
|
uuid: UUID,
|
||||||
|
},
|
||||||
|
data: string,
|
||||||
|
}): Promise<object> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/data/{uuid}/{name}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
queries,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Get back some data from the data environment
|
||||||
|
*/
|
||||||
|
export function retrieveDataId({
|
||||||
|
restConfig,
|
||||||
|
queries,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
queries: {
|
||||||
|
Authorization?: string,
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
uuid: UUID,
|
||||||
|
},
|
||||||
|
data: string,
|
||||||
|
}): Promise<object> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/data/{uuid}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
queries,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Get a thumbnail of from the data environment (if resize is possible)
|
||||||
|
*/
|
||||||
|
export function retrieveDataThumbnailId({
|
||||||
|
restConfig,
|
||||||
|
queries,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
queries: {
|
||||||
|
Authorization?: string,
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
uuid: UUID,
|
||||||
|
},
|
||||||
|
data: string,
|
||||||
|
}): Promise<object> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/data/thumbnail/{uuid}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
queries,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Insert a new data in the data environment
|
||||||
|
*/
|
||||||
|
export function uploadFile({
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
data: {
|
||||||
|
file: File,
|
||||||
|
},
|
||||||
|
}): Promise<void> {
|
||||||
|
return RESTRequestVoid({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/data//upload/",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
6
front2/src/back-api/api/front.ts
Normal file
6
front2/src/back-api/api/front.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
export namespace Front {
|
||||||
|
|
||||||
|
}
|
181
front2/src/back-api/api/gender-resource.ts
Normal file
181
front2/src/back-api/api/gender-resource.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTCallbacks,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
RESTRequestVoid,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import { z as zod } from "zod"
|
||||||
|
import {
|
||||||
|
Gender,
|
||||||
|
GenderWrite,
|
||||||
|
Long,
|
||||||
|
UUID,
|
||||||
|
ZodGender,
|
||||||
|
isGender,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace GenderResource {
|
||||||
|
|
||||||
|
export function get({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Gender> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/gender/{id}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isGender);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZodGetsTypeReturn = zod.array(ZodGender);
|
||||||
|
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
|
||||||
|
|
||||||
|
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
|
||||||
|
try {
|
||||||
|
ZodGetsTypeReturn.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gets({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<GetsTypeReturn> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/gender/",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isGetsTypeReturn);
|
||||||
|
};
|
||||||
|
export function patch({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: GenderWrite,
|
||||||
|
}): Promise<Gender> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/gender/{id}",
|
||||||
|
requestType: HTTPRequestModel.PATCH,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}, isGender);
|
||||||
|
};
|
||||||
|
export function post({
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
data: GenderWrite,
|
||||||
|
}): Promise<Gender> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/gender/",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}, isGender);
|
||||||
|
};
|
||||||
|
export function remove({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<void> {
|
||||||
|
return RESTRequestVoid({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/gender/{id}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export function removeCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
coverId: UUID,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Gender> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/gender/{id}/cover/{coverId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isGender);
|
||||||
|
};
|
||||||
|
export function uploadCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
file: File,
|
||||||
|
},
|
||||||
|
callbacks?: RESTCallbacks,
|
||||||
|
}): Promise<Gender> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/gender/{id}/cover",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}, isGender);
|
||||||
|
};
|
||||||
|
}
|
32
front2/src/back-api/api/health-check.ts
Normal file
32
front2/src/back-api/api/health-check.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import {
|
||||||
|
HealthResult,
|
||||||
|
isHealthResult,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace HealthCheck {
|
||||||
|
|
||||||
|
export function getHealth({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<HealthResult> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/health_check/",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isHealthResult);
|
||||||
|
};
|
||||||
|
}
|
12
front2/src/back-api/api/index.ts
Normal file
12
front2/src/back-api/api/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
export * from "./album-resource"
|
||||||
|
export * from "./artist-resource"
|
||||||
|
export * from "./data-resource"
|
||||||
|
export * from "./front"
|
||||||
|
export * from "./gender-resource"
|
||||||
|
export * from "./health-check"
|
||||||
|
export * from "./playlist-resource"
|
||||||
|
export * from "./track-resource"
|
||||||
|
export * from "./user-resource"
|
219
front2/src/back-api/api/playlist-resource.ts
Normal file
219
front2/src/back-api/api/playlist-resource.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
RESTRequestVoid,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import { z as zod } from "zod"
|
||||||
|
import {
|
||||||
|
Long,
|
||||||
|
Playlist,
|
||||||
|
PlaylistWrite,
|
||||||
|
UUID,
|
||||||
|
ZodPlaylist,
|
||||||
|
isPlaylist,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace PlaylistResource {
|
||||||
|
|
||||||
|
export function addTrack({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
trackId: Long,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Playlist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/{id}/track/{trackId}",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isPlaylist);
|
||||||
|
};
|
||||||
|
export function get({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Playlist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/{id}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isPlaylist);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZodGetsTypeReturn = zod.array(ZodPlaylist);
|
||||||
|
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
|
||||||
|
|
||||||
|
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
|
||||||
|
try {
|
||||||
|
ZodGetsTypeReturn.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gets({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<GetsTypeReturn> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isGetsTypeReturn);
|
||||||
|
};
|
||||||
|
export function patch({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: PlaylistWrite,
|
||||||
|
}): Promise<Playlist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/{id}",
|
||||||
|
requestType: HTTPRequestModel.PATCH,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}, isPlaylist);
|
||||||
|
};
|
||||||
|
export function post({
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
data: PlaylistWrite,
|
||||||
|
}): Promise<Playlist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}, isPlaylist);
|
||||||
|
};
|
||||||
|
export function remove({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<void> {
|
||||||
|
return RESTRequestVoid({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/{id}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export function removeCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
coverId: UUID,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Playlist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/{id}/cover/{coverId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isPlaylist);
|
||||||
|
};
|
||||||
|
export function removeTrack({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
trackId: Long,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Playlist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/{id}/track/{trackId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isPlaylist);
|
||||||
|
};
|
||||||
|
export function uploadCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
file: File,
|
||||||
|
},
|
||||||
|
}): Promise<Playlist> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/playlist/{id}/cover",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}, isPlaylist);
|
||||||
|
};
|
||||||
|
}
|
252
front2/src/back-api/api/track-resource.ts
Normal file
252
front2/src/back-api/api/track-resource.ts
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTCallbacks,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
RESTRequestVoid,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import { z as zod } from "zod"
|
||||||
|
import {
|
||||||
|
Long,
|
||||||
|
Track,
|
||||||
|
TrackWrite,
|
||||||
|
UUID,
|
||||||
|
ZodTrack,
|
||||||
|
isTrack,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace TrackResource {
|
||||||
|
|
||||||
|
export function addTrack({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
artistId: Long,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/{id}/artist/{artistId}",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
export function get({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/{id}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZodGetsTypeReturn = zod.array(ZodTrack);
|
||||||
|
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
|
||||||
|
|
||||||
|
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
|
||||||
|
try {
|
||||||
|
ZodGetsTypeReturn.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gets({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<GetsTypeReturn> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isGetsTypeReturn);
|
||||||
|
};
|
||||||
|
export function patch({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: TrackWrite,
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/{id}",
|
||||||
|
requestType: HTTPRequestModel.PATCH,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
export function post({
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
data: TrackWrite,
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.JSON,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
export function remove({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<void> {
|
||||||
|
return RESTRequestVoid({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/{id}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export function removeCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
coverId: UUID,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/{id}/cover/{coverId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
export function removeTrack({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
artistId: Long,
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/{id}/artist/{trackId}",
|
||||||
|
requestType: HTTPRequestModel.DELETE,
|
||||||
|
contentType: HTTPMimeType.TEXT_PLAIN,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
export function uploadCover({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
file: File,
|
||||||
|
},
|
||||||
|
callbacks?: RESTCallbacks,
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/{id}/cover",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
export function uploadTrack({
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
data: {
|
||||||
|
fileName: string,
|
||||||
|
file: File,
|
||||||
|
gender: string,
|
||||||
|
artist: string,
|
||||||
|
album: string,
|
||||||
|
trackId: Long,
|
||||||
|
title: string,
|
||||||
|
},
|
||||||
|
callbacks?: RESTCallbacks,
|
||||||
|
}): Promise<Track> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/track/upload/",
|
||||||
|
requestType: HTTPRequestModel.POST,
|
||||||
|
contentType: HTTPMimeType.MULTIPART,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
callbacks,
|
||||||
|
}, isTrack);
|
||||||
|
};
|
||||||
|
}
|
84
front2/src/back-api/api/user-resource.ts
Normal file
84
front2/src/back-api/api/user-resource.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
HTTPMimeType,
|
||||||
|
HTTPRequestModel,
|
||||||
|
RESTConfig,
|
||||||
|
RESTRequestJson,
|
||||||
|
} from "../rest-tools";
|
||||||
|
|
||||||
|
import { z as zod } from "zod"
|
||||||
|
import {
|
||||||
|
Long,
|
||||||
|
UserKarusic,
|
||||||
|
UserMe,
|
||||||
|
ZodUserKarusic,
|
||||||
|
isUserKarusic,
|
||||||
|
isUserMe,
|
||||||
|
} from "../model";
|
||||||
|
|
||||||
|
export namespace UserResource {
|
||||||
|
|
||||||
|
export function get({
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
params: {
|
||||||
|
id: Long,
|
||||||
|
},
|
||||||
|
}): Promise<UserKarusic> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/users/{id}",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
}, isUserKarusic);
|
||||||
|
};
|
||||||
|
export function getMe({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<UserMe> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/users/me",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isUserMe);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZodGetsTypeReturn = zod.array(ZodUserKarusic);
|
||||||
|
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
|
||||||
|
|
||||||
|
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
|
||||||
|
try {
|
||||||
|
ZodGetsTypeReturn.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gets({
|
||||||
|
restConfig,
|
||||||
|
}: {
|
||||||
|
restConfig: RESTConfig,
|
||||||
|
}): Promise<GetsTypeReturn> {
|
||||||
|
return RESTRequestJson({
|
||||||
|
restModel: {
|
||||||
|
endPoint: "/users/",
|
||||||
|
requestType: HTTPRequestModel.GET,
|
||||||
|
accept: HTTPMimeType.JSON,
|
||||||
|
},
|
||||||
|
restConfig,
|
||||||
|
}, isGetsTypeReturn);
|
||||||
|
};
|
||||||
|
}
|
7
front2/src/back-api/index.ts
Normal file
7
front2/src/back-api/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
export * from "./model";
|
||||||
|
export * from "./api";
|
||||||
|
export * from "./rest-tools";
|
||||||
|
|
53
front2/src/back-api/model/album.ts
Normal file
53
front2/src/back-api/model/album.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodUUID} from "./uuid";
|
||||||
|
import {ZodLocalDate} from "./local-date";
|
||||||
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
|
export const ZodAlbum = ZodGenericDataSoftDelete.extend({
|
||||||
|
name: zod.string().max(256).optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).optional(),
|
||||||
|
publication: ZodLocalDate.optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Album = zod.infer<typeof ZodAlbum>;
|
||||||
|
|
||||||
|
export function isAlbum(data: any): data is Album {
|
||||||
|
try {
|
||||||
|
ZodAlbum.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodAlbum' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodAlbumWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
|
name: zod.string().max(256).nullable().optional(),
|
||||||
|
description: zod.string().nullable().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).nullable().optional(),
|
||||||
|
publication: ZodLocalDate.nullable().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AlbumWrite = zod.infer<typeof ZodAlbumWrite>;
|
||||||
|
|
||||||
|
export function isAlbumWrite(data: any): data is AlbumWrite {
|
||||||
|
try {
|
||||||
|
ZodAlbumWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodAlbumWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
59
front2/src/back-api/model/artist.ts
Normal file
59
front2/src/back-api/model/artist.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodUUID} from "./uuid";
|
||||||
|
import {ZodLocalDate} from "./local-date";
|
||||||
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
|
export const ZodArtist = ZodGenericDataSoftDelete.extend({
|
||||||
|
name: zod.string().max(256).optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).optional(),
|
||||||
|
firstName: zod.string().max(256).optional(),
|
||||||
|
surname: zod.string().max(256).optional(),
|
||||||
|
birth: ZodLocalDate.optional(),
|
||||||
|
death: ZodLocalDate.optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Artist = zod.infer<typeof ZodArtist>;
|
||||||
|
|
||||||
|
export function isArtist(data: any): data is Artist {
|
||||||
|
try {
|
||||||
|
ZodArtist.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodArtist' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodArtistWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
|
name: zod.string().max(256).nullable().optional(),
|
||||||
|
description: zod.string().nullable().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).nullable().optional(),
|
||||||
|
firstName: zod.string().max(256).nullable().optional(),
|
||||||
|
surname: zod.string().max(256).nullable().optional(),
|
||||||
|
birth: ZodLocalDate.nullable().optional(),
|
||||||
|
death: ZodLocalDate.nullable().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ArtistWrite = zod.infer<typeof ZodArtistWrite>;
|
||||||
|
|
||||||
|
export function isArtistWrite(data: any): data is ArtistWrite {
|
||||||
|
try {
|
||||||
|
ZodArtistWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodArtistWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
50
front2/src/back-api/model/gender.ts
Normal file
50
front2/src/back-api/model/gender.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodUUID} from "./uuid";
|
||||||
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
|
export const ZodGender = ZodGenericDataSoftDelete.extend({
|
||||||
|
name: zod.string().max(256).optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Gender = zod.infer<typeof ZodGender>;
|
||||||
|
|
||||||
|
export function isGender(data: any): data is Gender {
|
||||||
|
try {
|
||||||
|
ZodGender.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGender' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodGenderWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
|
name: zod.string().max(256).nullable().optional(),
|
||||||
|
description: zod.string().nullable().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).nullable().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GenderWrite = zod.infer<typeof ZodGenderWrite>;
|
||||||
|
|
||||||
|
export function isGenderWrite(data: any): data is GenderWrite {
|
||||||
|
try {
|
||||||
|
ZodGenderWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGenderWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
41
front2/src/back-api/model/generic-data-soft-delete.ts
Normal file
41
front2/src/back-api/model/generic-data-soft-delete.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodGenericData, ZodGenericDataWrite } from "./generic-data";
|
||||||
|
|
||||||
|
export const ZodGenericDataSoftDelete = ZodGenericData.extend({
|
||||||
|
/**
|
||||||
|
* Deleted state
|
||||||
|
*/
|
||||||
|
deleted: zod.boolean().readonly().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GenericDataSoftDelete = zod.infer<typeof ZodGenericDataSoftDelete>;
|
||||||
|
|
||||||
|
export function isGenericDataSoftDelete(data: any): data is GenericDataSoftDelete {
|
||||||
|
try {
|
||||||
|
ZodGenericDataSoftDelete.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGenericDataSoftDelete' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodGenericDataSoftDeleteWrite = ZodGenericDataWrite.extend({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GenericDataSoftDeleteWrite = zod.infer<typeof ZodGenericDataSoftDeleteWrite>;
|
||||||
|
|
||||||
|
export function isGenericDataSoftDeleteWrite(data: any): data is GenericDataSoftDeleteWrite {
|
||||||
|
try {
|
||||||
|
ZodGenericDataSoftDeleteWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGenericDataSoftDeleteWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
42
front2/src/back-api/model/generic-data.ts
Normal file
42
front2/src/back-api/model/generic-data.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodLong} from "./long";
|
||||||
|
import {ZodGenericTiming, ZodGenericTimingWrite } from "./generic-timing";
|
||||||
|
|
||||||
|
export const ZodGenericData = ZodGenericTiming.extend({
|
||||||
|
/**
|
||||||
|
* Unique Id of the object
|
||||||
|
*/
|
||||||
|
id: ZodLong.readonly(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GenericData = zod.infer<typeof ZodGenericData>;
|
||||||
|
|
||||||
|
export function isGenericData(data: any): data is GenericData {
|
||||||
|
try {
|
||||||
|
ZodGenericData.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGenericData' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodGenericDataWrite = ZodGenericTimingWrite.extend({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GenericDataWrite = zod.infer<typeof ZodGenericDataWrite>;
|
||||||
|
|
||||||
|
export function isGenericDataWrite(data: any): data is GenericDataWrite {
|
||||||
|
try {
|
||||||
|
ZodGenericDataWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGenericDataWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
45
front2/src/back-api/model/generic-timing.ts
Normal file
45
front2/src/back-api/model/generic-timing.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodIsoDate} from "./iso-date";
|
||||||
|
|
||||||
|
export const ZodGenericTiming = zod.object({
|
||||||
|
/**
|
||||||
|
* Create time of the object
|
||||||
|
*/
|
||||||
|
createdAt: ZodIsoDate.readonly().optional(),
|
||||||
|
/**
|
||||||
|
* When update the object
|
||||||
|
*/
|
||||||
|
updatedAt: ZodIsoDate.readonly().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GenericTiming = zod.infer<typeof ZodGenericTiming>;
|
||||||
|
|
||||||
|
export function isGenericTiming(data: any): data is GenericTiming {
|
||||||
|
try {
|
||||||
|
ZodGenericTiming.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGenericTiming' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodGenericTimingWrite = zod.object({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GenericTimingWrite = zod.infer<typeof ZodGenericTimingWrite>;
|
||||||
|
|
||||||
|
export function isGenericTimingWrite(data: any): data is GenericTimingWrite {
|
||||||
|
try {
|
||||||
|
ZodGenericTimingWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodGenericTimingWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
36
front2/src/back-api/model/health-result.ts
Normal file
36
front2/src/back-api/model/health-result.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export const ZodHealthResult = zod.object({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type HealthResult = zod.infer<typeof ZodHealthResult>;
|
||||||
|
|
||||||
|
export function isHealthResult(data: any): data is HealthResult {
|
||||||
|
try {
|
||||||
|
ZodHealthResult.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodHealthResult' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodHealthResultWrite = zod.object({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type HealthResultWrite = zod.infer<typeof ZodHealthResultWrite>;
|
||||||
|
|
||||||
|
export function isHealthResultWrite(data: any): data is HealthResultWrite {
|
||||||
|
try {
|
||||||
|
ZodHealthResultWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodHealthResultWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
23
front2/src/back-api/model/index.ts
Normal file
23
front2/src/back-api/model/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
export * from "./album"
|
||||||
|
export * from "./artist"
|
||||||
|
export * from "./gender"
|
||||||
|
export * from "./generic-data"
|
||||||
|
export * from "./generic-data-soft-delete"
|
||||||
|
export * from "./generic-timing"
|
||||||
|
export * from "./health-result"
|
||||||
|
export * from "./int"
|
||||||
|
export * from "./iso-date"
|
||||||
|
export * from "./local-date"
|
||||||
|
export * from "./long"
|
||||||
|
export * from "./part-right"
|
||||||
|
export * from "./playlist"
|
||||||
|
export * from "./rest-error-response"
|
||||||
|
export * from "./timestamp"
|
||||||
|
export * from "./track"
|
||||||
|
export * from "./user"
|
||||||
|
export * from "./user-karusic"
|
||||||
|
export * from "./user-me"
|
||||||
|
export * from "./uuid"
|
36
front2/src/back-api/model/int.ts
Normal file
36
front2/src/back-api/model/int.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export const Zodint = zod.object({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type int = zod.infer<typeof Zodint>;
|
||||||
|
|
||||||
|
export function isint(data: any): data is int {
|
||||||
|
try {
|
||||||
|
Zodint.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='Zodint' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodintWrite = zod.object({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type intWrite = zod.infer<typeof ZodintWrite>;
|
||||||
|
|
||||||
|
export function isintWrite(data: any): data is intWrite {
|
||||||
|
try {
|
||||||
|
ZodintWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodintWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
8
front2/src/back-api/model/iso-date.ts
Normal file
8
front2/src/back-api/model/iso-date.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export const ZodIsoDate = zod.string().datetime({ precision: 3 });
|
||||||
|
export type IsoDate = zod.infer<typeof ZodIsoDate>;
|
8
front2/src/back-api/model/local-date.ts
Normal file
8
front2/src/back-api/model/local-date.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export const ZodLocalDate = zod.string().date();
|
||||||
|
export type LocalDate = zod.infer<typeof ZodLocalDate>;
|
8
front2/src/back-api/model/long.ts
Normal file
8
front2/src/back-api/model/long.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export const ZodLong = zod.number();
|
||||||
|
export type Long = zod.infer<typeof ZodLong>;
|
23
front2/src/back-api/model/part-right.ts
Normal file
23
front2/src/back-api/model/part-right.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export enum PartRight {
|
||||||
|
READ = 1,
|
||||||
|
WRITE = 2,
|
||||||
|
READ_WRITE = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZodPartRight = zod.nativeEnum(PartRight);
|
||||||
|
|
||||||
|
export function isPartRight(data: any): data is PartRight {
|
||||||
|
try {
|
||||||
|
ZodPartRight.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodPartRight' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
53
front2/src/back-api/model/playlist.ts
Normal file
53
front2/src/back-api/model/playlist.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodUUID} from "./uuid";
|
||||||
|
import {ZodLong} from "./long";
|
||||||
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
|
export const ZodPlaylist = ZodGenericDataSoftDelete.extend({
|
||||||
|
name: zod.string().max(256).optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).optional(),
|
||||||
|
tracks: zod.array(ZodLong),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Playlist = zod.infer<typeof ZodPlaylist>;
|
||||||
|
|
||||||
|
export function isPlaylist(data: any): data is Playlist {
|
||||||
|
try {
|
||||||
|
ZodPlaylist.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodPlaylist' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodPlaylistWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
|
name: zod.string().max(256).nullable().optional(),
|
||||||
|
description: zod.string().nullable().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).nullable().optional(),
|
||||||
|
tracks: zod.array(ZodLong).optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PlaylistWrite = zod.infer<typeof ZodPlaylistWrite>;
|
||||||
|
|
||||||
|
export function isPlaylistWrite(data: any): data is PlaylistWrite {
|
||||||
|
try {
|
||||||
|
ZodPlaylistWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodPlaylistWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
29
front2/src/back-api/model/rest-error-response.ts
Normal file
29
front2/src/back-api/model/rest-error-response.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodUUID} from "./uuid";
|
||||||
|
import {Zodint, ZodintWrite } from "./int";
|
||||||
|
|
||||||
|
export const ZodRestErrorResponse = zod.object({
|
||||||
|
uuid: ZodUUID.optional(),
|
||||||
|
name: zod.string(),
|
||||||
|
message: zod.string(),
|
||||||
|
time: zod.string(),
|
||||||
|
status: Zodint,
|
||||||
|
statusMessage: zod.string(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RestErrorResponse = zod.infer<typeof ZodRestErrorResponse>;
|
||||||
|
|
||||||
|
export function isRestErrorResponse(data: any): data is RestErrorResponse {
|
||||||
|
try {
|
||||||
|
ZodRestErrorResponse.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodRestErrorResponse' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
8
front2/src/back-api/model/timestamp.ts
Normal file
8
front2/src/back-api/model/timestamp.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export const ZodTimestamp = zod.string().datetime({ precision: 3 });
|
||||||
|
export type Timestamp = zod.infer<typeof ZodTimestamp>;
|
61
front2/src/back-api/model/track.ts
Normal file
61
front2/src/back-api/model/track.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodUUID} from "./uuid";
|
||||||
|
import {ZodLong} from "./long";
|
||||||
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
|
export const ZodTrack = ZodGenericDataSoftDelete.extend({
|
||||||
|
name: zod.string().max(256).optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).optional(),
|
||||||
|
genderId: ZodLong.optional(),
|
||||||
|
albumId: ZodLong.optional(),
|
||||||
|
track: ZodLong.optional(),
|
||||||
|
dataId: ZodUUID.optional(),
|
||||||
|
artists: zod.array(ZodLong),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Track = zod.infer<typeof ZodTrack>;
|
||||||
|
|
||||||
|
export function isTrack(data: any): data is Track {
|
||||||
|
try {
|
||||||
|
ZodTrack.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodTrack' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodTrackWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
|
name: zod.string().max(256).nullable().optional(),
|
||||||
|
description: zod.string().nullable().optional(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).nullable().optional(),
|
||||||
|
genderId: ZodLong.nullable().optional(),
|
||||||
|
albumId: ZodLong.nullable().optional(),
|
||||||
|
track: ZodLong.nullable().optional(),
|
||||||
|
dataId: ZodUUID.nullable().optional(),
|
||||||
|
artists: zod.array(ZodLong).optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TrackWrite = zod.infer<typeof ZodTrackWrite>;
|
||||||
|
|
||||||
|
export function isTrackWrite(data: any): data is TrackWrite {
|
||||||
|
try {
|
||||||
|
ZodTrackWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodTrackWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
37
front2/src/back-api/model/user-karusic.ts
Normal file
37
front2/src/back-api/model/user-karusic.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodUser, ZodUserWrite } from "./user";
|
||||||
|
|
||||||
|
export const ZodUserKarusic = ZodUser.extend({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UserKarusic = zod.infer<typeof ZodUserKarusic>;
|
||||||
|
|
||||||
|
export function isUserKarusic(data: any): data is UserKarusic {
|
||||||
|
try {
|
||||||
|
ZodUserKarusic.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodUserKarusic' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodUserKarusicWrite = ZodUserWrite.extend({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UserKarusicWrite = zod.infer<typeof ZodUserKarusicWrite>;
|
||||||
|
|
||||||
|
export function isUserKarusicWrite(data: any): data is UserKarusicWrite {
|
||||||
|
try {
|
||||||
|
ZodUserKarusicWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodUserKarusicWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
29
front2/src/back-api/model/user-me.ts
Normal file
29
front2/src/back-api/model/user-me.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodLong} from "./long";
|
||||||
|
import {ZodPartRight} from "./part-right";
|
||||||
|
|
||||||
|
export const ZodUserMe = zod.object({
|
||||||
|
id: ZodLong,
|
||||||
|
login: zod.string().max(255).optional(),
|
||||||
|
/**
|
||||||
|
* Map<EntityName, Map<PartName, Right>>
|
||||||
|
*/
|
||||||
|
rights: zod.record(zod.string(), zod.record(zod.string(), ZodPartRight)),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UserMe = zod.infer<typeof ZodUserMe>;
|
||||||
|
|
||||||
|
export function isUserMe(data: any): data is UserMe {
|
||||||
|
try {
|
||||||
|
ZodUserMe.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodUserMe' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
41
front2/src/back-api/model/user-out.ts
Normal file
41
front2/src/back-api/model/user-out.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodLong} from "./long";
|
||||||
|
|
||||||
|
export const ZodUserOut = zod.object({
|
||||||
|
id: ZodLong,
|
||||||
|
login: zod.string().max(255).optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UserOut = zod.infer<typeof ZodUserOut>;
|
||||||
|
|
||||||
|
export function isUserOut(data: any): data is UserOut {
|
||||||
|
try {
|
||||||
|
ZodUserOut.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodUserOut' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodUserOutWrite = zod.object({
|
||||||
|
id: ZodLong,
|
||||||
|
login: zod.string().max(255).nullable().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UserOutWrite = zod.infer<typeof ZodUserOutWrite>;
|
||||||
|
|
||||||
|
export function isUserOutWrite(data: any): data is UserOutWrite {
|
||||||
|
try {
|
||||||
|
ZodUserOutWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodUserOutWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
57
front2/src/back-api/model/user.ts
Normal file
57
front2/src/back-api/model/user.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
import {ZodTimestamp} from "./timestamp";
|
||||||
|
import {ZodUUID} from "./uuid";
|
||||||
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
|
export const ZodUser = ZodGenericDataSoftDelete.extend({
|
||||||
|
login: zod.string().max(128).optional(),
|
||||||
|
lastConnection: ZodTimestamp.optional(),
|
||||||
|
admin: zod.boolean(),
|
||||||
|
blocked: zod.boolean(),
|
||||||
|
removed: zod.boolean(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type User = zod.infer<typeof ZodUser>;
|
||||||
|
|
||||||
|
export function isUser(data: any): data is User {
|
||||||
|
try {
|
||||||
|
ZodUser.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodUser' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const ZodUserWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
|
login: zod.string().max(128).nullable().optional(),
|
||||||
|
lastConnection: ZodTimestamp.nullable().optional(),
|
||||||
|
admin: zod.boolean(),
|
||||||
|
blocked: zod.boolean(),
|
||||||
|
removed: zod.boolean(),
|
||||||
|
/**
|
||||||
|
* List of Id of the specific covers
|
||||||
|
*/
|
||||||
|
covers: zod.array(ZodUUID).nullable().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UserWrite = zod.infer<typeof ZodUserWrite>;
|
||||||
|
|
||||||
|
export function isUserWrite(data: any): data is UserWrite {
|
||||||
|
try {
|
||||||
|
ZodUserWrite.parse(data);
|
||||||
|
return true;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`Fail to parse data type='ZodUserWrite' error=${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
8
front2/src/back-api/model/uuid.ts
Normal file
8
front2/src/back-api/model/uuid.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Interface of the server (auto-generated code)
|
||||||
|
*/
|
||||||
|
import { z as zod } from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export const ZodUUID = zod.string().uuid();
|
||||||
|
export type UUID = zod.infer<typeof ZodUUID>;
|
445
front2/src/back-api/rest-tools.ts
Normal file
445
front2/src/back-api/rest-tools.ts
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
/** @file
|
||||||
|
* @author Edouard DUPIN
|
||||||
|
* @copyright 2024, Edouard DUPIN, all right reserved
|
||||||
|
* @license MPL-2
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RestErrorResponse, isRestErrorResponse } from "./model";
|
||||||
|
|
||||||
|
export enum HTTPRequestModel {
|
||||||
|
DELETE = "DELETE",
|
||||||
|
GET = "GET",
|
||||||
|
PATCH = "PATCH",
|
||||||
|
POST = "POST",
|
||||||
|
PUT = "PUT",
|
||||||
|
}
|
||||||
|
export enum HTTPMimeType {
|
||||||
|
ALL = "*/*",
|
||||||
|
CSV = "text/csv",
|
||||||
|
IMAGE = "image/*",
|
||||||
|
IMAGE_JPEG = "image/jpeg",
|
||||||
|
IMAGE_PNG = "image/png",
|
||||||
|
JSON = "application/json",
|
||||||
|
MULTIPART = "multipart/form-data",
|
||||||
|
OCTET_STREAM = "application/octet-stream",
|
||||||
|
TEXT_PLAIN = "text/plain",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTConfig {
|
||||||
|
// base of the server: http(s)://my.server.org/plop/api/
|
||||||
|
server: string;
|
||||||
|
// Token to access of the data.
|
||||||
|
token?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTModel {
|
||||||
|
// base of the local API request: "sheep/{id}".
|
||||||
|
endPoint: string;
|
||||||
|
// Type of the request.
|
||||||
|
requestType?: HTTPRequestModel;
|
||||||
|
// Input type requested.
|
||||||
|
accept?: HTTPMimeType;
|
||||||
|
// Content of the local data.
|
||||||
|
contentType?: HTTPMimeType;
|
||||||
|
// Mode of the TOKEN in URL or Header (?token:${tokenInUrl})
|
||||||
|
tokenInUrl?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelResponseHttp {
|
||||||
|
status: number;
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNullOrUndefined(data: any): data is undefined | null {
|
||||||
|
return data === undefined || data === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generic progression callback
|
||||||
|
export type ProgressCallback = (count: number, total: number) => void;
|
||||||
|
|
||||||
|
export interface RESTAbort {
|
||||||
|
abort?: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rest generic callback have a basic model to upload and download advancement.
|
||||||
|
export interface RESTCallbacks {
|
||||||
|
progressUpload?: ProgressCallback;
|
||||||
|
progressDownload?: ProgressCallback;
|
||||||
|
abortHandle?: RESTAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTRequestType {
|
||||||
|
restModel: RESTModel;
|
||||||
|
restConfig: RESTConfig;
|
||||||
|
data?: any;
|
||||||
|
params?: object;
|
||||||
|
queries?: object;
|
||||||
|
callbacks?: RESTCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceAll(input, searchValue, replaceValue) {
|
||||||
|
return input.split(searchValue).join(replaceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTrailingSlashes(input: string): string {
|
||||||
|
if (isNullOrUndefined(input)) {
|
||||||
|
return "undefined";
|
||||||
|
}
|
||||||
|
return input.replace(/\/+$/, "");
|
||||||
|
}
|
||||||
|
function removeLeadingSlashes(input: string): string {
|
||||||
|
if (isNullOrUndefined(input)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return input.replace(/^\/+/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RESTUrl({
|
||||||
|
restModel,
|
||||||
|
restConfig,
|
||||||
|
params,
|
||||||
|
queries,
|
||||||
|
}: RESTRequestType): string {
|
||||||
|
// Create the URL PATH:
|
||||||
|
let generateUrl = `${removeTrailingSlashes(
|
||||||
|
restConfig.server
|
||||||
|
)}/${removeLeadingSlashes(restModel.endPoint)}`;
|
||||||
|
if (params !== undefined) {
|
||||||
|
for (let key of Object.keys(params)) {
|
||||||
|
generateUrl = replaceAll(generateUrl, `{${key}}`, `${params[key]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
queries === undefined &&
|
||||||
|
(restConfig.token === undefined || restModel.tokenInUrl !== true)
|
||||||
|
) {
|
||||||
|
return generateUrl;
|
||||||
|
}
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
if (queries !== undefined) {
|
||||||
|
for (let key of Object.keys(queries)) {
|
||||||
|
const value = queries[key];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
for (const element of value) {
|
||||||
|
searchParams.append(`${key}`, `${element}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
searchParams.append(`${key}`, `${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (restConfig.token !== undefined && restModel.tokenInUrl === true) {
|
||||||
|
searchParams.append("Authorization", `Bearer ${restConfig.token}`);
|
||||||
|
}
|
||||||
|
return generateUrl + "?" + searchParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchProgress(
|
||||||
|
generateUrl: string,
|
||||||
|
{
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
}: {
|
||||||
|
method: HTTPRequestModel;
|
||||||
|
headers: any;
|
||||||
|
body: any;
|
||||||
|
},
|
||||||
|
{ progressUpload, progressDownload, abortHandle }: RESTCallbacks
|
||||||
|
): Promise<Response> {
|
||||||
|
const xhr: {
|
||||||
|
io?: XMLHttpRequest;
|
||||||
|
} = {
|
||||||
|
io: new XMLHttpRequest(),
|
||||||
|
};
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Stream the upload progress
|
||||||
|
if (progressUpload) {
|
||||||
|
xhr.io?.upload.addEventListener("progress", (dataEvent) => {
|
||||||
|
if (dataEvent.lengthComputable) {
|
||||||
|
progressUpload(dataEvent.loaded, dataEvent.total);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Stream the download progress
|
||||||
|
if (progressDownload) {
|
||||||
|
xhr.io?.addEventListener("progress", (dataEvent) => {
|
||||||
|
if (dataEvent.lengthComputable) {
|
||||||
|
progressDownload(dataEvent.loaded, dataEvent.total);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (abortHandle) {
|
||||||
|
abortHandle.abort = () => {
|
||||||
|
if (xhr.io) {
|
||||||
|
console.log(`Request abort on the XMLHttpRequest: ${generateUrl}`);
|
||||||
|
xhr.io.abort();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`Request abort (FAIL) on the XMLHttpRequest: ${generateUrl}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Check if we have an internal Fail:
|
||||||
|
xhr.io?.addEventListener("error", () => {
|
||||||
|
xhr.io = undefined;
|
||||||
|
reject(new TypeError("Failed to fetch"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Capture the end of the stream
|
||||||
|
xhr.io?.addEventListener("loadend", () => {
|
||||||
|
if (xhr.io?.readyState !== XMLHttpRequest.DONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xhr.io?.status === 0) {
|
||||||
|
//the stream has been aborted
|
||||||
|
reject(new TypeError("Fetch has been aborted"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Stream is ended, transform in a generic response:
|
||||||
|
const response = new Response(xhr.io.response, {
|
||||||
|
status: xhr.io.status,
|
||||||
|
statusText: xhr.io.statusText,
|
||||||
|
});
|
||||||
|
const headersArray = replaceAll(
|
||||||
|
xhr.io.getAllResponseHeaders().trim(),
|
||||||
|
"\r\n",
|
||||||
|
"\n"
|
||||||
|
).split("\n");
|
||||||
|
headersArray.forEach(function (header) {
|
||||||
|
const firstColonIndex = header.indexOf(":");
|
||||||
|
if (firstColonIndex !== -1) {
|
||||||
|
const key = header.substring(0, firstColonIndex).trim();
|
||||||
|
const value = header.substring(firstColonIndex + 1).trim();
|
||||||
|
response.headers.set(key, value);
|
||||||
|
} else {
|
||||||
|
response.headers.set(header, "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xhr.io = undefined;
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
xhr.io?.open(method, generateUrl, true);
|
||||||
|
if (!isNullOrUndefined(headers)) {
|
||||||
|
for (const [key, value] of Object.entries(headers)) {
|
||||||
|
xhr.io?.setRequestHeader(key, value as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.io?.send(body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RESTRequest({
|
||||||
|
restModel,
|
||||||
|
restConfig,
|
||||||
|
data,
|
||||||
|
params,
|
||||||
|
queries,
|
||||||
|
callbacks,
|
||||||
|
}: RESTRequestType): Promise<ModelResponseHttp> {
|
||||||
|
// Create the URL PATH:
|
||||||
|
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries });
|
||||||
|
let headers: any = {};
|
||||||
|
if (restConfig.token !== undefined && restModel.tokenInUrl !== true) {
|
||||||
|
headers["Authorization"] = `Bearer ${restConfig.token}`;
|
||||||
|
}
|
||||||
|
if (restModel.accept !== undefined) {
|
||||||
|
headers["Accept"] = restModel.accept;
|
||||||
|
}
|
||||||
|
if (restModel.requestType !== HTTPRequestModel.GET) {
|
||||||
|
// if Get we have not a content type, the body is empty
|
||||||
|
if (restModel.contentType !== HTTPMimeType.MULTIPART) {
|
||||||
|
// special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****"
|
||||||
|
headers["Content-Type"] = restModel.contentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let body = data;
|
||||||
|
if (restModel.contentType === HTTPMimeType.JSON) {
|
||||||
|
body = JSON.stringify(data);
|
||||||
|
} else if (restModel.contentType === HTTPMimeType.MULTIPART) {
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const name in data) {
|
||||||
|
formData.append(name, data[name]);
|
||||||
|
}
|
||||||
|
body = formData;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let action: undefined | Promise<Response> = undefined;
|
||||||
|
if (
|
||||||
|
isNullOrUndefined(callbacks) ||
|
||||||
|
(isNullOrUndefined(callbacks.progressDownload) &&
|
||||||
|
isNullOrUndefined(callbacks.progressUpload) &&
|
||||||
|
isNullOrUndefined(callbacks.abortHandle))
|
||||||
|
) {
|
||||||
|
// No information needed: call the generic fetch interface
|
||||||
|
action = fetch(generateUrl, {
|
||||||
|
method: restModel.requestType,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x
|
||||||
|
action = fetchProgress(
|
||||||
|
generateUrl,
|
||||||
|
{
|
||||||
|
method: restModel.requestType ?? HTTPRequestModel.GET,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
},
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
|
}
|
||||||
|
action
|
||||||
|
.then((response: Response) => {
|
||||||
|
if (response.status >= 200 && response.status <= 299) {
|
||||||
|
const contentType = response.headers.get("Content-Type");
|
||||||
|
if (
|
||||||
|
!isNullOrUndefined(restModel.accept) &&
|
||||||
|
restModel.accept !== contentType
|
||||||
|
) {
|
||||||
|
reject({
|
||||||
|
name: "Model accept type incompatible",
|
||||||
|
time: Date().toString(),
|
||||||
|
status: 901,
|
||||||
|
message: `REST Content type are not compatible: ${restModel.accept} != ${contentType}`,
|
||||||
|
statusMessage: "Fetch error",
|
||||||
|
error: "rest-tools.ts Wrong type in the message return type",
|
||||||
|
} as RestErrorResponse);
|
||||||
|
} else if (contentType === HTTPMimeType.JSON) {
|
||||||
|
response
|
||||||
|
.json()
|
||||||
|
.then((value: any) => {
|
||||||
|
resolve({ status: response.status, data: value });
|
||||||
|
})
|
||||||
|
.catch((reason: Error) => {
|
||||||
|
reject({
|
||||||
|
name: "API serialization error",
|
||||||
|
time: Date().toString(),
|
||||||
|
status: 902,
|
||||||
|
message: `REST parse json fail: ${reason}`,
|
||||||
|
statusMessage: "Fetch parse error",
|
||||||
|
error: "rest-tools.ts Wrong message model to parse",
|
||||||
|
} as RestErrorResponse);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve({ status: response.status, data: response.body });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the answer is not correct not a 2XX
|
||||||
|
// clone the response to keep the raw data if case of error:
|
||||||
|
response
|
||||||
|
.clone()
|
||||||
|
.json()
|
||||||
|
.then((value: any) => {
|
||||||
|
if (isRestErrorResponse(value)) {
|
||||||
|
reject(value);
|
||||||
|
} else {
|
||||||
|
response
|
||||||
|
.text()
|
||||||
|
.then((dataError: string) => {
|
||||||
|
reject({
|
||||||
|
name: "API serialization error",
|
||||||
|
time: Date().toString(),
|
||||||
|
status: 903,
|
||||||
|
message: `REST parse error json with wrong type fail. ${dataError}`,
|
||||||
|
statusMessage: "Fetch parse error",
|
||||||
|
error: "rest-tools.ts Wrong message model to parse",
|
||||||
|
} as RestErrorResponse);
|
||||||
|
})
|
||||||
|
.catch((reason: any) => {
|
||||||
|
reject({
|
||||||
|
name: "API serialization error",
|
||||||
|
time: Date().toString(),
|
||||||
|
status: response.status,
|
||||||
|
message: `unmanaged error model: ??? with error: ${reason}`,
|
||||||
|
statusMessage: "Fetch ERROR parse error",
|
||||||
|
error: "rest-tools.ts Wrong message model to parse",
|
||||||
|
} as RestErrorResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((reason: Error) => {
|
||||||
|
response
|
||||||
|
.text()
|
||||||
|
.then((dataError: string) => {
|
||||||
|
reject({
|
||||||
|
name: "API serialization error",
|
||||||
|
time: Date().toString(),
|
||||||
|
status: response.status,
|
||||||
|
message: `unmanaged error model: ${dataError} with error: ${reason}`,
|
||||||
|
statusMessage: "Fetch ERROR TEXT parse error",
|
||||||
|
error: "rest-tools.ts Wrong message model to parse",
|
||||||
|
} as RestErrorResponse);
|
||||||
|
})
|
||||||
|
.catch((reason: any) => {
|
||||||
|
reject({
|
||||||
|
name: "API serialization error",
|
||||||
|
time: Date().toString(),
|
||||||
|
status: response.status,
|
||||||
|
message: `unmanaged error model: ??? with error: ${reason}`,
|
||||||
|
statusMessage: "Fetch ERROR TEXT FAIL",
|
||||||
|
error: "rest-tools.ts Wrong message model to parse",
|
||||||
|
} as RestErrorResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
if (isRestErrorResponse(error)) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
reject({
|
||||||
|
name: "Request fail",
|
||||||
|
time: Date(),
|
||||||
|
status: 999,
|
||||||
|
message: error,
|
||||||
|
statusMessage: "Fetch catch error",
|
||||||
|
error: "rest-tools.ts detect an error in the fetch request",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RESTRequestJson<TYPE>(
|
||||||
|
request: RESTRequestType,
|
||||||
|
checker?: (data: any) => data is TYPE
|
||||||
|
): Promise<TYPE> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
RESTRequest(request)
|
||||||
|
.then((value: ModelResponseHttp) => {
|
||||||
|
if (isNullOrUndefined(checker)) {
|
||||||
|
console.log(`Have no check of MODEL in API: ${RESTUrl(request)}`);
|
||||||
|
resolve(value.data);
|
||||||
|
} else if (checker === undefined || checker(value.data)) {
|
||||||
|
resolve(value.data);
|
||||||
|
} else {
|
||||||
|
reject({
|
||||||
|
name: "Model check fail",
|
||||||
|
time: Date().toString(),
|
||||||
|
status: 950,
|
||||||
|
error: "REST Fail to verify the data",
|
||||||
|
statusMessage: "API cast ERROR",
|
||||||
|
message: "api.ts Check type as fail",
|
||||||
|
} as RestErrorResponse);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((reason: RestErrorResponse) => {
|
||||||
|
reject(reason);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RESTRequestVoid(request: RESTRequestType): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
RESTRequest(request)
|
||||||
|
.then((value: ModelResponseHttp) => {
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch((reason: RestErrorResponse) => {
|
||||||
|
reject(reason);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
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} />;
|
||||||
|
};
|
50
front2/src/components/Layout/PageLayout.tsx
Normal file
50
front2/src/components/Layout/PageLayout.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
export type LayoutProps = React.PropsWithChildren<unknown> & {
|
||||||
|
topBar?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PageLayout = ({ children }: LayoutProps) => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
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}
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
44
front2/src/components/Layout/PageLayoutInfoCenter.tsx
Normal file
44
front2/src/components/Layout/PageLayoutInfoCenter.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React, { ReactNode, 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 { useThemeMode } from '@/utils/theme-tools';
|
||||||
|
|
||||||
|
export type LayoutProps = FlexProps & {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PageLayoutInfoCenter = ({
|
||||||
|
children,
|
||||||
|
width = '25%',
|
||||||
|
...rest
|
||||||
|
}: LayoutProps) => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const { mode } = useThemeMode();
|
||||||
|
return (
|
||||||
|
<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={mode('#FFFFFF', '#000000')}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
};
|
190
front2/src/components/TopBar/TopBar.tsx
Normal file
190
front2/src/components/TopBar/TopBar.tsx
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Drawer,
|
||||||
|
DrawerBody,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerOverlay,
|
||||||
|
Flex,
|
||||||
|
HStack,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
LuAlignJustify,
|
||||||
|
LuArrowBigLeft,
|
||||||
|
LuArrowRightSquare,
|
||||||
|
LuArrowUpSquare,
|
||||||
|
LuHelpCircle,
|
||||||
|
LuHome,
|
||||||
|
LuLogIn,
|
||||||
|
LuLogOut,
|
||||||
|
LuMoon,
|
||||||
|
LuPlusCircle,
|
||||||
|
LuSettings,
|
||||||
|
LuSun,
|
||||||
|
LuUserCircle,
|
||||||
|
} from 'react-icons/lu';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useServiceContext } from '@/service/ServiceContext';
|
||||||
|
import { SessionState } from '@/service/SessionState';
|
||||||
|
import { colors } from '@/theme/foundations/colors';
|
||||||
|
import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
|
||||||
|
import { useThemeMode } from '@/utils/theme-tools';
|
||||||
|
|
||||||
|
export const TOP_BAR_HEIGHT = '50px';
|
||||||
|
|
||||||
|
export type TopBarProps = {
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TopBar = ({ children }: TopBarProps) => {
|
||||||
|
const { mode, colorMode, toggleColorMode } = useThemeMode();
|
||||||
|
const buttonProperty = {
|
||||||
|
variant: '@menu',
|
||||||
|
height: TOP_BAR_HEIGHT,
|
||||||
|
};
|
||||||
|
const { session } = useServiceContext();
|
||||||
|
const backColor = mode('back.100', 'back.800');
|
||||||
|
const drawerDisclose = useDisclosure();
|
||||||
|
const onChangeTheme = () => {
|
||||||
|
drawerDisclose.onOpen();
|
||||||
|
};
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const onSignIn = (): void => {
|
||||||
|
requestSignIn();
|
||||||
|
};
|
||||||
|
const onSignUp = (): void => {
|
||||||
|
requestSignUp();
|
||||||
|
};
|
||||||
|
const onSignOut = (): void => {
|
||||||
|
requestSignOut();
|
||||||
|
};
|
||||||
|
const onSelectHome = () => {
|
||||||
|
navigate('/');
|
||||||
|
};
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<Button {...buttonProperty} onClick={onChangeTheme} marginRight="auto">
|
||||||
|
<LuAlignJustify />
|
||||||
|
<Text paddingLeft="3px" fontWeight="bold">
|
||||||
|
Menu
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
{children}
|
||||||
|
<Text
|
||||||
|
fontSize="25px"
|
||||||
|
fontWeight="bold"
|
||||||
|
textTransform="uppercase"
|
||||||
|
marginRight="auto"
|
||||||
|
userSelect="none"
|
||||||
|
>
|
||||||
|
Karusic
|
||||||
|
</Text>
|
||||||
|
{session?.state !== SessionState.CONNECTED && (
|
||||||
|
<>
|
||||||
|
<Button {...buttonProperty} onClick={onSignIn}>
|
||||||
|
<LuLogIn />
|
||||||
|
<Text paddingLeft="3px" fontWeight="bold">
|
||||||
|
Sign-in
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
<Button {...buttonProperty} onClick={onSignUp} disabled={true}>
|
||||||
|
<LuPlusCircle />
|
||||||
|
<Text paddingLeft="3px" fontWeight="bold">
|
||||||
|
Sign-up
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{session?.state === SessionState.CONNECTED && (
|
||||||
|
<Menu>
|
||||||
|
<MenuButton
|
||||||
|
as={IconButton}
|
||||||
|
aria-label="Options"
|
||||||
|
icon={<LuUserCircle />}
|
||||||
|
{...buttonProperty}
|
||||||
|
width={TOP_BAR_HEIGHT}
|
||||||
|
/>
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}>
|
||||||
|
Sign in as {session?.login ?? 'Fail'}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<LuArrowUpSquare />}>Add Media</MenuItem>
|
||||||
|
<MenuItem icon={<LuSettings />}>Settings</MenuItem>
|
||||||
|
<MenuItem icon={<LuHelpCircle />}>Help</MenuItem>
|
||||||
|
<MenuItem icon={<LuLogOut onClick={onSignOut} />}>
|
||||||
|
Sign-out
|
||||||
|
</MenuItem>
|
||||||
|
{colorMode === 'light' ? (
|
||||||
|
<MenuItem icon={<LuMoon />} onClick={toggleColorMode}>
|
||||||
|
Set dark mode
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
<MenuItem icon={<LuSun />} onClick={toggleColorMode}>
|
||||||
|
Set light mode
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
<Drawer
|
||||||
|
placement="left"
|
||||||
|
onClose={drawerDisclose.onClose}
|
||||||
|
isOpen={drawerDisclose.isOpen}
|
||||||
|
>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader
|
||||||
|
paddingY="auto"
|
||||||
|
as="button"
|
||||||
|
onClick={drawerDisclose.onClose}
|
||||||
|
boxShadow={'0px 2px 4px ' + colors.back[900]}
|
||||||
|
backgroundColor={backColor}
|
||||||
|
color={mode('brand.900', 'brand.50')}
|
||||||
|
textTransform="uppercase"
|
||||||
|
>
|
||||||
|
<HStack height={TOP_BAR_HEIGHT}>
|
||||||
|
<LuArrowBigLeft />
|
||||||
|
<Text as="span" paddingLeft="3px">
|
||||||
|
Karusic
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</DrawerHeader>
|
||||||
|
<DrawerBody>
|
||||||
|
<Button {...buttonProperty} onClick={onSelectHome} width="fill">
|
||||||
|
<LuHome />
|
||||||
|
<Text paddingLeft="3px" fontWeight="bold">
|
||||||
|
Home
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
<p>Some contents...</p>
|
||||||
|
<p>Some contents...</p>
|
||||||
|
<p>Some contents...</p>
|
||||||
|
</DrawerBody>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
3
front2/src/components/index.ts
Normal file
3
front2/src/components/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
export * from './Icons';
|
||||||
|
|
28
front2/src/config/dayjs.ts
Normal file
28
front2/src/config/dayjs.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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);
|
2
front2/src/config/index.ts
Normal file
2
front2/src/config/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import './axios';
|
||||||
|
import './dayjs';
|
2
front2/src/constants/date.ts
Normal file
2
front2/src/constants/date.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const DATE_FORMAT = 'YYYY-MM-DD';
|
||||||
|
export const DATE_FORMAT_FULL = 'dddd DD MMMM HH:mm';
|
1
front2/src/constants/index.ts
Normal file
1
front2/src/constants/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './date'
|
107
front2/src/environment.ts
Normal file
107
front2/src/environment.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
export interface Environment {
|
||||||
|
production: boolean;
|
||||||
|
applName: string;
|
||||||
|
defaultServer: string;
|
||||||
|
server: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
ssoSite: string;
|
||||||
|
ssoSignIn: string;
|
||||||
|
ssoSignUp: string;
|
||||||
|
ssoSignOut: string;
|
||||||
|
tokenStoredInPermanentStorage: boolean;
|
||||||
|
replaceDataToRealServer?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverSSOAddress = 'http://atria-soft.org';
|
||||||
|
|
||||||
|
const environment_back_prod: Environment = {
|
||||||
|
production: false,
|
||||||
|
// URL of development API
|
||||||
|
applName: 'karusic',
|
||||||
|
defaultServer: 'karusic',
|
||||||
|
server: {
|
||||||
|
karusic: `${serverSSOAddress}/karusic/api`,
|
||||||
|
karso: `${serverSSOAddress}/karso/api`,
|
||||||
|
},
|
||||||
|
ssoSite: `${serverSSOAddress}/karso/`,
|
||||||
|
ssoSignIn: `${serverSSOAddress}/karso/signin/karusic-dev/`,
|
||||||
|
ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`,
|
||||||
|
ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`,
|
||||||
|
tokenStoredInPermanentStorage: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const environment_local: Environment = {
|
||||||
|
production: false,
|
||||||
|
// URL of development API
|
||||||
|
applName: 'karusic',
|
||||||
|
defaultServer: 'karusic',
|
||||||
|
server: {
|
||||||
|
karusic: 'http://localhost:19080/karusic/api',
|
||||||
|
karso: `${serverSSOAddress}/karso/api`,
|
||||||
|
},
|
||||||
|
ssoSite: `${serverSSOAddress}/karso/`,
|
||||||
|
ssoSignIn: `${serverSSOAddress}/karso/signin/karusic-dev/`,
|
||||||
|
ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`,
|
||||||
|
ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`,
|
||||||
|
tokenStoredInPermanentStorage: false,
|
||||||
|
replaceDataToRealServer: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const environment_full_local: Environment = {
|
||||||
|
production: false,
|
||||||
|
// URL of development API
|
||||||
|
applName: 'karusic',
|
||||||
|
defaultServer: 'karusic',
|
||||||
|
server: {
|
||||||
|
karusic: 'http://localhost:19080/karusic/api',
|
||||||
|
karso: 'http://localhost:15080/karso/api',
|
||||||
|
},
|
||||||
|
ssoSite: `${serverSSOAddress}/karso/`,
|
||||||
|
ssoSignIn: 'http://localhost:4200/signin/karusic-dev/',
|
||||||
|
ssoSignUp: 'http://localhost:4200/signup/karusic-dev/',
|
||||||
|
ssoSignOut: 'http://localhost:4200/signout/karusic-dev/',
|
||||||
|
tokenStoredInPermanentStorage: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const environment_hybrid: Environment = {
|
||||||
|
production: false,
|
||||||
|
// URL of development API
|
||||||
|
applName: 'karusic',
|
||||||
|
defaultServer: 'karusic',
|
||||||
|
server: {
|
||||||
|
karusic: `${serverSSOAddress}/karusic/api`,
|
||||||
|
karso: `${serverSSOAddress}/karso/api`,
|
||||||
|
},
|
||||||
|
ssoSite: `${serverSSOAddress}/karso/`,
|
||||||
|
ssoSignIn: 'http://localhost:4200/signin/karusic-dev/',
|
||||||
|
ssoSignUp: 'http://localhost:4200/signup/karusic-dev/',
|
||||||
|
ssoSignOut: 'http://localhost:4200/signout/karusic-dev/',
|
||||||
|
tokenStoredInPermanentStorage: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const environment = environment_local;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current environment is for development
|
||||||
|
* @returns true if development is active.
|
||||||
|
*/
|
||||||
|
export const isDevelopmentEnvironment = () => {
|
||||||
|
return import.meta.env.MODE === 'development';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the current REST api URL. Depend on the VITE_API_BASE_URL env variable.
|
||||||
|
* @returns The URL with http(s)://***
|
||||||
|
*/
|
||||||
|
export const getApiUrl = () => {
|
||||||
|
const baseUrl: string | undefined = import.meta.env.VITE_API_BASE_URL;
|
||||||
|
if (baseUrl === undefined || baseUrl === null) {
|
||||||
|
//return `${window.location.protocol}//${window.location.host}/api`;
|
||||||
|
return environment.server.karusic;
|
||||||
|
}
|
||||||
|
if (baseUrl.startsWith('http')) {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
return `${window.location.protocol}//${window.location.host}/${baseUrl}`;
|
||||||
|
};
|
132
front2/src/errors/Error404.tsx
Normal file
132
front2/src/errors/Error404.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Heading,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
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 }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const color = theme?.colors?.[colorScheme] ?? {};
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
width={400}
|
||||||
|
height={300}
|
||||||
|
maxW="full"
|
||||||
|
viewBox="0 0 400 300"
|
||||||
|
fill="none"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
// Left Hand
|
||||||
|
d="M65.013 104.416s-12.773-.562-13.719 11.938c-.946 12.5 16.13 8.397 13.719-11.938z"
|
||||||
|
fill={color['300']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Left Arm
|
||||||
|
d="M182.326 67.705s-35.463-20.529-67.804-13.535c-32.342 6.993-60.624 52.94-60.624 52.94l11.499 6.837s49.74-51.775 83.275-21.444c33.535 30.331 33.654-24.798 33.654-24.798z"
|
||||||
|
fill={color['800']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Search Zone
|
||||||
|
d="M334.098 220.092a14.333 14.333 0 01-9.838-7.37v-.106l-50.465-96.17-27.774 19.677 19.642 61.796a10.575 10.575 0 01-5.617 12.799 10.563 10.563 0 01-4.945.97 1037.507 1037.507 0 00-47.278-1.067c-85.178 0-154.23 9.93-154.23 22.184 0 12.255 69.052 22.195 154.23 22.195C293.001 255 362 245.07 362 232.837c0-4.756-10.328-9.14-27.902-12.745z"
|
||||||
|
fill={color['200']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Foots
|
||||||
|
d="M173.611 225.563s1.578 5.333 6.256 5.962c4.679.63 5.66 5.333 1.365 6.293-4.296.96-14.921-5.066-14.921-5.066l.671-6.773 6.629-.416zM82.518 224.657s-5.414 1.173-6.395 5.791c-.98 4.618-5.734 5.237-6.395.875-.66-4.362 6.193-14.484 6.193-14.484l6.693 1.205-.096 6.613z"
|
||||||
|
fill={color['900']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Left Leg
|
||||||
|
d="M83.5 143s-5.245 25.322 12.713 35.305c17.959 9.983 74.606-7.988 65.856 48.592h12.64s16.338-46.928-26.048-63.609l-12.864-14.601L83.5 143z"
|
||||||
|
fill={color['600']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Magnifying Glass Shadow
|
||||||
|
d="M257.632 128.216l-4.299-4.112-26.891 28.16 4.299 4.111 26.891-28.159z"
|
||||||
|
fill={color['700']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Magnifying Glass Handle
|
||||||
|
d="M255.537 126.2l-4.299-4.112-26.891 28.16 4.299 4.111 26.891-28.159z"
|
||||||
|
fill={color['500']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Magnifying Glass Shadow 2
|
||||||
|
d="M267.233 131.381c6.913-7.239 8.606-16.849 3.78-21.464-4.826-4.615-14.342-2.487-21.256 4.752-6.913 7.24-8.605 16.85-3.779 21.465 4.825 4.615 14.342 2.487 21.255-4.753z"
|
||||||
|
fill={color['700']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Magnifying Glass Ring
|
||||||
|
d="M265.133 129.382c6.914-7.24 8.606-16.849 3.78-21.464-4.825-4.615-14.342-2.487-21.255 4.752-6.913 7.24-8.606 16.849-3.78 21.464 4.826 4.615 14.342 2.487 21.255-4.752z"
|
||||||
|
fill={color['500']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Magnifying Glass
|
||||||
|
d="M262.167 126.545c4.566-4.782 5.685-11.13 2.497-14.178-3.187-3.048-9.473-1.642-14.04 3.14-4.567 4.782-5.685 11.13-2.498 14.178 3.188 3.048 9.474 1.642 14.041-3.14z"
|
||||||
|
fill={color['50']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Head
|
||||||
|
d="M217.261 74.257c1.932-2.325 3.256-4.248 3.256-4.248 3.133-4.106-.267-11.743-6.096-12.084a7.606 7.606 0 00-7.759 4.095l-.966 2.572-19.039 7.678 2.664 15.443 14.063-11.483c.418 1.245 1.052 2.35 1.7 3.26a4.269 4.269 0 004.448 1.638 4.267 4.267 0 001.51-.7 25.197 25.197 0 002.341-1.98l1.613.893a1.364 1.364 0 002.014-1.067l.256-4.02-.005.003z"
|
||||||
|
fill={color['300']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Body
|
||||||
|
d="M192.199 93.056l-1.791-10.42a25.45 25.45 0 00-15.251-19.325 45.122 45.122 0 00-10.754-2.827c-4.732-.66-11.361 0-18.779 1.952-31.953 8.394-55.4 35.484-60.25 68.141L83 145l52.797 3.687s-2.664-14.931 15.987-14.931 42.269.192 40.415-40.7z"
|
||||||
|
fill={color['700']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Right Arm
|
||||||
|
d="M169.945 95.488c4.977-16.617-14.324-30.055-28.084-19.496-11.51 8.82-24.513 24.701-25.024 51.503-.885 48.368 45.413 39.718 108.071 21.192l-1.993-14.089s-75.032 14.196-64.843-10.207c4.263-10.206 9.23-20.061 11.873-28.903z"
|
||||||
|
fill={color['800']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Right Leg
|
||||||
|
d="M143.609 163.406s1.545 53.487-60.995 63.491l-2.42-11.338s50.092-12.425 17.735-45.712l45.68-6.441z"
|
||||||
|
fill={color['600']}
|
||||||
|
/>
|
||||||
|
307s17
|
||||||
|
<path
|
||||||
|
// Right Hand
|
||||||
|
d="M223.298 137.307s17.244-1.824 18.758 4.917c1.513 6.741-1.791 10.313-17.309 5.333l-1.449-10.25z"
|
||||||
|
fill={color['300']}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
// Hair
|
||||||
|
d="M218.673 68.942a34.11 34.11 0 01-5.649-5.44 7.094 7.094 0 01-4.934 5.994 5.752 5.752 0 01-6.821-2.655l7.034-8.319a8.644 8.644 0 018.27-3.2c1.33.23 2.643.543 3.933.939 3.197 1.066 5.425 5.567 8.942 5.717a2.044 2.044 0 011.946 2.1c-.01.354-.111.7-.294 1.003-1.727 2.87-5.499 6.517-10.114 5.056a7.933 7.933 0 01-2.313-1.195z"
|
||||||
|
fill={color['800']}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Error404 = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TopBar />
|
||||||
|
<PageLayoutInfoCenter>
|
||||||
|
<Illustration />
|
||||||
|
<Box textAlign={{ base: 'center', md: 'left' }}>
|
||||||
|
<Heading>Erreur 404</Heading>
|
||||||
|
<Text color="gray.600">
|
||||||
|
Cette page n'existe plus ou l'URL a changé
|
||||||
|
</Text>
|
||||||
|
<Button as="a" variant="link" href={`/${environment.applName}`}>
|
||||||
|
Retour à l'accueil
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</PageLayoutInfoCenter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
51
front2/src/errors/ErrorBoundary.tsx
Normal file
51
front2/src/errors/ErrorBoundary.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertDescription,
|
||||||
|
AlertIcon,
|
||||||
|
AlertTitle,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Collapse,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FallbackProps,
|
||||||
|
ErrorBoundary as ReactErrorBoundary,
|
||||||
|
} from 'react-error-boundary';
|
||||||
|
import { LuChevronDown, LuChevronUp } from 'react-icons/lu';
|
||||||
|
|
||||||
|
const ErrorFallback = ({ error }: FallbackProps) => {
|
||||||
|
const { isOpen, onToggle } = useDisclosure();
|
||||||
|
return (
|
||||||
|
<Box p="4" m="auto">
|
||||||
|
<Alert status="error" borderRadius="md">
|
||||||
|
<AlertIcon />
|
||||||
|
<Box flex="1">
|
||||||
|
<AlertTitle>An unexpected error has occurred.</AlertTitle>
|
||||||
|
<AlertDescription display="block" lineHeight="1.4">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
color="red.800"
|
||||||
|
size="sm"
|
||||||
|
rightIcon={isOpen ? <LuChevronUp /> : <LuChevronDown />}
|
||||||
|
onClick={onToggle}
|
||||||
|
>
|
||||||
|
Show details
|
||||||
|
</Button>
|
||||||
|
<Collapse in={isOpen} animateOpacity>
|
||||||
|
<Box mt={4} fontFamily="monospace">
|
||||||
|
{error.message}
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
</AlertDescription>
|
||||||
|
</Box>
|
||||||
|
</Alert>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ErrorBoundary: FC<React.PropsWithChildren<unknown>> = (props) => {
|
||||||
|
return <ReactErrorBoundary FallbackComponent={ErrorFallback} {...props} />;
|
||||||
|
};
|
1
front2/src/errors/index.ts
Normal file
1
front2/src/errors/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Error404';
|
14
front2/src/icons/DoubleArrowIcon.tsx
Normal file
14
front2/src/icons/DoubleArrowIcon.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
1
front2/src/icons/index.ts
Normal file
1
front2/src/icons/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './DoubleArrowIcon';
|
16
front2/src/main.tsx
Normal file
16
front2/src/main.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { StrictMode } from 'react';
|
||||||
|
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
|
import App from '@/App';
|
||||||
|
|
||||||
|
// Render the app
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
if (rootElement && !rootElement.innerHTML) {
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
}
|
35
front2/src/scene/App.tsx
Normal file
35
front2/src/scene/App.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { createBrowserHistory } from 'history';
|
||||||
|
import {
|
||||||
|
unstable_HistoryRouter as HistoryRouter,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AudioPlayer } from '@/components/AudioPlayer';
|
||||||
|
import { Error404 } from '@/errors';
|
||||||
|
import { ErrorBoundary } from '@/errors/ErrorBoundary';
|
||||||
|
import { 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 (
|
||||||
|
<ServiceContextProvider>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<HistoryRouter
|
||||||
|
history={createBrowserHistory({ window })}
|
||||||
|
basename="/karusic"
|
||||||
|
>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
<Route path="artist/*" element={<ArtistRoutes />} />
|
||||||
|
<Route path="sso/*" element={<SSORoutes />} />
|
||||||
|
<Route path="*" element={<Error404 />} />
|
||||||
|
</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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
21
front2/src/scene/artist/ArtistRoutes.tsx
Normal file
21
front2/src/scene/artist/ArtistRoutes.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Error404 } from '@/errors';
|
||||||
|
import { ArtistAlbumDetailPage } from '@/scene/artist/ArtistAlbumDetailPage';
|
||||||
|
import { ArtistDetailPage } from '@/scene/artist/ArtistDetailPage';
|
||||||
|
import { ArtistsPage } from '@/scene/artist/ArtistsPage';
|
||||||
|
|
||||||
|
export const ArtistRoutes = () => {
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Navigate to="all" replace />} />
|
||||||
|
<Route path="all" element={<ArtistsPage />} />
|
||||||
|
<Route path=":artistId" element={<ArtistDetailPage />} />
|
||||||
|
<Route
|
||||||
|
path=":artistId/album/:albumId"
|
||||||
|
element={<ArtistAlbumDetailPage />}
|
||||||
|
/>
|
||||||
|
<Route path="*" 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
96
front2/src/scene/home/HomePage.tsx
Normal file
96
front2/src/scene/home/HomePage.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
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>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
85
front2/src/scene/sso/SSOPage.tsx
Normal file
85
front2/src/scene/sso/SSOPage.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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';
|
||||||
|
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||||
|
import { TopBar } from '@/components/TopBar/TopBar';
|
||||||
|
import { isDevelopmentEnvironment } from '@/environment';
|
||||||
|
import { SessionState } from '@/service/SessionState';
|
||||||
|
import { useSessionService } from '@/service/session';
|
||||||
|
import { b64_to_utf8 } from '@/utils/sso';
|
||||||
|
|
||||||
|
export const SSOPage = () => {
|
||||||
|
const { data, keepConnected, token } = useParams();
|
||||||
|
console.log(`- data: ${data}`);
|
||||||
|
console.log(`- keepConnected: ${keepConnected}`);
|
||||||
|
console.log(`- token: ${token}`);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { state, setToken, login, clearToken } = useSessionService();
|
||||||
|
useEffect(() => {
|
||||||
|
if (token) {
|
||||||
|
setToken(token);
|
||||||
|
} else {
|
||||||
|
clearToken();
|
||||||
|
}
|
||||||
|
}, [token, setToken, clearToken]);
|
||||||
|
const delay = isDevelopmentEnvironment() ? 20000 : 2000;
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === SessionState.CONNECTED) {
|
||||||
|
const destination = data ? b64_to_utf8(data) : '/';
|
||||||
|
console.log(`program redirect to: ${destination} (${delay}ms)`);
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate(`/${destination}`);
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
}, [state]);
|
||||||
|
/*
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
console.log(`data: ${searchParams.get('data')}`);
|
||||||
|
console.log(`keepConnected: ${searchParams.get('keepConnected')}`);
|
||||||
|
console.log(`token: ${searchParams.get('token')}`);
|
||||||
|
const dataFromParam = useGetCreateActionParams();
|
||||||
|
console.log(`data group: ${JSON.stringify(dataFromParam, null, 2)}`);
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TopBar />
|
||||||
|
<PageLayoutInfoCenter width="35%" gap="15px">
|
||||||
|
<Center w="full">
|
||||||
|
<Heading size="xl">LOGIN (after SSO) </Heading>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
<Center w="full">
|
||||||
|
<Image src={avatar_generic} boxSize="150px" borderRadius="full" />
|
||||||
|
</Center>
|
||||||
|
{token === '__CANCEL__' && (
|
||||||
|
<Text>
|
||||||
|
<b>ERROR: </b> Request cancel of connection !
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{token === '__FAIL__' && (
|
||||||
|
<Text>
|
||||||
|
<b>ERROR: </b> Connection FAIL !
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{token === '__LOGOUT__' && (
|
||||||
|
<Text>
|
||||||
|
<b>Dis-connected: </b> Redirect soon!{' '}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{!['__LOGOUT__', '__FAIL__', '__CANCEL__'].includes(token ?? '') && (
|
||||||
|
<>
|
||||||
|
<Text>
|
||||||
|
<b>Connected: </b> Redirect soon!
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<b>Welcome back: </b> {login}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PageLayoutInfoCenter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
13
front2/src/scene/sso/SSORoutes.tsx
Normal file
13
front2/src/scene/sso/SSORoutes.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
|
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={<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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
96
front2/src/service/ServiceContext.tsx
Normal file
96
front2/src/service/ServiceContext.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
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,
|
||||||
|
getRestConfig,
|
||||||
|
useSessionServiceWrapped,
|
||||||
|
} from '@/service/session';
|
||||||
|
|
||||||
|
export type ServiceContextType = {
|
||||||
|
session: SessionServiceProps;
|
||||||
|
track: TrackServiceProps;
|
||||||
|
artist: ArtistServiceProps;
|
||||||
|
album: AlbumServiceProps;
|
||||||
|
activePlaylist: ActivePlaylistServiceProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ServiceContext = createContext<ServiceContextType>({
|
||||||
|
session: {
|
||||||
|
setToken: (token: string) => {},
|
||||||
|
clearToken: () => {},
|
||||||
|
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) => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useServiceContext = () => useContext(ServiceContext);
|
||||||
|
|
||||||
|
export const ServiceContextProvider = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
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, track, artist, album]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ServiceContext.Provider value={contextObjectData}>
|
||||||
|
{children}
|
||||||
|
</ServiceContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
7
front2/src/service/SessionState.ts
Normal file
7
front2/src/service/SessionState.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum SessionState {
|
||||||
|
NO_USER,
|
||||||
|
CONNECTING,
|
||||||
|
CONNECTION_FAIL,
|
||||||
|
CONNECTED,
|
||||||
|
DISCONNECT,
|
||||||
|
}
|
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 };
|
||||||
|
};
|
139
front2/src/service/session.ts
Normal file
139
front2/src/service/session.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { PartRight, RESTConfig, UserMe, UserResource } from '@/back-api';
|
||||||
|
import { environment, getApiUrl } from '@/environment';
|
||||||
|
import { useServiceContext } from '@/service/ServiceContext';
|
||||||
|
import { SessionState } from '@/service/SessionState';
|
||||||
|
import { isBrowser } from '@/utils/layout';
|
||||||
|
import { parseToken } from '@/utils/sso';
|
||||||
|
|
||||||
|
const TOKEN_KEY = 'karusic-token-key-storage';
|
||||||
|
|
||||||
|
export const USERS = {
|
||||||
|
ADMIN:
|
||||||
|
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E',
|
||||||
|
NO_USER: '',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const getUserToken = () => {
|
||||||
|
return localStorage.getItem(TOKEN_KEY);
|
||||||
|
};
|
||||||
|
export type RightPart = 'ADMIN' | 'USER';
|
||||||
|
|
||||||
|
export function getRestConfig(): RESTConfig {
|
||||||
|
return {
|
||||||
|
server: getApiUrl(),
|
||||||
|
token: getUserToken() ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionServiceProps = {
|
||||||
|
token?: string;
|
||||||
|
setToken: (token: string) => void;
|
||||||
|
clearToken: () => void;
|
||||||
|
login?: string;
|
||||||
|
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
|
||||||
|
);
|
||||||
|
const [state, setState] = useState<SessionState>(SessionState.NO_USER);
|
||||||
|
const [config, setConfig] = useState<UserMe | undefined>(undefined);
|
||||||
|
|
||||||
|
const updateRight = useCallback(() => {
|
||||||
|
if (isBrowser) {
|
||||||
|
console.log('Detect a new token...');
|
||||||
|
setState(SessionState.NO_USER);
|
||||||
|
setConfig(undefined);
|
||||||
|
if (token === undefined) {
|
||||||
|
console.log(` ==> No User`);
|
||||||
|
setState(SessionState.NO_USER);
|
||||||
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
|
} else if (token === '__LOGOUT__') {
|
||||||
|
console.log(` ==> disconnection: ${token}`);
|
||||||
|
setState(SessionState.DISCONNECT);
|
||||||
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
|
} else if (!['__LOGOUT__', '__FAIL__', '__CANCEL__'].includes(token)) {
|
||||||
|
console.log(' ==> Login ... (try to keep right)');
|
||||||
|
setState(SessionState.CONNECTING);
|
||||||
|
localStorage.setItem(TOKEN_KEY, token);
|
||||||
|
UserResource.getMe({
|
||||||
|
restConfig: getRestConfig(),
|
||||||
|
})
|
||||||
|
.then((response: UserMe) => {
|
||||||
|
console.log(` ==> New right arrived to '${response.login}'`);
|
||||||
|
setState(SessionState.CONNECTED);
|
||||||
|
setConfig(response);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setState(SessionState.CONNECTION_FAIL);
|
||||||
|
console.log(` ==> Fail to get right: '${error}'`);
|
||||||
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [localStorage, parseToken, token]);
|
||||||
|
const setTokenLocal = useCallback(
|
||||||
|
(token: string) => {
|
||||||
|
setToken(token);
|
||||||
|
updateRight();
|
||||||
|
},
|
||||||
|
[updateRight, setToken]
|
||||||
|
);
|
||||||
|
const clearToken = useCallback(() => {
|
||||||
|
setToken(undefined);
|
||||||
|
updateRight();
|
||||||
|
}, [updateRight, setToken]);
|
||||||
|
const hasReadRight = useCallback(
|
||||||
|
(part: RightPart) => {
|
||||||
|
const right = config?.rights[environment.applName];
|
||||||
|
if (right === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
|
||||||
|
},
|
||||||
|
[config]
|
||||||
|
);
|
||||||
|
const hasWriteRight = useCallback(
|
||||||
|
(part: RightPart) => {
|
||||||
|
const right = config?.rights[environment.applName];
|
||||||
|
if (right === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
|
||||||
|
},
|
||||||
|
[config]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getRestConfig = useCallback((): RESTConfig => {
|
||||||
|
return {
|
||||||
|
server: getApiUrl(),
|
||||||
|
token: token ?? '',
|
||||||
|
};
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateRight();
|
||||||
|
}, [updateRight]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
setToken: setTokenLocal,
|
||||||
|
clearToken,
|
||||||
|
login: config?.login,
|
||||||
|
hasReadRight,
|
||||||
|
hasWriteRight,
|
||||||
|
state,
|
||||||
|
getRestConfig,
|
||||||
|
};
|
||||||
|
};
|
65
front2/src/test/setup.ts
Normal file
65
front2/src/test/setup.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import mediaQuery from 'css-mediaquery';
|
||||||
|
|
||||||
|
window.scrollTo = () => undefined;
|
||||||
|
|
||||||
|
global.matchMedia =
|
||||||
|
global.matchMedia ||
|
||||||
|
function (query) {
|
||||||
|
const instance = {
|
||||||
|
matches: mediaQuery.match(query, {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
}),
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addListener: jest.fn(), // Deprecated
|
||||||
|
removeListener: jest.fn(), // Deprecated
|
||||||
|
addEventListener: jest.fn(),
|
||||||
|
removeEventListener: jest.fn(),
|
||||||
|
dispatchEvent: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen to resize events from window.resizeTo and update the instance's match
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
const change = mediaQuery.match(query, {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (change != instance.matches) {
|
||||||
|
instance.matches = change;
|
||||||
|
instance.dispatchEvent('change');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock window.resizeTo's impl.
|
||||||
|
Object.defineProperty(window, 'resizeTo', {
|
||||||
|
value: (width, height) => {
|
||||||
|
Object.defineProperty(window, 'innerWidth', {
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: width,
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, 'outerWidth', {
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: width,
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, 'innerHeight', {
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: height,
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, 'outerHeight', {
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: height,
|
||||||
|
});
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
},
|
||||||
|
});
|
27
front2/src/test/utils.tsx
Normal file
27
front2/src/test/utils.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { ReactElement, ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { ChakraProvider } from '@chakra-ui/react';
|
||||||
|
import { RenderOptions, render } from '@testing-library/react';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import theme from '@/theme';
|
||||||
|
|
||||||
|
const CustomWrapper = ({ children }: { children: ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
|
<BrowserRouter>{children}</BrowserRouter>
|
||||||
|
</ChakraProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const customRender = (ui: ReactElement, options: RenderOptions = {}) =>
|
||||||
|
render(ui, {
|
||||||
|
wrapper: CustomWrapper,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
// re-export everything
|
||||||
|
export * from '@testing-library/react';
|
||||||
|
|
||||||
|
// override render method
|
||||||
|
export { customRender as render };
|
24
front2/src/theme/components/badge.ts
Normal file
24
front2/src/theme/components/badge.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export default {
|
||||||
|
sizes: {
|
||||||
|
'2xs': {
|
||||||
|
fontSize: '0.5em',
|
||||||
|
},
|
||||||
|
xs: {
|
||||||
|
fontSize: '0.6em',
|
||||||
|
},
|
||||||
|
sm: {
|
||||||
|
fontSize: '0.7em',
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
fontSize: '0.8em',
|
||||||
|
textTransform: 'none',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
fontSize: '0.9em',
|
||||||
|
textTransform: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultProps: {
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
};
|
187
front2/src/theme/components/button.ts
Normal file
187
front2/src/theme/components/button.ts
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import { border, defineStyleConfig, keyframes } from '@chakra-ui/react';
|
||||||
|
import { isAccessible, mode, transparentize } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
|
import { shadows } from '@/theme/foundations/shadows';
|
||||||
|
|
||||||
|
const shimmer = keyframes`
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const customVariant = ({
|
||||||
|
theme,
|
||||||
|
bg,
|
||||||
|
bgHover = bg,
|
||||||
|
bgActive = bgHover,
|
||||||
|
color,
|
||||||
|
colorHover = color,
|
||||||
|
boxColorFocus = '#0x000000',
|
||||||
|
boxShadowHover = 'outline-over',
|
||||||
|
}) => {
|
||||||
|
const isColorAccessible = isAccessible(color, bg, {
|
||||||
|
size: 'large',
|
||||||
|
level: 'AA',
|
||||||
|
})(theme);
|
||||||
|
|
||||||
|
return {
|
||||||
|
bg,
|
||||||
|
color: isColorAccessible ? color : 'black',
|
||||||
|
border: '1px solid #00000000',
|
||||||
|
_focus: {
|
||||||
|
//border: `1px solid ${boxColorFocus}`,
|
||||||
|
border: `1px solid #000000`,
|
||||||
|
},
|
||||||
|
_hover: {
|
||||||
|
bg: bgHover,
|
||||||
|
color: isColorAccessible ? colorHover : 'black',
|
||||||
|
boxShadow: boxShadowHover,
|
||||||
|
_disabled: {
|
||||||
|
bg,
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_active: { bg: bgActive },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineStyleConfig({
|
||||||
|
variants: {
|
||||||
|
// Custom variants
|
||||||
|
'@primary': (props) =>
|
||||||
|
customVariant({
|
||||||
|
theme: props.theme,
|
||||||
|
bg: mode('brand.600', 'brand.300')(props),
|
||||||
|
bgHover: mode('brand.700', 'brand.400')(props),
|
||||||
|
bgActive: mode('brand.600', 'brand.300')(props),
|
||||||
|
color: mode('white', 'brand.900')(props),
|
||||||
|
boxColorFocus: mode('brand.100', 'brand.600')(props),
|
||||||
|
}),
|
||||||
|
'@secondary': (props) =>
|
||||||
|
customVariant({
|
||||||
|
theme: props.theme,
|
||||||
|
bg: mode('brand.100', 'brand.900')(props),
|
||||||
|
bgHover: mode('brand.200', 'brand.800')(props),
|
||||||
|
bgActive: mode('brand.300', 'brand.700')(props),
|
||||||
|
color: mode('brand.700', 'brand.50')(props),
|
||||||
|
colorHover: mode('brand.800', 'brand.100')(props),
|
||||||
|
boxColorFocus: mode('brand.900', 'brand.300')(props),
|
||||||
|
}),
|
||||||
|
'@danger': (props) =>
|
||||||
|
customVariant({
|
||||||
|
theme: props.theme,
|
||||||
|
bg: mode('error.600', 'error.300')(props),
|
||||||
|
bgHover: mode('error.700', 'error.400')(props),
|
||||||
|
bgActive: mode('error.600', 'error.300')(props),
|
||||||
|
color: mode('white', 'error.900')(props),
|
||||||
|
boxColorFocus: mode('error.900', 'error.500')(props),
|
||||||
|
}),
|
||||||
|
|
||||||
|
'@progress': (props) => ({
|
||||||
|
...customVariant({
|
||||||
|
theme: props.theme,
|
||||||
|
bg: mode(`${props.colorScheme}.500`, `${props.colorScheme}.300`)(props),
|
||||||
|
bgHover: mode(
|
||||||
|
`${props.colorScheme}.600`,
|
||||||
|
`${props.colorScheme}.400`
|
||||||
|
)(props),
|
||||||
|
bgActive: mode(
|
||||||
|
`${props.colorScheme}.700`,
|
||||||
|
`${props.colorScheme}.500`
|
||||||
|
)(props),
|
||||||
|
color: mode('white', `${props.colorScheme}.900`)(props),
|
||||||
|
boxColorFocus: mode(
|
||||||
|
`${props.colorScheme}.900`,
|
||||||
|
`${props.colorScheme}.600`
|
||||||
|
)(props),
|
||||||
|
}),
|
||||||
|
overflow: 'hidden',
|
||||||
|
|
||||||
|
_after: !props.isLoading
|
||||||
|
? {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
transform: 'translateX(-100%)',
|
||||||
|
bgGradient: `linear(90deg, ${transparentize(
|
||||||
|
`${props.colorScheme}.100`,
|
||||||
|
0
|
||||||
|
)(props.theme)} 0, ${transparentize(
|
||||||
|
`${props.colorScheme}.100`,
|
||||||
|
0.2
|
||||||
|
)(props.theme)} 20%, ${transparentize(
|
||||||
|
`${props.colorScheme}.100`,
|
||||||
|
0.5
|
||||||
|
)(props.theme)} 60%, ${transparentize(
|
||||||
|
`${props.colorScheme}.100`,
|
||||||
|
0
|
||||||
|
)(props.theme)})`,
|
||||||
|
animation: `${shimmer} 3s infinite`,
|
||||||
|
content: '""',
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
'@menu': (props) => ({
|
||||||
|
bg: mode('back.100', 'back.800')(props),
|
||||||
|
color: mode('brand.900', 'brand.100')(props),
|
||||||
|
borderRadius: 0,
|
||||||
|
border: 0,
|
||||||
|
_hover: { background: mode('back.300', 'back.600')(props) },
|
||||||
|
_focus: { border: 'none' },
|
||||||
|
fontSize: '20px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Default variants
|
||||||
|
solid: (props) => ({
|
||||||
|
bg:
|
||||||
|
props.colorScheme === 'gray'
|
||||||
|
? mode('gray.100', 'whiteAlpha.100')(props)
|
||||||
|
: `${props.colorScheme}.600`,
|
||||||
|
_hover: {
|
||||||
|
bg:
|
||||||
|
props.colorScheme === 'gray'
|
||||||
|
? mode('gray.200', 'whiteAlpha.200')(props)
|
||||||
|
: `${props.colorScheme}.700`,
|
||||||
|
},
|
||||||
|
_focus: {
|
||||||
|
boxShadow: `outline-${props.colorScheme}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
light: (props) => ({
|
||||||
|
bg:
|
||||||
|
props.colorScheme === 'gray'
|
||||||
|
? mode('gray.100', 'whiteAlpha.100')(props)
|
||||||
|
: `${props.colorScheme}.100`,
|
||||||
|
color:
|
||||||
|
props.colorScheme === 'gray'
|
||||||
|
? mode('gray.700', 'whiteAlpha.700')(props)
|
||||||
|
: `${props.colorScheme}.700`,
|
||||||
|
_hover: {
|
||||||
|
bg:
|
||||||
|
props.colorScheme === 'gray'
|
||||||
|
? mode('gray.200', 'whiteAlpha.200')(props)
|
||||||
|
: `${props.colorScheme}.200`,
|
||||||
|
color:
|
||||||
|
props.colorScheme === 'gray'
|
||||||
|
? mode('gray.800', 'whiteAlpha.800')(props)
|
||||||
|
: `${props.colorScheme}.800`,
|
||||||
|
},
|
||||||
|
_focus: {
|
||||||
|
boxShadow: `outline-${props.colorScheme}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ghost: (props) => ({
|
||||||
|
bg: transparentize(`${props.colorScheme}.50`, 0.05)(props.theme),
|
||||||
|
borderRadius: 'full',
|
||||||
|
_hover: {
|
||||||
|
bg: transparentize(`${props.colorScheme}.50`, 0.15)(props.theme),
|
||||||
|
},
|
||||||
|
_focus: {
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
5
front2/src/theme/components/checkbox.ts
Normal file
5
front2/src/theme/components/checkbox.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
defaultProps: {
|
||||||
|
colorScheme: 'brand',
|
||||||
|
},
|
||||||
|
};
|
19
front2/src/theme/components/flex.ts
Normal file
19
front2/src/theme/components/flex.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineStyleConfig } from '@chakra-ui/react';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
|
|
||||||
|
const flexTheme = defineStyleConfig({
|
||||||
|
variants: {
|
||||||
|
'@menu': (props) => ({
|
||||||
|
bg: mode('back.100', 'back.800')(props),
|
||||||
|
color: mode('brand.900', 'brand.100')(props),
|
||||||
|
borderRadius: 0,
|
||||||
|
border: 0,
|
||||||
|
_hover: { background: mode('back.300', 'back.600')(props) },
|
||||||
|
_focus: { border: 'none' },
|
||||||
|
fontSize: '20px',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default flexTheme;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user