25 Commits

Author SHA1 Message Date
979ec4b576 [FEAT] local theme integration 2025-01-23 18:09:57 +01:00
12223347d3 [FEAT] add many basic element, ux bad but build is OK 2025-01-22 00:30:16 +01:00
f9019ec508 [DEV] restart from scratch with no toolbox ==> maybe slower but funny 2025-01-21 00:54:27 +01:00
d52052de90 [DEV] test step between receipice and direct managemnt 2025-01-20 18:45:19 +01:00
91defa42c2 [FIX] chakra ui component is not full 2025-01-14 18:56:03 +01:00
2812d21782 [FEAT] doen not work: regacto of the Chakra-ui 3.3 ==> very bad port 2025-01-13 21:59:06 +01:00
eb5a366a12 [FEAT] add on air 2025-01-11 20:48:22 +01:00
6b801d250f [FIX] remove a stupid log... 2025-01-11 19:31:51 +01:00
01fad1b9d4 [FEAT] add a shuffle in the artist list.
This feature take a random on the artist and keep the 25 first and ge a single track for each artist.
2025-01-11 19:30:01 +01:00
371bea79f9 [FEAT] add a tool to manage a shuffle of an array 2025-01-11 19:28:51 +01:00
35725e1320 [FIX] sign-in in production mode 2025-01-11 19:28:30 +01:00
d6a8c7d23f [FIX] build version 2025-01-11 18:12:27 +01:00
3c604e9593 [VERSION] update dev tag version 2025-01-11 17:33:31 +01:00
43d8108270 [RELEASE] Release v1.1.0 2025-01-11 17:33:21 +01:00
1a883193d0 [DEPENDENCY] update release of archidata 2025-01-11 17:32:43 +01:00
78b1970ba9 [FEAT] (front) Update versions of dependency 2025-01-11 17:31:01 +01:00
746d5dff96 [FIX] test will not able to connect on port 80 2025-01-11 17:31:01 +01:00
8780ea8e63 [FIX] initialization error 2025-01-11 17:31:01 +01:00
8911eed0fb [FEAT] update NCU to only update vertion but not the major 2025-01-11 17:31:01 +01:00
1a3652472e [FIX] update the error response with OID instead of UUID 2025-01-11 17:31:01 +01:00
2e62577103 [FIX] (front) fix the environment semection in production mode 2025-01-11 17:31:01 +01:00
653e77160b [FEAT] update logger to well support the backend 2025-01-11 17:31:01 +01:00
3898a6bf4f [DEV] remove old logger system 2025-01-11 17:31:01 +01:00
448cf1569b [FIX] remove old loger system 2025-01-11 17:31:01 +01:00
d3e2b3e601 [VERSION] update dev tag version 2025-01-06 23:49:35 +01:00
137 changed files with 3540 additions and 5760 deletions

View File

@@ -40,6 +40,23 @@ cd front
pnpm run unlink_kar_cw pnpm run unlink_kar_cw
``` ```
Tools in production mode
========================
Changing the Log Level
----------------------
In a production environment, you can adjust the log level to help diagnose bugs more effectively.
The available log levels are:
| **Log Level Tag** | **org.kar.karusic** | **org.kar.archidata** | **other** |
| ----------------- | ------------------- | --------------------- | --------- |
| `prod` | INFO | INFO | INFO |
| `prod-debug` | DEBUG | INFO | INFO |
| `prod-trace` | TRACE | DEBUG | INFO |
| `prod-trace-full` | TRACE | TRACE | INFO |
| `dev` | TRACE | DEBUG | INFO |
Manual set in production: Manual set in production:
========================= =========================

View File

@@ -1,7 +1,8 @@
FROM maven:3-openjdk-18 AS build FROM maven:3-openjdk-23 AS build
COPY pom.xml /tmp/ COPY pom.xml /tmp/
COPY src /tmp/src/ COPY src /tmp/src/
COPY Formatter.xml /tmp/
WORKDIR /tmp/ WORKDIR /tmp/
RUN mvn clean compile assembly:single RUN mvn clean compile assembly:single

View File

@@ -3,11 +3,11 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.kar</groupId> <groupId>org.kar</groupId>
<artifactId>karusic</artifactId> <artifactId>karusic</artifactId>
<version>1.0.4</version> <version>1.1.1-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.version>3.1</maven.compiler.version> <maven.compiler.version>3.13.0</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>23</maven.compiler.target>
<maven.dependency.version>3.1.1</maven.dependency.version> <maven.dependency.version>3.1.1</maven.dependency.version>
</properties> </properties>
<repositories> <repositories>
@@ -20,7 +20,7 @@
<dependency> <dependency>
<groupId>kangaroo-and-rabbit</groupId> <groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.20.4</version> <version>0.21.0</version>
</dependency> </dependency>
<!-- Loopback of logger JDK logging API to SLF4J --> <!-- Loopback of logger JDK logging API to SLF4J -->
<dependency> <dependency>
@@ -39,10 +39,15 @@
<artifactId>xercesImpl</artifactId> <artifactId>xercesImpl</artifactId>
<version>2.12.2</version> <version>2.12.2</version>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.1.9</version>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.0-rc1</version> <version>2.18.1</version>
</dependency> </dependency>
<!-- <!--
************************************************************ ************************************************************
@@ -108,7 +113,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version> <version>4.0.0-beta-1</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
@@ -122,10 +127,12 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version> <version>3.2.5</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
@@ -137,81 +144,21 @@
</descriptorRefs> </descriptorRefs>
</configuration> </configuration>
</plugin> </plugin>
<!-- Create coverage -->
<!--
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
-->
<!-- Java-doc generation for stand-alone site --> <!-- Java-doc generation for stand-alone site -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>private</show> <show>private</show>
<nohelp>true</nohelp> <nohelp>true</nohelp>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>exec-application</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.kar.karusic.WebLauncher</mainClass>
</configuration>
</plugin>
<!-- Check the style of the code --> <!-- Check the style of the code -->
<plugin> <plugin>
<groupId>net.revelc.code.formatter</groupId> <groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId> <artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version> <version>2.24.1</version>
<configuration> <configuration>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding> <lineEnding>LF</lineEnding>
@@ -242,14 +189,6 @@
<configuration> <configuration>
<includeFilterFile>spotbugs-security-include.xml</includeFilterFile> <includeFilterFile>spotbugs-security-include.xml</includeFilterFile>
<excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile> <excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile>
<!--<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.12.0</version>
</plugin>
</plugins>
-->
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
@@ -260,7 +199,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>public</show> <show>public</show>
</configuration> </configuration>

View File

@@ -1,60 +0,0 @@
package org.kar.karusic.internal;
//import io.scenarium.logger.LogLevel;
//import io.scenarium.logger.Logger;
public class Log {
// private static final String LIB_NAME = "logger";
// private static final String LIB_NAME_DRAW = Logger.getDrawableName(LIB_NAME);
// private static final boolean PRINT_CRITICAL = Logger.getNeedPrint(LIB_NAME, LogLevel.CRITICAL);
// private static final boolean PRINT_ERROR = Logger.getNeedPrint(LIB_NAME, LogLevel.ERROR);
// private static final boolean PRINT_WARNING = Logger.getNeedPrint(LIB_NAME, LogLevel.WARNING);
// private static final boolean PRINT_INFO = Logger.getNeedPrint(LIB_NAME, LogLevel.INFO);
// private static final boolean PRINT_DEBUG = Logger.getNeedPrint(LIB_NAME, LogLevel.DEBUG);
// private static final boolean PRINT_VERBOSE = Logger.getNeedPrint(LIB_NAME, LogLevel.VERBOSE);
// private static final boolean PRINT_TODO = Logger.getNeedPrint(LIB_NAME, LogLevel.TODO);
// private static final boolean PRINT_PRINT = Logger.getNeedPrint(LIB_NAME, LogLevel.PRINT);
//
// private Log() {}
//
// public static void print(String data) {
// if (PRINT_PRINT)
// Logger.print(LIB_NAME_DRAW, data);
// }
//
// public static void todo(String data) {
// if (PRINT_TODO)
// Logger.todo(LIB_NAME_DRAW, data);
// }
//
// public static void critical(String data) {
// if (PRINT_CRITICAL)
// Logger.critical(LIB_NAME_DRAW, data);
// }
//
// public static void error(String data) {
// if (PRINT_ERROR)
// Logger.error(LIB_NAME_DRAW, data);
// }
//
// public static void warning(String data) {
// if (PRINT_WARNING)
// Logger.warning(LIB_NAME_DRAW, data);
// }
//
// public static void info(String data) {
// if (PRINT_INFO)
// Logger.info(LIB_NAME_DRAW, data);
// }
//
// public static void debug(String data) {
// if (PRINT_DEBUG)
// Logger.debug(LIB_NAME_DRAW, data);
// }
//
// public static void verbose(String data) {
// if (PRINT_VERBOSE)
// Logger.verbose(LIB_NAME_DRAW, data);
// }
}

View File

@@ -25,14 +25,10 @@ public class Initialization extends MigrationSqlStep {
return "Initialization"; return "Initialization";
} }
public Initialization() {
}
@Override @Override
public void generateStep() throws Exception { public void generateStep() throws Exception {
for (final Class<?> elem : CLASSES_BASE) { for (final Class<?> clazz : CLASSES_BASE) {
addClass(elem); addClass(clazz);
} }
addAction((final DBAccess da) -> { addAction((final DBAccess da) -> {
@@ -63,6 +59,7 @@ public class Initialization extends MigrationSqlStep {
new Gender(24L, "Bande Originale"), // new Gender(24L, "Bande Originale"), //
new Gender(25L, "Variété Belge"), // new Gender(25L, "Variété Belge"), //
new Gender(26L, "Gospel")); new Gender(26L, "Gospel"));
da.insertMultiple(data);
}); });
// set start increment element to permit to add after default elements // set start increment element to permit to add after default elements
addAction(""" addAction("""

View File

@@ -1,18 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<!-- environment detection (defaut: dev) -->
<property name="LOG_LEVEL_ENV" value="${LOG_LEVEL:-dev}" />
<!-- Appender for development -->
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;dev&quot;)">
<then>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern> <pattern>%green(%d{HH:mm:ss.SSS}) %highlight(%-5level) %-30((%file:%line\)): %msg%n</pattern>
%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger - %msg%n
</pattern>
<!--
<pattern>
%d{HH:mm:ss.SSS} | %thread | %highlight(%-5level) | %logger - %msg%n
</pattern>
-->
</encoder> </encoder>
</appender> </appender>
<logger name="org.kar.karusic" level="TRACE" />
<root level="info"> <logger name="org.kar.archidata" level="DEBUG" />
<root level="INFO">
<appender-ref ref="CONSOLE" /> <appender-ref ref="CONSOLE" />
</root> </root>
</then>
</if>
<!-- Appender for production -->
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).matches(&quot;^prod.*&quot;)">
<then>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%thread] %level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</then>
</if>
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;prod-debug&quot;)">
<then>
<logger name="org.kar.karusic" level="DEBUG" />
</then>
</if>
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;prod-trace&quot;)">
<then>
<logger name="org.kar.karusic" level="TRACE" />
<logger name="org.kar.archidata" level="DEBUG" />
</then>
</if>
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;prod-trace-full&quot;)">
<then>
<logger name="org.kar.karusic" level="TRACE" />
<logger name="org.kar.archidata" level="TRACE" />
</then>
</if>
</configuration> </configuration>

View File

@@ -1,42 +0,0 @@
# SLF4J's SimpleLogger configuration file
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
# Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info".
org.slf4j.simpleLogger.defaultLogLevel=INFO
# Logging detail level for a SimpleLogger instance named "xxxxx".
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, the default logging detail level is used.
#org.slf4j.simpleLogger.log.xxxxx=
org.slf4j.simpleLogger.log.org.kar.archidata=TRACE
org.slf4j.simpleLogger.log.org.kar.karusic=TRACE
# Set to true if you want the current date and time to be included in output messages.
# Default is false, and will output the number of milliseconds elapsed since startup.
#org.slf4j.simpleLogger.showDateTime=false
# The date and time format to be used in the output messages.
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
# If the format is not specified or is invalid, the default format is used.
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
# Set to true if you want to output the current thread name.
# Defaults to true.
org.slf4j.simpleLogger.showThreadName=true
# Set to true if you want the Logger instance name to be included in output messages.
# Defaults to true.
#org.slf4j.simpleLogger.showLogName=true
# Set to true if you want the last component of the name to be included in output messages.
# Defaults to false.
#org.slf4j.simpleLogger.showShortLogName=false
# Utilise les codes ANSI pour la couleur
org.slf4j.simpleLogger.warnColor=\u001B[33m
org.slf4j.simpleLogger.errorColor=\u001B[31m
org.slf4j.simpleLogger.infoColor=\u001B[32m
org.slf4j.simpleLogger.debugColor=\u001B[34m

View File

@@ -35,6 +35,10 @@ public class ConfigureDb {
if (modeTestForced != null) { if (modeTestForced != null) {
modeTest = modeTestForced; modeTest = modeTestForced;
} }
// for local test:
ConfigBaseVariable.apiAdress = "http://127.0.0.1:12342/test/api/";
// Enable the test mode permit to access to the test token (never use it in production).
ConfigBaseVariable.testMode = "true";
final List<Class<?>> listObject = List.of( // final List<Class<?>> listObject = List.of( //
Album.class, // Album.class, //
Artist.class, // Artist.class, //

View File

@@ -25,8 +25,6 @@ public class TestBase {
ConfigureDb.configure(); ConfigureDb.configure();
LOGGER.info("configure server ..."); LOGGER.info("configure server ...");
webInterface = new WebLauncherTest(); webInterface = new WebLauncherTest();
LOGGER.info("Clean previous table");
LOGGER.info("Start REST (BEGIN)"); LOGGER.info("Start REST (BEGIN)");
webInterface.process(); webInterface.process();
LOGGER.info("Start REST (DONE)"); LOGGER.info("Start REST (DONE)");

View File

@@ -1,6 +1,6 @@
{ {
"display": "2025-01-06", "display": "2025-01-14",
"version": "0.0.1-dev\n - 2025-01-06T00:49:52+01:00", "version": "0.0.1-dev\n - 2025-01-14T20:19:20+01:00",
"commit": "0.0.1-dev\n", "commit": "0.0.1-dev\n",
"date": "2025-01-06T00:49:52+01:00" "date": "2025-01-14T20:19:20+01:00"
} }

View File

@@ -12,7 +12,8 @@
"node": ">=20" "node": ">=20"
}, },
"scripts": { "scripts": {
"update_packages": "ncu --upgrade", "update_packages": "ncu --target minor",
"upgrade_packages": "ncu --upgrade ",
"install_dependency": "pnpm install", "install_dependency": "pnpm install",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest watch", "test:watch": "vitest watch",
@@ -28,46 +29,18 @@
"*.{ts,tsx,js,jsx,json}": "prettier --write" "*.{ts,tsx,js,jsx,json}": "prettier --write"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/anatomy": "2.2.2",
"@chakra-ui/cli": "2.4.1",
"@chakra-ui/react": "2.8.2",
"@chakra-ui/theme-tools": "2.2.6",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/modifiers": "9.0.0",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@emotion/react": "11.14.0",
"@emotion/styled": "11.14.0",
"@formiz/core": "2.4.5",
"@formiz/validations": "2.0.1",
"allotment": "1.20.2",
"css-mediaquery": "0.1.2",
"dayjs": "1.11.13",
"history": "5.3.0", "history": "5.3.0",
"react": "18.3.1", "react": "18.3.1",
"react-color-palette": "7.3.0",
"react-currency-input-field": "3.9.0",
"react-custom-scrollbars": "4.2.1",
"react-day-picker": "9.5.0",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-error-boundary": "4.0.13", "react-error-boundary": "5.0.0",
"react-focus-lock": "2.13.2", "react-icons": "5.4.0",
"react-icons": "5.3.0", "react-router-dom": "7.1.1",
"react-popper": "2.3.0",
"react-router-dom": "6.26.2",
"react-select": "5.9.0", "react-select": "5.9.0",
"react-simple-keyboard": "3.8.33",
"react-sticky-el": "2.1.1",
"react-use": "17.6.0", "react-use": "17.6.0",
"react-use-draggable-scroll": "0.4.7",
"react-virtuoso": "4.12.3",
"ts-pattern": "5.6.0",
"uuid": "11.0.4",
"zod": "3.24.1", "zod": "3.24.1",
"zustand": "5.0.2" "zustand": "5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@chakra-ui/styled-system": "2.12.0",
"@playwright/test": "1.49.1", "@playwright/test": "1.49.1",
"@storybook/addon-actions": "8.4.7", "@storybook/addon-actions": "8.4.7",
"@storybook/addon-essentials": "8.4.7", "@storybook/addon-essentials": "8.4.7",
@@ -81,30 +54,28 @@
"@testing-library/user-event": "14.5.2", "@testing-library/user-event": "14.5.2",
"@trivago/prettier-plugin-sort-imports": "5.2.1", "@trivago/prettier-plugin-sort-imports": "5.2.1",
"@types/jest": "29.5.14", "@types/jest": "29.5.14",
"@types/node": "22.10.5", "@types/node": "22.10.6",
"@types/react": "18.3.8", "@types/react": "18.3.8",
"@types/react-dom": "18.3.0", "@types/react-dom": "18.3.0",
"@types/react-sticky-el": "1.0.7", "@typescript-eslint/eslint-plugin": "8.20.0",
"@typescript-eslint/eslint-plugin": "8.19.0", "@typescript-eslint/parser": "8.20.0",
"@typescript-eslint/parser": "8.19.0",
"@vitejs/plugin-react": "4.3.4", "@vitejs/plugin-react": "4.3.4",
"eslint": "9.17.0", "eslint": "9.18.0",
"eslint-plugin-codeceptjs": "1.3.0", "eslint-plugin-codeceptjs": "1.3.0",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.3", "eslint-plugin-react": "7.37.4",
"eslint-plugin-react-hooks": "5.1.0", "eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-storybook": "0.11.2", "eslint-plugin-storybook": "0.11.2",
"jest": "29.7.0", "jest": "29.7.0",
"jest-environment-jsdom": "29.7.0", "jest-environment-jsdom": "29.7.0",
"knip": "5.41.1", "knip": "5.42.0",
"lint-staged": "15.3.0", "lint-staged": "15.3.0",
"npm-check-updates": "^17.1.13", "npm-check-updates": "^17.1.13",
"prettier": "3.4.2", "prettier": "3.4.2",
"puppeteer": "23.11.1",
"react-is": "19.0.0", "react-is": "19.0.0",
"storybook": "8.4.7", "storybook": "8.4.7",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"typescript": "5.7.2", "typescript": "5.7.3",
"vite": "6.0.7", "vite": "6.0.7",
"vitest": "2.1.8" "vitest": "2.1.8"
} }

3447
front/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,13 @@
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 { App as SpaApp } from '@/scene/App';
import { USERS } from '@/service/session'; import { Div, FullPage } from './ui';
import theme from '@/theme'; import { useDisclosure } from './utils/disclosure';
import { hashLocalData } from '@/utils/sso'; import { useState } from 'react';
import { environment } from './environment';
import { hashLocalData } from './utils/sso';
import { USERS } from './service/session';
const AppEnvHint = () => { const AppEnvHint = () => {
const modal = useDisclosure(); const dialog = useDisclosure();
const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER'); const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER');
//const setUser = useRightsStore((store) => store.setUser); //const setUser = useRightsStore((store) => store.setUser);
const buildEnv = const buildEnv =
@@ -40,7 +24,7 @@ const AppEnvHint = () => {
setSelectUserTest(selectedOption.target.value); setSelectUserTest(selectedOption.target.value);
}; };
const onClose = () => { const onClose = () => {
modal.onClose(); dialog.onClose();
if (selectUserTest == 'NO_USER') { if (selectUserTest == 'NO_USER') {
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`; window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`;
} else { } else {
@@ -50,68 +34,82 @@ const AppEnvHint = () => {
return ( return (
<> <>
<Box <Div style={{
zIndex="100000" zIndex: "100000",
position="fixed" position: "fixed",
top="0" top: "0",
insetStart="0" height: "2px",
insetEnd="0" width: "100%",
h="2px" background: "warning.400",
bg="warning.400" cursor: "pointer"
as="button" }}
cursor="pointer"
data-test-id="devtools" data-test-id="devtools"
onClick={modal.onOpen} onClick={dialog.onOpen}
> >
<Text <Div style={{
position="fixed" position: "fixed",
top="0" top: "0",
insetStart="4" background: "warning.400",
bg="warning.400" color: "warning.900",
color="warning.900" fontSize: "0.6rem",
fontSize="0.6rem" fontWeight: "bold",
fontWeight="bold" paddingLeft: "10px",
px="10px" paddingRight: "10px",
marginLeft="25%" marginLeft: "25%",
borderBottomStartRadius="sm" borderRadius: "0 0 10px 10px",
borderBottomEndRadius="sm" textTransform: "uppercase",
textTransform="uppercase" }}
> >
{envName.join(' : ')} {envName.join(' : ')}
</Text> </Div>
</Box> </Div>
<Modal isOpen={modal.isOpen} onClose={modal.onClose}> {/* <Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}>
<ModalOverlay /> <Dialog.Trigger asChild>
<ModalContent> <Button>
<ModalHeader>Outils développeurs</ModalHeader> {dialog.open ? "Close" : "Open"} Dialog
<ModalCloseButton /> </Button>
<ModalBody> </Dialog.Trigger>
<Stack> <Portal>
<Text>Utilisateur</Text> <Dialog.Backdrop />
<Select placeholder="Select test user" onChange={handleChange}> <Dialog.Positioner>
{Object.keys(USERS).map((key) => ( <Dialog.Content>
<option value={key} key={key}> <Dialog.Title>Outils développeurs</Dialog.Title>
{key} <Dialog.Description>
</option> <HStack>
<Text>User</Text>
<Select.Root onChange={handleChange} collection={USERS_COLLECTION}>
<Select.Trigger>
<SelectValueText placeholder="Select test user" />
</Select.Trigger>
<Select.Content>
{USERS_COLLECTION.items.map((value) => (
<Select.Item item={value} key={value.value}>
{value.label}
</Select.Item>
))} ))}
</Select> </Select.Content>
</Stack> </Select.Root>
</ModalBody> </HStack>
<ModalFooter> </Dialog.Description>
<Dialog.CloseTrigger>
<Button onClick={onClose}>Apply</Button> <Button onClick={onClose}>Apply</Button>
</ModalFooter> </Dialog.CloseTrigger>
</ModalContent> </Dialog.Content>
</Modal> </Dialog.Positioner>
</Portal>
</Dialog.Root> */}
</> </>
); );
}; };
const App = () => { const App = () => {
return ( return (
<ChakraProvider theme={theme}> <FullPage data-test-id="Full-root-page">
<AppEnvHint /> <AppEnvHint />
<SpaApp /> <SpaApp data-test-id="app" />
</ChakraProvider> {/* <Toaster /> */}
</FullPage>
); );
}; };

View File

@@ -3,11 +3,11 @@
*/ */
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodUUID} from "./uuid"; import {ZodObjectId} from "./object-id";
import {ZodInteger} from "./integer"; import {ZodInteger} from "./integer";
export const ZodRestErrorResponse = zod.object({ export const ZodRestErrorResponse = zod.object({
uuid: ZodUUID.optional(), oid: ZodObjectId.optional(),
name: zod.string(), name: zod.string(),
message: zod.string(), message: zod.string(),
time: zod.string(), time: zod.string(),

View File

@@ -1,26 +1,13 @@
import { SyntheticEvent, useEffect, useRef, useState } from 'react'; import { SyntheticEvent, useEffect, useRef, useState } from 'react';
import { import {
Box,
Button,
Flex,
IconButton,
Slider,
SliderFilledTrack,
SliderThumb,
SliderTrack,
Text,
position,
} from '@chakra-ui/react';
import {
MdCheck,
MdFastForward, MdFastForward,
MdFastRewind, MdFastRewind,
MdGraphicEq, MdGraphicEq,
MdLooksOne, MdLooksOne,
MdNavigateBefore, MdNavigateBefore,
MdNavigateNext, MdNavigateNext,
MdOutlinePlayArrow,
MdPause, MdPause,
MdPlayArrow, MdPlayArrow,
MdRepeat, MdRepeat,
@@ -35,8 +22,10 @@ import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track'; import { useSpecificTrack } from '@/service/Track';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Icon } from './Icon';
import { Flex, Text } from '@/ui';
export enum PlayMode { export enum PlayMode {
PLAY_ONE, PLAY_ONE,
@@ -66,7 +55,6 @@ const formatTime = (time) => {
}; };
export const AudioPlayer = ({ }: AudioPlayerProps) => { export const AudioPlayer = ({ }: AudioPlayerProps) => {
const { mode } = useThemeMode();
const { playTrackList, trackOffset, previous, next, first } = const { playTrackList, trackOffset, previous, next, first } =
useActivePlaylistService(); useActivePlaylistService();
const audioRef = useRef<HTMLAudioElement>(null); const audioRef = useRef<HTMLAudioElement>(null);
@@ -89,14 +77,17 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
: '' : ''
); );
}, [dataTrack, setMediaSource]); }, [dataTrack, setMediaSource]);
const backColor = mode('back.100', 'back.800'); const backColor = useColorThemeValue('back.100', 'back.800');
const configButton = { const configButton = {
borderRadius: 'full', borderRadius: 'full',
backgroundColor: '#00000000', background: '#00000000',
_hover: { _hover: {
boxShadow: 'outline-over', boxShadow: 'outline-over',
bgColor: 'brand.500', bgColor: 'brand.500',
}, },
width: "50px",
height: "50px",
padding: "5px",
}; };
useEffect(() => { useEffect(() => {
@@ -213,131 +204,129 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
<> <>
{!isNullOrUndefined(trackOffset) && ( {!isNullOrUndefined(trackOffset) && (
<Flex <Flex
position="absolute" style={{
height="150px" position: "absolute",
minHeight="150px" height: "150px",
paddingY="5px" minHeight: "150px",
paddingX="10px" padding: "5px 10px 5px 10px",
marginX="15px" margin: "0 15px 0 15px",
bottom={0} bottom: 0,
left={0} left: 0,
right={0} right: 0,
zIndex={1000} zIndex: 1000,
borderWidth="1px" borderWidth: "1px",
borderColor="brand.900" borderColor: "brand.900",
bgColor={backColor} background: backColor,
borderTopRadius="10px" borderRadius: "10px 10px 0 0",
}}
direction="column" direction="column"
> >
<Text <Text
align="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none" style={{
marginRight="auto" alignContent: "left",
overflow="hidden" userSelect: "none",
noOfLines={1} marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={1}
> >
{dataTrack?.name ?? '???'} {dataTrack?.name ?? '???'}
</Text> </Text>
<Text <Text
align="left"
fontSize="16px" fontSize="16px"
userSelect="none" style={{
marginRight="auto" alignContent: "left",
overflow="hidden" userSelect: "none",
noOfLines={1} marginRight: "auto",
overflow: "hidden",
//noOfLines:1
}}
> >
{dataArtists.map((data) => data.name).join(', ')} /{' '} {dataArtists.map((data) => data.name).join(', ')} /{' '}
{dataAlbum && dataAlbum?.name} {dataAlbum && dataAlbum?.name}
{dataGender && ` / ${dataGender.name}`} {dataGender && ` / ${dataGender.name}`}
</Text> </Text>
<Box width="full" paddingX="15px"> <Flex style={{ width: "100%", padding: "0 15px 0 15px" }}>
<Slider <>TODO ... </>
aria-label="slider-ex-4" {/* <Slider.Root
defaultValue={0} defaultValue={[0]}
value={timeProgress} value={[timeProgress]}
min={0} min={0}
max={duration} max={duration}
step={0.1} step={0.1}
onChange={onSeek} onChange={onSeek}
focusThumbOnChange={false} variant="outline"
// focusThumbOnChange={false}
> >
<SliderTrack bg="gray.200" height="10px" borderRadius="full"> <SliderTrack bg="gray.200" height="10px" borderRadius="full">
<SliderFilledTrack bg="brand.600" /> {/ * <SliderFilledTrack bg="brand.600" /> * /}
</SliderTrack> </SliderTrack>
<SliderThumb boxSize={6}> </Slider.Root> */}
<Box color="brand.600" as={MdGraphicEq} /> </Flex>
</SliderThumb>
</Slider>
</Box>
<Flex> <Flex>
<Text <Text
align="left"
fontSize="16px" fontSize="16px"
userSelect="none" style={{
marginRight="auto" alignContent: "left",
overflow="hidden" userSelect: "none",
noOfLines={1} marginRight: "auto",
overflow: "hidden",
// noOfLines={1},
}}
> >
{formatTime(timeProgress)} {formatTime(timeProgress)}
</Text> </Text>
<Text align="left" fontSize="16px" userSelect="none"> <Text fontSize="16px" style={{ alignContent: "left", userSelect: "none" }}>
{formatTime(duration)} {formatTime(duration)}
</Text> </Text>
</Flex> </Flex>
<Flex gap="5px"> {/* <Flex gap="5px">
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Play'} aria-label={'Play'}
icon={ onClick={onPlay}
isPlaying ? ( >
{isPlaying ? (
<MdPause size="30px" /> <MdPause size="30px" />
) : ( ) : (
<MdPlayArrow size="30px" /> <MdPlayArrow size="30px" />
) )}
} </IconButton>
onClick={onPlay}
/>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Stop'} aria-label={'Stop'}
icon={<MdStop size="30px" />}
onClick={onStop} onClick={onStop}
/> ><MdStop size="30px" /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Previous track'} aria-label={'Previous track'}
icon={<MdNavigateBefore size="30px" />}
onClick={onNavigatePrevious} onClick={onNavigatePrevious}
marginLeft="auto" marginLeft="auto"
/> ><MdNavigateBefore size="30px" /> </IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'jump 15sec in past'} aria-label={'jump 15sec in past'}
icon={<MdFastRewind size="30px" />}
onClick={onFastRewind} onClick={onFastRewind}
/> ><MdFastRewind size="30px" /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'jump 15sec in future'} aria-label={'jump 15sec in future'}
icon={<MdFastForward size="30px" />}
onClick={onFastForward} onClick={onFastForward}
/> ><MdFastForward style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'Next track'} aria-label={'Next track'}
icon={<MdNavigateNext size="30px" />}
marginRight="auto" marginRight="auto"
onClick={onNavigateNext} onClick={onNavigateNext}
/> ><MdNavigateNext style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'continue to the end'} aria-label={'continue to the end'}
icon={playModeIcon[playingMode]}
onClick={onTypePlay} onClick={onTypePlay}
/> >{playModeIcon[playingMode]}</IconButton>
</Flex> </Flex> */}
</Flex> </Flex>
)} )}

View File

@@ -1,16 +1,14 @@
import { ReactElement, useEffect, useState } from 'react'; import { CSSProperties, ReactElement, useEffect, useState } from 'react';
import { As, Box, BoxProps, Flex, StyleProps } from '@chakra-ui/react';
import { Image } from '@chakra-ui/react';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { ObjectId } from '@/back-api'; import { ObjectId } from '@/back-api';
import { DivProps, Flex } from '@/ui';
export type CoversProps = BoxProps & { export type CoversProps = Omit<DivProps, "iconEmpty"> & {
data?: ObjectId[]; data?: ObjectId[];
size?: StyleProps["width"]; size?: CSSProperties["width"];
iconEmpty?: As; iconEmpty?: ReactElement;
slideshow?: boolean; slideshow?: boolean;
}; };
@@ -45,26 +43,34 @@ export const Covers = ({
return <Icon icon={iconEmpty} sizeIcon={size} />; return <Icon icon={iconEmpty} sizeIcon={size} />;
} else { } else {
return ( return (
<Box <Flex
width={size} style={{
height={size} width: size,
minHeight={size} height: size,
minWidth={size} minHeight: size,
borderColor="blue" minWidth: size,
borderWidth="1px" borderColor: "blue",
margin="auto" borderWidth: "1px",
margin: "auto",
}}
{...rest} {...rest}
></Box> ></Flex>
); );
} }
} }
if (slideshow === false || data.length === 1) { if (slideshow === false || data.length === 1) {
const url = DataUrlAccess.getThumbnailUrl(data[0]); const url = DataUrlAccess.getThumbnailUrl(data[0]);
return <Image loading="lazy" src={url} maxWidth={size} boxSize={size} {...rest} />; return <></>;//<image loading="lazy" src={url} maxWidth={size} boxSize={size} /*{...rest}*/ />;
} }
const urlCurrent = DataUrlAccess.getThumbnailUrl(data[currentImageIndex]); const urlCurrent = DataUrlAccess.getThumbnailUrl(data[currentImageIndex]);
const urlPrevious = DataUrlAccess.getThumbnailUrl(data[previousImageIndex]); const urlPrevious = DataUrlAccess.getThumbnailUrl(data[previousImageIndex]);
return <Flex position="relative" {...rest} maxWidth={size} width={size} height={size} overflow="hidden"> return <></>/*<Flex
position="relative"
// {...rest}
maxWidth={size}
width={size}
height={size}
overflow="hidden">
<Image <Image
src={urlPrevious} src={urlPrevious}
loading="lazy" loading="lazy"
@@ -89,5 +95,5 @@ export const Covers = ({
opacity={topOpacity} opacity={topOpacity}
zIndex={2} zIndex={2}
/> />
</Flex> </Flex>*/
}; };

View File

@@ -1,13 +1,15 @@
import { Box } from '@chakra-ui/react'; import { Flex } from "@/ui";
export const EmptyEnd = () => { export const EmptyEnd = () => {
return ( return (
<Box <Flex
width="full" style={{
height="25%" width: "100%",
minHeight="250px" height: "25%",
minHeight: "250px",
}}
// borderWidth="1px" // borderWidth="1px"
// borderColor="red" // borderColor="red"
></Box> ></Flex>
); );
}; };

View File

@@ -1,42 +1,42 @@
import {
As, import { Flex, FlexProps } from '@/ui';
Box, import { forwardRef, ReactNode } from 'react';
BoxProps,
Icon as ChakraIcon,
IconProps as ChakraIconProps,
Flex,
FlexProps,
forwardRef,
LayoutProps,
} from '@chakra-ui/react';
export type IconProps = FlexProps & { export type IconProps = FlexProps & {
icon: As; icon: ReactNode;
color?: string; color?: string;
sizeIcon?: LayoutProps['width']; sizeIcon?: FlexProps['width'];
}; };
export const Icon = forwardRef<IconProps, 'span'>( export const Icon = forwardRef<HTMLDivElement, IconProps>(
({ icon: IconEl, color, sizeIcon = '1em', ...rest }, ref) => { ({ icon: IconEl, color, sizeIcon = '1em', style, ...rest }, ref) => {
return ( return (
<Flex flex="none" <Flex
minWidth={sizeIcon}
minHeight={sizeIcon}
maxWidth={sizeIcon}
maxHeight={sizeIcon}
align="center" align="center"
padding="1px" style={{
ref={ref} flex: "none",
minWidth: sizeIcon,
minHeight: sizeIcon,
maxWidth: sizeIcon,
maxHeight: sizeIcon,
padding: "1px",
...style
}}
{...rest}> {...rest}>
<Box <Flex
marginX="auto" style={{
as={IconEl} margin: "0 auto 0 auto",
width="100%" width: "100%",
minWidth="100%" minWidth: "100%",
height="100%" height: "100%",
color={color} color: color,
/> }}
>
{IconEl}
</Flex>
</Flex> </Flex>
); );
} }
); );
Icon.displayName = 'Icon';

View File

@@ -1,10 +1,10 @@
import React, { ReactNode, useEffect } from 'react'; import React, { ReactNode, useEffect } from 'react';
import { Flex, Image } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import background from '@/assets/images/ikon.svg'; import background from '@/assets/images/ikon.svg';
import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar'; import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar';
import { Flex, Image } from '@/ui';
export type LayoutProps = React.PropsWithChildren<unknown> & { export type LayoutProps = React.PropsWithChildren<unknown> & {
topBar?: ReactNode; topBar?: ReactNode;
@@ -20,30 +20,34 @@ export const PageLayout = ({ children }: LayoutProps) => {
return ( return (
<> <>
<Flex <Flex
minH={`calc(100vh - ${TOP_BAR_HEIGHT})`} style={{
maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`} position: "absolute",
position="absolute" minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`,
top={TOP_BAR_HEIGHT} maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`,
bottom={0} top: TOP_BAR_HEIGHT,
left={0} bottom: 0,
right={0} left: 0,
minWidth="300px" right: 0,
zIndex={-1} minWidth: "300px",
zIndex: -1,
background: "back.800",
}}
> >
<Image src={background} boxSize="90%" margin="auto" opacity="30%" /> <Image src={background} boxSize="90%" style={{ margin: "auto", opacity: "30%" }} />
</Flex> </Flex>
<Flex <Flex
direction="column" direction="column"
overflowX="auto" style={{
overflowY="auto" overflow: "auto",
minH={`calc(100vh - ${TOP_BAR_HEIGHT})`} minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`,
maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`} maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`,
position="absolute" position: "absolute",
top={TOP_BAR_HEIGHT} top: TOP_BAR_HEIGHT,
bottom={0} bottom: 0,
left={0} left: 0,
right={0} right: 0,
minWidth="300px" minWidth: "300px",
}}
> >
{children} {children}
</Flex> </Flex>

View File

@@ -1,19 +1,20 @@
import React, { ReactNode, useEffect } from 'react'; import { CSSProperties, useEffect } from 'react';
import { Flex, FlexProps } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { colors } from '@/theme/foundations/colors'; import { basicColor } from '@/theme/colors';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { Flex, FlexProps } from '@/ui';
export type LayoutProps = FlexProps & { export type LayoutProps = FlexProps & {
children: ReactNode; width?: CSSProperties['width'];
}; };
export const PageLayoutInfoCenter = ({ export const PageLayoutInfoCenter = ({
children, children,
width = '25%', width = "75%",
style,
...rest ...rest
}: LayoutProps) => { }: LayoutProps) => {
const { pathname } = useLocation(); const { pathname } = useLocation();
@@ -22,19 +23,21 @@ export const PageLayoutInfoCenter = ({
window.scrollTo(0, 0); window.scrollTo(0, 0);
}, [pathname]); }, [pathname]);
const { mode } = useThemeMode();
return ( return (
<PageLayout> <PageLayout>
<Flex <Flex
direction="column" direction="column"
margin="auto" style={{
minWidth={width} margin: "auto",
border="back.900" width,
borderWidth="1px" border: "back.900",
borderRadius="8px" borderWidth: "1px",
padding="10px" borderRadius: "8px",
boxShadow={'0px 0px 16px ' + colors.back[900]} padding: "10px",
backgroundColor={mode('#FFFFFF', '#000000')} boxShadow: '0px 0px 16px ' + basicColor.back[900],
background: useColorThemeValue('#FFFFFF', '#000000'),
...style
}}
{...rest} {...rest}
> >
{children} {children}

View File

@@ -1,10 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import {
Input,
InputGroup,
InputLeftElement,
} from '@chakra-ui/react';
import { MdSearch } from 'react-icons/md'; import { MdSearch } from 'react-icons/md';
export type SearchInputProps = { export type SearchInputProps = {
@@ -43,17 +38,15 @@ export const SearchInput = ({
onSubmitValue(inputData); onSubmitValue(inputData);
} }
} }
return ( return (<></>
<InputGroup maxWidth="200px" marginLeft="auto" {...searchInputProperty}> //<Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}>
<InputLeftElement pointerEvents="none"> // <MdSearch color="gray.300" />
<MdSearch color="gray.300" /> // <Input
</InputLeftElement> // onFocus={onFocusKeep}
<Input // onBlur={() => setTimeout(() => onFocusLost(), 200)}
onFocus={onFocusKeep} // onChange={onChange}
onBlur={() => setTimeout(() => onFocusLost(), 200)} // onSubmit={onSubmit}
onChange={onChange} // />
onSubmit={onSubmit} //</Group>
/>
</InputGroup>
); );
}; };

View File

@@ -1,49 +1,31 @@
import { ReactNode } from 'react'; 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 { import {
LuAlignJustify, LuAlignJustify,
LuArrowBigLeft, LuArrowBigLeft,
LuArrowUpSquare,
LuHelpCircle,
LuHome,
LuLogIn, LuLogIn,
LuLogOut, LuLogOut,
LuMoon, LuMoon,
LuPlusCircle,
LuSettings, LuSettings,
LuSun, LuSun,
LuUserCircle,
} from 'react-icons/lu'; } from 'react-icons/lu';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState'; import { SessionState } from '@/service/SessionState';
import { colors } from '@/theme/foundations/colors'; import { basicColor } from '@/theme/colors';
import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso'; import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
import { useThemeMode } from '@/utils/theme-tools';
import { useSessionService } from '@/service/session'; import { useSessionService } from '@/service/session';
import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md';
import { useColorThemeValue, useTheme } from '@/theme/ThemeContext';
import { useDisclosure } from '@/utils/disclosure';
import { Button, Flex, Text } from '@/ui';
export const TOP_BAR_HEIGHT = '50px'; export const TOP_BAR_HEIGHT = '50px';
export const BUTTON_TOP_BAR_PROPERTY = { export const BUTTON_TOP_BAR_PROPERTY = {
variant: '@menu', theme: '@menu',
height: TOP_BAR_HEIGHT, height: TOP_BAR_HEIGHT,
}; };
@@ -53,11 +35,11 @@ export type TopBarProps = {
}; };
export const TopBar = ({ title, children }: TopBarProps) => { export const TopBar = ({ title, children }: TopBarProps) => {
const { mode, colorMode, toggleColorMode } = useThemeMode(); const { theme, toggleTheme } = useTheme();
const { clearToken } = useSessionService(); const { clearToken } = useSessionService();
const { session } = useServiceContext(); const { session } = useServiceContext();
const backColor = mode('back.100', 'back.800'); const backColor = useColorThemeValue('back.100', 'back.800');
const drawerDisclose = useDisclosure(); const drawerDisclose = useDisclosure();
const onChangeTheme = () => { const onChangeTheme = () => {
drawerDisclose.onOpen(); drawerDisclose.onOpen();
@@ -81,6 +63,9 @@ export const TopBar = ({ title, children }: TopBarProps) => {
const onSelectHome = () => { const onSelectHome = () => {
navigate('/'); navigate('/');
}; };
const onSelectOnAir = () => {
navigate('/on-air');
};
const onHelp = () => { const onHelp = () => {
navigate('/help'); navigate('/help');
}; };
@@ -89,22 +74,24 @@ export const TopBar = ({ title, children }: TopBarProps) => {
}; };
return ( return (
<Flex <Flex
position="absolute"
top={0}
left={0}
right={0}
height={TOP_BAR_HEIGHT}
alignItems="center"
justifyContent="space-between"
backgroundColor={backColor}
gap="2" gap="2"
px="2" align="center"
boxShadow={'0px 2px 4px ' + colors.back[900]} justify="space-between"
zIndex={200} style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: TOP_BAR_HEIGHT,
background: backColor,
padding: "0 2 0 2",
boxShadow: `0px 2px 4px ${basicColor.back[900]}`,
zIndex: 200,
}}
> >
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}> <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
<LuAlignJustify /> <LuAlignJustify />
<Text paddingLeft="3px" fontWeight="bold"> <Text style={{ padding: "0 0 0 3px" }} fontWeight="bold">
Karusic Karusic
</Text> </Text>
</Button> </Button>
@@ -112,80 +99,85 @@ export const TopBar = ({ title, children }: TopBarProps) => {
<Text <Text
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
textTransform="uppercase" style={{
marginRight="auto" textTransform: "uppercase",
userSelect="none" marginRight: "auto",
userSelect: "none",
}}
> >
{title} {title}
</Text> </Text>
)} )}
{children} {children}
<Flex right="0"> <Flex style={{ right: 0 }}>
{session?.state !== SessionState.CONNECTED && ( {session?.state !== SessionState.CONNECTED && (
<> <>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onSignIn}> <Button {...BUTTON_TOP_BAR_PROPERTY} /*{...THEME.Button.primary}*/ onClick={onSignIn}>
<LuLogIn /> <LuLogIn />
<Text paddingLeft="3px" fontWeight="bold"> <Text style={{ paddingLeft: "0 0 0 3px" }} fontWeight="bold">
Sign-in Sign-in
</Text> </Text>
</Button> </Button>
<Button <Button
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
onClick={onSignUp} onClick={onSignUp}
disabled={true} // disabled={true}
> >
<LuPlusCircle /> <MdMore />
<Text paddingLeft="3px" fontWeight="bold"> <Text style={{ padding: "0 0 0 3px" }} fontWeight="bold">
Sign-up Sign-up
</Text> </Text>
</Button> </Button>
</> </>
)} )}
{session?.state === SessionState.CONNECTED && ( {/* {session?.state === SessionState.CONNECTED && (
<Menu> <Menu.Root>
<MenuButton <Menu.Trigger asChild>
<IconButton
as={IconButton} as={IconButton}
aria-label="Options" aria-label="Options"
icon={<LuUserCircle />}
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
width={TOP_BAR_HEIGHT} width={TOP_BAR_HEIGHT}
/> ><MdSupervisedUserCircle /></IconButton>
<MenuList> </Menu.Trigger>
<MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}> <Menu.Content>
Sign in as {session?.login ?? 'Fail'} <Menu.Item value="user" valueText="user" _hover={{}} color={useColorModeValue('brand.800', 'brand.200')}>
</MenuItem> <MdSupervisedUserCircle />
<MenuItem icon={<LuSettings />} onClick={onSettings}>Settings</MenuItem> <Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box>
<MenuItem icon={<LuHelpCircle />} onClick={onHelp}>Help</MenuItem> </Menu.Item>
<MenuItem icon={<LuLogOut />} onClick={onSignOut}> <Menu.Item value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</Menu.Item>
Sign-out <Menu.Item value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</Menu.Item>
</MenuItem> <Menu.Item value="Sign-out" valueText="Sign-out" onClick={onSignOut}>
<LuLogOut /> Sign-out
</Menu.Item>
{colorMode === 'light' ? ( {colorMode === 'light' ? (
<MenuItem icon={<LuMoon />} onClick={toggleColorMode}> <Menu.Item value="set-dark" valueText="set-dark" onClick={toggleColorMode}>
Set dark mode <LuMoon /> Set dark mode
</MenuItem> </Menu.Item>
) : ( ) : (
<MenuItem icon={<LuSun />} onClick={toggleColorMode}> <Menu.Item value="set-light" valueText="set-light" onClick={toggleColorMode}>
Set light mode <LuSun /> Set light mode
</MenuItem> </Menu.Item>
)}
</MenuList>
</Menu>
)} )}
</Menu.Content>
</Menu.Root>
)} */}
</Flex> </Flex>
<Drawer {/* <Drawer.Root
placement="left" placement="start"
onClose={drawerDisclose.onClose} onOpenChange={drawerDisclose.onClose}
isOpen={drawerDisclose.isOpen} open={drawerDisclose.open}
data-testid="top-bar_drawer-root"
> >
<DrawerOverlay /> <Drawer.Content
<DrawerContent> data-test-id="top-bar_drawer-content">
<DrawerHeader <Drawer.Header
paddingY="auto" paddingY="auto"
as="button" as="button"
onClick={drawerDisclose.onClose} onClick={drawerDisclose.onClose}
boxShadow={'0px 2px 4px ' + colors.back[900]} boxShadow={'0px 2px 4px ' + colors.back[900]}
backgroundColor={backColor} background={backColor}
color={mode('brand.900', 'brand.50')} color={useColorModeValue('brand.900', 'brand.50')}
textTransform="uppercase" textTransform="uppercase"
> >
<HStack height={TOP_BAR_HEIGHT}> <HStack height={TOP_BAR_HEIGHT}>
@@ -194,15 +186,15 @@ export const TopBar = ({ title, children }: TopBarProps) => {
Karusic Karusic
</Text> </Text>
</HStack> </HStack>
</DrawerHeader> </Drawer.Header>
<DrawerBody paddingX="0px"> <Drawer.Body paddingX="0px">
<Button <Button
background="#00000000" background="#00000000"
borderRadius="0px" borderRadius="0px"
onClick={onSelectHome} onClick={onSelectHome}
width="full" width="100%"
> >
<LuHome /> <MdHome />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> <Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
Home Home
</Text> </Text>
@@ -211,17 +203,29 @@ export const TopBar = ({ title, children }: TopBarProps) => {
<Button <Button
background="#00000000" background="#00000000"
borderRadius="0px" borderRadius="0px"
onClick={onSelectAdd} onClick={onSelectOnAir}
width="full" width="100%"
> >
<LuArrowUpSquare /> <MdOutlinePlaylistPlay />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
On air
</Text>
</Button>
<hr />
<Button
background="#00000000"
borderRadius="0px"
onClick={onSelectAdd}
width="100%"
>
<MdOutlineUploadFile />
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> <Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
Add Media Add Media
</Text> </Text>
</Button> </Button>
</DrawerBody> </Drawer.Body>
</DrawerContent> </Drawer.Content>
</Drawer> </Drawer.Root> */}
</Flex> </Flex>
); );
}; };

View File

@@ -1,10 +1,12 @@
import { Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu'; import { LuDisc3 } from 'react-icons/lu';
import { Album } from '@/back-api'; import { Album } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { useCountTracksWithAlbumId } from '@/service/Track'; import { useCountTracksWithAlbumId } from '@/service/Track';
import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing'; import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing';
import { Flex, Text } from '@/ui';
import { Span } from '@/ui/Span';
export type DisplayAlbumProps = { export type DisplayAlbumProps = {
dataAlbum?: Album; dataAlbum?: Album;
@@ -13,18 +15,19 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
const { countTracksOfAnAlbum } = useCountTracksWithAlbumId(dataAlbum?.id); const { countTracksOfAnAlbum } = useCountTracksWithAlbumId(dataAlbum?.id);
if (!dataAlbum) { if (!dataAlbum) {
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full">
Fail to retrieve Album Data. Fail to retrieve Album Data.
</Flex> </Flex>
); );
} }
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full"
data-testid="display-album_flex">
<Covers <Covers
data={dataAlbum?.covers} data={dataAlbum?.covers}
size={BASE_WRAP_ICON_SIZE} size={BASE_WRAP_ICON_SIZE}
flex={1} flex={1}
iconEmpty={LuDisc3} // TODO: iconEmpty={LuDisc3}
/> />
<Flex <Flex
direction="column" direction="column"
@@ -32,32 +35,37 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
//maxWidth="150px" //maxWidth="150px"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden" style={{
flex={1} marginRight: "auto",
overflow: "hidden",
flex: 1,
}}
> >
<Text <Span
as="span" // align="left"
align="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
marginRight="auto" style={{
overflow="hidden" marginRight: "auto",
noOfLines={[1, 2]} overflow: "hidden",
}}
// noOfLines={[1, 2]}
> >
{dataAlbum?.name} {dataAlbum?.name}
</Text> </Span>
<Text <Span
as="span" // align="left"
align="left"
fontSize="15px" fontSize="15px"
userSelect="none" userSelect="none"
marginRight="auto" style={{
overflow="hidden" marginRight: "auto",
noOfLines={1} overflow: "hidden",
}}
// noOfLines={1}
> >
{countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'} {countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'}
</Text> </Span>
</Flex> </Flex>
</Flex> </Flex>
); );

View File

@@ -6,5 +6,6 @@ export type DisplayAlbumIdProps = {
}; };
export const DisplayAlbumId = ({ id }: DisplayAlbumIdProps) => { export const DisplayAlbumId = ({ id }: DisplayAlbumIdProps) => {
const { dataAlbum } = useSpecificAlbum(id); const { dataAlbum } = useSpecificAlbum(id);
return <DisplayAlbum dataAlbum={dataAlbum} />; return <DisplayAlbum dataAlbum={dataAlbum}
data-testid="display-album-id" />;
}; };

View File

@@ -1,14 +1,7 @@
import { useState } from 'react';
import {
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
} from '@chakra-ui/react';
import { LuMenu } from 'react-icons/lu'; import { LuMenu } from 'react-icons/lu';
export type MenuElement = { export type MenuElement = {
name: string; name: string;
onClick: () => void; onClick: () => void;
@@ -19,24 +12,28 @@ export type ContextMenuProps = {
}; };
export const ContextMenu = ({ elements }: ContextMenuProps) => { export const ContextMenu = ({ elements }: ContextMenuProps) => {
if (!elements) { // if (!elements) {
return <></>; return <></>;
} // }
return ( // return (
<Menu> // <Menu.Root
<MenuButton // data-testid="context-menu">
as={IconButton} // <Menu.Trigger asChild
aria-label="Options" // data-testid="context-menu_trigger">
icon={<LuMenu />} // {/* This is very stupid, we need to set as span to prevent a button in button... WTF */}
marginY="auto" // <Button {...THEME.Button.primary} >
/> // <LuMenu />
<MenuList> // </Button>
{elements?.map((data) => ( // </Menu.Trigger>
<MenuItem key={data.name} onClick={data.onClick}> // <Menu.Content
{data.name} // data-testid="context-menu_content">
</MenuItem> // {elements?.map((data) => (
))} // <Menu.Item key={data.name} value={data.name} onClick={data.onClick}
</MenuList> // data-testid="context-menu_item">
</Menu> // {data.name}
); // </Menu.Item>
// ))}
// </Menu.Content>
// </Menu.Root >
//);
}; };

View File

@@ -3,14 +3,7 @@ import {
RefObject, RefObject,
} from 'react'; } from 'react';
import {
Box,
BoxProps,
Center,
Image,
Wrap,
WrapItem,
} from '@chakra-ui/react';
import { import {
MdHighlightOff, MdHighlightOff,
MdUploadFile, MdUploadFile,
@@ -19,6 +12,7 @@ import {
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { Flex, FlexProps } from '@/ui';
export type DragNdropProps = { export type DragNdropProps = {
onFilesSelected?: (file: File[]) => void; onFilesSelected?: (file: File[]) => void;
@@ -58,7 +52,8 @@ export const DragNdrop = ({
onUriSelected(listUri); onUriSelected(listUri);
} }
}; };
return <></>;
/*
return ( return (
<Box <Box
width={width} width={width}
@@ -89,9 +84,10 @@ export const DragNdrop = ({
</label> </label>
</Box> </Box>
); );
*/
}; };
export type CenterIconProps = BoxProps & { export type CenterIconProps = FlexProps & {
icon: any; icon: any;
sizeIcon?: string; sizeIcon?: string;
}; };
@@ -99,11 +95,13 @@ export type CenterIconProps = BoxProps & {
export const CenterIcon = ({ export const CenterIcon = ({
icon: IconEl, icon: IconEl,
sizeIcon = '15px', sizeIcon = '15px',
style,
...rest ...rest
}: CenterIconProps) => { }: CenterIconProps) => {
return ( return (
<Box position="relative" w={sizeIcon} h={sizeIcon} flex="none" {...rest}> <Flex style={{ position: "relative", width: sizeIcon, height: sizeIcon, flex: "none", ...style }}
<Box {...rest}>
{/*<Flex
as={IconEl} as={IconEl}
w={sizeIcon} w={sizeIcon}
h={sizeIcon} h={sizeIcon}
@@ -111,8 +109,8 @@ export const CenterIcon = ({
top="50%" top="50%"
left="50%" left="50%"
transform="translate(-50%, -50%)" transform="translate(-50%, -50%)"
/> />*/}
</Box> </Flex>
); );
}; };
@@ -143,10 +141,10 @@ export const FormCovers = ({
isModify={form.isModify[variableName]} isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })} onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest} {...rest}
> > <></>
<Wrap width="full"> {/* <HStack wrap="wrap" width="100%">
{urls.map((data, index) => ( {urls.map((data, index) => (
<WrapItem key={data}> <Flex align="flex-start" key={data}>
<Box width="125px" height="125px" position="relative"> <Box width="125px" height="125px" position="relative">
<Box width="125px" height="125px" position="absolute"> <Box width="125px" height="125px" position="absolute">
<CenterIcon <CenterIcon
@@ -161,17 +159,17 @@ export const FormCovers = ({
</Box> </Box>
<Image loading="lazy" src={data} boxSize="full" /> <Image loading="lazy" src={data} boxSize="full" />
</Box> </Box>
</WrapItem> </Flex>
))} ))}
<WrapItem key="data"> <Flex align="flex-start" key="data">
<DragNdrop <DragNdrop
height="125px" height="125px"
width="125px" width="125px"
onFilesSelected={onFilesSelected} onFilesSelected={onFilesSelected}
onUriSelected={onUriSelected} onUriSelected={onUriSelected}
/> />
</WrapItem> </Flex>
</Wrap> </HStack> */}
</FormGroup> </FormGroup>
); );
}; };

View File

@@ -1,6 +1,7 @@
import { Flex, Text } from '@/ui';
import { Span } from '@/ui/Span';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Flex, Text } from '@chakra-ui/react';
import { MdErrorOutline, MdHelpOutline, MdRefresh } from 'react-icons/md'; import { MdErrorOutline, MdHelpOutline, MdRefresh } from 'react-icons/md';
export type FormGroupProps = { export type FormGroupProps = {
@@ -23,20 +24,22 @@ export const FormGroup = ({
onRestore, onRestore,
}: FormGroupProps) => ( }: FormGroupProps) => (
<Flex <Flex
borderLeftWidth="3px" style={{
borderLeftColor={error ? 'red' : isModify ? 'blue' : '#00000000'} borderLeftWidth: "3px",
borderLeftColor: error ? 'red' : isModify ? 'blue' : '#00000000',
}}
paddingLeft="7px" paddingLeft="7px"
paddingY="4px" padding="0 4px"
direction="column" direction="column"
> >
<Flex direction="row" width="full" gap="52px"> <Flex direction="row" width="100%" gap="52px">
{!!label && ( {!!label && (
<Text marginRight="auto" fontWeight="bold"> <Text style={{ marginRight: "auto" }} fontWeight="bold">
{label}{' '} {label}{' '}
{isRequired && ( {isRequired && (
<Text as="span" color="red.600"> <Span color="red.600">
* *
</Text> </Span>
)} )}
</Text> </Text>
)} )}

View File

@@ -1,9 +1,8 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Input } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';
import { Input } from '@/ui';
export type FormInputProps = { export type FormInputProps = {
form: UseFormidableReturn; form: UseFormidableReturn;

View File

@@ -1,19 +1,11 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import {
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputProps,
NumberInputStepper,
} from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';
export type FormNumberProps = Pick< export type FormNumberProps = Pick<
NumberInputProps, NumberInput.RootProps,
'step' | 'defaultValue' | 'min' | 'max' 'step' | 'defaultValue' | 'min' | 'max'
> & { > & {
form: UseFormidableReturn; form: UseFormidableReturn;
@@ -41,21 +33,18 @@ export const FormNumber = ({
onRestore={() => form.restoreValue({ [variableName]: true })} onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest} {...rest}
> >
<NumberInput <></>
{/* <NumberInput.Root
ref={ref} ref={ref}
value={form.values[variableName]} value={form.values[variableName]}
onChange={(_, value) => form.setValues({ [variableName]: value })} onValueChange={(value) => form.setValues({ [variableName]: value })}
step={step} step={step}
defaultValue={defaultValue} defaultValue={defaultValue}
min={min} min={min}
max={max} max={max}
> >
<NumberInputField /> <NumberInput.Input />
<NumberInputStepper> </NumberInput.Root> */}
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</FormGroup> </FormGroup>
); );
}; };

View File

@@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Box } from '@chakra-ui/react';
import { FormSelect } from '@/components/form/FormSelect'; import { FormSelect } from '@/components/form/FormSelect';
import { useFormidable } from '@/components/form/Formidable'; import { useFormidable } from '@/components/form/Formidable';
import { Flex } from '@/ui';
export default { export default {
title: 'Components/FormSelect', title: 'Components/FormSelect',
@@ -94,7 +94,7 @@ export const DarkBackground = {
render: () => { render: () => {
const form = useFormidable<BasicFormData>({}); const form = useFormidable<BasicFormData>({});
return ( return (
<Box p="4" color="white" bg="gray.800"> <Flex style={{ padding: "4", color: "white", background: "gray.800" }}>
<FormSelect <FormSelect
label="Simple Title for (DarkBackground)" label="Simple Title for (DarkBackground)"
form={form} form={form}
@@ -105,7 +105,7 @@ export const DarkBackground = {
{ id: 333, name: 'third item' }, { id: 333, name: 'third item' },
]} ]}
/> />
</Box> </Flex>
); );
}, },

View File

@@ -1,6 +1,5 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Text } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';

View File

@@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Box } from '@chakra-ui/react';
import { FormSelectMultiple } from '@/components/form/FormSelectMultiple'; import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
import { useFormidable } from '@/components/form/Formidable'; import { useFormidable } from '@/components/form/Formidable';
import { Flex } from '@/ui';
export default { export default {
title: 'Components/FormSelectMultipleMultiple', title: 'Components/FormSelectMultipleMultiple',
@@ -94,7 +94,7 @@ export const DarkBackground = {
render: () => { render: () => {
const form = useFormidable<BasicFormData>({}); const form = useFormidable<BasicFormData>({});
return ( return (
<Box p="4" color="white" bg="gray.800"> <Flex style={{ padding: "4", color: "white", background: "gray.800" }}>
<FormSelectMultiple <FormSelectMultiple
label="Simple Title for (DarkBackground)" label="Simple Title for (DarkBackground)"
form={form} form={form}
@@ -105,7 +105,7 @@ export const DarkBackground = {
{ id: 333, name: 'third item' }, { id: 333, name: 'third item' },
]} ]}
/> />
</Box> </Flex>
); );
}, },

View File

@@ -1,6 +1,5 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Textarea } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable'; import { UseFormidableReturn } from '@/components/form/Formidable';

View File

@@ -1,9 +1,10 @@
import { Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu'; import { LuDisc3 } from 'react-icons/lu';
import { Gender } from '@/back-api'; import { Gender } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { useCountTracksOfAGender } from '@/service/Track'; import { useCountTracksOfAGender } from '@/service/Track';
import { Flex, Span, Text } from '@/ui';
export type DisplayGenderProps = { export type DisplayGenderProps = {
dataGender?: Gender; dataGender?: Gender;
@@ -12,18 +13,18 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => {
const { countTracksOnAGender } = useCountTracksOfAGender(dataGender?.id); const { countTracksOnAGender } = useCountTracksOfAGender(dataGender?.id);
if (!dataGender) { if (!dataGender) {
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full">
Fail to retrieve Gender Data. Fail to retrieve Gender Data.
</Flex> </Flex>
); );
} }
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full">
<Covers <Covers
data={dataGender?.covers} data={dataGender?.covers}
size="100" size="100"
height="full" height="full"
iconEmpty={LuDisc3} //TODO: iconEmpty={LuDisc3}
/> />
<Flex <Flex
direction="column" direction="column"
@@ -31,31 +32,36 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => {
maxWidth="150px" maxWidth="150px"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden" style={{
overflowX: "hidden"
}}
> >
<Text <Span
as="span"
align="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none" style={{
marginRight="auto" userSelect: "none",
overflow="hidden" marginRight: "auto",
noOfLines={[1, 2]} overflow: "hidden",
alignContent: "left",
}}
//TODO: noOfLines={[1, 2]}
> >
{dataGender?.name} {dataGender?.name}
</Text> </Span>
<Text <Span
as="span"
align="left"
fontSize="15px" fontSize="15px"
userSelect="none" style={{
marginRight="auto" userSelect: "none",
overflow="hidden" marginRight: "auto",
noOfLines={1} overflow: "hidden",
alignContent: "left",
}}
//TODO: noOfLines={1}
> >
{countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'} {countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'}
</Text> </Span>
</Flex> </Flex>
</Flex> </Flex>
); );

View File

@@ -1,18 +1,5 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { import {
MdAdminPanelSettings, MdAdminPanelSettings,
MdDeleteForever, MdDeleteForever,
@@ -32,6 +19,7 @@ import { useAlbumService, useSpecificAlbum } from '@/service/Album';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksWithAlbumId } from '@/service/Track'; import { useCountTracksWithAlbumId } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure';
export type AlbumEditPopUpProps = {}; export type AlbumEditPopUpProps = {};
@@ -65,8 +53,8 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
); );
onClose(); onClose();
}; };
const initialRef = useRef(null); const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef(null); const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<Album>({ const form = useFormidable<Album>({
initialValues: dataAlbum, initialValues: dataAlbum,
}); });
@@ -140,107 +128,110 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
}) })
); );
}; };
return ( return <></>;
<Modal // return (
initialFocusRef={initialRef} // <Dialog.Root
finalFocusRef={finalRef} // //initialFocusRef={initialRef}
closeOnOverlayClick={false} // //finalFocusRef={finalRef}
onClose={onClose} // //closeOnOverlayClick={false}
isOpen={true} // onOpenChange={onClose}
> // open={true}
<ModalOverlay /> // data-testid="album-edit-pop-up"
<ModalContent> // >
<ModalHeader>Edit Album</ModalHeader> // {/* <DialogOverlay /> */}
<ModalCloseButton ref={finalRef} /> // {/* <DialogCloseTrigger /> */}
// <Dialog.Content>
// <Dialog.Header>Edit Album</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<ModalBody pb={6} gap="0px" paddingLeft="18px"> // <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
{admin && ( // {admin && (
<> // <>
<FormGroup isRequired label="Id"> // <FormGroup isRequired label="Id">
<Text>{dataAlbum?.id}</Text> // <Text>{dataAlbum?.id}</Text>
</FormGroup> // </FormGroup>
{countTracksOfAnAlbum !== 0 && ( // {countTracksOfAnAlbum !== 0 && (
<Flex paddingLeft="14px"> // <Flex paddingLeft="14px">
<MdWarning color="red.600" /> // <MdWarning color="red.600" />
<Text paddingLeft="6px" color="red.600" fontWeight="bold"> // <Text paddingLeft="6px" color="red.600" fontWeight="bold">
Can not remove album {countTracksOfAnAlbum} track(s) depend // Can not remove album {countTracksOfAnAlbum} track(s) depend
on it. // on it.
</Text> // </Text>
</Flex> // </Flex>
)} // )}
<FormGroup label="Action(s):"> // <FormGroup label="Action(s):">
<Button // <Button
onClick={disclosure.onOpen} // onClick={disclosure.onOpen}
marginRight="auto" // marginRight="auto"
variant="@danger" // theme="@danger"
isDisabled={countTracksOfAnAlbum !== 0} // disabled={countTracksOfAnAlbum !== 0}
> // >
<MdDeleteForever /> Remove Media // <MdDeleteForever /> Remove Media
</Button> // </Button>
</FormGroup> // </FormGroup>
<ConfirmPopUp // <ConfirmPopUp
disclosure={disclosure} // disclosure={disclosure}
title="Remove album" // title="Remove album"
body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`} // body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`}
confirmTitle="Remove" // confirmTitle="Remove"
onConfirm={onRemove} // onConfirm={onRemove}
/> // />
</> // </>
)} // )}
{!admin && ( // {!admin && (
<> // <>
<FormInput // <FormInput
form={form} // form={form}
variableName="name" // variableName="name"
isRequired // isRequired
label="Title" // label="Title"
ref={initialRef} // ref={initialRef}
/> // />
<FormTextarea // <FormTextarea
form={form} // form={form}
variableName="description" // variableName="description"
label="Description" // label="Description"
/> // />
<FormInput // <FormInput
form={form} // form={form}
variableName="publication" // variableName="publication"
label="Publication" // label="Publication"
/> // />
<FormCovers // <FormCovers
form={form} // form={form}
variableName="covers" // variableName="covers"
onFilesSelected={onFilesSelected} // onFilesSelected={onFilesSelected}
onUriSelected={onUriSelected} // onUriSelected={onUriSelected}
onRemove={onRemoveCover} // onRemove={onRemoveCover}
/> // />
</> // </>
)} // )}
</ModalBody> // </Dialog.Body>
<ModalFooter> // <Dialog.Footer>
<Button // <Button
onClick={() => setAdmin((value) => !value)} // onClick={() => setAdmin((value) => !value)}
marginRight="auto" // marginRight="auto"
> // >
{admin ? ( // {admin ? (
<> // <>
<MdEdit /> // <MdEdit />
Edit // Edit
</> // </>
) : ( // ) : (
<> // <>
<MdAdminPanelSettings /> // <MdAdminPanelSettings />
Admin // Admin
</> // </>
)} // )}
</Button> // </Button>
{!admin && form.isFormModified && ( // {!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}> // <Button colorScheme="blue" mr={3} onClick={onSave}>
Save // Save
</Button> // </Button>
)} // )}
<Button onClick={onClose}>Cancel</Button> // <Button onClick={onClose}>Cancel</Button>
</ModalFooter> // </Dialog.Footer>
</ModalContent> // </Dialog.Content>
</Modal> // </Dialog.Root>
); // );
}; };

View File

@@ -1,18 +1,6 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { import {
MdAdminPanelSettings, MdAdminPanelSettings,
MdDeleteForever, MdDeleteForever,
@@ -32,6 +20,7 @@ import { useArtistService, useSpecificArtist } from '@/service/Artist';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksOfAnArtist } from '@/service/Track'; import { useCountTracksOfAnArtist } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure';
export type ArtistEditPopUpProps = {}; export type ArtistEditPopUpProps = {};
@@ -65,8 +54,8 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
); );
onClose(); onClose();
}; };
const initialRef = useRef(null); const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef(null); const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<Artist>({ const form = useFormidable<Artist>({
initialValues: dataArtist, initialValues: dataArtist,
}); });
@@ -140,109 +129,110 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
); );
}; };
return ( return (
<Modal // <Dialog.Root
initialFocusRef={initialRef} // //initialFocusRef={initialRef}
finalFocusRef={finalRef} // //finalFocusRef={finalRef}
closeOnOverlayClick={false} // //closeOnOverlayClick={false}
onClose={onClose} // onOpenChange={onClose}
isOpen={true} // open={true}
> // data-testid="artist-edit-pop-up"
<ModalOverlay /> // >
<ModalContent> // {/* <DialogOverlay /> */}
<ModalHeader>Edit Artist</ModalHeader> // <Dialog.Content>
<ModalCloseButton ref={finalRef} /> // <Dialog.Header>Edit Artist</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<ModalBody pb={6} gap="0px" paddingLeft="18px"> // <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
{admin && ( // {admin && (
<> // <>
<FormGroup isRequired label="Id"> // <FormGroup isRequired label="Id">
<Text>{dataArtist?.id}</Text> // <Text>{dataArtist?.id}</Text>
</FormGroup> // </FormGroup>
{countTracksOnAnArtist !== 0 && ( // {countTracksOnAnArtist !== 0 && (
<Flex paddingLeft="14px"> // <Flex paddingLeft="14px">
<MdWarning color="red.600" /> // <MdWarning color="red.600" />
<Text paddingLeft="6px" color="red.600" fontWeight="bold"> // <Text paddingLeft="6px" color="red.600" fontWeight="bold">
Can not remove artist {countTracksOnAnArtist} track(s) // Can not remove artist {countTracksOnAnArtist} track(s)
depend on it. // depend on it.
</Text> // </Text>
</Flex> // </Flex>
)} // )}
<FormGroup label="Action(s):"> // <FormGroup label="Action(s):">
<Button // <Button
onClick={disclosure.onOpen} // onClick={disclosure.onOpen}
marginRight="auto" // marginRight="auto"
variant="@danger" // theme="@danger"
isDisabled={countTracksOnAnArtist !== 0} // disabled={countTracksOnAnArtist !== 0}
> // >
<MdDeleteForever /> Remove Media // <MdDeleteForever /> Remove Media
</Button> // </Button>
</FormGroup> // </FormGroup>
<ConfirmPopUp // <ConfirmPopUp
disclosure={disclosure} // disclosure={disclosure}
title="Remove artist" // title="Remove artist"
body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`} // body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`}
confirmTitle="Remove" // confirmTitle="Remove"
onConfirm={onRemove} // onConfirm={onRemove}
/> // />
</> // </>
)} // )}
{!admin && ( // {!admin && (
<> // <>
<FormInput // <FormInput
form={form} // form={form}
variableName="name" // variableName="name"
isRequired // isRequired
label="Artist name" // label="Artist name"
ref={initialRef} // ref={initialRef}
/> // />
<FormTextarea // <FormTextarea
form={form} // form={form}
variableName="description" // variableName="description"
label="Description" // label="Description"
/> // />
<FormInput // <FormInput
form={form} // form={form}
variableName="firstName" // variableName="firstName"
label="First Name" // label="First Name"
/> // />
<FormInput form={form} variableName="surname" label="SurName" /> // <FormInput form={form} variableName="surname" label="SurName" />
<FormInput form={form} variableName="birth" label="Birth date" /> // <FormInput form={form} variableName="birth" label="Birth date" />
<FormInput form={form} variableName="death" label="Death date" /> // <FormInput form={form} variableName="death" label="Death date" />
<FormCovers // <FormCovers
form={form} // form={form}
variableName="covers" // variableName="covers"
onFilesSelected={onFilesSelected} // onFilesSelected={onFilesSelected}
onUriSelected={onUriSelected} // onUriSelected={onUriSelected}
onRemove={onRemoveCover} // onRemove={onRemoveCover}
/> // />
</> // </>
)} // )}
</ModalBody> // </Dialog.Body>
<ModalFooter> // <Dialog.Footer>
<Button // <Button
onClick={() => setAdmin((value) => !value)} // onClick={() => setAdmin((value) => !value)}
marginRight="auto" // marginRight="auto"
> // >
{admin ? ( // {admin ? (
<> // <>
<MdEdit /> // <MdEdit />
Edit // Edit
</> // </>
) : ( // ) : (
<> // <>
<MdAdminPanelSettings /> // <MdAdminPanelSettings />
Admin // Admin
</> // </>
)} // )}
</Button> // </Button>
{!admin && form.isFormModified && ( // {!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}> // <Button colorScheme="blue" mr={3} onClick={onSave}>
Save // Save
</Button> // </Button>
)} // )}
<Button onClick={onClose}>Cancel</Button> // <Button onClick={onClose}>Cancel</Button>
</ModalFooter> // </Dialog.Footer>
</ModalContent> // </Dialog.Content>
</Modal> // </Dialog.Root>
); <></>);
}; };

View File

@@ -1,15 +1,7 @@
import { UseDisclosureReturn } from '@/utils/disclosure';
import { useRef } from 'react'; import { useRef } from 'react';
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
UseDisclosureReturn,
} from '@chakra-ui/react';
export type ConfirmPopUpProps = { export type ConfirmPopUpProps = {
title: string; title: string;
@@ -30,31 +22,29 @@ export const ConfirmPopUp = ({
onConfirm(); onConfirm();
disclosure.onClose(); disclosure.onClose();
}; };
const cancelRef = useRef(null); const cancelRef = useRef<HTMLButtonElement>(null);
return ( return <></>;
<AlertDialog // return (
isOpen={disclosure.isOpen} // <Dialog.Root role="alertdialog"
leastDestructiveRef={cancelRef} // open={disclosure.open}
onClose={disclosure.onClose} // //leastDestructiveRef={cancelRef}
> // onOpenChange={disclosure.onClose}
<AlertDialogOverlay> // data-testid="confirm-pop-up"
<AlertDialogContent> // >
<AlertDialogHeader fontSize="lg" fontWeight="bold"> // <Dialog.Content>
{title} // <Dialog.Header fontSize="lg" fontWeight="bold">
</AlertDialogHeader> // {title}
// </Dialog.Header>
<AlertDialogBody>{body}</AlertDialogBody> // <Dialog.Body>{body}</Dialog.Body>
// <Dialog.Footer>
<AlertDialogFooter> // <Button onClick={disclosure.onClose} ref={cancelRef}>
<Button onClick={disclosure.onClose} ref={cancelRef}> // Cancel
Cancel // </Button>
</Button> // <Button colorScheme="red" onClick={onClickConfirm} ml={3}>
<Button colorScheme="red" onClick={onClickConfirm} ml={3}> // {confirmTitle}
{confirmTitle} // </Button>
</Button> // </Dialog.Footer>
</AlertDialogFooter> // </Dialog.Content>
</AlertDialogContent> // </Dialog.Root>
</AlertDialogOverlay> // );
</AlertDialog>
);
}; };

View File

@@ -1,24 +1,13 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { import {
MdAdminPanelSettings, MdAdminPanelSettings,
MdDeleteForever, MdDeleteForever,
MdEdit, MdEdit,
MdWarning, MdWarning,
} from 'react-icons/md'; } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Gender, GenderResource } from '@/back-api'; import { Gender, GenderResource } from '@/back-api';
@@ -32,6 +21,7 @@ import { useGenderService, useSpecificGender } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksOfAGender } from '@/service/Track'; import { useCountTracksOfAGender } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure';
export type GenderEditPopUpProps = {}; export type GenderEditPopUpProps = {};
@@ -65,8 +55,8 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => {
); );
onClose(); onClose();
}; };
const initialRef = useRef(null); const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef(null); const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<Gender>({ const form = useFormidable<Gender>({
initialValues: dataGender, initialValues: dataGender,
}); });
@@ -138,102 +128,104 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => {
}) })
); );
}; };
return ( return <></>;
<Modal // return (
initialFocusRef={initialRef} // <Dialog.Root
finalFocusRef={finalRef} // //initialFocusRef={initialRef}
closeOnOverlayClick={false} // //finalFocusRef={finalRef}
onClose={onClose} // //closeOnOverlayClick={false}
isOpen={true} // onOpenChange={onClose}
> // open={true}
<ModalOverlay /> // data-testid="gender-edit-pop-up"
<ModalContent> // >
<ModalHeader>Edit Gender</ModalHeader> // {/* <DialogOverlay /> */}
<ModalCloseButton ref={finalRef} /> // <Dialog.Content>
// <Dialog.Header>Edit Gender</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<ModalBody pb={6} gap="0px" paddingLeft="18px"> // <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
{admin && ( // {admin && (
<> // <>
<FormGroup isRequired label="Id"> // <FormGroup isRequired label="Id">
<Text>{dataGender?.id}</Text> // <Text>{dataGender?.id}</Text>
</FormGroup> // </FormGroup>
{countTracksOnAGender !== 0 && ( // {countTracksOnAGender !== 0 && (
<Flex paddingLeft="14px"> // <Flex paddingLeft="14px">
<MdWarning color="red.600" /> // <MdWarning color="red.600" />
<Text paddingLeft="6px" color="red.600" fontWeight="bold"> // <Text paddingLeft="6px" color="red.600" fontWeight="bold">
Can not remove gender {countTracksOnAGender} track(s) depend // Can not remove gender {countTracksOnAGender} track(s) depend
on it. // on it.
</Text> // </Text>
</Flex> // </Flex>
)} // )}
<FormGroup label="Action(s):"> // <FormGroup label="Action(s):">
<Button // <Button
onClick={disclosure.onOpen} // onClick={disclosure.onOpen}
marginRight="auto" // marginRight="auto"
variant="@danger" // theme="@danger"
isDisabled={countTracksOnAGender !== 0} // disabled={countTracksOnAGender !== 0}
> // >
<MdDeleteForever /> Remove gender // <MdDeleteForever /> Remove gender
</Button> // </Button>
</FormGroup> // </FormGroup>
<ConfirmPopUp // <ConfirmPopUp
disclosure={disclosure} // disclosure={disclosure}
title="Remove gender" // title="Remove gender"
body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`} // body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`}
confirmTitle="Remove" // confirmTitle="Remove"
onConfirm={onRemove} // onConfirm={onRemove}
/> // />
</> // </>
)} // )}
{!admin && ( // {!admin && (
<> // <>
<FormInput // <FormInput
form={form} // form={form}
variableName="name" // variableName="name"
isRequired // isRequired
label="Gender name" // label="Gender name"
ref={initialRef} // ref={initialRef}
/> // />
<FormTextarea // <FormTextarea
form={form} // form={form}
variableName="description" // variableName="description"
label="Description" // label="Description"
/> // />
<FormCovers // <FormCovers
form={form} // form={form}
variableName="covers" // variableName="covers"
onFilesSelected={onFilesSelected} // onFilesSelected={onFilesSelected}
onUriSelected={onUriSelected} // onUriSelected={onUriSelected}
onRemove={onRemoveCover} // onRemove={onRemoveCover}
/> // />
</> // </>
)} // )}
</ModalBody> // </Dialog.Body>
<ModalFooter> // <Dialog.Footer>
<Button // <Button
onClick={() => setAdmin((value) => !value)} // onClick={() => setAdmin((value) => !value)}
marginRight="auto" // marginRight="auto"
> // >
{admin ? ( // {admin ? (
<> // <>
<MdEdit /> // <MdEdit />
Edit // Edit
</> // </>
) : ( // ) : (
<> // <>
<MdAdminPanelSettings /> // <MdAdminPanelSettings />
Admin // Admin
</> // </>
)} // )}
</Button> // </Button>
{!admin && form.isFormModified && ( // {!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}> // <Button colorScheme="blue" mr={3} onClick={onSave}>
Save // Save
</Button> // </Button>
)} // )}
<Button onClick={onClose}>Cancel</Button> // <Button onClick={onClose}>Cancel</Button>
</ModalFooter> // </Dialog.Footer>
</ModalContent> // </Dialog.Content>
</Modal> // </Dialog.Root>
); // );
}; };

View File

@@ -1,38 +1,5 @@
import { ReactElement, useRef, useState } from 'react'; import { useRef } from 'react';
import {
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Progress,
Text,
useDisclosure,
} from '@chakra-ui/react';
import {
MdAdminPanelSettings,
MdDeleteForever,
MdEdit,
MdWarning,
} from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { Artist, ArtistResource } from '@/back-api';
import { FormCovers } from '@/components/form/FormCovers';
import { FormGroup } from '@/components/form/FormGroup';
import { FormInput } from '@/components/form/FormInput';
import { FormTextarea } from '@/components/form/FormTextarea';
import { useFormidable } from '@/components/form/Formidable';
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
import { useArtistService, useSpecificArtist } from '@/service/Artist';
import { useServiceContext } from '@/service/ServiceContext';
import { useCountTracksOfAnArtist } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';
export type PopUpUploadProgressProps = { export type PopUpUploadProgressProps = {
title: string; title: string;
@@ -63,65 +30,68 @@ export const PopUpUploadProgress = ({
title, title,
totalSize, totalSize,
}: PopUpUploadProgressProps) => { }: PopUpUploadProgressProps) => {
const initialRef = useRef(null); const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef(null); const finalRef = useRef<HTMLButtonElement>(null);
return ( return <></>;
<Modal
initialFocusRef={initialRef}
finalFocusRef={finalRef}
closeOnOverlayClick={false}
onClose={onClose}
isOpen={true}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>{title}</ModalHeader>
<ModalCloseButton ref={finalRef} />
<ModalBody pb={6} paddingLeft="18px"> // return (
<Flex direction="column" gap="10px"> // <Dialog.Root
{isFinished ? ( // //initialFocusRef={initialRef}
<Text fontSize="20px" fontWeight="bold"> // //finalFocusRef={finalRef}
All {elements.length} element have been sent // //closeOnOverlayClick={false}
</Text> // onOpenChange={onClose}
) : ( // open={true}
<Text fontSize="20px" fontWeight="bold"> // data-testid="upload-progress-edit-pop-up"
[{index + 1}/{elements.length}] {elements[index]} // >
</Text> // {/* <DialogOverlay /> */}
)} // <Dialog.Content>
<Progress // <Dialog.Header>{title}</Dialog.Header>
colorScheme="green" // {/* <DialogCloseButton ref={finalRef} /> */}
hasStripe
value={currentSize} // <Dialog.Body pb={6} paddingLeft="18px">
isAnimated // <Flex direction="column" gap="10px">
max={totalSize} // {isFinished ? (
height="24px" // <Text fontSize="20px" fontWeight="bold">
/> // All {elements.length} element have been sent
<Flex> // </Text>
<Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text> // ) : (
<Text marginLeft="auto"> // <Text fontSize="20px" fontWeight="bold">
{totalSize.toLocaleString('fr-FR')} Bytes // [{index + 1}/{elements.length}] {elements[index]}
</Text> // </Text>
</Flex> // )}
{error && ( // <Progress.Root
<Text fontWeight="bold" color="darkred"> // colorScheme="green"
{error} // striped
</Text> // value={currentSize}
)} // animated
</Flex> // max={totalSize}
</ModalBody> // height="24px"
<ModalFooter> // />
{isFinished ? ( // <Flex>
<Button onClick={onClose} variant="@success"> // <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text>
Ok // <Text marginLeft="auto">
</Button> // {totalSize.toLocaleString('fr-FR')} Bytes
) : ( // </Text>
<Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}> // </Flex>
Abort // {error && (
</Button> // <Text fontWeight="bold" color="darkred">
)} // {error}
</ModalFooter> // </Text>
</ModalContent> // )}
</Modal> // </Flex>
); // </Dialog.Body>
// <Dialog.Footer>
// {isFinished ? (
// <Button onClick={onClose} theme="@success">
// Ok
// </Button>
// ) : (
// <Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}>
// Abort
// </Button>
// )}
// </Dialog.Footer>
// </Dialog.Content>
// </Dialog.Root>
// );
}; };

View File

@@ -1,17 +1,6 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md'; import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
@@ -30,6 +19,7 @@ import { useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useSpecificTrack, useTrackService } from '@/service/Track'; import { useSpecificTrack, useTrackService } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { useDisclosure } from '@/utils/disclosure';
export type TrackEditPopUpProps = {}; export type TrackEditPopUpProps = {};
@@ -65,8 +55,8 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
); );
onClose(); onClose();
}; };
const initialRef = useRef(null); const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef(null); const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<Track>({ const form = useFormidable<Track>({
//onSubmit, //onSubmit,
//onValuesChange, //onValuesChange,
@@ -90,115 +80,117 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
}) })
); );
}; };
return ( return <></>;
<Modal // return (
initialFocusRef={initialRef} // <Dialog.Root
finalFocusRef={finalRef} // //initialFocusRef={initialRef}
closeOnOverlayClick={false} // //finalFocusRef={finalRef}
onClose={onClose} // //closeOnOverlayClick={false}
isOpen={true} // onOpenChange={onClose}
> // open={true}
<ModalOverlay /> // data-testid="track-edit-pop-up"
<ModalContent> // >
<ModalHeader>Edit Track</ModalHeader> // {/* <DialogOverlay /> */}
<ModalCloseButton ref={finalRef} /> // <Dialog.Content>
// <Dialog.Header>Edit Track</Dialog.Header>
// {/* <DialogCloseButton ref={finalRef} /> */}
<ModalBody pb={6} gap="0px" paddingLeft="18px"> // <Dialog.Body pb={6} gap="0px" paddingLeft="18px">
{admin && ( // {admin && (
<> // <>
<FormGroup isRequired label="Id"> // <FormGroup isRequired label="Id">
<Text>{dataTrack?.id}</Text> // <Text>{dataTrack?.id}</Text>
</FormGroup> // </FormGroup>
<FormGroup label="Data Id"> // <FormGroup label="Data Id">
<Text>{dataTrack?.dataId}</Text> // <Text>{dataTrack?.dataId}</Text>
</FormGroup> // </FormGroup>
<FormGroup label="Action(s):"> // <FormGroup label="Action(s):">
<Button // <Button
onClick={disclosure.onOpen} // onClick={disclosure.onOpen}
marginRight="auto" // marginRight="auto"
variant="@danger" // theme="@danger"
> // >
<MdDeleteForever /> Remove Media // <MdDeleteForever /> Remove Media
</Button> // </Button>
</FormGroup> // </FormGroup>
<ConfirmPopUp // <ConfirmPopUp
disclosure={disclosure} // disclosure={disclosure}
title="Remove track" // title="Remove track"
body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`} // body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`}
confirmTitle="Remove" // confirmTitle="Remove"
onConfirm={onRemove} // onConfirm={onRemove}
/> // />
</> // </>
)} // )}
{!admin && ( // {!admin && (
<> // <>
<FormInput // <FormInput
form={form} // form={form}
variableName="name" // variableName="name"
isRequired // isRequired
label="Title" // label="Title"
ref={initialRef} // ref={initialRef}
/> // />
<FormTextarea // <FormTextarea
form={form} // form={form}
variableName="description" // variableName="description"
label="Description" // label="Description"
/> // />
<FormSelect // <FormSelect
form={form} // form={form}
variableName="genderId" // variableName="genderId"
options={dataGenders} // options={dataGenders}
label="Gender" // label="Gender"
/> // />
<FormSelectMultiple // <FormSelectMultiple
form={form} // form={form}
variableName="artists" // variableName="artists"
options={dataArtist} // options={dataArtist}
label="Artist(s)" // label="Artist(s)"
/> // />
<FormSelect // <FormSelect
form={form} // form={form}
variableName="albumId" // variableName="albumId"
options={dataAlbums} // options={dataAlbums}
label="Album" // label="Album"
/> // />
<FormNumber // <FormNumber
form={form} // form={form}
variableName="track" // variableName="track"
label="Track n°" // label="Track n°"
step={1} // step={1}
defaultValue={0} // //defaultValue={0}
min={0} // min={0}
max={1000} // max={1000}
/> // />
</> // </>
)} // )}
</ModalBody> // </Dialog.Body>
<ModalFooter> // <Dialog.Footer>
<Button // <Button
onClick={() => setAdmin((value) => !value)} // onClick={() => setAdmin((value) => !value)}
marginRight="auto" // marginRight="auto"
> // >
{admin ? ( // {admin ? (
<> // <>
<MdEdit /> // <MdEdit />
Edit // Edit
</> // </>
) : ( // ) : (
<> // <>
<MdAdminPanelSettings /> // <MdAdminPanelSettings />
Admin // Admin
</> // </>
)} // )}
</Button> // </Button>
{!admin && form.isFormModified && ( // {!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}> // <Button colorScheme="blue" mr={3} onClick={onSave}>
Save // Save
</Button> // </Button>
)} // )}
<Button onClick={onClose}>Cancel</Button> // <Button onClick={onClose}>Cancel</Button>
</ModalFooter> // </Dialog.Footer>
</ModalContent> // </Dialog.Content>
</Modal> // </Dialog.Root>
); // );
}; };

View File

@@ -1,9 +1,9 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { MdAdd } from 'react-icons/md'; import { MdAdd } from 'react-icons/md';
import { isNullOrUndefined, isNumber } from '@/utils/validator'; import { isNullOrUndefined, isNumber } from '@/utils/validator';
import { Button, Flex, Text } from '@/ui';
export type SelectListModel = { export type SelectListModel = {
id: any; id: any;
@@ -65,56 +65,65 @@ export const SelectList = ({
} }
}, []); }, []);
return ( return (
<Box position="relative"> <Flex style={{ position: "relative" }}>
<Flex <Flex
direction="column" direction="column"
width="full" style={{
position="absolute" width: "100%",
border="1px" position: "absolute",
borderColor="black" border: "1px",
backgroundColor="gray.700" borderColor: "black",
overflowY="auto" background: "gray.700",
overflowX="hidden" overflowY: "auto",
maxHeight="300px" overflowX: "hidden",
zIndex={300} maxHeight: "300px",
transform="translateY(1px)" zIndex: 300,
transform: "translateY(1px)",
}}
> >
{displayedValue.length === 0 && ( {displayedValue.length === 0 && (
<Text marginX="auto" color="red.500" fontWeight="bold" marginY="10px"> <Text fontWeight="bold" color="red.500" style={{ margin: "0 auto" }}>
... No element found... ... No element found...
</Text> </Text>
)} )}
{displayedValue.map((data) => ( {displayedValue.map((data) => (
<Button <Button
key={data.id} key={data.id}
marginY="1px" style={{
borderRadius="0px" margin: "1px 0",
autoFocus={false} borderRadius: "0px",
backgroundColor={data.isSelected ? 'green.800' : '0x00000000'} //autoFocus: false,
_hover={{ backgroundColor: 'gray.400' }} background: data.isSelected ? 'green.800' : '0x00000000',
//_hover={ background: 'gray.400' },
}}
onClick={() => onSelectValue(data)} onClick={() => onSelectValue(data)}
ref={data.isSelected ? scrollToRef : undefined} //ref={data.isSelected ? scrollToRef : undefined}
> >
<Text marginRight="auto" autoFocus={false}> <Text style={{ margin: "0 auto", }} /*autoFocus={false}*/>
{data.name} {data.name}
</Text> </Text>
</Button> </Button>
))} ))}
{onCreate && search && search.length > 0 && ( {onCreate && search && search.length > 0 && (
<Button <Button
marginY="1px" style={{
borderRadius="0px" margin: "1px 0",
autoFocus={false} borderRadius: "0px",
_hover={{ backgroundColor: 'gray.400' }} //autoFocus:false,
//_hover={ background: 'gray.400' }
}}
onClick={() => onCreate(search)} onClick={() => onCreate(search)}
> >
<Flex marginRight="auto"> <Flex
style={{
margin: "0 auto",
}}>
<MdAdd /> <MdAdd />
<Text autoFocus={false}>Create '{search}'</Text> <Text /*autoFocus={false}*/>Create '{search}'</Text>
</Flex> </Flex>
</Button> </Button>
)} )}
</Flex> </Flex>
</Box> </Flex>
); );
}; };

View File

@@ -1,20 +1,10 @@
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import {
Button,
Flex,
Input,
Spinner,
Tag,
TagCloseButton,
TagLabel,
Wrap,
WrapItem,
} from '@chakra-ui/react';
import { MdEdit, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md'; import { MdEdit, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md';
import { SelectList, SelectListModel } from '@/components/select/SelectList'; import { SelectList, SelectListModel } from '@/components/select/SelectList';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Button, Flex, HStack, Input } from '@/ui';
export type SelectMultipleProps = { export type SelectMultipleProps = {
options?: object[]; options?: object[];
@@ -85,7 +75,7 @@ export const SelectMultiple = ({
} }
}; };
if (!options) { if (!options) {
return <Spinner />; return <></>;// TODO: <Spinner />;
} }
const onChangeInput = (value: string): void => { const onChangeInput = (value: string): void => {
setHasSuggestion(false); setHasSuggestion(false);
@@ -109,42 +99,47 @@ export const SelectMultiple = ({
}; };
return ( return (
<Flex direction="column" width="full" gap="0px"> <Flex direction="column" width="100%" gap="0px">
{selectedOptions && ( {selectedOptions && (
<Wrap spacing="5px" justify="left" width="full" marginBottom="2px"> <HStack style={{ flexWrap: "wrap", /*spacing="5px"*/ justifyContent: "left", width: "100%", marginBottom: "2px" }}>
{selectedOptions.map((data) => ( {selectedOptions.map((data) => (
<WrapItem key={data[keyKey]}> <Flex align="flex-start" key={data[keyKey]}>
<Tag {/* <Tag.Root
size="md" size="md"
key="md" key="md"
borderRadius="5px" borderRadius="5px"
variant="solid" variant="solid"
backgroundColor="green.500" background="green.500"
> >
<TagLabel>{data[keyValue] ?? `id=${data[keyKey]}`}</TagLabel> <Tag.Label>{data[keyValue] ?? `id=${data[keyKey]}`}</Tag.Label>
<TagCloseButton onClick={() => selectValue(data)} /> <Tag.CloseTrigger onClick={() => selectValue(data)} />
</Tag> </Tag.Root> */}
</WrapItem> </Flex>
))} ))}
</Wrap> </HStack>
)} )}
<Flex> <Flex>
<Input <Input
ref={refFocus} ref={refFocus}
width="full" style={{
width: "100%",
borderRadius: "5px 0 0 5px",
}}
onChange={(e) => onChangeInput(e.target.value)} onChange={(e) => onChangeInput(e.target.value)}
//onSubmit={onSubmit} //onSubmit={onSubmit}
onFocus={() => setShowList(true)} // TODO: onFocus={() => setShowList(true)}
onBlur={() => setTimeout(() => setShowList(false), 200)} // TODO: onBlur={() => setTimeout(() => setShowList(false), 200)}
value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''} value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''}
borderRadius="5px 0 0 5px"
/> />
<Button <Button
onClick={onOpenClose} onClick={onOpenClose}
variant="outline" //variant="outline"
borderRadius="0 5px 5px 0" style={{
borderWidth="1px 1px 1px 0" borderRadius: "0 5px 5px 0",
borderWidth: "1px 1px 1px 0",
}}
> >
{showList ? ( {showList ? (
<MdKeyboardArrowUp color="gray.300" /> <MdKeyboardArrowUp color="gray.300" />

View File

@@ -1,6 +1,5 @@
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { Button, Flex, Input, Spinner } from '@chakra-ui/react';
import { import {
MdClose, MdClose,
MdEdit, MdEdit,
@@ -10,6 +9,7 @@ import {
import { SelectList, SelectListModel } from '@/components/select/SelectList'; import { SelectList, SelectListModel } from '@/components/select/SelectList';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Button, Flex, Input } from '@/ui';
export type SelectSingleProps = { export type SelectSingleProps = {
options?: object[]; options?: object[];
@@ -70,7 +70,7 @@ export const SelectSingle = ({
} }
}; };
if (!transformedOption) { if (!transformedOption) {
return <Spinner />; return <></>; // TODO: <Spinner />;
} }
function onChangeInput(value: string): void { function onChangeInput(value: string): void {
setHasSuggestion(false); setHasSuggestion(false);
@@ -101,29 +101,32 @@ export const SelectSingle = ({
}; };
return ( return (
<Flex direction="column" width="full" gap="0px"> <Flex direction="column" width="100%" gap="0px">
<Flex> <Flex>
<Input <Input
ref={refFocus} ref={refFocus}
width="full" style={{
width: "100%",
background: showList || !selectedOptions ? undefined : 'green.500',
borderRadius: "5px 0 0 5px",
}}
onChange={(e) => onChangeInput(e.target.value)} onChange={(e) => onChangeInput(e.target.value)}
onFocus={() => setShowList(true)} //onFocus={() => setShowList(true)}
onBlur={() => setTimeout(() => setShowList(false), 200)} //onBlur={() => setTimeout(() => setShowList(false), 200)}
value={ value={
showList ? (currentSearch ?? '') : (selectedOptions?.name ?? (hasSuggestion ? `suggest: ${currentSearch}` : '')) showList ? (currentSearch ?? '') : (selectedOptions?.name ?? (hasSuggestion ? `suggest: ${currentSearch}` : ''))
} }
backgroundColor={
showList || !selectedOptions ? undefined : 'green.500'
}
borderRadius="5px 0 0 5px"
/> />
<Button <Button
onClick={onRemoveItem} onClick={onRemoveItem}
variant="outline" // TODO: variant="outline"
borderRadius="0 5px 5px 0" style={{
borderWidth="1px 1px 1px 0" borderRadius: "0 5px 5px 0",
borderWidth: "1px 1px 1px 0",
}}
> >
{selectedOptions ? ( {
selectedOptions ? (
<MdClose color="gray.300" /> <MdClose color="gray.300" />
) : showList ? ( ) : showList ? (
<MdKeyboardArrowUp color="gray.300" /> <MdKeyboardArrowUp color="gray.300" />
@@ -134,7 +137,8 @@ export const SelectSingle = ({
)} )}
</Button> </Button>
</Flex> </Flex>
{showList && ( {
showList && (
<SelectList <SelectList
options={transformedOption} options={transformedOption}
selected={selectedOptions ? [selectedOptions] : []} selected={selectedOptions ? [selectedOptions] : []}
@@ -142,7 +146,8 @@ export const SelectSingle = ({
onSelectValue={selectValue} onSelectValue={selectValue}
onCreate={createNewItem} onCreate={createNewItem}
/> />
)} )
}
</Flex > </Flex >
); );
}; };

View File

@@ -1,10 +1,10 @@
import { Flex, Text } from '@chakra-ui/react';
import { LuMusic2, LuPlay } from 'react-icons/lu';
import { Track } from '@/back-api'; import { Track } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu'; import { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { Flex, Span } from '@/ui';
export type DisplayTrackProps = { export type DisplayTrackProps = {
track: Track; track: Track;
@@ -18,38 +18,41 @@ export const DisplayTrack = ({
}: DisplayTrackProps) => { }: DisplayTrackProps) => {
const { trackActive } = useActivePlaylistService(); const { trackActive } = useActivePlaylistService();
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full">
<Covers <Covers
data={track?.covers} data={track?.covers}
size="50" size="50"
height="full" height="full"
iconEmpty={ /* TODO: iconEmpty={
trackActive?.id === track.id ? LuPlay : LuMusic2 trackActive?.id === track.id ? LuPlay : LuMusic2
} } */
onClick={onClick} onClick={onClick}
/> />
<Flex <Flex
direction="column" direction="column"
width="full" width="100%"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden" style={{
overflowX: "hidden",
}}
onClick={onClick} onClick={onClick}
> >
<Text <Span
as="span"
align="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
noOfLines={[1, 2]}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
[{track.track}] {track.name} [{track.track}] {track.name}
</Text> </Span>
</Flex> </Flex>
<ContextMenu elements={contextMenu} /> <ContextMenu elements={contextMenu} />
</Flex> </Flex>

View File

@@ -1,7 +1,4 @@
import { Suspense } from 'react';
import { Flex, Text } from '@chakra-ui/react';
import { LuMusic2, LuPlay } from 'react-icons/lu';
import { Track } from '@/back-api'; import { Track } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
@@ -10,6 +7,7 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album'; import { useSpecificAlbum } from '@/service/Album';
import { useSpecificArtists } from '@/service/Artist'; import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { Flex, Span } from '@/ui';
export type DisplayTrackProps = { export type DisplayTrackProps = {
track: Track; track: Track;
@@ -26,87 +24,98 @@ export const DisplayTrackFull = ({
const { dataGender } = useSpecificGender(track?.genderId); const { dataGender } = useSpecificGender(track?.genderId);
const { dataArtists } = useSpecificArtists(track?.artists); const { dataArtists } = useSpecificArtists(track?.artists);
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full"
data-testid="display-track-full">
<Covers <Covers
data={track?.covers} data={track?.covers}
size="50" size="50"
marginY="auto" marginY="auto"
iconEmpty={ /* TODO: iconEmpty={
trackActive?.id === track.id ? LuPlay : LuMusic2 trackActive?.id === track.id ? LuPlay : LuMusic2
} } */
onClick={onClick} onClick={onClick}
/> />
<Flex <Flex
direction="column" direction="column"
width="full" width="100%"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden"
onClick={onClick} onClick={onClick}
style={{
overflowX: "hidden",
}}
> >
<Text <Span
as="span"
align="left"
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
noOfLines={1}
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={1}
margin: "auto 0",
}}
> >
{track.name} {track.track && ` [${track.track}]`} {track.name} {track.track && ` [${track.track}]`}
</Text> </Span>
{dataAlbum && ( {dataAlbum && (
<Text <Span
as="span"
align="left"
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none" //noOfLines={1}
marginRight="auto"
overflow="hidden"
noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Text as="span" fontWeight="normal">Album:</Text> {dataAlbum.name} <Span fontWeight="normal">Album:</Span> {dataAlbum.name}
</Text> </Span>
)} )}
{dataArtists && ( {dataArtists && (
<Text <Span
as="span"
align="left"
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
marginRight="auto"
overflow="hidden"
noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Text as="span" fontWeight="normal">Artist(s):</Text> {dataArtists.map((data) => data.name).join(', ')} <Span fontWeight="normal">Artist(s):</Span> {dataArtists.map((data) => data.name).join(', ')}
</Text> </Span>
)} )}
{dataGender && ( {dataGender && (
<Text <Span
as="span"
align="left"
fontSize="15px" fontSize="15px"
fontWeight="bold" fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
noOfLines={1}
marginY="auto"
color={trackActive?.id === track.id ? 'green.700' : undefined} color={trackActive?.id === track.id ? 'green.700' : undefined}
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// TODO: noOfLines={[1, 2]}
margin: "auto 0",
}}
> >
<Text as="span" fontWeight="normal">Gender:</Text> {dataGender.name} <Span fontWeight="normal">Gender:</Span> {dataGender.name}
</Text> </Span>
)} )}
</Flex> </Flex>
<ContextMenu elements={contextMenu} /> <ContextMenu elements={contextMenu}
data-testid="display-track-full_context-menu" />
</Flex> </Flex>
); );
}; };

View File

@@ -0,0 +1,27 @@
import { Track } from '@/back-api';
import { MenuElement } from '@/components/contextMenu/ContextMenu';
import { useSpecificTrack } from '@/service/Track';
import { DisplayTrackFull } from './DisplayTrackFull';
import { DisplayTrackSkeleton } from './DisplayTrackSkeleton';
export type DisplayTrackProps = {
trackId: Track["id"];
onClick?: () => void;
contextMenu?: MenuElement[];
};
export const DisplayTrackFullId = ({
trackId,
onClick,
contextMenu,
}: DisplayTrackProps) => {
const { dataTrack } = useSpecificTrack(trackId);
if (dataTrack) {
return (
<DisplayTrackFull track={dataTrack} onClick={onClick} contextMenu={contextMenu} />
);
} else {
return (
<DisplayTrackSkeleton />
);
}
};

View File

@@ -1,29 +1,32 @@
import { Flex, Skeleton, SkeletonText } from '@chakra-ui/react'; import { Flex } from "@/ui";
export const DisplayTrackSkeleton = () => { export const DisplayTrackSkeleton = () => {
return ( return (
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full">
<Skeleton {/* <Skeleton
borderRadius="0px" borderRadius="0px"
height="50" height="50"
width="50" width="50"
minWidth="50" minWidth="50"
minHeight="50" minHeight="50"
/> /> */}
<Flex <Flex
direction="column" direction="column"
width="full" width="100%"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden" style={{
overflowX: "hidden"
}}
> >
<SkeletonText {/* <SkeletonText
skeletonHeight="20px" skeletonHeight="20px"
noOfLines={1} noOfLines={1}
spacing={0} spacing={0}
width="50%" width="50%"
marginY="auto" marginY="auto"
/> /> */}
</Flex> </Flex>
</Flex> </Flex>
); );

View File

View File

@@ -0,0 +1,39 @@
"use client"
import { RestErrorResponse } from "@/back-api";
import { HStack } from "@/ui";
// export const toaster = createToaster({
// placement: "bottom-end",
// pauseOnPageIdle: true,
// })
// export const toasterAPIError = (error: RestErrorResponse) => {
// toaster.create({
// title: `[${error.status}] ${error.statusMessage}`,
// description: error.message,
// });
// };
// export const Toaster = () => {
// return (
// <Portal>
// <ArkToaster toaster={toaster}>
// {(toast) => (
// <Toast.Root>
// <HStack spacing="1" style={{ width: "100%" }}>
// {toast.title && <Toast.Title>{toast.title}</Toast.Title>}
// {toast.description && (
// <Toast.Description>{toast.description}</Toast.Description>
// )}
// </HStack>
// {toast.action && (
// <Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger>
// )}
// {toast.meta?.closable && <Toast.CloseTrigger />}
// </Toast.Root>
// )}
// </ArkToaster>
// </Portal>
// )
// }

View File

@@ -1,28 +0,0 @@
import dayjs from 'dayjs';
import 'dayjs/locale/fr';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import dayOfYear from 'dayjs/plugin/dayOfYear';
import duration from 'dayjs/plugin/duration';
import isBetween from 'dayjs/plugin/isBetween';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import isToday from 'dayjs/plugin/isToday';
import isTomorrow from 'dayjs/plugin/isTomorrow';
import isYesterday from 'dayjs/plugin/isYesterday';
import quarterOfYear from 'dayjs/plugin/quarterOfYear';
import relativeTime from 'dayjs/plugin/relativeTime';
import weekOfYear from 'dayjs/plugin/weekOfYear';
dayjs.locale('fr');
dayjs.extend(relativeTime);
dayjs.extend(customParseFormat);
dayjs.extend(weekOfYear);
dayjs.extend(isSameOrAfter);
dayjs.extend(isToday);
dayjs.extend(isTomorrow);
dayjs.extend(isYesterday);
dayjs.extend(dayOfYear);
dayjs.extend(isBetween);
dayjs.extend(advancedFormat);
dayjs.extend(quarterOfYear);
dayjs.extend(duration);

View File

@@ -1,2 +0,0 @@
import './axios';
import './dayjs';

View File

@@ -1,4 +1,4 @@
export const BASE_WRAP_SPACING = { base: "5px", md: "10px", lg: "20px" }; export const BASE_WRAP_SPACING = "5px"; // { base: "5px", md: "10px", lg: "20px" };
export const BASE_WRAP_WIDTH = { base: "90%", md: "45%", lg: "270px" }; export const BASE_WRAP_WIDTH = "90%"; // { base: "90%", md: "45%", lg: "270px" };
export const BASE_WRAP_HEIGHT = { base: "75px", lg: "120px" }; export const BASE_WRAP_HEIGHT = "75px"; // { base: "75px", lg: "120px" };
export const BASE_WRAP_ICON_SIZE = { base: "50px", lg: "100px" }; export const BASE_WRAP_ICON_SIZE = "50px";// { base: "50px", lg: "100px" };

View File

@@ -25,9 +25,9 @@ const environment_back_prod: Environment = {
karso: `${serverSSOAddress}/karso/api`, karso: `${serverSSOAddress}/karso/api`,
}, },
ssoSite: `${serverSSOAddress}/karso/`, ssoSite: `${serverSSOAddress}/karso/`,
ssoSignIn: `${serverSSOAddress}/karso/signin/karusic-dev/`, ssoSignIn: `${serverSSOAddress}/karso/signin/karusic/`,
ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`, ssoSignUp: `${serverSSOAddress}/karso/signup/karusic/`,
ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`, ssoSignOut: `${serverSSOAddress}/karso/signout/karusic/`,
tokenStoredInPermanentStorage: false, tokenStoredInPermanentStorage: false,
}; };
@@ -45,7 +45,7 @@ const environment_local: Environment = {
ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`, ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`,
ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`, ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`,
tokenStoredInPermanentStorage: false, tokenStoredInPermanentStorage: false,
//replaceDataToRealServer: true, replaceDataToRealServer: true,
}; };
const environment_full_local: Environment = { const environment_full_local: Environment = {
@@ -80,7 +80,6 @@ const environment_hybrid: Environment = {
tokenStoredInPermanentStorage: false, tokenStoredInPermanentStorage: false,
}; };
export const environment = environment_local;
/** /**
* Check if the current environment is for development * Check if the current environment is for development
@@ -90,6 +89,9 @@ export const isDevelopmentEnvironment = () => {
return import.meta.env.MODE === 'development'; return import.meta.env.MODE === 'development';
}; };
export const environment = isDevelopmentEnvironment() ? environment_local : environment_back_prod;
/** /**
* get the current REST api URL. Depend on the VITE_API_BASE_URL env variable. * get the current REST api URL. Depend on the VITE_API_BASE_URL env variable.
* @returns The URL with http(s)://*** * @returns The URL with http(s)://***

View File

@@ -1,26 +1,27 @@
import { Box, Button, Center, Heading, Text } from '@chakra-ui/react';
import { MdControlCamera } from 'react-icons/md'; import { MdControlCamera } from 'react-icons/md';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { Flex, Link, Text } from '@/ui';
export const Error401 = () => { export const Error401 = () => {
return ( return (
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter padding="25px"> <PageLayoutInfoCenter padding="25px" width="75%">
<Center> <Flex align="center">
<MdControlCamera size="250px" color="red.600" /> <MdControlCamera size="250px" color="red.600" />
</Center> </Flex>
<Box textAlign="center"> <Flex style={{ textAlign: "center" }}>
<Heading>Erreur 401</Heading> <Text fontSize="px">Erreur 401</Text>
<Text color="red.600"> <Text color="red.600">
Vous n'êtes pas autorisé a accéder a ce contenu. Vous n'êtes pas autorisé a accéder a ce contenu.
</Text> </Text>
<Button as="a" variant="link" href="/"> <Link href="/">
Retour à l'accueil Retour à l'accueil
</Button> </Link>
</Box> </Flex>
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
); );

View File

@@ -1,24 +1,26 @@
import { Box, Button, Center, Heading, Text } from '@chakra-ui/react'; import { MdControlCamera } from 'react-icons/md';
import { MdDangerous } from 'react-icons/md';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { Flex, Link, Text } from '@/ui';
export const Error403 = () => { export const Error403 = () => {
return ( return (
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter padding="25px"> <PageLayoutInfoCenter padding="25px" width="75%">
<Center> <Flex align="center">
<MdDangerous size="250px" color="orange.600" /> <MdControlCamera size="250px" color="red.600" />
</Center> </Flex>
<Box textAlign="center"> <Flex style={{ textAlign: "center" }}>
<Heading>Erreur 401</Heading> <Text fontSize="px">Erreur 403</Text>
<Text color="orange.600">Cette page vous est interdite</Text> <Text color="red.600">
<Button as="a" variant="link" href="/"> Cette page vous est interdite.
</Text>
<Link href="/">
Retour à l'accueil Retour à l'accueil
</Button> </Link>
</Box> </Flex>
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
); );

View File

@@ -1,26 +1,26 @@
import { Box, Button, Center, Heading, Text } from '@chakra-ui/react'; import { MdControlCamera } from 'react-icons/md';
import { MdSignpost } from 'react-icons/md';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { Flex, Link, Text } from '@/ui';
export const Error404 = () => { export const Error404 = () => {
return ( return (
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter padding="25px"> <PageLayoutInfoCenter padding="25px" width="75%">
<Center> <Flex align="center">
<MdSignpost size="250px" /> <MdControlCamera size="250px" color="red.600" />
</Center> </Flex>
<Box textAlign="center"> <Flex style={{ textAlign: "center" }}>
<Heading>Erreur 404</Heading> <Text fontSize="px">Erreur 404</Text>
<Text color="gray.600"> <Text color="red.600">
Cette page n'existe plus ou l'URL a changé Cette page n'existe plus ou l'URL a changé.
</Text> </Text>
<Button as="a" variant="link" href="/"> <Link href="/">
Retour à l'accueil Retour à l'accueil
</Button> </Link>
</Box> </Flex>
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
); );

View File

@@ -1,48 +1,44 @@
import { Button, Flex, Text } from '@/ui';
import { useDisclosure } from '@/utils/disclosure';
import React, { FC } from 'react'; import React, { FC } from 'react';
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box,
Button,
Collapse,
useDisclosure,
} from '@chakra-ui/react';
import { import {
FallbackProps, FallbackProps,
ErrorBoundary as ReactErrorBoundary, ErrorBoundary as ReactErrorBoundary,
} from 'react-error-boundary'; } from 'react-error-boundary';
import { LuChevronDown, LuChevronUp } from 'react-icons/lu'; import { LuChevronUp, LuChevronDown } from 'react-icons/lu';
const ErrorFallback = ({ error }: FallbackProps) => { const ErrorFallback = ({ error }: FallbackProps) => {
const { isOpen, onToggle } = useDisclosure(); const { open, onToggle } = useDisclosure();
return ( return (
<Box p="4" m="auto"> <Flex direction="column" style={{ padding: 4, margin: "auto", background: "red.500" }}>
<Alert status="error" borderRadius="md"> <Text color='red'>An unexpected error has occurred.</Text>
<AlertIcon /> <Text>Message: {error.message}</Text>
<Box flex="1"> {/*
<AlertRoot status="error" borderRadius="md">
<Flex style={{ flex: "1" }}>
<AlertTitle>An unexpected error has occurred.</AlertTitle> <AlertTitle>An unexpected error has occurred.</AlertTitle>
<AlertDescription display="block" lineHeight="1.4"> <AlertDescription display="block" lineHeight="1.4">
<Button <Button
variant="link" //theme="@secondary"
color="red.800" color="red.800"
size="sm" //size="sm"
rightIcon={isOpen ? <LuChevronUp /> : <LuChevronDown />}
onClick={onToggle} onClick={onToggle}
> >
Show details Show details {open ? <LuChevronUp /> : <LuChevronDown />}
</Button> </Button>
<Collapse in={isOpen} animateOpacity> <Collapsible.Root open={open}>
<Box mt={4} fontFamily="monospace"> <Collapsible.Content>
<Flex mt={4} fontFamily="monospace">
{error.message} {error.message}
</Box> </Flex>
</Collapse> </Collapsible.Content>
</Collapsible.Root>
</AlertDescription> </AlertDescription>
</Box> </Flex>
</Alert> </AlertRoot> */}
</Box> </Flex >
); );
}; };

View File

@@ -1,14 +1,15 @@
import { createIcon } from '@chakra-ui/react';
export const DoubleArrowIcon = createIcon({
displayName: 'DoubleArrowIcon', export const DoubleArrowIcon = <></>;
viewBox: '0 0 24 24', // createIcon({
path: ( // displayName: 'DoubleArrowIcon',
<path // viewBox: '0 0 24 24',
fillRule="evenodd" // path: (
clipRule="evenodd" // <path
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" // fillRule="evenodd"
fill="currentColor" // 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"
}); // />
// ),
// });

View File

@@ -1,16 +1,16 @@
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from '@/App'; import App from '@/App';
import { ThemeProvider } from './theme/ThemeContext';
import { StrictMode } from 'react';
// Render the app // Render the app
const rootElement = document.getElementById('root'); const rootElement = document.getElementById('root');
if (rootElement && !rootElement.innerHTML) { if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement); const root = ReactDOM.createRoot(rootElement);
root.render( root.render(
<StrictMode> <StrictMode>
<ThemeProvider>
<App /> <App />
</ThemeProvider>
</StrictMode> </StrictMode>
); );
} }

View File

@@ -1,6 +1,7 @@
import { AudioPlayer } from '@/components/AudioPlayer'; import { AudioPlayer } from '@/components/AudioPlayer';
import { ErrorBoundary } from '@/errors/ErrorBoundary'; import { ErrorBoundary } from '@/errors/ErrorBoundary';
import { AppRoutes } from '@/scene/AppRoutes'; import { AppRoutes } from '@/scene/AppRoutes';
import { Text } from '@/ui';
import { ServiceContextProvider } from '@/service/ServiceContext'; import { ServiceContextProvider } from '@/service/ServiceContext';
export const App = () => { export const App = () => {

View File

@@ -16,6 +16,7 @@ import { TrackRoutes } from '@/scene/track/TrackRoutes';
import { useHasRight } from '@/service/session'; import { useHasRight } from '@/service/session';
import { SettingsPage } from './home/SettingsPage'; import { SettingsPage } from './home/SettingsPage';
import { AddPage } from './home/AddPage'; import { AddPage } from './home/AddPage';
import { OnAirPage } from './onAir/OnAirPage';
export const AppRoutes = () => { export const AppRoutes = () => {
const { isReadable } = useHasRight('user'); const { isReadable } = useHasRight('user');
@@ -34,6 +35,7 @@ export const AppRoutes = () => {
<Route path="help" element={<HelpPage />} /> <Route path="help" element={<HelpPage />} />
<Route path="settings" element={<SettingsPage />} /> <Route path="settings" element={<SettingsPage />} />
<Route path="add" element={<AddPage />} /> <Route path="add" element={<AddPage />} />
<Route path="on-air/*" element={<OnAirPage />} />
<Route path="artist/*" element={<ArtistRoutes />} /> <Route path="artist/*" element={<ArtistRoutes />} />
<Route path="album/*" element={<AlbumRoutes />} /> <Route path="album/*" element={<AlbumRoutes />} />
<Route path="gender/*" element={<GenderRoutes />} /> <Route path="gender/*" element={<GenderRoutes />} />

View File

@@ -1,5 +1,4 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { MdEdit } from 'react-icons/md'; import { MdEdit } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
@@ -10,18 +9,17 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp'; import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album'; import { useSpecificAlbum } from '@/service/Album';
import { useTracksOfAnAlbum } from '@/service/Track'; import { useTracksOfAnAlbum } from '@/service/Track';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; import { BASE_WRAP_SPACING } from '@/constants/genericSpacing';
import { Button, Flex, Text } from '@/ui';
export const AlbumDetailPage = () => { export const AlbumDetailPage = () => {
const { albumId } = useParams(); const { albumId } = useParams();
const albumIdInt = albumId ? parseInt(albumId, 10) : undefined; const albumIdInt = albumId ? parseInt(albumId, 10) : undefined;
const { mode } = useThemeMode();
const { playInList } = useActivePlaylistService(); const { playInList } = useActivePlaylistService();
const { dataAlbum } = useSpecificAlbum(albumIdInt); const { dataAlbum } = useSpecificAlbum(albumIdInt);
const { tracksOnAnAlbum } = useTracksOfAnAlbum(albumIdInt); const { tracksOnAnAlbum } = useTracksOfAnAlbum(albumIdInt);
@@ -48,7 +46,7 @@ export const AlbumDetailPage = () => {
return ( return (
<> <>
<TopBar title="Album detail" /> <TopBar title="Album detail" />
<PageLayoutInfoCenter> <PageLayoutInfoCenter width="75%">
Fail to load artist id: {albumId} Fail to load artist id: {albumId}
</PageLayoutInfoCenter> </PageLayoutInfoCenter>
</> </>
@@ -66,20 +64,23 @@ export const AlbumDetailPage = () => {
<MdEdit /> <MdEdit />
</Button> </Button>
</TopBar> </TopBar>
<PageLayout> <PageLayout
data-testid="Album-detail-page_layout">
<Flex <Flex
direction="row" direction="row"
width="80%"
marginX="auto"
padding="10px"
gap="10px" gap="10px"
style={{
width: "80%",
margin: "0 auto",
padding: "10px",
}}
> >
<Covers <Covers
data={dataAlbum?.covers} data={dataAlbum?.covers}
iconEmpty={LuDisc3} // TODO: iconEmpty={LuDisc3}
slideshow slideshow
/> />
<Flex direction="column" width="80%" marginRight="auto"> <Flex direction="column" style={{ width: "80%", marginRight: "auto" }}>
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataAlbum?.name} {dataAlbum?.name}
</Text> </Text>
@@ -94,24 +95,27 @@ export const AlbumDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap={BASE_WRAP_SPACING} gap={BASE_WRAP_SPACING} style={{
marginX="auto" margin: "0 auto",
padding="20px" padding: "20px",
width="80%" width: "80%",
}}
data-test-id="Album-detail-page_flex-list"
> >
{tracksOnAnAlbum?.map((data) => ( {tracksOnAnAlbum?.map((data) => (
<Box <Flex
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" minWidth: "100%",
_hover={{ //height="60px"
boxShadow: 'outline-over', border: "1px",
bgColor: mode('#FFFFFFF7', '#000000F7'), borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFull <DisplayTrackFull
@@ -126,8 +130,9 @@ export const AlbumDetailPage = () => {
}, },
{ name: 'Add Playlist', onClick: () => { } }, { name: 'Add Playlist', onClick: () => { } },
]} ]}
data-testid="Album-detail-page_display-detail"
/> />
</Box> </Flex>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@@ -1,6 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Wrap, WrapItem } from '@chakra-ui/react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
@@ -10,13 +9,13 @@ import { SearchInput } from '@/components/SearchInput';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { DisplayAlbum } from '@/components/album/DisplayAlbum'; import { DisplayAlbum } from '@/components/album/DisplayAlbum';
import { useOrderedAlbums } from '@/service/Album'; import { useOrderedAlbums } from '@/service/Album';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { BASE_WRAP_SPACING, BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing'; import { BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing';
import { Button, Flex, HStack } from '@/ui';
export const AlbumsPage = () => { export const AlbumsPage = () => {
const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined);
const { isLoading, dataAlbums } = useOrderedAlbums(filterTitle); const { isLoading, dataAlbums } = useOrderedAlbums(filterTitle);
const { mode } = useThemeMode();
const navigate = useNavigate(); const navigate = useNavigate();
const onSelectItem = (albumId: number) => { const onSelectItem = (albumId: number) => {
navigate(`/album/${albumId}`); navigate(`/album/${albumId}`);
@@ -26,7 +25,7 @@ export const AlbumsPage = () => {
return ( return (
<> <>
<TopBar title="All Albums" /> <TopBar title="All Albums" />
<PageLayoutInfoCenter>No Album available</PageLayoutInfoCenter> <PageLayoutInfoCenter width="75%">No Album available</PageLayoutInfoCenter>
</> </>
); );
} }
@@ -37,27 +36,29 @@ export const AlbumsPage = () => {
<SearchInput onChange={setFilterTitle} /> <SearchInput onChange={setFilterTitle} />
</TopBar> </TopBar>
<PageLayout> <PageLayout>
<Wrap spacing={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center"> <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}>
{dataAlbums.map((data) => ( {dataAlbums.map((data) => (
<WrapItem <Button
width={BASE_WRAP_WIDTH}
height={BASE_WRAP_HEIGHT}
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" width: BASE_WRAP_WIDTH,
_hover={{ height: BASE_WRAP_HEIGHT,
boxShadow: 'outline-over', border: "1px",
bgColor: mode('#FFFFFFF7', '#000000F7'), borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover={
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
alignContent: "flex-start",
}} }}
onClick={() => onSelectItem(data.id)} onClick={() => onSelectItem(data.id)}
> >
<DisplayAlbum dataAlbum={data} /> <DisplayAlbum dataAlbum={data} />
</WrapItem> </Button>
))} ))}
</Wrap> </HStack>
<EmptyEnd /> <EmptyEnd />
</PageLayout > </PageLayout >
</> </>

View File

@@ -1,6 +1,5 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { LuDisc3, LuUser } from 'react-icons/lu'; import { MdEdit } from 'react-icons/md';
import { MdEdit, MdPerson } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
@@ -15,13 +14,13 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album'; import { useSpecificAlbum } from '@/service/Album';
import { useSpecificArtist } from '@/service/Artist'; import { useSpecificArtist } from '@/service/Artist';
import { useTracksOfAnAlbum } from '@/service/Track'; import { useTracksOfAnAlbum } from '@/service/Track';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { Button, Flex, Text } from '@/ui';
export const ArtistAlbumDetailPage = () => { export const ArtistAlbumDetailPage = () => {
const { artistId, albumId } = useParams(); const { artistId, albumId } = useParams();
const artistIdInt = artistId ? parseInt(artistId, 10) : undefined; const artistIdInt = artistId ? parseInt(artistId, 10) : undefined;
const albumIdInt = albumId ? parseInt(albumId, 10) : undefined; const albumIdInt = albumId ? parseInt(albumId, 10) : undefined;
const { mode } = useThemeMode();
const { playInList } = useActivePlaylistService(); const { playInList } = useActivePlaylistService();
const { dataArtist } = useSpecificArtist(artistIdInt); const { dataArtist } = useSpecificArtist(artistIdInt);
const { dataAlbum } = useSpecificAlbum(albumIdInt); const { dataAlbum } = useSpecificAlbum(albumIdInt);
@@ -61,14 +60,19 @@ export const ArtistAlbumDetailPage = () => {
<> <>
<Button <Button
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto"
style={{
marginRight: "auto"
}}
onClick={() => navigate(`/artist/${dataArtist.id}`)} onClick={() => navigate(`/artist/${dataArtist.id}`)}
> >
<Covers <Covers
data={dataArtist?.covers} data={dataArtist?.covers}
size="35px" size="35px"
borderRadius="full" style={{
iconEmpty={MdPerson} borderRadius: "full",
}}
// TODO: iconEmpty={MdPerson}
/> />
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataArtist?.name} {dataArtist?.name}
@@ -88,17 +92,20 @@ export const ArtistAlbumDetailPage = () => {
<PageLayout> <PageLayout>
<Flex <Flex
direction="row" direction="row"
width="80%" style={{
marginX="auto" width: "80%",
padding="10px" margin: "0 auto",
padding: "10px",
}}
gap="10px" gap="10px"
> >
<Covers <Covers
data={dataAlbum?.covers} data={dataAlbum?.covers}
iconEmpty={LuDisc3} // TODO: iconEmpty={LuDisc3}
slideshow slideshow
/> />
<Flex direction="column" width="80%" marginRight="auto"> <Flex direction="column"
style={{ width: "80%", marginRight: "auto" }}>
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataAlbum?.name} {dataAlbum?.name}
</Text> </Text>
@@ -114,23 +121,28 @@ export const ArtistAlbumDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap="20px" gap="20px"
marginX="auto"
padding="20px" style={{
width="80%" margin: "0 auto",
padding: "20px",
width: "80%",
}}
> >
{tracksOnAnAlbum?.map((data) => ( {tracksOnAnAlbum?.map((data) => (
<Box <Flex
minWidth="100%"
height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px"
as="button" style={{
_hover={{ minWidth: "100%",
boxShadow: 'outline-over', height: "60px",
bgColor: mode('#FFFFFFF7', '#000000F7'), border: "1px",
borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrack <DisplayTrack
@@ -148,7 +160,7 @@ export const ArtistAlbumDetailPage = () => {
{ name: 'Add Playlist', onClick: () => { } }, { name: 'Add Playlist', onClick: () => { } },
]} ]}
/> />
</Box> </Flex>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@@ -1,5 +1,4 @@
import { Button, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
import { LuUser } from 'react-icons/lu';
import { MdEdit, MdGroup } from 'react-icons/md'; import { MdEdit, MdGroup } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
@@ -12,13 +11,13 @@ import { DisplayAlbumId } from '@/components/album/DisplayAlbumId';
import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp'; import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp';
import { useSpecificArtist } from '@/service/Artist'; import { useSpecificArtist } from '@/service/Artist';
import { useAlbumIdsOfAnArtist } from '@/service/Track'; import { useAlbumIdsOfAnArtist } from '@/service/Track';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { BASE_WRAP_HEIGHT, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; import { BASE_WRAP_HEIGHT, BASE_WRAP_WIDTH } from '@/constants/genericSpacing';
import { Button, Flex, HStack, Text } from '@/ui';
export const ArtistDetailPage = () => { export const ArtistDetailPage = () => {
const { artistId } = useParams(); const { artistId } = useParams();
const artistIdInt = artistId ? parseInt(artistId, 10) : undefined; const artistIdInt = artistId ? parseInt(artistId, 10) : undefined;
const { mode } = useThemeMode();
const navigate = useNavigate(); const navigate = useNavigate();
const onSelectItem = (albumId: number) => { const onSelectItem = (albumId: number) => {
navigate(`/artist/${artistIdInt}/album/${albumId}`); navigate(`/artist/${artistIdInt}/album/${albumId}`);
@@ -42,7 +41,7 @@ export const ArtistDetailPage = () => {
<> <>
<Button <Button
{...BUTTON_TOP_BAR_PROPERTY} {...BUTTON_TOP_BAR_PROPERTY}
marginRight="auto" style={{ marginRight: "auto" }}
onClick={() => navigate(`/artist/all`)} onClick={() => navigate(`/artist/all`)}
> >
<MdGroup height="full" /> <MdGroup height="full" />
@@ -65,13 +64,13 @@ export const ArtistDetailPage = () => {
<Flex <Flex
direction="row" direction="row"
width="80%" width="80%"
marginX="auto" margin="0 auto"
padding="10px" padding="10px"
gap="10px" gap="10px"
> >
<Covers <Covers
data={dataArtist?.covers} data={dataArtist?.covers}
iconEmpty={LuUser} // TODO: iconEmpty={LuUser}
slideshow slideshow
/> />
<Flex direction="column" width="80%" marginRight="auto"> <Flex direction="column" width="80%" marginRight="auto">
@@ -89,27 +88,29 @@ export const ArtistDetailPage = () => {
</Flex> </Flex>
</Flex> </Flex>
<Wrap spacing={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center"> <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}>
{albumIdsOfAnArtist?.map((data) => ( {albumIdsOfAnArtist?.map((data) => (
<WrapItem <Button
width={BASE_WRAP_WIDTH}
height={BASE_WRAP_HEIGHT}
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data} key={data}
padding="5px" style={{
as="button" alignContent: "flex-start",
_hover={{ width: BASE_WRAP_WIDTH,
boxShadow: 'outline-over', height: BASE_WRAP_HEIGHT,
bgColor: mode('#FFFFFFF7', '#000000F7'), border: "1px",
borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover={
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
<DisplayAlbumId id={data} key={data} /> <DisplayAlbumId id={data} key={data} />
</WrapItem> </Button>
))} ))}
</Wrap> </HStack>
<EmptyEnd /> <EmptyEnd />
<Routes> <Routes>
<Route path="edit-artist/:artistId" element={<ArtistEditPopUp />} /> <Route path="edit-artist/:artistId" element={<ArtistEditPopUp />} />

View File

@@ -1,56 +1,91 @@
import { useState } from 'react'; import { useState } from 'react';
import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
import { LuUser } from 'react-icons/lu';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Artist } from '@/back-api'; import { Artist, Track } from '@/back-api';
import { Covers } from '@/components/Cover'; import { Covers } from '@/components/Cover';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { SearchInput } from '@/components/SearchInput'; import { SearchInput } from '@/components/SearchInput';
import { TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { useArtistService, useOrderedArtists } from '@/service/Artist'; import { useOrderedArtists } from '@/service/Artist';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_WIDTH } from '@/constants/genericSpacing';
import { MdOutlineForkRight } from 'react-icons/md';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { shuffleArray } from '@/utils/arrayTools';
import { useTrackService } from '@/service/Track';
import { DataTools, TypeCheck } from '@/utils/data-tools';
import { Button, Flex, HStack, Span, Text } from '@/ui';
const LIMIT_RANDOM_VALUES = 25;
export const ArtistsPage = () => { export const ArtistsPage = () => {
const { mode } = useThemeMode();
const [filterName, setFilterName] = useState<string | undefined>(undefined); const [filterName, setFilterName] = useState<string | undefined>(undefined);
const navigate = useNavigate(); const navigate = useNavigate();
const { playInList } = useActivePlaylistService();
const onSelectItem = (data: Artist) => { const onSelectItem = (data: Artist) => {
navigate(`/artist/${data.id}/`); navigate(`/artist/${data.id}/`);
}; };
const { dataArtist } = useOrderedArtists(filterName); const { dataArtist } = useOrderedArtists(filterName);
const { store: trackStore } = useTrackService();
const onRandomPlay = () => {
const data = shuffleArray(dataArtist)
const playingList: number[] = [];
for (let i = 0; i < Math.min(data.length, LIMIT_RANDOM_VALUES); i++) {
const selectedArtist: Artist = data[i];
const listArtistTracks: Track[] = DataTools.getsWhere(
trackStore.data,
[
{
check: TypeCheck.CONTAINS,
key: 'artists',
value: selectedArtist.id,
},
],
[]
);
if (listArtistTracks.length === 0) {
continue;
}
playingList.push(listArtistTracks[Math.floor(Math.random() * listArtistTracks.length)].id);
}
playInList(0, playingList)
}
return ( return (
<> <>
<TopBar title="All artists"> <TopBar title="All artists">
<SearchInput onChange={setFilterName} /> <SearchInput onChange={setFilterName} />
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onRandomPlay}>
<MdOutlineForkRight />
</Button>
</TopBar> </TopBar>
<PageLayout> <PageLayout>
<Wrap spacing={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center"> <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", alignContent: "center" }}>
{dataArtist?.map((data) => ( {dataArtist?.map((data) => (
<WrapItem <Button
width={BASE_WRAP_WIDTH}
height={BASE_WRAP_HEIGHT}
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" width: BASE_WRAP_WIDTH,
_hover={{ height: BASE_WRAP_HEIGHT,
boxShadow: 'outline-over', alignContent: "flex-start",
bgColor: mode('#FFFFFFF7', '#000000F7'), border: "1px",
borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover:{
// boxShadow: 'outline-over',
// bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
<Flex direction="row" width="full" height="full"> <Flex direction="row" width="100%" height="full">
<Covers <Covers
data={data.covers} data={data.covers}
size={BASE_WRAP_ICON_SIZE} size={BASE_WRAP_ICON_SIZE}
height="full" style={{ height: "100%" }}
iconEmpty={LuUser} // iconEmpty={LuUser}
/> />
<Flex <Flex
direction="column" direction="column"
@@ -58,25 +93,30 @@ export const ArtistsPage = () => {
maxWidth="150px" maxWidth="150px"
height="full" height="full"
paddingLeft="5px" paddingLeft="5px"
overflowX="hidden" style={{ overflowX: "hidden" }}
> >
<Text <Text
as="span" /*align="left"*/
align="left" /*noOfLines={[1, 2]}*/
>
<Span
fontSize="20px" fontSize="20px"
fontWeight="bold" fontWeight="bold"
userSelect="none" userSelect="none"
marginRight="auto" style={{
overflow="hidden" marginRight: "auto",
noOfLines={[1, 2]} overflow: "hidden",
}}
> >
{data.name} {data.name}
</Span>
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
</WrapItem> </Button>
))} ))}
</Wrap> </HStack>
<EmptyEnd /> <EmptyEnd />
</PageLayout> </PageLayout>
</> </>

View File

@@ -1,5 +1,4 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { LuDisc3 } from 'react-icons/lu';
import { MdEdit } from 'react-icons/md'; import { MdEdit } from 'react-icons/md';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
@@ -10,18 +9,17 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
import { GenderEditPopUp } from '@/components/popup/GenderEditPopUp'; import { GenderEditPopUp } from '@/components/popup/GenderEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificGender } from '@/service/Gender'; import { useSpecificGender } from '@/service/Gender';
import { useTracksOfAGender } from '@/service/Track'; import { useTracksOfAGender } from '@/service/Track';
//import { useTracksOfAGender } from '@/service/Track'; //import { useTracksOfAGender } from '@/service/Track';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { Button, Flex, Text } from '@/ui';
export const GenderDetailPage = () => { export const GenderDetailPage = () => {
const { genderId } = useParams(); const { genderId } = useParams();
const genderIdInt = genderId ? parseInt(genderId, 10) : undefined; const genderIdInt = genderId ? parseInt(genderId, 10) : undefined;
const { mode } = useThemeMode();
const { playInList } = useActivePlaylistService(); const { playInList } = useActivePlaylistService();
const { dataGender } = useSpecificGender(genderIdInt); const { dataGender } = useSpecificGender(genderIdInt);
const { tracksOnAGender } = useTracksOfAGender(genderIdInt); const { tracksOnAGender } = useTracksOfAGender(genderIdInt);
@@ -69,17 +67,19 @@ export const GenderDetailPage = () => {
<PageLayout> <PageLayout>
<Flex <Flex
direction="row" direction="row"
width="80%"
marginX="auto"
padding="10px"
gap="10px" gap="10px"
style={{
width: "80%",
margin: "0 auto",
padding: "10px",
}}
> >
<Covers <Covers
data={dataGender?.covers} data={dataGender?.covers}
iconEmpty={LuDisc3} // TODO: iconEmpty={LuDisc3}
slideshow slideshow
/> />
<Flex direction="column" width="80%" marginRight="auto"> <Flex direction="column" style={{ width: "80%", marginRight: "auto" }}>
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">
{dataGender?.name} {dataGender?.name}
</Text> </Text>
@@ -92,23 +92,26 @@ export const GenderDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap="20px" gap="20px"
marginX="auto" style={{
padding="20px" margin: "0 auto",
width="80%" padding: "20px",
width: "80%",
}}
> >
{tracksOnAGender?.map((data) => ( {tracksOnAGender?.map((data) => (
<Box <Flex
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" minWidth: "100%",
_hover={{ //height="60px",
boxShadow: 'outline-over', border: "1px",
bgColor: mode('#FFFFFFF7', '#000000F7'), borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFull <DisplayTrackFull
@@ -124,7 +127,7 @@ export const GenderDetailPage = () => {
{ name: 'Add Playlist', onClick: () => { } }, { name: 'Add Playlist', onClick: () => { } },
]} ]}
/> />
</Box> </Flex>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@@ -1,6 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Wrap, WrapItem } from '@chakra-ui/react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
@@ -10,12 +9,12 @@ import { SearchInput } from '@/components/SearchInput';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { DisplayGender } from '@/components/gender/DisplayGender'; import { DisplayGender } from '@/components/gender/DisplayGender';
import { useOrderedGenders } from '@/service/Gender'; import { useOrderedGenders } from '@/service/Gender';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { Flex, HStack } from '@/ui';
export const GendersPage = () => { export const GendersPage = () => {
const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined);
const { isLoading, dataGenders } = useOrderedGenders(filterTitle); const { isLoading, dataGenders } = useOrderedGenders(filterTitle);
const { mode } = useThemeMode();
const navigate = useNavigate(); const navigate = useNavigate();
const onSelectItem = (genderId: number) => { const onSelectItem = (genderId: number) => {
navigate(`/gender/${genderId}`); navigate(`/gender/${genderId}`);
@@ -36,27 +35,31 @@ export const GendersPage = () => {
<SearchInput onChange={setFilterTitle} /> <SearchInput onChange={setFilterTitle} />
</TopBar> </TopBar>
<PageLayout> <PageLayout>
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center"> <HStack style={{
// wrap: "wrap",
/*spacing="20px"*/ margin: "0 auto 0 auto", padding: "20px", justifyContent: "center"
}}>
{dataGenders.map((data) => ( {dataGenders.map((data) => (
<WrapItem <Flex align="flex-start"
width="270px"
height="120px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" width: "270px",
_hover={{ height: "120px",
boxShadow: 'outline-over', border: "1px",
bgColor: mode('#FFFFFFF7', '#000000F7'), borderColor: "brand.900",
//background:{useColorModeValue('#FFFFFF88', '#00000088')},
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
onClick={() => onSelectItem(data.id)} onClick={() => onSelectItem(data.id)}
> >
<DisplayGender dataGender={data} /> <DisplayGender dataGender={data} />
</WrapItem> </Flex>
))} ))}
</Wrap> </HStack>
<EmptyEnd /> <EmptyEnd />
</PageLayout> </PageLayout>
</> </>

View File

@@ -1,18 +1,5 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import {
Button,
Flex,
Input,
Table,
TableContainer,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
} from '@chakra-ui/react';
import { LuTrash } from 'react-icons/lu'; import { LuTrash } from 'react-icons/lu';
import { MdCloudUpload } from 'react-icons/md'; import { MdCloudUpload } from 'react-icons/md';
@@ -38,6 +25,7 @@ import { useGenderService, useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { useTrackService } from '@/service/Track'; import { useTrackService } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Button, Flex, Table, Text } from '@/ui';
export class ElementList { export class ElementList {
constructor( constructor(
@@ -127,7 +115,7 @@ export const AddPage = () => {
updateNeedSend(); updateNeedSend();
}; };
const removeElementFromList = (data: FileParsedElement, value: any): void => { const removeElementFromList = (data: FileParsedElement): void => {
const parsedElementTmp = [...parsedElement]; const parsedElementTmp = [...parsedElement];
for (let iii = 0; iii < parsedElementTmp.length; iii++) { for (let iii = 0; iii < parsedElementTmp.length; iii++) {
if (parsedElementTmp[iii] === data) { if (parsedElementTmp[iii] === data) {
@@ -426,15 +414,17 @@ export const AddPage = () => {
<PageLayout> <PageLayout>
<Flex <Flex
direction="column" direction="column"
width="80%"
marginX="auto"
padding="10px"
gap="10px" gap="10px"
style={{
width: "80%",
margin: "0 auto 0 auto",
padding: "10px",
}}
> >
<Flex direction="column" width="full"> <Flex direction="column" style={{ width: "100%" }}>
<Flex> <Flex>
<Text flex={1}>format:</Text> <Text>format:</Text>
<Text flex={4}> <Text>
The format of the media permit to automatic find meta-data: The format of the media permit to automatic find meta-data:
<br /> <br />
Artist~album#idTrack-my name of my media.webm Artist~album#idTrack-my name of my media.webm
@@ -443,15 +433,15 @@ export const AddPage = () => {
</Text> </Text>
</Flex> </Flex>
<Flex> <Flex>
<Text flex={1}>Media:</Text> <Text>Media:</Text>
<Input {/* <Input
flex={4} flex={4}
type="file" type="file"
placeholder="Select a media file" placeholder="Select a media file"
accept=".webm" accept=".webm"
multiple multiple
onChange={onChangeFile} onChange={onChangeFile}
/> /> */}
</Flex> </Flex>
</Flex> </Flex>
{parsedElement && parsedElement.length !== 0 && ( {parsedElement && parsedElement.length !== 0 && (
@@ -483,102 +473,99 @@ export const AddPage = () => {
addNewItem={addNewAlbum} addNewItem={addNewAlbum}
suggestion={suggestedAlbum} suggestion={suggestedAlbum}
/> />
<TableContainer> <Table.Root
<Table
variant="striped"
colorScheme="teal"
background="gray.700" background="gray.700"
> >
<Thead> <Table.Header>
<Tr> <Table.Row>
<Th>track ID</Th> <Table.ColumnHeader>track ID</Table.ColumnHeader>
<Th width="full">Title</Th> <Table.ColumnHeader style={{ width: "100%" }}>Title</Table.ColumnHeader>
<Th>actions</Th> <Table.ColumnHeader>actions</Table.ColumnHeader>
</Tr> </Table.Row>
</Thead> </Table.Header>
<Tbody> <Table.Body>
{parsedElement.map((data) => ( {parsedElement.map((data) => (
<Tr key={data.uniqueId}> <Table.Row key={data.uniqueId}>
<Td> <Table.Cell>
<Input {/* <Input
type="number" type="number"
pattern="[0-9]{0-4}" pattern="[0-9]{0-4}"
placeholder="e?" placeholder="e?"
value={data.trackId} value={data.trackId}
onChange={(e) => onTrackId(data, e.target.value)} onChange={(e) => onTrackId(data, e.target.value)}
backgroundColor={ background={
data.trackIdDetected === true data.trackIdDetected === true
? 'darkred' ? 'darkred'
: undefined : undefined
} }
/> /> */}
</Td> </Table.Cell>
<Td> <Table.Cell>
<Input {/* <Input
type="text" type="text"
placeholder="Name of the Media" placeholder="Name of the Media"
value={data.title} value={data.title}
onChange={(e) => onTitle(data, e.target.value)} onChange={(e) => onTitle(data, e.target.value)}
backgroundColor={ background={
data.title === '' ? 'darkred' : undefined data.title === '' ? 'darkred' : undefined
} }
/> /> */}
{data.nameDetected === true && ( {data.nameDetected === true && (
<> <>
<br /> <br />
<Text as="span" color="@danger"> <Text style={{ background: "red" }}>
^^^This title already exist !!! ^^^This title already exist !!!
</Text> </Text>
</> </>
)} )}
</Td> </Table.Cell>
<Td> <Table.Cell>
<Button <Button
onClick={(e) => onClick={() =>
removeElementFromList(data, e.target) removeElementFromList(data)
} }
> >
<LuTrash /> Remove <LuTrash /> Remove
</Button> </Button>
</Td> </Table.Cell>
</Tr> </Table.Row>
))} ))}
</Tbody> </Table.Body>
</Table> </Table.Root>
<Flex marginY="15px"> <Flex style={{ margin: "15px 0 15px 0" }}>
<Button <Button
variant="@primary" //theme="@primary"
onClick={sendFile} onClick={sendFile}
disabled={!needSend} style={{
marginLeft="auto" //disabled:!needSend,
marginRight="30px" margin: "0 auto 0 30px"
}}
> >
<MdCloudUpload /> Upload <MdCloudUpload /> Upload
</Button> </Button>
</Flex> </Flex>
</TableContainer>
</> </>
)} )}
{listFileInBdd && ( {listFileInBdd && (
<> <Table.Root
<TableContainer> //fontPalette="striped"
<Table //colorScheme="teal"
variant="striped" style={{
colorScheme="teal" background: "gray.700"
background="gray.700" }}
> >
<Thead> <Table.Header>
<Tr> <Table.Row>
<Th>track ID</Th> <Table.ColumnHeader>track ID</Table.ColumnHeader>
<Th width="full">Title</Th> <Table.ColumnHeader style={{ width: "100%" }}>Title</Table.ColumnHeader>
<Th>actions</Th> <Table.ColumnHeader>actions</Table.ColumnHeader>
</Tr> </Table.Row>
</Thead> </Table.Header>
<Tbody> <Table.Body>
{listFileInBdd.map((data) => ( {listFileInBdd.map((data) => (
<Tr> <Table.Row>
<Td> <Table.Cell>
<Text <Text
color={ color={
data.episodeDetected === true ? 'red' : undefined data.episodeDetected === true ? 'red' : undefined
@@ -586,8 +573,8 @@ export const AddPage = () => {
> >
{data.trackId} {data.trackId}
</Text> </Text>
</Td> </Table.Cell>
<Td> <Table.Cell>
<Text <Text
color={ color={
data.nameDetected === true ? 'red' : undefined data.nameDetected === true ? 'red' : undefined
@@ -595,41 +582,37 @@ export const AddPage = () => {
> >
{data.title} {data.title}
</Text> </Text>
</Td> </Table.Cell>
<Td></Td> <Table.Cell></Table.Cell>
</Tr> </Table.Row>
))} ))}
</Tbody> </Table.Body>
</Table> </Table.Root>
</TableContainer>
</>
)} )}
{parsedFailedElement && ( {parsedFailedElement && (
<> <>
<Text fontSize="30px">Rejected:</Text> <Text fontSize="30px">Rejected:</Text>
<TableContainer> <Table.Root
<Table //colorPalette="striped"
variant="striped" //colorScheme="teal"
colorScheme="teal" style={{ background: "gray.700" }}
background="gray.700"
> >
<Thead> <Table.Header>
<Tr> <Table.Row>
<Th maxWidth="80%">file</Th> <Table.ColumnHeader style={{ maxWidth: "80%" }}>file</Table.ColumnHeader>
<Th>Reason</Th> <Table.ColumnHeader>Reason</Table.ColumnHeader>
</Tr> </Table.Row>
</Thead> </Table.Header>
<Tbody> <Table.Body>
{parsedFailedElement.map((data) => ( {parsedFailedElement.map((data) => (
<Tr key={data.uniqueId}> <Table.Row key={data.uniqueId}>
<Td>{data.file.name}</Td> <Table.Cell>{data.file.name}</Table.Cell>
<Td>{data.reason}</Td> <Table.Cell>{data.reason}</Table.Cell>
</Tr> </Table.Row>
))} ))}
</Tbody> </Table.Body>
</Table> </Table.Root>
</TableContainer>
</> </>
)} )}
</Flex> </Flex>

View File

@@ -1,13 +1,14 @@
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { Center, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
import { LuCrown, LuDisc3, LuEar, LuFileAudio } from 'react-icons/lu'; import { LuCrown, LuDisc3, LuEar, LuFileAudio } from 'react-icons/lu';
import { MdGroup } from 'react-icons/md'; import { MdGroup } from 'react-icons/md';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { useThemeMode } from '@/utils/theme-tools';
import { useColorThemeValue } from '@/theme/ThemeContext';
import { Div, Flex, HStack, Text } from '@/ui';
type HomeListType = { type HomeListType = {
id: number; id: number;
@@ -19,37 +20,36 @@ const homeList: HomeListType[] = [
{ {
id: 1, id: 1,
name: 'Genders', name: 'Genders',
icon: <LuCrown size="60%" height="full" />, icon: <LuCrown style={{ width: "100px", height: "100px" }} />,
to: 'gender', to: 'gender',
}, },
{ {
id: 2, id: 2,
name: 'Artists', name: 'Artists',
icon: <MdGroup size="60%" height="full" />, icon: <MdGroup style={{ width: "100px", height: "100px" }} />,
to: 'artist', to: 'artist',
}, },
{ {
id: 3, id: 3,
name: 'Albums', name: 'Albums',
icon: <LuDisc3 size="60%" height="full" />, icon: <LuDisc3 style={{ width: "100px", height: "100px" }} />,
to: 'album', to: 'album',
}, },
{ {
id: 4, id: 4,
name: 'Tracks', name: 'Tracks',
icon: <LuFileAudio size="60%" height="full" />, icon: <LuFileAudio style={{ width: "100px", height: "100px" }} />,
to: 'track', to: 'track',
}, },
{ {
id: 5, id: 5,
name: 'Playlists', name: 'Playlists',
icon: <LuEar size="60%" height="full" />, icon: <LuEar style={{ width: "100px", height: "100px" }} />,
to: 'playlists', to: 'playlists',
}, },
]; ];
export const HomePage = () => { export const HomePage = () => {
const { mode } = useThemeMode();
const navigate = useNavigate(); const navigate = useNavigate();
const onSelectItem = (data: HomeListType) => { const onSelectItem = (data: HomeListType) => {
navigate(data.to); navigate(data.to);
@@ -58,39 +58,55 @@ export const HomePage = () => {
<> <>
<TopBar title="Home" /> <TopBar title="Home" />
<PageLayout> <PageLayout>
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center"> <HStack style={{
flexWrap: "wrap",
/*spacing:"20px",*/
margin: "0 auto",
padding: "20px",
alignContent: "center"
}}>
{homeList.map((data) => ( {homeList.map((data) => (
<WrapItem <Flex align="flex-start"
width="200px" margin="auto"
height="190px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" width: "200px",
height: "190px",
border: "1px",
borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
}}
_hover={{ _hover={{
boxShadow: 'outline-over', boxShadow: 'outline-over',
bgColor: mode('#FFFFFFF7', '#000000F7'), background: useColorThemeValue('#FFFFFFF7', '#000000F7'),
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
<Flex direction="column" width="full" height="full"> <Flex
<Center height="full">{data.icon}</Center> direction="column"
<Center> style={{
width: "100%",
margin: "auto"
}}>
<Div style={{ margin: "auto", height: "100%", color: "black.50" }}>{data.icon}</Div>
<Div style={{ margin: "auto", height: "100%" }}>
<Text <Text
fontSize="25px" fontSize="25px"
fontWeight="bold" fontWeight="bold"
textTransform="uppercase" color="black.50"
userSelect="none" style={{
textTransform: "uppercase",
userSelect: "none",
}}
> >
{data.name} {data.name}
</Text> </Text>
</Center> </Div>
</Flex>
</Flex> </Flex>
</WrapItem>
))} ))}
</Wrap> </HStack>
</PageLayout > </PageLayout >
</> </>
); );

View File

@@ -0,0 +1,128 @@
import { Route, Routes, useNavigate } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar';
import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp';
import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useColorThemeValue } from '@/theme/ThemeContext';
import { BASE_WRAP_SPACING } from '@/constants/genericSpacing';
import { DisplayTrackFullId } from '@/components/track/DisplayTrackFullId';
import { Flex } from '@/ui';
export const OnAirPage = () => {
const { playInList } = useActivePlaylistService();
const { playTrackList, trackOffset, setNewPlaylist } = useActivePlaylistService();
const navigate = useNavigate();
const onSelectItem = (trackId: number) => {
let currentPlay = 0;
if (!playTrackList) {
console.log('Fail to get list of on air...');
return;
}
for (let iii = 0; iii < playTrackList.length; iii++) {
if (playTrackList[iii] === trackId) {
currentPlay = iii;
}
}
playInList(currentPlay, playTrackList);
};
const removeTrack = (trackId: number) => {
let elementToRemoveOffset = 0;
if (!playTrackList) {
console.log('Fail to remove element of on air...');
return;
}
const newList: number[] = [];
for (let iii = 0; iii < playTrackList.length; iii++) {
if (playTrackList[iii] === trackId) {
elementToRemoveOffset = iii;
} else {
newList.push(playTrackList[iii])
}
}
let newPlayed = trackOffset;
if (newPlayed != undefined) {
if (elementToRemoveOffset < newPlayed) {
newPlayed = newPlayed - 1;
}
playInList(newPlayed, newList);
} else {
setNewPlaylist(newList);
}
};
console.log(`playTrackList = ${JSON.stringify(playTrackList, null, 2)}`);
if (!playTrackList) {
return (
<>
<TopBar title="Album detail" />
<PageLayoutInfoCenter>
No data is currently playing...
</PageLayoutInfoCenter>
</>
);
}
return (
<>
<TopBar title="On Air ...">
</TopBar>
<PageLayout>
<Flex
direction="column"
gap={BASE_WRAP_SPACING}
style={{
margin: "0 auto",
padding: "20px",
width: "80%",
}}
>
{!playTrackList && <>No playing</>}
{playTrackList && playTrackList?.map((data) => (
<Flex
key={data}
style={{
minWidth: "100%",
//height:"60px",
border: "1px",
borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}}
>
<DisplayTrackFullId
trackId={data}
onClick={() => onSelectItem(data)}
contextMenu={[
{
name: 'Edit',
onClick: () => {
navigate(`edit-track/${data}`);
},
},
{
name: 'Remove from playlist',
onClick: () => {
removeTrack(data);
},
},
]}
/>
</Flex>
))}
<EmptyEnd />
</Flex>
<Routes>
<Route path="edit-track/:trackId" element={<TrackEditPopUp />} />
<Route path="edit-album/:albumId" element={<AlbumEditPopUp />} />
</Routes>
</PageLayout>
</>
);
};

View File

@@ -1,6 +1,5 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Center, Heading, Image, Text } from '@chakra-ui/react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import avatar_generic from '@/assets/images/avatar_generic.svg'; import avatar_generic from '@/assets/images/avatar_generic.svg';
@@ -11,6 +10,8 @@ import { SessionState } from '@/service/SessionState';
import { useSessionService } from '@/service/session'; import { useSessionService } from '@/service/session';
import { b64_to_utf8 } from '@/utils/sso'; import { b64_to_utf8 } from '@/utils/sso';
import { Flex, Text, Image } from '@/ui';
export const SSOPage = () => { export const SSOPage = () => {
const { data, keepConnected, token } = useParams(); const { data, keepConnected, token } = useParams();
console.log(`- data: ${data}`); console.log(`- data: ${data}`);
@@ -47,13 +48,11 @@ export const SSOPage = () => {
<> <>
<TopBar /> <TopBar />
<PageLayoutInfoCenter width="35%" gap="15px"> <PageLayoutInfoCenter width="35%" gap="15px">
<Center w="full"> <Text>LOGIN (after SSO)</Text>
<Heading size="xl">LOGIN (after SSO) </Heading>
</Center>
<Center w="full"> <Flex width="100%">
<Image src={avatar_generic} boxSize="150px" borderRadius="full" /> <Image src={avatar_generic} boxSize="150px" style={{ borderRadius: "full" }} />
</Center> </Flex>
{token === '__CANCEL__' && ( {token === '__CANCEL__' && (
<Text> <Text>
<b>ERROR: </b> Request cancel of connection ! <b>ERROR: </b> Request cancel of connection !

View File

@@ -1,12 +1,8 @@
import { ReactElement } from 'react';
import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { DataTools, TypeCheck } from '@/utils/data-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { useThemeMode } from '@/utils/theme-tools';
export const alphabet = [ export const alphabet = [
'a', 'a',
@@ -39,7 +35,6 @@ export const alphabet = [
const possibilities = [...alphabet, '^^']; const possibilities = [...alphabet, '^^'];
export const TrackSelectionPage = () => { export const TrackSelectionPage = () => {
const { mode } = useThemeMode();
const navigate = useNavigate(); const navigate = useNavigate();
const onSelectItem = (data: string) => { const onSelectItem = (data: string) => {
navigate(`/track/${data}`); navigate(`/track/${data}`);
@@ -48,24 +43,24 @@ export const TrackSelectionPage = () => {
<> <>
<TopBar title="All Tracks" /> <TopBar title="All Tracks" />
<PageLayout> <PageLayout>
<Wrap spacing="20px" marginX="auto" padding="20px" justify="center"> <HStack wrap="wrap" /*spacing="20px"*/ marginX="auto" padding="20px" justify="center">
{possibilities.map((data) => ( {possibilities.map((data) => (
<WrapItem <Flex align="flex-start"
width="75px" width="75px"
height="75px" height="75px"
border="1px" border="1px"
borderColor="brand.900" borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')} background={useColorThemeValue('#FFFFFF88', '#00000088')}
key={data} key={data}
padding="5px" padding="5px"
as="button" as="button"
_hover={{ _hover={{
boxShadow: 'outline-over', boxShadow: 'outline-over',
bgColor: mode('#FFFFFFF7', '#000000F7'), bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'),
}} }}
onClick={() => onSelectItem(data)} onClick={() => onSelectItem(data)}
> >
<Flex direction="column" width="full" height="full"> <Flex direction="column" width="100%" height="full">
<Text <Text
margin="auto" margin="auto"
fontSize="25px" fontSize="25px"
@@ -76,9 +71,9 @@ export const TrackSelectionPage = () => {
{data.toUpperCase()} {data.toUpperCase()}
</Text> </Text>
</Flex> </Flex>
</WrapItem> </Flex>
))} ))}
</Wrap> </HStack>
</PageLayout> </PageLayout>
</> </>
); );

View File

@@ -1,22 +1,18 @@
import { ReactElement } from 'react'; import { useParams } from 'react-router-dom';
import { Box, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react';
import { useNavigate, useParams } from 'react-router-dom';
import { EmptyEnd } from '@/components/EmptyEnd'; import { EmptyEnd } from '@/components/EmptyEnd';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { DisplayTrack } from '@/components/track/DisplayTrack';
import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; import { DisplayTrackFull } from '@/components/track/DisplayTrackFull';
import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton'; import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton';
import { alphabet } from '@/scene/track/TrackSelectionPage'; import { alphabet } from '@/scene/track/TrackSelectionPage';
import { useActivePlaylistService } from '@/service/ActivePlaylist'; import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useTrackService, useTracksWithStartName } from '@/service/Track'; import { useTracksWithStartName } from '@/service/Track';
import { useThemeMode } from '@/utils/theme-tools'; import { useColorThemeValue } from '@/theme/ThemeContext';
import { Flex } from '@/ui';
export const TracksStartLetterDetailPage = () => { export const TracksStartLetterDetailPage = () => {
const { startLetter } = useParams(); const { startLetter } = useParams();
const { mode } = useThemeMode();
const { isLoading, tracksStartsWith } = useTracksWithStartName( const { isLoading, tracksStartsWith } = useTracksWithStartName(
startLetter !== '^^' startLetter !== '^^'
? startLetter ? startLetter
@@ -52,50 +48,55 @@ export const TracksStartLetterDetailPage = () => {
<Flex <Flex
direction="column" direction="column"
gap="20px" gap="20px"
marginX="auto" style={{
padding="20px" margin: "0 auto",
width="80%" padding: "20px",
width: "80%",
}}
> >
{isLoading && {isLoading &&
[1, 2, 3, 4]?.map((data) => ( [1, 2, 3, 4]?.map((data) => (
<Box <Flex
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data} key={data}
padding="5px" style={{
as="button" minWidth: "100%",
_hover={{ //height:"60px",
boxShadow: 'outline-over', border: "1px",
bgColor: mode('#FFFFFFF7', '#000000F7'), borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackSkeleton /> <DisplayTrackSkeleton />
</Box> </Flex>
))} ))}
{!isLoading && {!isLoading &&
tracksStartsWith?.map((data) => ( tracksStartsWith?.map((data) => (
<Box <Flex
minWidth="100%"
//height="60px"
border="1px"
borderColor="brand.900"
backgroundColor={mode('#FFFFFF88', '#00000088')}
key={data.id} key={data.id}
padding="5px" style={{
as="button" minWidth: "100%",
_hover={{ //height:"60px",
boxShadow: 'outline-over', border: "1px",
bgColor: mode('#FFFFFFF7', '#000000F7'), borderColor: "brand.900",
background: useColorThemeValue('#FFFFFF88', '#00000088'),
padding: "5px",
// _hover: {
// boxShadow: 'outline-over',
// bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
// }
}} }}
> >
<DisplayTrackFull <DisplayTrackFull
track={data} track={data}
onClick={() => onSelectItem(data.id)} onClick={() => onSelectItem(data.id)}
/> />
</Box> </Flex>
))} ))}
<EmptyEnd /> <EmptyEnd />
</Flex> </Flex>

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react'; import { useMemo } from 'react';
import { Album, AlbumResource } from '@/back-api'; import { Album, AlbumResource } from '@/back-api';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useMemo } from 'react';
import { Track, TrackResource } from '@/back-api'; import { Track, TrackResource } from '@/back-api';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';

View File

@@ -9,6 +9,10 @@ import { parseToken } from '@/utils/sso';
const TOKEN_KEY = 'karusic-token-key-storage'; const TOKEN_KEY = 'karusic-token-key-storage';
export const USERS_COLLECTION = [
{ label: "admin", value: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E" },
{ label: "NO_USER", value: "svelte" },
];
export const USERS = { export const USERS = {
admin: admin:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E',

View File

@@ -1,14 +1,11 @@
import { ReactElement, ReactNode } from 'react'; import { ReactElement, ReactNode } from 'react';
import { ChakraProvider } from '@chakra-ui/react';
import { RenderOptions, render } from '@testing-library/react'; import { RenderOptions, render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import theme from '@/theme';
const CustomWrapper = ({ children }: { children: ReactNode }) => { const CustomWrapper = ({ children }: { children: ReactNode }) => {
return ( return (
<ChakraProvider theme={theme}> <ChakraProvider value={defaultSystem}>
<BrowserRouter>{children}</BrowserRouter> <BrowserRouter>{children}</BrowserRouter>
</ChakraProvider> </ChakraProvider>
); );

View File

@@ -0,0 +1,202 @@
import { createContext, useContext, useState, useEffect, CSSProperties, ReactNode, useCallback } from "react";
import { basicColor } from "./colors";
export type ApplyThemeProperty = {
componentType?: String;
// define the format of the shape
shape?: String;
// Define the color model used
palette?: String;
}
export type ThemeContextProps = {
theme: string;
themeList: string[];
setTheme: (string) => void;
toggleTheme: () => void;
convertStyle: (style: CSSProperties) => CSSProperties,
applyTheme: (style: CSSProperties, theme: ApplyThemeProperty) => CSSProperties,
}
const ThemeContext = createContext<ThemeContextProps>({
theme: "error",
themeList: ["error"],
setTheme: (_: string) => {
console.error("Request setTheme without context");
},
toggleTheme: () => {
console.error("Request toggleTheme without context");
},
convertStyle: (style: CSSProperties): CSSProperties => {
console.error("Request convertStyle without context");
return style;
},
applyTheme: (style: CSSProperties, _theme: ApplyThemeProperty) => {
console.error("Request applyTheme without context");
return style;
},
});
const themes = {
baseColors: {
...basicColor
},
themeColors: {
light: {
primary: "#007bff",
secondary: "#6c757d",
background: "#ffffff",
text: "#000000",
},
dark: {
primary: "#375a7f",
secondary: "#444444",
background: "#181818",
text: "#ffffff",
},
},
base: {
Button: {
border: '1px solid red',
},
Flex: {
},
},
shape: {
outline: {
border: '1px solid transparent',
},
},
palette: {
primary: {
borderColor: "green"
},
secondary: {
},
danger: {
},
success: {
}
}
};
export function ThemeProvider({ children, themeList = ["light", "dark"] }: { children: ReactNode, themeList?: string[] }) {
const [theme, setTheme] = useState<string>(themeList[1]);
const [basicsColor, setBasicsColor] = useState<{ [key: string]: string }>({});
// update the global CSS wen theme is updated:
useEffect(() => {
// generates the colors:
const baseColor = themes.baseColors;
const newBasicsColor: { [key: string]: string } = {}
Object.entries(baseColor).forEach(([tableColorName, colorValues]) => {
Object.entries(colorValues).forEach(([key, value]) => {
const colorName = `${tableColorName}.${key}`;
newBasicsColor[colorName] = value;
});
});
setBasicsColor(newBasicsColor);
const root = document.documentElement;
//const currentColorTheme = themes.baseColors[theme];
Object.entries(newBasicsColor).forEach(([key, value]) => {
const cssKeyValue = `--${key.replace(".", "-")}`;
console.log(` generate CSS color: ${cssKeyValue}:${value}`);
root.style.setProperty(cssKeyValue, value);
});
}, [theme, setBasicsColor]);
const toggleTheme = useCallback(() => {
setTheme((previous) => {
if (themeList.length <= 1) {
return previous;
}
for (let iii = 0; iii < themeList.length - 1; iii++) {
if (themeList[iii] == theme) {
return themeList[iii + 1]
}
}
return themeList[0];
})
}, [setTheme]);
const convertElementStyle = useCallback((style: CSSProperties, key: string) => {
const value = style[key];
if (typeof value !== "string") {
return;
}
// console.log(`request convert value: ${value}`);
if (basicsColor[value]) {
// console.log(`convert value: ${value} in ${basicsColor[value]}`);
style[key] = basicsColor[value];
}
}, [basicsColor]);
const convertStyle = useCallback((style: CSSProperties): CSSProperties => {
//console.log(`plop: ${theme}`);
if (!style) {
return style;
}
const out = { ...style }
convertElementStyle(out, "background");
convertElementStyle(out, "backgroundColor");
convertElementStyle(out, "borderColor");
convertElementStyle(out, "color");
return out;
}, [convertElementStyle]);
const applyTheme = useCallback((style: CSSProperties, selectedTheme?: ApplyThemeProperty): CSSProperties => {
let out = style;
console.log(`apply style ... ${JSON.stringify(selectedTheme, null, 2)}`);
// Apply the basic theme of the component:
if (selectedTheme?.componentType) {
console.log(`detect component type theme ... ${selectedTheme?.componentType}`);
const base = themes?.base[selectedTheme.componentType];
console.log(` base = ${JSON.stringify(base, null, 2)}`);
if (base) {
out = { ...base, ...out };
}
}
// Apply the variant selected:
if (selectedTheme?.shape) {
const shape = themes?.shape[selectedTheme.shape];
if (shape) {
out = { ...shape, ...out };
}
}
// Apply the palette selected:
if (selectedTheme?.palette) {
const palette = themes?.shape[selectedTheme.palette];
if (palette) {
const palette2 = palette[theme];
if (palette2) {
out = { ...palette2, ...out };
}
}
}
return convertStyle(out);
}, [convertStyle]);
return (
<ThemeContext.Provider value={{ theme, themeList, setTheme, toggleTheme, convertStyle, applyTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
export function useColorThemeValue<T>(firstTheme: T, secondTheme: T) {
const { theme, themeList } = useTheme()
//console.log(`use theme =${theme} ==> ${theme === themeList[0] ? firstTheme : secondTheme}`);
return theme === themeList[0] ? firstTheme : secondTheme
}

View File

@@ -1,44 +1,4 @@
type ThemeModel = { // Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
50: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
};
const isLightMode = false;
const reverseColor = (data: ThemeModel) => {
return {
50: data[900],
100: data[800],
200: data[700],
300: data[600],
400: data[500],
500: data[400],
600: data[300],
700: data[200],
800: data[100],
900: data[50],
};
};
const back = {
50: '#ebf4fa',
100: '#d1dbe0',
200: '#b6c2c9',
300: '#99aab4',
400: '#7c939e',
500: '#637985',
600: '#4d5e67',
700: '#37444a',
800: '#1f292e',
900: '#020f12',
};
const brand = { const brand = {
50: '#e3edff', 50: '#e3edff',
100: '#b6c9fd', 100: '#b6c9fd',
@@ -51,7 +11,8 @@ const brand = {
800: '#02164a', 800: '#02164a',
900: '#00071e', 900: '#00071e',
}; };
const normalText = {
const black = {
50: '#f2f2f2', 50: '#f2f2f2',
100: '#d9d9d9', 100: '#d9d9d9',
200: '#bfbfbf', 200: '#bfbfbf',
@@ -88,7 +49,6 @@ const blue = {
800: '#1e40af', 800: '#1e40af',
900: '#1e3a8a', 900: '#1e3a8a',
}; };
const orange = { const orange = {
50: '#fff7ed', 50: '#fff7ed',
100: '#ffedd5', 100: '#ffedd5',
@@ -113,15 +73,54 @@ const red = {
800: '#991b1b', 800: '#991b1b',
900: '#7f1d1d', 900: '#7f1d1d',
}; };
const yellow = {
50: '#ffffda',
100: '#ffffad',
200: '#ffff7d',
300: '#ffff4b',
400: '#ffff1a',
500: '#e5e600',
600: '#b2b300',
700: '#7f8000',
800: '#4c4d00',
900: '#191b00',
};
const purple = {
50: '#ffe3ff',
100: '#ffb2ff',
200: '#ff80ff',
300: '#fe4efe',
400: '#fe20fe',
500: '#e50ce4',
600: '#b204b1',
700: '#80007f',
800: '#4e004d',
900: '#1d001c',
};
export const colors = { const cyan = {
// Update me with other Tailwind colors or with https://smart-swatch.netlify.app/ 50: '#d6ffff',
100: '#aaffff',
200: '#7affff',
300: '#47ffff',
400: '#1affff',
500: '#00e5e6',
600: '#00b2b3',
700: '#008081',
800: '#004d4e',
900: '#001b1d',
}
brand: brand, export const basicColor = {
back: back, brand,
text: normalText, green,
/// ???? red,
success: green, orange,
error: red, black,
blue,
yellow,
purple,
cyan,
back: black,
warning: orange, warning: orange,
} as const; }

View File

@@ -1,196 +0,0 @@
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.600')(props),
bgHover: mode('error.700', 'error.500')(props),
bgActive: mode('error.600', 'error.500')(props),
color: mode('white', 'error.900')(props),
boxColorFocus: mode('error.900', 'error.500')(props),
}),
'@success': (props) =>
customVariant({
theme: props.theme,
bg: mode('green.300', 'green.300')(props),
bgHover: mode('green.400', 'green.400')(props),
bgActive: mode('green.500', 'green.400')(props),
color: mode('white', 'green.900')(props),
boxColorFocus: mode('green.900', 'green.400')(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',
},
}),
},
});

View File

@@ -1,19 +0,0 @@
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;

View File

@@ -1,25 +0,0 @@
import { getColor, mode } from '@chakra-ui/theme-tools';
export default {
variants: {
outline: (props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
field: {
bg: mode('gray.50', 'whiteAlpha.50')(props),
borderColor: mode('gray.200', 'whiteAlpha.100')(props),
color: mode('gray.800', 'gray.50')(props),
_focus: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
},
},
};
},
},
};

View File

@@ -1,33 +0,0 @@
import { numberInputAnatomy } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
import { getColor, mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(numberInputAnatomy.keys);
const baseStyle = definePartsStyle((props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
field: {
border: 0,
_focusVisible: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
ring: '1px',
ringColor: focusBorderColor,
ringOffset: '1px',
ringOffsetColor: focusBorderColor,
},
},
};
});
export default defineMultiStyleConfig({
baseStyle,
});

View File

@@ -1,24 +0,0 @@
import { getColor, mode } from '@chakra-ui/theme-tools';
export default {
variants: {
outline: (props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
field: {
bg: mode('gray.50', 'whiteAlpha.50')(props),
borderColor: mode('blackAlpha.100', 'whiteAlpha.100')(props),
_focus: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
},
},
};
},
},
};

View File

@@ -1,22 +0,0 @@
import { getColor, mode } from '@chakra-ui/theme-tools';
export default {
variants: {
outline: (props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
bg: mode('gray.50', 'whiteAlpha.50')(props),
borderColor: mode('blackAlpha.100', 'whiteAlpha.100')(props),
_focus: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
},
};
},
},
};

View File

@@ -1,9 +0,0 @@
import { colors } from './colors';
import { shadows } from './shadows';
const foundations = {
colors,
shadows,
};
export default foundations;

View File

@@ -1,26 +0,0 @@
import { transparentize } from '@chakra-ui/theme-tools';
import { colors } from './colors';
const createOutline = (colorScheme = 'gray') =>
`0 0 0 3px ${transparentize(`${colorScheme}.500`, 0.3)({ colors })}`;
export const shadows = {
outline: createOutline('brand'),
'outline-brand': '0 0 0 1px brand.900',
'outline-gray': createOutline('gray'),
'outline-over': `4px 4px 5px #00000088`,
'outline-darkgray': `0 0 0 3px ${transparentize(
'gray.500',
0.8
)({ colors })}`,
'outline-success': createOutline('success'),
'outline-warning': createOutline('warning'),
'outline-error': createOutline('error'),
'outline-doing': createOutline('doing'),
'outline-paused': createOutline('paused'),
layout: '0 0 24px 1px rgba(0, 0, 0, 0.05)',
smooth: 'inset 0px 0px 16px rgba(0, 0, 0, 0.05)',
// smooth-light is used for dark backgrounds
'smooth-light': 'inset 0px 0px 16px rgba(255, 255, 255, 0.1)',
};

View File

@@ -1 +0,0 @@
export { theme as default } from './theme';

View File

@@ -0,0 +1,103 @@
// https://medium.com/@a.heydari.dev/simplifying-chakra-ui-v3-recipes-vs-chakra-factory-a-developers-perspective-4020b62f1b4d
// const shimmer = keyframes`
// 100% {
// transform: translateX(100%);
// }
// `;
export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxShadowHover }) => {
return {
bg,
color,
border: '1px solid transparent',
_focus: {
border: '1px solid',
borderColor: 'black',
},
_hover: {
bg: bgHover,
color: colorHover,
boxShadow: boxShadowHover,
_disabled: {
bg,
boxShadow: 'none',
},
},
_active: {
bg: bgActive,
},
};
};
const buttonTheme = {
primary:
customVariant({
bg: { _light: 'brand.600', _dark: 'brand.300' },
bgHover: { _light: 'brand.700', _dark: 'brand.400' },
bgActive: { _light: 'brand.600', _dark: 'brand.300' },
color: { _light: 'white', _dark: 'brand.900' },
colorHover: { _light: 'brand.800', _dark: 'brand.100' },
boxShadowHover: 'outline-over'
}),
secondary:
customVariant({
bg: { _light: 'brand.100', _dark: 'brand.900' },
bgHover: { _light: 'brand.200', _dark: 'brand.800' },
bgActive: { _light: 'brand.300', _dark: 'brand.700' },
color: { _light: 'brand.700', _dark: 'brand.50' },
colorHover: { _light: 'brand.800', _dark: 'brand.100' },
boxShadowHover: 'outline-over',
}),
danger:
customVariant({
bg: { _light: 'error.600', _dark: 'error.600' },
bgHover: { _light: 'error.700', _dark: 'error.500' },
bgActive: { _light: 'error.600', _dark: 'error.500' },
color: { _light: 'white', _dark: 'error.900' },
colorHover: { _light: 'error.700', _dark: 'error.900' },
boxShadowHover: 'outline-over',
}),
success:
customVariant({
bg: { _light: 'green.300', _dark: 'green.300' },
bgHover: { _light: 'green.400', _dark: 'green.400' },
bgActive: { _light: 'green.500', _dark: 'green.400' },
color: { _light: 'white', _dark: 'green.900' },
colorHover: { _light: 'green.500', _dark: 'green.900' },
boxShadowHover: 'outline-over',
}),
progress:
{
bg: { _light: `brand.500`, _dark: `brand.300` },
overflow: 'hidden',
/*
_after: !props.isLoading
? {
content: '""',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
transform: 'translateX(-100%)',
bgGradient: `linear(90deg, brand.100/0 0%, brand.100/2 20%, brand.100/5 60%, v.100/0`,
}
: undefined,
*/
},
menu: {
bg: 'back.100',
color: 'brand.900',
borderRadius: 0,
border: 0,
_hover: { background: 'back.300' },
_focus: { border: 'none' },
fontSize: '20px',
textTransform: 'uppercase',
},
};
export default buttonTheme;

View File

@@ -0,0 +1,29 @@
const drawerRecipe = {
base: {
bg: { _light: 'white', _dark: 'gray.800' },
color: { _light: 'gray.900', _dark: 'whiteAlpha.900' },
boxShadow: { _light: 'lg', _dark: 'dark-lg' },
padding: 4,
borderRadius: 'md',
},
variants: {
variant: {
solid: {
bg: { _light: 'blue.500', _dark: 'blue.300' },
color: 'white',
},
outline: {
border: '1px solid',
borderColor: { _light: 'gray.300', _dark: 'whiteAlpha.300' },
bg: { _light: 'white', _dark: 'gray.900' },
},
}
},
defaultVariants: {
variant: 'solid',
},
};
export default drawerRecipe;

View File

@@ -0,0 +1,20 @@
const flexTheme = {
variants: {
variant: {
'@menu': {
bg: { _light: 'back.100', _dark: 'back.800' },
color: { _light: 'brand.900', _dark: 'brand.100' },
borderRadius: 0,
border: 0,
_hover: { background: { _light: 'back.300', _dark: 'back.600' } },
_focus: { border: 'none' },
fontSize: '20px',
},
},
},
};
export default flexTheme;

View File

@@ -2,11 +2,12 @@ export { default as Badge } from './badge';
export { default as Button } from './button'; export { default as Button } from './button';
export { default as Checkbox } from './checkbox'; export { default as Checkbox } from './checkbox';
export { default as Input } from './input'; export { default as Input } from './input';
export { default as NumberInput } from './numberInput'; //export { default as NumberInput } from './numberInput.ts_';
export { default as Popover } from './popover'; export { default as Popover } from './popover';
export { default as Radio } from './radio'; export { default as Radio } from './radio';
export { default as Select } from './select'; export { default as Select } from './select';
export { default as Switch } from './switch'; export { default as Switch } from './switch';
export { default as Textarea } from './textarea'; export { default as Textarea } from './textarea';
export { default as Modal } from './modal'; //export { default as Modal } from './modal';
export { default as Flex } from './flex'; export { default as Flex } from './flex';
export { default as Drawer } from './drawer';

View File

@@ -0,0 +1,20 @@
const inputTheme = {
variants: {
variant: {
outline: {
field: {
bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' },
borderColor: { _light: 'gray.200', _dark: 'whiteAlpha.100' },
color: { _light: 'gray.800', _dark: 'gray.50' },
_focus: {
borderColor: { _light: 'gray.800', _dark: 'gray.50' },
boxShadow: `0 0 0 1px gray.800`,
},
},
},
},
},
};
export default inputTheme;

View File

@@ -1,5 +1,3 @@
import { modalAnatomy as parts } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);

View File

@@ -0,0 +1,19 @@
const selectTheme = {
variants: {
variant: {
outline: {
field: {
bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' },
borderColor: { _light: 'blackAlpha.100', _dark: 'whiteAlpha.100' },
_focus: {
borderColor: { _light: 'brand.500', _dark: 'brand.300' },
boxShadow: `0 0 0 1px black`,
},
},
},
},
},
};
export default selectTheme;

Some files were not shown because too many files have changed in this diff Show More