Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
88b27e5f39 | |||
eaf0f5688e | |||
4ebfa4e2ca | |||
c65e7d5e25 | |||
de61cc156f | |||
4d18438914 | |||
80dfabcf48 | |||
83bfeda4ca | |||
c489fabb77 | |||
d52052de90 | |||
91defa42c2 | |||
2812d21782 | |||
eb5a366a12 | |||
6b801d250f | |||
01fad1b9d4 | |||
371bea79f9 | |||
35725e1320 | |||
d6a8c7d23f | |||
3c604e9593 | |||
43d8108270 | |||
1a883193d0 | |||
78b1970ba9 | |||
746d5dff96 | |||
8780ea8e63 | |||
8911eed0fb | |||
1a3652472e | |||
2e62577103 | |||
653e77160b | |||
3898a6bf4f | |||
448cf1569b | |||
d3e2b3e601 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -65,3 +65,4 @@ __pycache__
|
|||||||
.design/
|
.design/
|
||||||
.vscode/
|
.vscode/
|
||||||
front/storybook-static
|
front/storybook-static
|
||||||
|
back/bin
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -6,7 +6,7 @@
|
|||||||
FROM archlinux:base-devel AS common
|
FROM archlinux:base-devel AS common
|
||||||
# update system
|
# update system
|
||||||
RUN pacman -Syu --noconfirm && pacman-db-upgrade \
|
RUN pacman -Syu --noconfirm && pacman-db-upgrade \
|
||||||
&& pacman -S --noconfirm jdk-openjdk \
|
&& pacman -S --noconfirm jdk-openjdk wget\
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
@ -53,13 +53,13 @@ RUN pnpm install --prod=false
|
|||||||
FROM dependency_front AS load_sources_front
|
FROM dependency_front AS load_sources_front
|
||||||
|
|
||||||
# JUST to get the vertion of the application and his sha...
|
# JUST to get the vertion of the application and his sha...
|
||||||
COPY front/build.js \
|
COPY \
|
||||||
front/version.txt \
|
|
||||||
front/tsconfig.json \
|
front/tsconfig.json \
|
||||||
front/tsconfig.node.json \
|
front/tsconfig.node.json \
|
||||||
front/vite.config.mts \
|
front/vite.config.mts \
|
||||||
front/index.html \
|
front/index.html \
|
||||||
./
|
./
|
||||||
|
|
||||||
COPY front/public ./public
|
COPY front/public ./public
|
||||||
COPY front/src ./src
|
COPY front/src ./src
|
||||||
|
|
||||||
@ -103,4 +103,8 @@ WORKDIR /application/
|
|||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
# To verify health-check: docker inspect --format "{{json .State.Health }}" YOUR_SERVICE_NAME | jq
|
||||||
|
HEALTHCHECK --start-period=10s --start-interval=2s --interval=30s --timeout=5s --retries=10 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:80/api/health_check || exit 1
|
||||||
|
|
||||||
CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.kar.karusic.WebLauncher"]
|
CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.kar.karusic.WebLauncher"]
|
||||||
|
86
README.md
86
README.md
@ -6,40 +6,90 @@ Karideo
|
|||||||
Run in local:
|
Run in local:
|
||||||
=============
|
=============
|
||||||
|
|
||||||
so simple...
|
Start tools
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Start the server basic interfaces: (DB(mySQL), Adminer)
|
||||||
|
|
||||||
```{.bash}
|
```{.bash}
|
||||||
# start the Bdd interface (no big data > 50Mo)
|
# start the Bdd interface (no big data > 50Mo)
|
||||||
cd bdd
|
docker compose -f env_dev/docker-compose.yaml up -d
|
||||||
docker-compose up -d
|
|
||||||
# start the REST API
|
|
||||||
cd back
|
|
||||||
docker-compose up -d
|
|
||||||
# start the front API
|
|
||||||
cd ../front
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Start the Back-end:
|
||||||
|
-------------------
|
||||||
|
|
||||||
convert in an angular application:
|
backend is developed in JAVA
|
||||||
https://betterprogramming.pub/how-to-convert-your-angular-application-to-a-native-mobile-app-android-and-ios-c212b38976df
|
|
||||||
|
|
||||||
|
The first step is configuring your JAVA version (or select the JVM with the OS)
|
||||||
|
```bash
|
||||||
|
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
|
||||||
|
```
|
||||||
|
|
||||||
Link with the external sub-library (front)
|
Install the dependency:
|
||||||
------------------------------------------
|
```bash
|
||||||
|
mvn install
|
||||||
|
```
|
||||||
|
|
||||||
Link:
|
Run the test
|
||||||
|
```bash
|
||||||
|
mvn test
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it for external use
|
||||||
|
```bash
|
||||||
|
mvn install
|
||||||
|
```
|
||||||
|
|
||||||
|
Execute the local server:
|
||||||
|
```bash
|
||||||
|
mvn exec:java@dev-mode
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the Front-end:
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
backend is developed in JAVA
|
||||||
```bash
|
```bash
|
||||||
cd front
|
cd front
|
||||||
pnpm run link_kar_cw
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
un-link:
|
Display the result:
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
[show the webpage: http://localhost:4203](http://localhost:4203)
|
||||||
|
|
||||||
|
Some other dev tools:
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Format code:
|
||||||
|
------------
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd front
|
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
|
||||||
pnpm run unlink_kar_cw
|
mvn formatter:format
|
||||||
|
mvn test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
=========================
|
=========================
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
128
back/pom.xml
128
back/pom.xml
@ -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.23.6</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>
|
||||||
<!--
|
<!--
|
||||||
************************************************************
|
************************************************************
|
||||||
@ -99,16 +104,45 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
<version>1.4.0</version>
|
<version>3.2.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>prod-mode</id>
|
||||||
|
<goals>
|
||||||
|
<goal>java</goal>
|
||||||
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<mainClass>org.kar.karusic.WebLauncher</mainClass>
|
<mainClass>org.kar.karusic.WebLauncher</mainClass>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>dev-mode</id>
|
||||||
|
<goals>
|
||||||
|
<goal>java</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>org.kar.karusic.WebLauncherLocal</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>generate-api</id>
|
||||||
|
<goals>
|
||||||
|
<goal>java</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>org.kar.karusic.GenerateApi</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<mainClass/>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<!-- Create the source bundle -->
|
<!-- Create the source bundle -->
|
||||||
<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 +156,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 +173,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 +218,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 +228,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>
|
||||||
|
17
back/src/org/kar/karusic/GenerateApi.java
Normal file
17
back/src/org/kar/karusic/GenerateApi.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package org.kar.karusic;
|
||||||
|
|
||||||
|
import org.kar.karusic.migration.Initialization;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class GenerateApi {
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(GenerateApi.class);
|
||||||
|
|
||||||
|
private GenerateApi() {}
|
||||||
|
|
||||||
|
public static void main(final String[] args) throws Exception {
|
||||||
|
LOGGER.info("Generate API");
|
||||||
|
Initialization.generateObjects();
|
||||||
|
LOGGER.info("STOP the REST server.");
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,10 @@
|
|||||||
package org.kar.karusic;
|
package org.kar.karusic;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.LogManager;
|
import java.util.logging.LogManager;
|
||||||
|
|
||||||
import org.kar.archidata.api.DataResource;
|
|
||||||
import org.kar.archidata.api.ProxyResource;
|
|
||||||
import org.kar.archidata.exception.DataAccessException;
|
import org.kar.archidata.exception.DataAccessException;
|
||||||
import org.kar.archidata.externalRestApi.AnalyzeApi;
|
|
||||||
import org.kar.archidata.externalRestApi.TsGenerateApi;
|
|
||||||
import org.kar.archidata.tools.ConfigBaseVariable;
|
import org.kar.archidata.tools.ConfigBaseVariable;
|
||||||
import org.kar.karusic.api.AlbumResource;
|
import org.kar.karusic.migration.Initialization;
|
||||||
import org.kar.karusic.api.ArtistResource;
|
|
||||||
import org.kar.karusic.api.Front;
|
|
||||||
import org.kar.karusic.api.GenderResource;
|
|
||||||
import org.kar.karusic.api.HealthCheck;
|
|
||||||
import org.kar.karusic.api.PlaylistResource;
|
|
||||||
import org.kar.karusic.api.TrackResource;
|
|
||||||
import org.kar.karusic.api.UserResource;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.slf4j.bridge.SLF4JBridgeHandler;
|
import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||||
@ -26,27 +14,17 @@ public class WebLauncherLocal extends WebLauncher {
|
|||||||
|
|
||||||
private WebLauncherLocal() {}
|
private WebLauncherLocal() {}
|
||||||
|
|
||||||
public static void generateObjects() throws Exception {
|
|
||||||
LOGGER.info("Generate APIs");
|
|
||||||
final List<Class<?>> listOfResources = List.of(AlbumResource.class, ArtistResource.class, Front.class, GenderResource.class, HealthCheck.class, PlaylistResource.class, UserResource.class,
|
|
||||||
TrackResource.class, DataResource.class, ProxyResource.class);
|
|
||||||
final AnalyzeApi api = new AnalyzeApi();
|
|
||||||
api.addAllApi(listOfResources);
|
|
||||||
TsGenerateApi.generateApi(api, "../front/src/back-api/");
|
|
||||||
LOGGER.info("Generate APIs (DONE)");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
// Loop-back of logger JDK logging API to SLF4J
|
// Loop-back of logger JDK logging API to SLF4J
|
||||||
LogManager.getLogManager().reset();
|
LogManager.getLogManager().reset();
|
||||||
SLF4JBridgeHandler.install();
|
SLF4JBridgeHandler.install();
|
||||||
// Generate the APIs in type-script
|
// Generate the APIs in type-script
|
||||||
generateObjects();
|
Initialization.generateObjects();
|
||||||
final WebLauncherLocal launcher = new WebLauncherLocal();
|
final WebLauncherLocal launcher = new WebLauncherLocal();
|
||||||
launcher.process();
|
launcher.process();
|
||||||
launcher.LOGGER.info("end-configure the server & wait finish process:");
|
LOGGER.info("end-configure the server & wait finish process:");
|
||||||
Thread.currentThread().join();
|
Thread.currentThread().join();
|
||||||
launcher.LOGGER.info("STOP the REST server:");
|
LOGGER.info("STOP the REST server:");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -2,10 +2,22 @@ package org.kar.karusic.migration;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.kar.archidata.api.DataResource;
|
||||||
|
import org.kar.archidata.api.ProxyResource;
|
||||||
import org.kar.archidata.dataAccess.DBAccess;
|
import org.kar.archidata.dataAccess.DBAccess;
|
||||||
|
import org.kar.archidata.externalRestApi.AnalyzeApi;
|
||||||
|
import org.kar.archidata.externalRestApi.TsGenerateApi;
|
||||||
import org.kar.archidata.migration.MigrationSqlStep;
|
import org.kar.archidata.migration.MigrationSqlStep;
|
||||||
import org.kar.archidata.model.Data;
|
import org.kar.archidata.model.Data;
|
||||||
import org.kar.archidata.model.User;
|
import org.kar.archidata.model.User;
|
||||||
|
import org.kar.karusic.api.AlbumResource;
|
||||||
|
import org.kar.karusic.api.ArtistResource;
|
||||||
|
import org.kar.karusic.api.Front;
|
||||||
|
import org.kar.karusic.api.GenderResource;
|
||||||
|
import org.kar.karusic.api.HealthCheck;
|
||||||
|
import org.kar.karusic.api.PlaylistResource;
|
||||||
|
import org.kar.karusic.api.TrackResource;
|
||||||
|
import org.kar.karusic.api.UserResource;
|
||||||
import org.kar.karusic.model.Album;
|
import org.kar.karusic.model.Album;
|
||||||
import org.kar.karusic.model.Artist;
|
import org.kar.karusic.model.Artist;
|
||||||
import org.kar.karusic.model.Gender;
|
import org.kar.karusic.model.Gender;
|
||||||
@ -25,14 +37,20 @@ public class Initialization extends MigrationSqlStep {
|
|||||||
return "Initialization";
|
return "Initialization";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Initialization() {
|
public static void generateObjects() throws Exception {
|
||||||
|
LOGGER.info("Generate APIs");
|
||||||
|
final List<Class<?>> listOfResources = List.of(AlbumResource.class, ArtistResource.class, Front.class, GenderResource.class, HealthCheck.class, PlaylistResource.class, UserResource.class,
|
||||||
|
TrackResource.class, DataResource.class, ProxyResource.class);
|
||||||
|
final AnalyzeApi api = new AnalyzeApi();
|
||||||
|
api.addAllApi(listOfResources);
|
||||||
|
TsGenerateApi.generateApi(api, "../front/src/back-api/");
|
||||||
|
LOGGER.info("Generate APIs (DONE)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 +81,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("""
|
||||||
|
@ -6,7 +6,7 @@ import java.util.List;
|
|||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
import org.kar.archidata.annotation.DataIfNotExists;
|
import org.kar.archidata.annotation.DataIfNotExists;
|
||||||
import org.kar.archidata.annotation.DataJson;
|
import org.kar.archidata.annotation.DataJson;
|
||||||
import org.kar.archidata.dataAccess.options.CheckJPA;
|
import org.kar.archidata.checker.CheckJPA;
|
||||||
import org.kar.archidata.model.Data;
|
import org.kar.archidata.model.Data;
|
||||||
import org.kar.archidata.model.GenericDataSoftDelete;
|
import org.kar.archidata.model.GenericDataSoftDelete;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import java.util.List;
|
|||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
import org.kar.archidata.annotation.DataIfNotExists;
|
import org.kar.archidata.annotation.DataIfNotExists;
|
||||||
import org.kar.archidata.annotation.DataJson;
|
import org.kar.archidata.annotation.DataJson;
|
||||||
import org.kar.archidata.dataAccess.options.CheckJPA;
|
import org.kar.archidata.checker.CheckJPA;
|
||||||
import org.kar.archidata.model.Data;
|
import org.kar.archidata.model.Data;
|
||||||
import org.kar.archidata.model.GenericDataSoftDelete;
|
import org.kar.archidata.model.GenericDataSoftDelete;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import java.util.List;
|
|||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
import org.kar.archidata.annotation.DataIfNotExists;
|
import org.kar.archidata.annotation.DataIfNotExists;
|
||||||
import org.kar.archidata.annotation.DataJson;
|
import org.kar.archidata.annotation.DataJson;
|
||||||
import org.kar.archidata.dataAccess.options.CheckJPA;
|
import org.kar.archidata.checker.CheckJPA;
|
||||||
import org.kar.archidata.model.Data;
|
import org.kar.archidata.model.Data;
|
||||||
import org.kar.archidata.model.GenericDataSoftDelete;
|
import org.kar.archidata.model.GenericDataSoftDelete;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import java.util.List;
|
|||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
import org.kar.archidata.annotation.DataIfNotExists;
|
import org.kar.archidata.annotation.DataIfNotExists;
|
||||||
import org.kar.archidata.annotation.DataJson;
|
import org.kar.archidata.annotation.DataJson;
|
||||||
import org.kar.archidata.dataAccess.options.CheckJPA;
|
import org.kar.archidata.checker.CheckJPA;
|
||||||
import org.kar.archidata.model.Data;
|
import org.kar.archidata.model.Data;
|
||||||
import org.kar.archidata.model.GenericDataSoftDelete;
|
import org.kar.archidata.model.GenericDataSoftDelete;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import java.util.List;
|
|||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
import org.kar.archidata.annotation.DataIfNotExists;
|
import org.kar.archidata.annotation.DataIfNotExists;
|
||||||
import org.kar.archidata.annotation.DataJson;
|
import org.kar.archidata.annotation.DataJson;
|
||||||
import org.kar.archidata.dataAccess.options.CheckJPA;
|
import org.kar.archidata.checker.CheckJPA;
|
||||||
import org.kar.archidata.model.Data;
|
import org.kar.archidata.model.Data;
|
||||||
import org.kar.archidata.model.GenericDataSoftDelete;
|
import org.kar.archidata.model.GenericDataSoftDelete;
|
||||||
|
|
||||||
|
@ -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("LOG_LEVEL_ENV").equals("dev")">
|
||||||
|
<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("LOG_LEVEL_ENV").matches("^prod.*")">
|
||||||
|
<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("LOG_LEVEL_ENV").equals("prod-debug")">
|
||||||
|
<then>
|
||||||
|
<logger name="org.kar.karusic" level="DEBUG" />
|
||||||
|
</then>
|
||||||
|
</if>
|
||||||
|
<if condition="property("LOG_LEVEL_ENV").equals("prod-trace")">
|
||||||
|
<then>
|
||||||
|
<logger name="org.kar.karusic" level="TRACE" />
|
||||||
|
<logger name="org.kar.archidata" level="DEBUG" />
|
||||||
|
</then>
|
||||||
|
</if>
|
||||||
|
<if condition="property("LOG_LEVEL_ENV").equals("prod-trace-full")">
|
||||||
|
<then>
|
||||||
|
<logger name="org.kar.karusic" level="TRACE" />
|
||||||
|
<logger name="org.kar.archidata" level="TRACE" />
|
||||||
|
</then>
|
||||||
|
</if>
|
||||||
</configuration>
|
</configuration>
|
@ -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
|
|
||||||
|
|
@ -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, //
|
||||||
|
@ -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)");
|
||||||
|
@ -4,23 +4,11 @@ import { Box } from '@chakra-ui/react';
|
|||||||
import { ChakraProvider } from '@chakra-ui/react';
|
import { ChakraProvider } from '@chakra-ui/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import theme from '../src/theme';
|
import { ColorModeProvider } from '../src/components/ui/color-mode';
|
||||||
|
import { Toaster } from '../src/components/ui/toaster';
|
||||||
// .storybook/preview.js
|
import { systemTheme } from '../src/theme/theme';
|
||||||
export const parameters = {
|
|
||||||
options: {
|
|
||||||
storySort: {
|
|
||||||
order: ['StyleGuide', 'Components', 'Fields', 'App Layout'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {},
|
|
||||||
layout: 'fullscreen',
|
|
||||||
backgrounds: { disable: true, grid: { disable: true } },
|
|
||||||
chakra: {
|
|
||||||
theme,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// .
|
||||||
const DocumentationWrapper = ({ children }) => {
|
const DocumentationWrapper = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<Box id="start-ui-storybook-wrapper" p="4" pb="8" flex="1">
|
<Box id="start-ui-storybook-wrapper" p="4" pb="8" flex="1">
|
||||||
@ -31,13 +19,16 @@ const DocumentationWrapper = ({ children }) => {
|
|||||||
|
|
||||||
export const decorators = [
|
export const decorators = [
|
||||||
(Story, context) => (
|
(Story, context) => (
|
||||||
<ChakraProvider theme={theme}>
|
<ColorModeProvider>
|
||||||
|
<ChakraProvider value={systemTheme}>
|
||||||
{/* Using MemoryRouter to avoid route clashing with Storybook */}
|
{/* Using MemoryRouter to avoid route clashing with Storybook */}
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<DocumentationWrapper>
|
<DocumentationWrapper>
|
||||||
<Story {...context} />
|
<Story {...context} />
|
||||||
</DocumentationWrapper>
|
</DocumentationWrapper>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
|
<Toaster />
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
|
</ColorModeProvider>
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"display": "2025-01-06",
|
|
||||||
"version": "0.0.1-dev\n - 2025-01-06T00:49:52+01:00",
|
|
||||||
"commit": "0.0.1-dev\n",
|
|
||||||
"date": "2025-01-06T00:49:52+01:00"
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
const dayjs = require('dayjs');
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const generateAppBuild = () => {
|
|
||||||
const getVersion = () => fs.readFileSync('version.txt', 'utf8');
|
|
||||||
|
|
||||||
const commit = process.env.VERCEL_GIT_COMMIT_SHA
|
|
||||||
? process.env.VERCEL_GIT_COMMIT_SHA
|
|
||||||
: getVersion();
|
|
||||||
|
|
||||||
const appBuildContent = {
|
|
||||||
display: `${dayjs().format('YYYY-MM-DD')}`,
|
|
||||||
version: `${commit} - ${dayjs().format()}`,
|
|
||||||
commit,
|
|
||||||
date: dayjs().format(),
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
'./app-build.json',
|
|
||||||
JSON.stringify(appBuildContent, null, 2)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
generateAppBuild();
|
|
@ -3,16 +3,7 @@ import type { KnipConfig } from 'knip';
|
|||||||
const config: KnipConfig = {
|
const config: KnipConfig = {
|
||||||
// Ignoring mostly shell binaries
|
// Ignoring mostly shell binaries
|
||||||
ignoreBinaries: ['export', 'sleep'],
|
ignoreBinaries: ['export', 'sleep'],
|
||||||
ignore: [
|
ignore: [],
|
||||||
// Related to tests
|
|
||||||
'tests/**',
|
|
||||||
'**.conf.js',
|
|
||||||
'steps.d.ts',
|
|
||||||
'steps_file.js',
|
|
||||||
'env_ci/codecept.conf.js',
|
|
||||||
// Generic components are useful.
|
|
||||||
'src/components/**',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -12,12 +12,13 @@
|
|||||||
"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",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"static:build": "node build.js && pnpm build",
|
"static:build": "pnpm build",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"pretty": "prettier -w .",
|
"pretty": "prettier -w .",
|
||||||
"lint": "pnpm tsc --noEmit",
|
"lint": "pnpm tsc --noEmit",
|
||||||
@ -28,84 +29,63 @@
|
|||||||
"*.{ts,tsx,js,jsx,json}": "prettier --write"
|
"*.{ts,tsx,js,jsx,json}": "prettier --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/anatomy": "2.2.2",
|
"@trivago/prettier-plugin-sort-imports": "5.2.2",
|
||||||
"@chakra-ui/cli": "2.4.1",
|
"@chakra-ui/cli": "3.7.0",
|
||||||
"@chakra-ui/react": "2.8.2",
|
"@chakra-ui/react": "3.7.0",
|
||||||
"@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/react": "11.14.0",
|
||||||
"@emotion/styled": "11.14.0",
|
|
||||||
"@formiz/core": "2.4.5",
|
|
||||||
"@formiz/validations": "2.0.1",
|
|
||||||
"allotment": "1.20.2",
|
"allotment": "1.20.2",
|
||||||
"css-mediaquery": "0.1.2",
|
"css-mediaquery": "0.1.2",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"react": "18.3.1",
|
"next-themes": "^0.4.4",
|
||||||
"react-color-palette": "7.3.0",
|
"react": "19.0.0-rc.1",
|
||||||
"react-currency-input-field": "3.9.0",
|
"react-dom": "19.0.0-rc.1",
|
||||||
"react-custom-scrollbars": "4.2.1",
|
"react-error-boundary": "5.0.0",
|
||||||
"react-day-picker": "9.5.0",
|
"react-icons": "5.4.0",
|
||||||
"react-dom": "18.3.1",
|
"react-router-dom": "7.1.5",
|
||||||
"react-error-boundary": "4.0.13",
|
"react-select": "5.10.0",
|
||||||
"react-focus-lock": "2.13.2",
|
|
||||||
"react-icons": "5.3.0",
|
|
||||||
"react-popper": "2.3.0",
|
|
||||||
"react-router-dom": "6.26.2",
|
|
||||||
"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",
|
"@chakra-ui/styled-system": "^2.12.0",
|
||||||
"@playwright/test": "1.49.1",
|
"@playwright/test": "1.50.1",
|
||||||
"@storybook/addon-actions": "8.4.7",
|
"@storybook/addon-actions": "8.5.4",
|
||||||
"@storybook/addon-essentials": "8.4.7",
|
"@storybook/addon-essentials": "8.5.4",
|
||||||
"@storybook/addon-links": "8.4.7",
|
"@storybook/addon-links": "8.5.4",
|
||||||
"@storybook/addon-mdx-gfm": "8.4.7",
|
"@storybook/addon-mdx-gfm": "8.5.4",
|
||||||
"@storybook/react": "8.4.7",
|
"@storybook/react": "8.5.4",
|
||||||
"@storybook/react-vite": "8.4.7",
|
"@storybook/react-vite": "8.5.4",
|
||||||
"@storybook/theming": "8.4.7",
|
"@storybook/theming": "8.5.4",
|
||||||
"@testing-library/jest-dom": "6.6.3",
|
"@testing-library/jest-dom": "6.6.3",
|
||||||
"@testing-library/react": "16.1.0",
|
"@testing-library/react": "16.2.0",
|
||||||
"@testing-library/user-event": "14.5.2",
|
"@testing-library/user-event": "14.6.1",
|
||||||
"@trivago/prettier-plugin-sort-imports": "5.2.1",
|
"@trivago/prettier-plugin-sort-imports": "5.2.2",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/node": "22.10.5",
|
"@types/node": "22.13.1",
|
||||||
"@types/react": "18.3.8",
|
"@types/react": "19.0.8",
|
||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "19.0.3",
|
||||||
"@types/react-sticky-el": "1.0.7",
|
"@typescript-eslint/eslint-plugin": "8.24.0",
|
||||||
"@typescript-eslint/eslint-plugin": "8.19.0",
|
"@typescript-eslint/parser": "8.24.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.20.1",
|
||||||
"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.44.0",
|
||||||
"lint-staged": "15.3.0",
|
"lint-staged": "15.4.3",
|
||||||
"npm-check-updates": "^17.1.13",
|
"npm-check-updates": "^17.1.14",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.5.0",
|
||||||
"puppeteer": "23.11.1",
|
"puppeteer": "24.2.0",
|
||||||
"react-is": "19.0.0",
|
"react-is": "19.0.0",
|
||||||
"storybook": "8.4.7",
|
"storybook": "8.5.4",
|
||||||
"ts-node": "10.9.2",
|
"ts-node": "10.9.2",
|
||||||
"typescript": "5.7.2",
|
"typescript": "5.7.3",
|
||||||
"vite": "6.0.7",
|
"vite": "6.1.0",
|
||||||
"vitest": "2.1.8"
|
"vitest": "3.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6910
front/pnpm-lock.yaml
generated
6910
front/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,117 +1,19 @@
|
|||||||
import { useState } from 'react';
|
import { ErrorBoundary } from '@/errors/ErrorBoundary';
|
||||||
|
|
||||||
import { ChakraProvider, Select } from '@chakra-ui/react';
|
import { AudioPlayer } from './components';
|
||||||
import {
|
import { EnvDevelopment } from './components/EnvDevelopment/EnvDevelopment';
|
||||||
Box,
|
import { AppRoutes } from './scene/AppRoutes';
|
||||||
Button,
|
import { ServiceContextProvider } from './service/ServiceContext';
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
|
|
||||||
import { environment } from '@/environment';
|
|
||||||
import { App as SpaApp } from '@/scene/App';
|
|
||||||
import { USERS } from '@/service/session';
|
|
||||||
import theme from '@/theme';
|
|
||||||
import { hashLocalData } from '@/utils/sso';
|
|
||||||
|
|
||||||
const AppEnvHint = () => {
|
|
||||||
const modal = useDisclosure();
|
|
||||||
const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER');
|
|
||||||
//const setUser = useRightsStore((store) => store.setUser);
|
|
||||||
const buildEnv =
|
|
||||||
process.env.NODE_ENV === 'development'
|
|
||||||
? 'Development'
|
|
||||||
: import.meta.env.VITE_DEV_ENV_NAME;
|
|
||||||
const envName: Array<string> = [];
|
|
||||||
!!buildEnv && envName.push(buildEnv);
|
|
||||||
if (!envName.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const handleChange = (selectedOption) => {
|
|
||||||
console.log(`SELECT: [${selectedOption.target.value}]`);
|
|
||||||
setSelectUserTest(selectedOption.target.value);
|
|
||||||
};
|
|
||||||
const onClose = () => {
|
|
||||||
modal.onClose();
|
|
||||||
if (selectUserTest == 'NO_USER') {
|
|
||||||
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`;
|
|
||||||
} else {
|
|
||||||
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/true/${USERS[selectUserTest]}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<ServiceContextProvider>
|
||||||
<Box
|
<EnvDevelopment />
|
||||||
zIndex="100000"
|
<ErrorBoundary>
|
||||||
position="fixed"
|
<AppRoutes />
|
||||||
top="0"
|
</ErrorBoundary>
|
||||||
insetStart="0"
|
<AudioPlayer />
|
||||||
insetEnd="0"
|
</ServiceContextProvider>
|
||||||
h="2px"
|
|
||||||
bg="warning.400"
|
|
||||||
as="button"
|
|
||||||
cursor="pointer"
|
|
||||||
data-test-id="devtools"
|
|
||||||
onClick={modal.onOpen}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
position="fixed"
|
|
||||||
top="0"
|
|
||||||
insetStart="4"
|
|
||||||
bg="warning.400"
|
|
||||||
color="warning.900"
|
|
||||||
fontSize="0.6rem"
|
|
||||||
fontWeight="bold"
|
|
||||||
px="10px"
|
|
||||||
marginLeft="25%"
|
|
||||||
borderBottomStartRadius="sm"
|
|
||||||
borderBottomEndRadius="sm"
|
|
||||||
textTransform="uppercase"
|
|
||||||
>
|
|
||||||
{envName.join(' : ')}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Modal isOpen={modal.isOpen} onClose={modal.onClose}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>Outils développeurs</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
<Stack>
|
|
||||||
<Text>Utilisateur</Text>
|
|
||||||
<Select placeholder="Select test user" onChange={handleChange}>
|
|
||||||
{Object.keys(USERS).map((key) => (
|
|
||||||
<option value={key} key={key}>
|
|
||||||
{key}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Stack>
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onClick={onClose}>Apply</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const App = () => {
|
|
||||||
return (
|
|
||||||
<ChakraProvider theme={theme}>
|
|
||||||
<AppEnvHint />
|
|
||||||
<SpaApp />
|
|
||||||
</ChakraProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="currentColor"
|
|
||||||
stroke-width="0"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
height="250px"
|
|
||||||
width="250px"
|
|
||||||
version="1.1"
|
|
||||||
id="svg2"
|
|
||||||
sodipodi:docname="404.svg"
|
|
||||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview2"
|
|
||||||
pagecolor="#505050"
|
|
||||||
bordercolor="#eeeeee"
|
|
||||||
borderopacity="1"
|
|
||||||
inkscape:showpageshadow="0"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
showgrid="true"
|
|
||||||
inkscape:zoom="3.448"
|
|
||||||
inkscape:cx="134.28074"
|
|
||||||
inkscape:cy="125"
|
|
||||||
inkscape:window-width="1918"
|
|
||||||
inkscape:window-height="1044"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="17"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg2">
|
|
||||||
<inkscape:grid
|
|
||||||
id="grid2"
|
|
||||||
units="px"
|
|
||||||
originx="0"
|
|
||||||
originy="0"
|
|
||||||
spacingx="0.096"
|
|
||||||
spacingy="0.096"
|
|
||||||
empcolor="#0099e5"
|
|
||||||
empopacity="0.30196078"
|
|
||||||
color="#0099e5"
|
|
||||||
opacity="0.14901961"
|
|
||||||
empspacing="5"
|
|
||||||
dotted="false"
|
|
||||||
gridanglex="30"
|
|
||||||
gridanglez="30"
|
|
||||||
visible="true" />
|
|
||||||
</sodipodi:namedview>
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
d="M0 0h24v24H0z"
|
|
||||||
id="path1" />
|
|
||||||
<path
|
|
||||||
d="M13 10h5l3-3-3-3h-5V2h-2v2H4v6h7v2H6l-3 3 3 3h5v4h2v-4h7v-6h-7z"
|
|
||||||
id="path2" />
|
|
||||||
<path
|
|
||||||
id="rect2"
|
|
||||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.384;stroke-linecap:square"
|
|
||||||
d="M 17.394219,5.0400499 19.459554,6.9903325 17.438107,9.0722051 4.9259029,9.0569946 4.9284452,5.0338374 Z"
|
|
||||||
sodipodi:nodetypes="cccccc" />
|
|
||||||
<path
|
|
||||||
id="rect2-3"
|
|
||||||
style="fill:#f8fefb;fill-opacity:1;stroke:none;stroke-width:0.384;stroke-linecap:square"
|
|
||||||
d="m 6.5757719,13.021525 -2.065335,1.950283 2.021447,2.081873 12.5122061,-0.01521 -0.0025,-4.023157 z"
|
|
||||||
sodipodi:nodetypes="cccccc" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.1 KiB |
@ -8,7 +8,7 @@ import {ZodLocalDate} from "./local-date";
|
|||||||
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
export const ZodAlbum = ZodGenericDataSoftDelete.extend({
|
export const ZodAlbum = ZodGenericDataSoftDelete.extend({
|
||||||
name: zod.string().max(256).optional(),
|
name: zod.string().optional(),
|
||||||
description: zod.string().optional(),
|
description: zod.string().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
@ -30,7 +30,7 @@ export function isAlbum(data: any): data is Album {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodAlbumWrite = ZodGenericDataSoftDeleteWrite.extend({
|
export const ZodAlbumWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
name: zod.string().max(256).nullable().optional(),
|
name: zod.string().nullable().optional(),
|
||||||
description: zod.string().nullable().optional(),
|
description: zod.string().nullable().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
|
@ -8,14 +8,14 @@ import {ZodLocalDate} from "./local-date";
|
|||||||
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
export const ZodArtist = ZodGenericDataSoftDelete.extend({
|
export const ZodArtist = ZodGenericDataSoftDelete.extend({
|
||||||
name: zod.string().max(256).optional(),
|
name: zod.string().optional(),
|
||||||
description: zod.string().optional(),
|
description: zod.string().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
*/
|
*/
|
||||||
covers: zod.array(ZodObjectId).optional(),
|
covers: zod.array(ZodObjectId).optional(),
|
||||||
firstName: zod.string().max(256).optional(),
|
firstName: zod.string().optional(),
|
||||||
surname: zod.string().max(256).optional(),
|
surname: zod.string().optional(),
|
||||||
birth: ZodLocalDate.optional(),
|
birth: ZodLocalDate.optional(),
|
||||||
death: ZodLocalDate.optional(),
|
death: ZodLocalDate.optional(),
|
||||||
|
|
||||||
@ -33,14 +33,14 @@ export function isArtist(data: any): data is Artist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodArtistWrite = ZodGenericDataSoftDeleteWrite.extend({
|
export const ZodArtistWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
name: zod.string().max(256).nullable().optional(),
|
name: zod.string().nullable().optional(),
|
||||||
description: zod.string().nullable().optional(),
|
description: zod.string().nullable().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
*/
|
*/
|
||||||
covers: zod.array(ZodObjectId).nullable().optional(),
|
covers: zod.array(ZodObjectId).nullable().optional(),
|
||||||
firstName: zod.string().max(256).nullable().optional(),
|
firstName: zod.string().nullable().optional(),
|
||||||
surname: zod.string().max(256).nullable().optional(),
|
surname: zod.string().nullable().optional(),
|
||||||
birth: ZodLocalDate.nullable().optional(),
|
birth: ZodLocalDate.nullable().optional(),
|
||||||
death: ZodLocalDate.nullable().optional(),
|
death: ZodLocalDate.nullable().optional(),
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {ZodObjectId} from "./object-id";
|
|||||||
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
export const ZodGender = ZodGenericDataSoftDelete.extend({
|
export const ZodGender = ZodGenericDataSoftDelete.extend({
|
||||||
name: zod.string().max(256).optional(),
|
name: zod.string().optional(),
|
||||||
description: zod.string().optional(),
|
description: zod.string().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
@ -28,7 +28,7 @@ export function isGender(data: any): data is Gender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodGenderWrite = ZodGenericDataSoftDeleteWrite.extend({
|
export const ZodGenderWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
name: zod.string().max(256).nullable().optional(),
|
name: zod.string().nullable().optional(),
|
||||||
description: zod.string().nullable().optional(),
|
description: zod.string().nullable().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
|
@ -24,9 +24,7 @@ export function isGenericDataSoftDelete(data: any): data is GenericDataSoftDelet
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodGenericDataSoftDeleteWrite = ZodGenericDataWrite.extend({
|
export const ZodGenericDataSoftDeleteWrite = ZodGenericDataWrite;
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export type GenericDataSoftDeleteWrite = zod.infer<typeof ZodGenericDataSoftDeleteWrite>;
|
export type GenericDataSoftDeleteWrite = zod.infer<typeof ZodGenericDataSoftDeleteWrite>;
|
||||||
|
|
||||||
|
@ -25,9 +25,7 @@ export function isGenericData(data: any): data is GenericData {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodGenericDataWrite = ZodGenericTimingWrite.extend({
|
export const ZodGenericDataWrite = ZodGenericTimingWrite;
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export type GenericDataWrite = zod.infer<typeof ZodGenericDataWrite>;
|
export type GenericDataWrite = zod.infer<typeof ZodGenericDataWrite>;
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {ZodObjectId} from "./object-id";
|
|||||||
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
export const ZodPlaylist = ZodGenericDataSoftDelete.extend({
|
export const ZodPlaylist = ZodGenericDataSoftDelete.extend({
|
||||||
name: zod.string().max(256).optional(),
|
name: zod.string().optional(),
|
||||||
description: zod.string().optional(),
|
description: zod.string().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
@ -29,7 +29,7 @@ export function isPlaylist(data: any): data is Playlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodPlaylistWrite = ZodGenericDataSoftDeleteWrite.extend({
|
export const ZodPlaylistWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
name: zod.string().max(256).nullable().optional(),
|
name: zod.string().nullable().optional(),
|
||||||
description: zod.string().nullable().optional(),
|
description: zod.string().nullable().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
|
@ -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(),
|
||||||
|
@ -8,7 +8,7 @@ import {ZodLong} from "./long";
|
|||||||
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
|
||||||
|
|
||||||
export const ZodTrack = ZodGenericDataSoftDelete.extend({
|
export const ZodTrack = ZodGenericDataSoftDelete.extend({
|
||||||
name: zod.string().max(256).optional(),
|
name: zod.string().optional(),
|
||||||
description: zod.string().optional(),
|
description: zod.string().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
@ -34,7 +34,7 @@ export function isTrack(data: any): data is Track {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodTrackWrite = ZodGenericDataSoftDeleteWrite.extend({
|
export const ZodTrackWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
name: zod.string().max(256).nullable().optional(),
|
name: zod.string().nullable().optional(),
|
||||||
description: zod.string().nullable().optional(),
|
description: zod.string().nullable().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
|
@ -5,9 +5,7 @@ import { z as zod } from "zod";
|
|||||||
|
|
||||||
import {ZodUser, ZodUserWrite } from "./user";
|
import {ZodUser, ZodUserWrite } from "./user";
|
||||||
|
|
||||||
export const ZodUserKarusic = ZodUser.extend({
|
export const ZodUserKarusic = ZodUser;
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export type UserKarusic = zod.infer<typeof ZodUserKarusic>;
|
export type UserKarusic = zod.infer<typeof ZodUserKarusic>;
|
||||||
|
|
||||||
@ -20,9 +18,7 @@ export function isUserKarusic(data: any): data is UserKarusic {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const ZodUserKarusicWrite = ZodUserWrite.extend({
|
export const ZodUserKarusicWrite = ZodUserWrite;
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export type UserKarusicWrite = zod.infer<typeof ZodUserKarusicWrite>;
|
export type UserKarusicWrite = zod.infer<typeof ZodUserKarusicWrite>;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {ZodPartRight} from "./part-right";
|
|||||||
|
|
||||||
export const ZodUserMe = zod.object({
|
export const ZodUserMe = zod.object({
|
||||||
id: ZodLong,
|
id: ZodLong,
|
||||||
login: zod.string().max(255).optional(),
|
login: zod.string().optional(),
|
||||||
/**
|
/**
|
||||||
* Map<EntityName, Map<PartName, Right>>
|
* Map<EntityName, Map<PartName, Right>>
|
||||||
*/
|
*/
|
||||||
|
@ -10,7 +10,7 @@ import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generi
|
|||||||
export const ZodUser = ZodGenericDataSoftDelete.extend({
|
export const ZodUser = ZodGenericDataSoftDelete.extend({
|
||||||
login: zod.string().min(3).max(128),
|
login: zod.string().min(3).max(128),
|
||||||
lastConnection: ZodTimestamp.optional(),
|
lastConnection: ZodTimestamp.optional(),
|
||||||
blocked: zod.boolean(),
|
blocked: zod.boolean().optional(),
|
||||||
blockedReason: zod.string().max(512).optional(),
|
blockedReason: zod.string().max(512).optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
@ -33,7 +33,7 @@ export function isUser(data: any): data is User {
|
|||||||
export const ZodUserWrite = ZodGenericDataSoftDeleteWrite.extend({
|
export const ZodUserWrite = ZodGenericDataSoftDeleteWrite.extend({
|
||||||
login: zod.string().min(3).max(128).optional(),
|
login: zod.string().min(3).max(128).optional(),
|
||||||
lastConnection: ZodTimestamp.nullable().optional(),
|
lastConnection: ZodTimestamp.nullable().optional(),
|
||||||
blocked: zod.boolean(),
|
blocked: zod.boolean().nullable().optional(),
|
||||||
blockedReason: zod.string().max(512).nullable().optional(),
|
blockedReason: zod.string().max(512).nullable().optional(),
|
||||||
/**
|
/**
|
||||||
* List of Id of the specific covers
|
* List of Id of the specific covers
|
||||||
|
@ -1,26 +1,12 @@
|
|||||||
import { SyntheticEvent, useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { Box, Flex, IconButton, SliderTrack, Text } from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
IconButton,
|
|
||||||
Slider,
|
|
||||||
SliderFilledTrack,
|
|
||||||
SliderThumb,
|
|
||||||
SliderTrack,
|
|
||||||
Text,
|
|
||||||
position,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
|
||||||
MdCheck,
|
|
||||||
MdFastForward,
|
MdFastForward,
|
||||||
MdFastRewind,
|
MdFastRewind,
|
||||||
MdGraphicEq,
|
|
||||||
MdLooksOne,
|
MdLooksOne,
|
||||||
MdNavigateBefore,
|
MdNavigateBefore,
|
||||||
MdNavigateNext,
|
MdNavigateNext,
|
||||||
MdOutlinePlayArrow,
|
|
||||||
MdPause,
|
MdPause,
|
||||||
MdPlayArrow,
|
MdPlayArrow,
|
||||||
MdRepeat,
|
MdRepeat,
|
||||||
@ -29,15 +15,17 @@ import {
|
|||||||
MdTrendingFlat,
|
MdTrendingFlat,
|
||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
|
|
||||||
|
import { useColorModeValue } from '@/components/ui/color-mode';
|
||||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
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 { 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 { isNullOrUndefined } from '@/utils/validator';
|
import { isNullOrUndefined } from '@/utils/validator';
|
||||||
|
|
||||||
|
import { Slider } from './ui/slider';
|
||||||
|
|
||||||
export enum PlayMode {
|
export enum PlayMode {
|
||||||
PLAY_ONE,
|
PLAY_ONE,
|
||||||
PLAY_ALL,
|
PLAY_ALL,
|
||||||
@ -46,10 +34,16 @@ export enum PlayMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const playModeIcon = {
|
const playModeIcon = {
|
||||||
[PlayMode.PLAY_ONE]: <MdLooksOne size="30px" />,
|
[PlayMode.PLAY_ONE]: <MdLooksOne style={{ width: '100%', height: '100%' }} />,
|
||||||
[PlayMode.PLAY_ALL]: <MdTrendingFlat size="30px" />,
|
[PlayMode.PLAY_ALL]: (
|
||||||
[PlayMode.PLAY_ONE_LOOP]: <MdRepeatOne size="30px" />,
|
<MdTrendingFlat style={{ width: '100%', height: '100%' }} />
|
||||||
[PlayMode.PLAY_ALL_LOOP]: <MdRepeat size="30px" />,
|
),
|
||||||
|
[PlayMode.PLAY_ONE_LOOP]: (
|
||||||
|
<MdRepeatOne style={{ width: '100%', height: '100%' }} />
|
||||||
|
),
|
||||||
|
[PlayMode.PLAY_ALL_LOOP]: (
|
||||||
|
<MdRepeat style={{ width: '100%', height: '100%' }} />
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AudioPlayerProps = {};
|
export type AudioPlayerProps = {};
|
||||||
@ -66,7 +60,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 +82,16 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
|||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
}, [dataTrack, setMediaSource]);
|
}, [dataTrack, setMediaSource]);
|
||||||
const backColor = mode('back.100', 'back.800');
|
const backColor = useColorModeValue('back.100', 'back.800');
|
||||||
const configButton = {
|
const configButton = {
|
||||||
borderRadius: 'full',
|
borderRadius: 'full',
|
||||||
backgroundColor: '#00000000',
|
backgroundColor: 'transparent',
|
||||||
_hover: {
|
_hover: {
|
||||||
boxShadow: 'outline-over',
|
|
||||||
bgColor: 'brand.500',
|
bgColor: 'brand.500',
|
||||||
},
|
},
|
||||||
|
width: '50px',
|
||||||
|
height: '50px',
|
||||||
|
padding: '5px',
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -209,6 +204,14 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
|||||||
const onChangeStateToPause = () => {
|
const onChangeStateToPause = () => {
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
};
|
};
|
||||||
|
const marks = () => {
|
||||||
|
const minutes = Math.floor(duration / 60);
|
||||||
|
const result: number[] = [];
|
||||||
|
for (let i = 1; i <= minutes; i++) {
|
||||||
|
result.push(60 * i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isNullOrUndefined(trackOffset) && (
|
{!isNullOrUndefined(trackOffset) && (
|
||||||
@ -230,23 +233,23 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
|||||||
direction="column"
|
direction="column"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="20px"
|
fontSize="20px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
// noOfLines={1}
|
||||||
>
|
>
|
||||||
{dataTrack?.name ?? '???'}
|
{dataTrack?.name ?? '???'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="16px"
|
fontSize="16px"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
// noOfLines={1}
|
||||||
>
|
>
|
||||||
{dataArtists.map((data) => data.name).join(', ')} /{' '}
|
{dataArtists.map((data) => data.name).join(', ')} /{' '}
|
||||||
{dataAlbum && dataAlbum?.name}
|
{dataAlbum && dataAlbum?.name}
|
||||||
@ -254,35 +257,36 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
|||||||
</Text>
|
</Text>
|
||||||
<Box width="full" paddingX="15px">
|
<Box width="full" paddingX="15px">
|
||||||
<Slider
|
<Slider
|
||||||
aria-label="slider-ex-4"
|
defaultValue={[0]}
|
||||||
defaultValue={0}
|
value={[timeProgress]}
|
||||||
value={timeProgress}
|
|
||||||
min={0}
|
min={0}
|
||||||
max={duration}
|
max={duration}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
onChange={onSeek}
|
onValueChange={(e) => onSeek(e.value)}
|
||||||
focusThumbOnChange={false}
|
variant="outline"
|
||||||
|
colorPalette="brand"
|
||||||
|
marks={marks()}
|
||||||
|
//focusCapture={false}
|
||||||
>
|
>
|
||||||
<SliderTrack bg="gray.200" height="10px" borderRadius="full">
|
<SliderTrack
|
||||||
<SliderFilledTrack bg="brand.600" />
|
bg="brand.200"
|
||||||
</SliderTrack>
|
height="10px"
|
||||||
<SliderThumb boxSize={6}>
|
borderRadius="full"
|
||||||
<Box color="brand.600" as={MdGraphicEq} />
|
></SliderTrack>
|
||||||
</SliderThumb>
|
|
||||||
</Slider>
|
</Slider>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Text
|
<Text
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="16px"
|
fontSize="16px"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
// noOfLines={1}
|
||||||
>
|
>
|
||||||
{formatTime(timeProgress)}
|
{formatTime(timeProgress)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text align="left" fontSize="16px" userSelect="none">
|
<Text alignContent="left" fontSize="16px" userSelect="none">
|
||||||
{formatTime(duration)}
|
{formatTime(duration)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -290,53 +294,65 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
{...configButton}
|
{...configButton}
|
||||||
aria-label={'Play'}
|
aria-label={'Play'}
|
||||||
icon={
|
|
||||||
isPlaying ? (
|
|
||||||
<MdPause size="30px" />
|
|
||||||
) : (
|
|
||||||
<MdPlayArrow size="30px" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={onPlay}
|
onClick={onPlay}
|
||||||
/>
|
variant="ghost"
|
||||||
|
>
|
||||||
|
{isPlaying ? (
|
||||||
|
<MdPause style={{ width: '100%', height: '100%' }} />
|
||||||
|
) : (
|
||||||
|
<MdPlayArrow style={{ width: '100%', height: '100%' }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
{...configButton}
|
{...configButton}
|
||||||
aria-label={'Stop'}
|
aria-label={'Stop'}
|
||||||
icon={<MdStop size="30px" />}
|
|
||||||
onClick={onStop}
|
onClick={onStop}
|
||||||
/>
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<MdStop style={{ width: '100%', height: '100%' }} />
|
||||||
|
</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"
|
||||||
/>
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<MdNavigateBefore style={{ width: '100%', height: '100%' }} />{' '}
|
||||||
|
</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}
|
||||||
/>
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<MdFastRewind style={{ width: '100%', height: '100%' }} />
|
||||||
|
</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}
|
||||||
/>
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<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}
|
||||||
/>
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<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}
|
||||||
/>
|
variant="ghost"
|
||||||
|
>
|
||||||
|
{playModeIcon[playingMode]}
|
||||||
|
</IconButton>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { ReactElement, useEffect, useState } from 'react';
|
import { ReactElement, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { As, Box, BoxProps, Flex, StyleProps } from '@chakra-ui/react';
|
import { Box, BoxProps, Flex, FlexProps } from '@chakra-ui/react';
|
||||||
import { Image } from '@chakra-ui/react';
|
import { Image } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { DataUrlAccess } from '@/utils/data-url-access';
|
|
||||||
import { Icon } from './Icon';
|
|
||||||
import { ObjectId } from '@/back-api';
|
import { ObjectId } from '@/back-api';
|
||||||
|
import { DataUrlAccess } from '@/utils/data-url-access';
|
||||||
|
|
||||||
export type CoversProps = BoxProps & {
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
|
export type CoversProps = Omit<BoxProps, 'iconEmpty'> & {
|
||||||
data?: ObjectId[];
|
data?: ObjectId[];
|
||||||
size?: StyleProps["width"];
|
size?: BoxProps['width'];
|
||||||
iconEmpty?: As;
|
iconEmpty?: ReactElement;
|
||||||
slideshow?: boolean;
|
slideshow?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,7 +34,9 @@ export const Covers = ({
|
|||||||
setPreviousImageIndex(currentImageIndex);
|
setPreviousImageIndex(currentImageIndex);
|
||||||
setTopOpacity(0.0);
|
setTopOpacity(0.0);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setCurrentImageIndex((prevIndex) => (prevIndex + 1) % (data?.length ?? 1));
|
setCurrentImageIndex(
|
||||||
|
(prevIndex) => (prevIndex + 1) % (data?.length ?? 1)
|
||||||
|
);
|
||||||
setTopOpacity(1.0);
|
setTopOpacity(1.0);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
@ -42,7 +45,7 @@ export const Covers = ({
|
|||||||
|
|
||||||
if (!data || data.length < 1) {
|
if (!data || data.length < 1) {
|
||||||
if (iconEmpty) {
|
if (iconEmpty) {
|
||||||
return <Icon icon={iconEmpty} sizeIcon={size} />;
|
return <Icon children={iconEmpty} sizeIcon={size} />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -60,11 +63,26 @@ export const Covers = ({
|
|||||||
}
|
}
|
||||||
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"
|
||||||
@ -90,4 +108,5 @@ export const Covers = ({
|
|||||||
zIndex={2}
|
zIndex={2}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
117
front/src/components/EnvDevelopment/EnvDevelopment.tsx
Normal file
117
front/src/components/EnvDevelopment/EnvDevelopment.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
createListCollection,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { environment } from '@/environment';
|
||||||
|
import { USERS } from '@/service/session';
|
||||||
|
import { hashLocalData } from '@/utils/sso';
|
||||||
|
|
||||||
|
export const USERS_COLLECTION = createListCollection({
|
||||||
|
items: [
|
||||||
|
{ label: 'karadmin', value: 'adminA@666' },
|
||||||
|
{ label: 'karuser', value: 'userA@666' },
|
||||||
|
{ label: 'NO_USER', value: '' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const EnvDevelopment = () => {
|
||||||
|
const dialog = useDisclosure();
|
||||||
|
const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER');
|
||||||
|
//const setUser = useRightsStore((store) => store.setUser);
|
||||||
|
const buildEnv =
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? 'Development'
|
||||||
|
: import.meta.env.VITE_DEV_ENV_NAME;
|
||||||
|
const envName: Array<string> = [];
|
||||||
|
!!buildEnv && envName.push(buildEnv);
|
||||||
|
if (!envName.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const handleChange = (selectedOption) => {
|
||||||
|
console.log(`SELECT: [${selectedOption.target.value}]`);
|
||||||
|
setSelectUserTest(selectedOption.target.value);
|
||||||
|
};
|
||||||
|
const onClose = () => {
|
||||||
|
dialog.onClose();
|
||||||
|
if (selectUserTest == 'NO_USER') {
|
||||||
|
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`;
|
||||||
|
} else {
|
||||||
|
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/true/${USERS[selectUserTest]}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
as="button"
|
||||||
|
zIndex="100000"
|
||||||
|
position="fixed"
|
||||||
|
top="0"
|
||||||
|
insetStart="0"
|
||||||
|
insetEnd="0"
|
||||||
|
h="2px"
|
||||||
|
bg="warning.400"
|
||||||
|
cursor="pointer"
|
||||||
|
data-test-id="devtools"
|
||||||
|
onClick={dialog.onOpen}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
position="fixed"
|
||||||
|
top="0"
|
||||||
|
insetStart="4"
|
||||||
|
bg="warning.400"
|
||||||
|
color="warning.900"
|
||||||
|
fontSize="0.6rem"
|
||||||
|
fontWeight="bold"
|
||||||
|
px="10px"
|
||||||
|
marginLeft="25%"
|
||||||
|
borderBottomStartRadius="sm"
|
||||||
|
borderBottomEndRadius="sm"
|
||||||
|
textTransform="uppercase"
|
||||||
|
>
|
||||||
|
{envName.join(' : ')}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}>
|
||||||
|
<Dialog.Positioner>
|
||||||
|
<Dialog.Backdrop />
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Header>Outils développeurs</Dialog.Header>
|
||||||
|
<Dialog.Body>
|
||||||
|
<Stack>
|
||||||
|
<Text>User</Text>
|
||||||
|
<Select.Root
|
||||||
|
onChange={handleChange}
|
||||||
|
collection={USERS_COLLECTION}
|
||||||
|
>
|
||||||
|
<Select.Trigger>
|
||||||
|
<Select.ValueText placeholder="Select test user" />
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content>
|
||||||
|
{USERS_COLLECTION.items.map((value) => (
|
||||||
|
<Select.Item item={value} key={value.value}>
|
||||||
|
{value.label}
|
||||||
|
</Select.Item>
|
||||||
|
))}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</Stack>
|
||||||
|
</Dialog.Body>
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Button onClick={onClose}>Close</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Positioner>
|
||||||
|
</Dialog.Root>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,25 +1,18 @@
|
|||||||
import {
|
import { ReactNode, forwardRef } from 'react';
|
||||||
As,
|
|
||||||
Box,
|
import { Box, Flex, FlexProps } from '@chakra-ui/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;
|
children: 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) => {
|
({ children, color, sizeIcon = '1em', ...rest }, ref) => {
|
||||||
return (
|
return (
|
||||||
<Flex flex="none"
|
<Flex
|
||||||
|
flex="none"
|
||||||
minWidth={sizeIcon}
|
minWidth={sizeIcon}
|
||||||
minHeight={sizeIcon}
|
minHeight={sizeIcon}
|
||||||
maxWidth={sizeIcon}
|
maxWidth={sizeIcon}
|
||||||
@ -27,16 +20,21 @@ export const Icon = forwardRef<IconProps, 'span'>(
|
|||||||
align="center"
|
align="center"
|
||||||
padding="1px"
|
padding="1px"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...rest}>
|
{...rest}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
marginX="auto"
|
marginX="auto"
|
||||||
as={IconEl}
|
|
||||||
width="100%"
|
width="100%"
|
||||||
minWidth="100%"
|
minWidth="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
color={color}
|
color={color}
|
||||||
/>
|
asChild
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Icon.displayName = 'Icon';
|
||||||
|
@ -3,7 +3,7 @@ import React, { ReactNode, useEffect } from 'react';
|
|||||||
import { Flex, Image } from '@chakra-ui/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 ikon from '@/assets/images/ikon.svg';
|
||||||
import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar';
|
import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar';
|
||||||
|
|
||||||
export type LayoutProps = React.PropsWithChildren<unknown> & {
|
export type LayoutProps = React.PropsWithChildren<unknown> & {
|
||||||
@ -28,9 +28,9 @@ export const PageLayout = ({ children }: LayoutProps) => {
|
|||||||
left={0}
|
left={0}
|
||||||
right={0}
|
right={0}
|
||||||
minWidth="300px"
|
minWidth="300px"
|
||||||
zIndex={-1}
|
zIndex={0}
|
||||||
>
|
>
|
||||||
<Image src={background} boxSize="90%" margin="auto" opacity="30%" />
|
<Image src={ikon} boxSize="90vh" margin="auto" />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { ReactNode, useEffect } from 'react';
|
import { ReactNode, useEffect } from 'react';
|
||||||
|
|
||||||
import { Flex, FlexProps } from '@chakra-ui/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 { useColorModeValue } from '@/components/ui/color-mode';
|
||||||
import { useThemeMode } from '@/utils/theme-tools';
|
import { colors } from '@/theme/colors';
|
||||||
|
|
||||||
export type LayoutProps = FlexProps & {
|
export type LayoutProps = FlexProps & {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -22,7 +22,6 @@ export const PageLayoutInfoCenter = ({
|
|||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
const { mode } = useThemeMode();
|
|
||||||
return (
|
return (
|
||||||
<PageLayout>
|
<PageLayout>
|
||||||
<Flex
|
<Flex
|
||||||
@ -34,7 +33,7 @@ export const PageLayoutInfoCenter = ({
|
|||||||
borderRadius="8px"
|
borderRadius="8px"
|
||||||
padding="10px"
|
padding="10px"
|
||||||
boxShadow={'0px 0px 16px ' + colors.back[900]}
|
boxShadow={'0px 0px 16px ' + colors.back[900]}
|
||||||
backgroundColor={mode('#FFFFFF', '#000000')}
|
backgroundColor={useColorModeValue('#FFFFFF', '#000000')}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
20
front/src/components/ParameterLayout/ParameterLayout.ts
Normal file
20
front/src/components/ParameterLayout/ParameterLayout.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export {
|
||||||
|
ParameterLayoutContent as Content,
|
||||||
|
type ParameterLayoutContentProps as ContentProps,
|
||||||
|
} from './ParameterLayoutContent';
|
||||||
|
export {
|
||||||
|
ParameterLayoutFooter as Footer,
|
||||||
|
type ParameterLayoutFooterProps as FooterProps,
|
||||||
|
} from './ParameterLayoutFooter';
|
||||||
|
export {
|
||||||
|
ParameterLayoutHeader as Header,
|
||||||
|
type ParameterLayoutHeaderProps as HeaderProps,
|
||||||
|
} from './ParameterLayoutHeader';
|
||||||
|
export {
|
||||||
|
ParameterLayoutHeaderBase as HeaderBase,
|
||||||
|
type ParameterLayoutHeaderBaseProps as HeaderBaseProps,
|
||||||
|
} from './ParameterLayoutHeaderBase';
|
||||||
|
export {
|
||||||
|
ParameterLayoutRoot as Root,
|
||||||
|
type ParameterLayoutRootProps as RootProps,
|
||||||
|
} from './ParameterLayoutRoot';
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export type ParameterLayoutContentProps = {
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ParameterLayoutContent = ({
|
||||||
|
children,
|
||||||
|
}: ParameterLayoutContentProps) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
width="full"
|
||||||
|
borderY="1px solid black"
|
||||||
|
paddingY="15px"
|
||||||
|
paddingX="25px"
|
||||||
|
minHeight="10px"
|
||||||
|
background="gray.700"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export type ParameterLayoutFooterProps = {
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ParameterLayoutFooter = ({
|
||||||
|
children,
|
||||||
|
}: ParameterLayoutFooterProps) => {
|
||||||
|
return (
|
||||||
|
<Flex width="full" paddingY="15px" paddingX="25px" minHeight="10px">
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export type ParameterLayoutHeaderProps = {
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ParameterLayoutHeader = ({
|
||||||
|
children,
|
||||||
|
}: ParameterLayoutHeaderProps) => {
|
||||||
|
return (
|
||||||
|
<Flex width="full" paddingY="15px" paddingX="25px" minHeight="10px">
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Flex, Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { ParameterLayoutHeader } from './ParameterLayoutHeader';
|
||||||
|
|
||||||
|
export type ParameterLayoutHeaderBaseProps = {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ParameterLayoutHeaderBase = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
}: ParameterLayoutHeaderBaseProps) => {
|
||||||
|
return (
|
||||||
|
<ParameterLayoutHeader>
|
||||||
|
<Flex direction="column">
|
||||||
|
<Text fontSize="25px" fontWeight="bold">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
{description && <Text>{description}</Text>}
|
||||||
|
</Flex>
|
||||||
|
</ParameterLayoutHeader>
|
||||||
|
);
|
||||||
|
};
|
24
front/src/components/ParameterLayout/ParameterLayoutRoot.tsx
Normal file
24
front/src/components/ParameterLayout/ParameterLayoutRoot.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { VStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export type ParameterLayoutRootProps = {
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ParameterLayoutRoot = ({ children }: ParameterLayoutRootProps) => {
|
||||||
|
return (
|
||||||
|
<VStack
|
||||||
|
gap="0px"
|
||||||
|
marginX="15%"
|
||||||
|
marginY="20px"
|
||||||
|
justify="center"
|
||||||
|
//borderRadius="20px"
|
||||||
|
borderRadius="0px"
|
||||||
|
border="1px solid black"
|
||||||
|
background="gray.500"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
1
front/src/components/ParameterLayout/index.ts
Normal file
1
front/src/components/ParameterLayout/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * as ParameterLayout from './ParameterLayout';
|
@ -1,10 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import {
|
import { Group, Input } from '@chakra-ui/react';
|
||||||
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 = {
|
||||||
@ -44,16 +40,14 @@ export const SearchInput = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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" />
|
||||||
</InputLeftElement>
|
|
||||||
<Input
|
<Input
|
||||||
onFocus={onFocusKeep}
|
onFocus={onFocusKeep}
|
||||||
onBlur={() => setTimeout(() => onFocusLost(), 200)}
|
onBlur={() => setTimeout(() => onFocusLost(), 200)}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,49 +1,36 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Drawer,
|
import { Box, Button, ConditionalValue, Flex, HStack, IconButton, Span, Text, useDisclosure } from '@chakra-ui/react';
|
||||||
DrawerBody,
|
import { LuAlignJustify, LuArrowBigLeft, LuCircleUserRound, LuKeySquare, LuLogIn, LuLogOut, LuMoon, LuSettings, LuSun } from 'react-icons/lu';
|
||||||
DrawerContent,
|
import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md';
|
||||||
DrawerHeader,
|
|
||||||
DrawerOverlay,
|
|
||||||
Flex,
|
|
||||||
HStack,
|
|
||||||
IconButton,
|
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuItem,
|
|
||||||
MenuList,
|
|
||||||
Text,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
|
||||||
LuAlignJustify,
|
|
||||||
LuArrowBigLeft,
|
|
||||||
LuArrowUpSquare,
|
|
||||||
LuHelpCircle,
|
|
||||||
LuHome,
|
|
||||||
LuLogIn,
|
|
||||||
LuLogOut,
|
|
||||||
LuMoon,
|
|
||||||
LuPlusCircle,
|
|
||||||
LuSettings,
|
|
||||||
LuSun,
|
|
||||||
LuUserCircle,
|
|
||||||
} from 'react-icons/lu';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import { useColorMode, useColorModeValue } from '@/components/ui/color-mode';
|
||||||
|
import { DrawerBody, DrawerContent, DrawerHeader, DrawerRoot } from '@/components/ui/drawer';
|
||||||
|
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '@/components/ui/menu';
|
||||||
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 { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
|
|
||||||
import { useThemeMode } from '@/utils/theme-tools';
|
|
||||||
import { useSessionService } from '@/service/session';
|
import { useSessionService } from '@/service/session';
|
||||||
|
import { colors } from '@/theme/colors';
|
||||||
|
import { requestOpenSite, requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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',
|
variant: 'ghost' as ConditionalValue<
|
||||||
|
'ghost' | 'outline' | 'solid' | 'subtle' | 'surface' | 'plain' | undefined
|
||||||
|
>,
|
||||||
|
//colorPalette: "brand",
|
||||||
|
fontSize: '20px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
height: TOP_BAR_HEIGHT,
|
height: TOP_BAR_HEIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,17 +39,51 @@ export type TopBarProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ButtonMenuLeft = ({
|
||||||
|
dest,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
onClickEnd = () => {},
|
||||||
|
}: {
|
||||||
|
dest: string;
|
||||||
|
title: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
onClickEnd?: () => void;
|
||||||
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
background="#00000000"
|
||||||
|
borderRadius="0px"
|
||||||
|
onClick={() => {
|
||||||
|
navigate(dest);
|
||||||
|
onClickEnd();
|
||||||
|
}}
|
||||||
|
width="full"
|
||||||
|
{...BUTTON_TOP_BAR_PROPERTY}
|
||||||
|
>
|
||||||
|
<Box asChild style={{ width: '45px', height: '45px' }}>
|
||||||
|
{icon}
|
||||||
|
</Box>
|
||||||
|
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
<Box marginY="5" marginX="10" height="2px" background="brand.600" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
export const TopBar = ({ title, children }: TopBarProps) => {
|
export const TopBar = ({ title, children }: TopBarProps) => {
|
||||||
const { mode, colorMode, toggleColorMode } = useThemeMode();
|
const navigate = useNavigate();
|
||||||
const { clearToken } = useSessionService();
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
|
||||||
const { session } = useServiceContext();
|
const { session } = useServiceContext();
|
||||||
const backColor = mode('back.100', 'back.800');
|
const { clearToken } = useSessionService();
|
||||||
|
const backColor = useColorModeValue('back.100', 'back.800');
|
||||||
const drawerDisclose = useDisclosure();
|
const drawerDisclose = useDisclosure();
|
||||||
const onChangeTheme = () => {
|
const onChangeTheme = () => {
|
||||||
drawerDisclose.onOpen();
|
drawerDisclose.onOpen();
|
||||||
};
|
};
|
||||||
const navigate = useNavigate();
|
|
||||||
const onSignIn = (): void => {
|
const onSignIn = (): void => {
|
||||||
clearToken();
|
clearToken();
|
||||||
requestSignIn();
|
requestSignIn();
|
||||||
@ -75,17 +96,8 @@ export const TopBar = ({ title, children }: TopBarProps) => {
|
|||||||
clearToken();
|
clearToken();
|
||||||
requestSignOut();
|
requestSignOut();
|
||||||
};
|
};
|
||||||
const onSelectAdd = () => {
|
const onKarso = (): void => {
|
||||||
navigate('/add');
|
requestOpenSite();
|
||||||
};
|
|
||||||
const onSelectHome = () => {
|
|
||||||
navigate('/');
|
|
||||||
};
|
|
||||||
const onHelp = () => {
|
|
||||||
navigate('/help');
|
|
||||||
};
|
|
||||||
const onSettings = () => {
|
|
||||||
navigate('/settings');
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -103,10 +115,12 @@ export const TopBar = ({ title, children }: TopBarProps) => {
|
|||||||
zIndex={200}
|
zIndex={200}
|
||||||
>
|
>
|
||||||
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
|
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
|
||||||
|
<HStack>
|
||||||
<LuAlignJustify />
|
<LuAlignJustify />
|
||||||
<Text paddingLeft="3px" fontWeight="bold">
|
<Text paddingLeft="3px" fontWeight="bold">
|
||||||
Karusic
|
Karusic
|
||||||
</Text>
|
</Text>
|
||||||
|
</HStack>
|
||||||
</Button>
|
</Button>
|
||||||
{title && (
|
{title && (
|
||||||
<Text
|
<Text
|
||||||
@ -115,6 +129,7 @@ export const TopBar = ({ title, children }: TopBarProps) => {
|
|||||||
textTransform="uppercase"
|
textTransform="uppercase"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
|
color="brand.500"
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
@ -134,7 +149,7 @@ export const TopBar = ({ title, children }: TopBarProps) => {
|
|||||||
onClick={onSignUp}
|
onClick={onSignUp}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
>
|
>
|
||||||
<LuPlusCircle />
|
<MdMore />
|
||||||
<Text paddingLeft="3px" fontWeight="bold">
|
<Text paddingLeft="3px" fontWeight="bold">
|
||||||
Sign-up
|
Sign-up
|
||||||
</Text>
|
</Text>
|
||||||
@ -142,86 +157,111 @@ export const TopBar = ({ title, children }: TopBarProps) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{session?.state === SessionState.CONNECTED && (
|
{session?.state === SessionState.CONNECTED && (
|
||||||
<Menu>
|
<MenuRoot>
|
||||||
<MenuButton
|
<MenuTrigger asChild>
|
||||||
as={IconButton}
|
<IconButton {...BUTTON_TOP_BAR_PROPERTY} width={TOP_BAR_HEIGHT}>
|
||||||
aria-label="Options"
|
<LuCircleUserRound />
|
||||||
icon={<LuUserCircle />}
|
</IconButton>
|
||||||
{...BUTTON_TOP_BAR_PROPERTY}
|
</MenuTrigger>
|
||||||
width={TOP_BAR_HEIGHT}
|
<MenuContent>
|
||||||
/>
|
<MenuItem
|
||||||
<MenuList>
|
value="user"
|
||||||
<MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}>
|
valueText="user"
|
||||||
Sign in as {session?.login ?? 'Fail'}
|
color={useColorModeValue('brand.800', 'brand.200')}
|
||||||
|
>
|
||||||
|
<MdSupervisedUserCircle />
|
||||||
|
<Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem icon={<LuSettings />} onClick={onSettings}>Settings</MenuItem>
|
<MenuItem
|
||||||
<MenuItem icon={<LuHelpCircle />} onClick={onHelp}>Help</MenuItem>
|
value="Settings"
|
||||||
<MenuItem icon={<LuLogOut />} onClick={onSignOut}>
|
valueText="Settings"
|
||||||
Sign-out
|
onClick={() => navigate('/settings')}
|
||||||
|
>
|
||||||
|
<LuSettings />
|
||||||
|
Settings
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
value="Help"
|
||||||
|
valueText="Help"
|
||||||
|
onClick={() => navigate('/help')}
|
||||||
|
>
|
||||||
|
<MdHelp /> Help
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
value="Sign-out"
|
||||||
|
valueText="Sign-out"
|
||||||
|
onClick={onSignOut}
|
||||||
|
>
|
||||||
|
<LuLogOut /> Sign-out
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem value="karso" valueText="Karso" onClick={onKarso}>
|
||||||
|
<LuKeySquare /> Karso (SSO)
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{colorMode === 'light' ? (
|
{colorMode === 'light' ? (
|
||||||
<MenuItem icon={<LuMoon />} onClick={toggleColorMode}>
|
<MenuItem
|
||||||
Set dark mode
|
value="set-dark"
|
||||||
|
valueText="set-dark"
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
>
|
||||||
|
<LuMoon /> Set dark mode
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
<MenuItem icon={<LuSun />} onClick={toggleColorMode}>
|
<MenuItem
|
||||||
Set light mode
|
value="set-light"
|
||||||
|
valueText="set-light"
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
>
|
||||||
|
<LuSun /> Set light mode
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</MenuList>
|
</MenuContent>
|
||||||
</Menu>
|
</MenuRoot>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Drawer
|
<DrawerRoot
|
||||||
placement="left"
|
placement="start"
|
||||||
onClose={drawerDisclose.onClose}
|
onOpenChange={drawerDisclose.onClose}
|
||||||
isOpen={drawerDisclose.isOpen}
|
open={drawerDisclose.open}
|
||||||
|
data-testid="top-bar_drawer-root"
|
||||||
>
|
>
|
||||||
<DrawerOverlay />
|
<DrawerContent data-testid="top-bar_drawer-content">
|
||||||
<DrawerContent>
|
|
||||||
<DrawerHeader
|
<DrawerHeader
|
||||||
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}
|
backgroundColor={backColor}
|
||||||
color={mode('brand.900', 'brand.50')}
|
color={useColorModeValue('brand.900', 'brand.50')}
|
||||||
textTransform="uppercase"
|
textTransform="uppercase"
|
||||||
>
|
>
|
||||||
<HStack height={TOP_BAR_HEIGHT}>
|
<HStack {...BUTTON_TOP_BAR_PROPERTY} cursor="pointer">
|
||||||
<LuArrowBigLeft />
|
<LuArrowBigLeft />
|
||||||
<Text as="span" paddingLeft="3px">
|
<Span paddingLeft="3px">Karusic</Span>
|
||||||
Karusic
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<DrawerBody paddingX="0px">
|
<DrawerBody paddingX="0px">
|
||||||
<Button
|
<Box marginY="3" />
|
||||||
background="#00000000"
|
<ButtonMenuLeft
|
||||||
borderRadius="0px"
|
onClickEnd={drawerDisclose.onClose}
|
||||||
onClick={onSelectHome}
|
dest="/"
|
||||||
width="full"
|
title="Home"
|
||||||
>
|
icon={<MdHome />}
|
||||||
<LuHome />
|
/>
|
||||||
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
|
<ButtonMenuLeft
|
||||||
Home
|
onClickEnd={drawerDisclose.onClose}
|
||||||
</Text>
|
dest="/on-air"
|
||||||
</Button>
|
title="On air"
|
||||||
<hr />
|
icon={<MdOutlinePlaylistPlay />}
|
||||||
<Button
|
/>
|
||||||
background="#00000000"
|
<ButtonMenuLeft
|
||||||
borderRadius="0px"
|
onClickEnd={drawerDisclose.onClose}
|
||||||
onClick={onSelectAdd}
|
dest="/add"
|
||||||
width="full"
|
title="Add Media"
|
||||||
>
|
icon={<MdOutlineUploadFile />}
|
||||||
<LuArrowUpSquare />
|
/>
|
||||||
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
|
|
||||||
Add Media
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
</DrawerBody>
|
</DrawerBody>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</DrawerRoot>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import { Flex, Text } from '@chakra-ui/react';
|
import { Flex, Span } 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 { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing';
|
import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing';
|
||||||
|
import { useCountTracksWithAlbumId } from '@/service/Track';
|
||||||
|
|
||||||
export type DisplayAlbumProps = {
|
export type DisplayAlbumProps = {
|
||||||
dataAlbum?: Album;
|
dataAlbum?: Album;
|
||||||
@ -19,12 +19,17 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Flex direction="row" width="full" height="full">
|
<Flex
|
||||||
|
direction="row"
|
||||||
|
width="full"
|
||||||
|
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}
|
iconEmpty={<LuDisc3 />}
|
||||||
/>
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
@ -35,29 +40,27 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => {
|
|||||||
overflowX="hidden"
|
overflowX="hidden"
|
||||||
flex={1}
|
flex={1}
|
||||||
>
|
>
|
||||||
<Text
|
<Span
|
||||||
as="span"
|
textAlign="left"
|
||||||
align="left"
|
|
||||||
fontSize="20px"
|
fontSize="20px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={[1, 2]}
|
// noOfLines={[1, 2]}
|
||||||
>
|
>
|
||||||
{dataAlbum?.name}
|
{dataAlbum?.name}
|
||||||
</Text>
|
</Span>
|
||||||
<Text
|
<Span
|
||||||
as="span"
|
textAlign="left"
|
||||||
align="left"
|
|
||||||
fontSize="15px"
|
fontSize="15px"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
// noOfLines={1}
|
||||||
>
|
>
|
||||||
{countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'}
|
{countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'}
|
||||||
</Text>
|
</Span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -6,5 +6,5 @@ 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" />;
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import { useState } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import {
|
import { Box } from '@chakra-ui/react';
|
||||||
IconButton,
|
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuItem,
|
|
||||||
MenuList,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { LuMenu } from 'react-icons/lu';
|
import { LuMenu } from 'react-icons/lu';
|
||||||
|
|
||||||
|
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '../ui/menu';
|
||||||
|
|
||||||
export type MenuElement = {
|
export type MenuElement = {
|
||||||
|
icon?: ReactNode;
|
||||||
name: string;
|
name: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
@ -23,20 +20,32 @@ export const ContextMenu = ({ elements }: ContextMenuProps) => {
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<MenuRoot data-testid="context-menu">
|
||||||
<MenuButton
|
<MenuTrigger
|
||||||
as={IconButton}
|
asChild
|
||||||
aria-label="Options"
|
|
||||||
icon={<LuMenu />}
|
|
||||||
marginY="auto"
|
marginY="auto"
|
||||||
/>
|
marginRight="4px"
|
||||||
<MenuList>
|
data-testid="context-menu_trigger"
|
||||||
|
>
|
||||||
|
<Box asChild color="brand.500" cursor="pointer">
|
||||||
|
<LuMenu />
|
||||||
|
</Box>
|
||||||
|
</MenuTrigger>
|
||||||
|
<MenuContent data-testid="context-menu_content">
|
||||||
{elements?.map((data) => (
|
{elements?.map((data) => (
|
||||||
<MenuItem key={data.name} onClick={data.onClick}>
|
<MenuItem
|
||||||
|
key={data.name}
|
||||||
|
value={data.name}
|
||||||
|
onClick={data.onClick}
|
||||||
|
height="65px"
|
||||||
|
fontSize="25px"
|
||||||
|
data-test-id="context-menu_item"
|
||||||
|
>
|
||||||
|
{data.icon}
|
||||||
{data.name}
|
{data.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</MenuList>
|
</MenuContent>
|
||||||
</Menu>
|
</MenuRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,25 +1,13 @@
|
|||||||
import {
|
import { DragEventHandler, ReactNode, RefObject } from 'react';
|
||||||
DragEventHandler,
|
|
||||||
RefObject,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import {
|
import { Box, BoxProps, Center, Flex, HStack, Image } from '@chakra-ui/react';
|
||||||
Box,
|
import { MdHighlightOff, MdUploadFile } from 'react-icons/md';
|
||||||
BoxProps,
|
|
||||||
Center,
|
|
||||||
Image,
|
|
||||||
Wrap,
|
|
||||||
WrapItem,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
|
||||||
MdHighlightOff,
|
|
||||||
MdUploadFile,
|
|
||||||
} from 'react-icons/md';
|
|
||||||
|
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
import { FormGroup } from '@/components/form/FormGroup';
|
||||||
import { UseFormidableReturn } from '@/components/form/Formidable';
|
|
||||||
import { DataUrlAccess } from '@/utils/data-url-access';
|
import { DataUrlAccess } from '@/utils/data-url-access';
|
||||||
|
|
||||||
|
import { useFormidableContextElement } from '../formidable';
|
||||||
|
|
||||||
export type DragNdropProps = {
|
export type DragNdropProps = {
|
||||||
onFilesSelected?: (file: File[]) => void;
|
onFilesSelected?: (file: File[]) => void;
|
||||||
onUriSelected?: (uri: string) => void;
|
onUriSelected?: (uri: string) => void;
|
||||||
@ -92,33 +80,33 @@ export const DragNdrop = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type CenterIconProps = BoxProps & {
|
export type CenterIconProps = BoxProps & {
|
||||||
icon: any;
|
children: ReactNode;
|
||||||
sizeIcon?: string;
|
sizeIcon?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CenterIcon = ({
|
export const CenterIcon = ({
|
||||||
icon: IconEl,
|
children,
|
||||||
sizeIcon = '15px',
|
sizeIcon = '15px',
|
||||||
...rest
|
...rest
|
||||||
}: CenterIconProps) => {
|
}: CenterIconProps) => {
|
||||||
return (
|
return (
|
||||||
<Box position="relative" w={sizeIcon} h={sizeIcon} flex="none" {...rest}>
|
<Box position="relative" w={sizeIcon} h={sizeIcon} flex="none" {...rest}>
|
||||||
<Box
|
<Box
|
||||||
as={IconEl}
|
|
||||||
w={sizeIcon}
|
w={sizeIcon}
|
||||||
h={sizeIcon}
|
h={sizeIcon}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top="50%"
|
top="50%"
|
||||||
left="50%"
|
left="50%"
|
||||||
transform="translate(-50%, -50%)"
|
transform="translate(-50%, -50%)"
|
||||||
/>
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FormCoversProps = {
|
export type FormCoversProps = {
|
||||||
form: UseFormidableReturn;
|
name: string;
|
||||||
variableName: string;
|
|
||||||
ref?: RefObject<any>;
|
ref?: RefObject<any>;
|
||||||
label?: string;
|
label?: string;
|
||||||
isRequired?: boolean;
|
isRequired?: boolean;
|
||||||
@ -127,51 +115,48 @@ export type FormCoversProps = {
|
|||||||
onRemove?: (index: number) => void;
|
onRemove?: (index: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** This field component is a direct insertion component ==> not manage with formidable */
|
||||||
export const FormCovers = ({
|
export const FormCovers = ({
|
||||||
form,
|
name,
|
||||||
variableName,
|
|
||||||
ref,
|
ref,
|
||||||
onFilesSelected = () => {},
|
onFilesSelected = () => {},
|
||||||
onUriSelected = () => {},
|
onUriSelected = () => {},
|
||||||
onRemove = () => {},
|
onRemove = () => {},
|
||||||
...rest
|
...rest
|
||||||
}: FormCoversProps) => {
|
}: FormCoversProps) => {
|
||||||
const urls =
|
const { value } = useFormidableContextElement(name);
|
||||||
DataUrlAccess.getListThumbnailUrl(form.values[variableName]) ?? [];
|
const urls = DataUrlAccess.getListThumbnailUrl(value) ?? [];
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup name={name} {...rest}>
|
||||||
isModify={form.isModify[variableName]}
|
<HStack wrap="wrap" width="full">
|
||||||
onRestore={() => form.restoreValue({ [variableName]: true })}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<Wrap width="full">
|
|
||||||
{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
|
||||||
icon={MdHighlightOff}
|
|
||||||
width="125px"
|
width="125px"
|
||||||
sizeIcon="100%"
|
sizeIcon="100%"
|
||||||
zIndex="+1"
|
zIndex="+1"
|
||||||
color="#00000020"
|
color="#00000020"
|
||||||
_hover={{ color: 'red' }}
|
_hover={{ color: 'red' }}
|
||||||
onClick={() => onRemove && onRemove(index)}
|
onClick={() => onRemove && onRemove(index)}
|
||||||
/>
|
>
|
||||||
|
<MdHighlightOff />
|
||||||
|
</CenterIcon>
|
||||||
</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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,35 +3,21 @@ import { ReactNode } from 'react';
|
|||||||
import { Flex, Text } from '@chakra-ui/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 = {
|
import { Icon } from '../Icon';
|
||||||
error?: ReactNode;
|
import { useFormidableContextElement } from '../formidable';
|
||||||
help?: ReactNode;
|
|
||||||
label?: ReactNode;
|
|
||||||
isModify?: boolean;
|
|
||||||
onRestore?: () => void;
|
|
||||||
isRequired?: boolean;
|
|
||||||
children: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FormGroup = ({
|
const DisplayLabel = ({
|
||||||
children,
|
|
||||||
error,
|
|
||||||
help,
|
|
||||||
label,
|
label,
|
||||||
isModify = false,
|
isRequired,
|
||||||
isRequired = false,
|
}: {
|
||||||
onRestore,
|
label?: ReactNode;
|
||||||
}: FormGroupProps) => (
|
isRequired: boolean;
|
||||||
<Flex
|
}) => {
|
||||||
borderLeftWidth="3px"
|
if (!label) {
|
||||||
borderLeftColor={error ? 'red' : isModify ? 'blue' : '#00000000'}
|
return <></>;
|
||||||
paddingLeft="7px"
|
}
|
||||||
paddingY="4px"
|
return (
|
||||||
direction="column"
|
<Text marginRight="auto" paddingY="5px" fontWeight="bold">
|
||||||
>
|
|
||||||
<Flex direction="row" width="full" gap="52px">
|
|
||||||
{!!label && (
|
|
||||||
<Text marginRight="auto" fontWeight="bold">
|
|
||||||
{label}{' '}
|
{label}{' '}
|
||||||
{isRequired && (
|
{isRequired && (
|
||||||
<Text as="span" color="red.600">
|
<Text as="span" color="red.600">
|
||||||
@ -39,24 +25,151 @@ export const FormGroup = ({
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
);
|
||||||
{!!onRestore && isModify && (
|
};
|
||||||
<MdRefresh size="15px" onClick={onRestore} cursor="pointer" />
|
|
||||||
)}
|
const DisplayHelp = ({ help }: { help?: ReactNode }) => {
|
||||||
</Flex>
|
if (!help) {
|
||||||
{children}
|
return <></>;
|
||||||
{!!help && (
|
}
|
||||||
|
return (
|
||||||
<Flex direction="row">
|
<Flex direction="row">
|
||||||
<MdHelpOutline />
|
<MdHelpOutline />
|
||||||
<Text>{help}</Text>
|
<Text alignContent="center">{help}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
);
|
||||||
|
};
|
||||||
{!!error && (
|
const DisplayError = ({ error }: { error?: ReactNode }) => {
|
||||||
<Flex direction="row">
|
if (!error) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Flex direction="row" color="red.600">
|
||||||
<MdErrorOutline />
|
<MdErrorOutline />
|
||||||
<Text>{error}</Text>
|
<Text alignContent="center">{error}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormGroupProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
name: string;
|
||||||
|
error?: ReactNode;
|
||||||
|
help?: ReactNode;
|
||||||
|
label?: ReactNode;
|
||||||
|
isRequired?: boolean;
|
||||||
|
disableSingleLine?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormGroup = ({
|
||||||
|
children,
|
||||||
|
name,
|
||||||
|
help,
|
||||||
|
label,
|
||||||
|
isRequired = false,
|
||||||
|
disableSingleLine,
|
||||||
|
}: FormGroupProps) => {
|
||||||
|
const { form, error, isModify, onRestore } =
|
||||||
|
useFormidableContextElement(name);
|
||||||
|
const enableModifyNotification =
|
||||||
|
form.configuration.enableModifyNotification ?? false;
|
||||||
|
const enableReset = form.configuration.enableReset ?? false;
|
||||||
|
const singleLine = disableSingleLine
|
||||||
|
? false
|
||||||
|
: form.configuration.singleLineForm;
|
||||||
|
return (
|
||||||
|
<FormGroupShow
|
||||||
|
help={help}
|
||||||
|
label={label}
|
||||||
|
isRequired={isRequired}
|
||||||
|
error={error}
|
||||||
|
isModify={isModify}
|
||||||
|
enableModifyNotification={enableModifyNotification}
|
||||||
|
enableReset={enableReset}
|
||||||
|
singleLine={singleLine}
|
||||||
|
onRestore={onRestore}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</FormGroupShow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormGroupShowProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
help?: ReactNode;
|
||||||
|
label?: ReactNode;
|
||||||
|
isRequired?: boolean;
|
||||||
|
error?: ReactNode;
|
||||||
|
isModify?: boolean;
|
||||||
|
enableModifyNotification?: boolean;
|
||||||
|
enableReset?: boolean;
|
||||||
|
singleLine?: boolean;
|
||||||
|
onRestore?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormGroupShow = ({
|
||||||
|
children,
|
||||||
|
help,
|
||||||
|
label,
|
||||||
|
isRequired = false,
|
||||||
|
error,
|
||||||
|
isModify = false,
|
||||||
|
enableModifyNotification = true,
|
||||||
|
enableReset = true,
|
||||||
|
singleLine = false,
|
||||||
|
onRestore,
|
||||||
|
}: FormGroupShowProps) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
borderLeftWidth="3px"
|
||||||
|
borderLeftColor={
|
||||||
|
error
|
||||||
|
? 'red.600'
|
||||||
|
: enableModifyNotification && isModify
|
||||||
|
? 'blue.600'
|
||||||
|
: '#00000000'
|
||||||
|
}
|
||||||
|
paddingLeft="7px"
|
||||||
|
paddingY="4px"
|
||||||
|
width="full"
|
||||||
|
direction="column"
|
||||||
|
>
|
||||||
|
{singleLine && (
|
||||||
|
<>
|
||||||
|
<Flex direction="row" width="full" gap="52px">
|
||||||
|
<Flex width="10%">
|
||||||
|
<DisplayLabel label={label} isRequired={isRequired} />
|
||||||
|
{!!onRestore && isModify && enableReset && (
|
||||||
|
<Icon sizeIcon="150px">
|
||||||
|
<MdRefresh onClick={onRestore} cursor="pointer" />
|
||||||
|
</Icon>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Flex direction="column" width={'90%'} gap="5px">
|
||||||
|
{children}
|
||||||
|
<DisplayHelp help={help} />
|
||||||
|
<DisplayError error={error} />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!singleLine && (
|
||||||
|
<>
|
||||||
|
<Flex direction="row" width="full" gap="52px">
|
||||||
|
<Flex width="full">
|
||||||
|
<DisplayLabel label={label} isRequired={isRequired} />
|
||||||
|
{!!onRestore && isModify && enableReset && (
|
||||||
|
<Icon sizeIcon="30px" onClick={onRestore} cursor="pointer">
|
||||||
|
<MdRefresh />
|
||||||
|
</Icon>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
{children}
|
||||||
|
<DisplayHelp help={help} />
|
||||||
|
<DisplayError error={error} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -2,35 +2,34 @@ import { RefObject } from 'react';
|
|||||||
|
|
||||||
import { Input } from '@chakra-ui/react';
|
import { Input } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
import { FormGroup, FormGroupProps } from '@/components/form/FormGroup';
|
||||||
import { UseFormidableReturn } from '@/components/form/Formidable';
|
|
||||||
|
import { useFormidableContextElement } from '../formidable';
|
||||||
|
|
||||||
export type FormInputProps = {
|
export type FormInputProps = {
|
||||||
form: UseFormidableReturn;
|
name: string;
|
||||||
variableName: string;
|
|
||||||
ref?: RefObject<any>;
|
ref?: RefObject<any>;
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
isRequired?: boolean;
|
isRequired?: boolean;
|
||||||
};
|
} & Omit<FormGroupProps, 'children'>;
|
||||||
|
|
||||||
export const FormInput = ({
|
export const FormInput = ({
|
||||||
form,
|
name,
|
||||||
variableName,
|
|
||||||
ref,
|
ref,
|
||||||
placeholder,
|
placeholder,
|
||||||
...rest
|
...rest
|
||||||
}: FormInputProps) => {
|
}: FormInputProps) => {
|
||||||
|
const { value, onChange } = useFormidableContextElement(name);
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup name={name} {...rest}>
|
||||||
isModify={form.isModify[variableName]}
|
|
||||||
onRestore={() => form.restoreValue({ [variableName]: true })}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
value={form.values[variableName]}
|
type="text"
|
||||||
onChange={(e) => form.setValues({ [variableName]: e.target.value })}
|
name={name}
|
||||||
|
autoComplete={name}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
|
|
||||||
|
import { FormGroup } from '@/components/form/FormGroup';
|
||||||
|
|
||||||
|
import { useFormidableContextElement } from '../formidable';
|
||||||
import {
|
import {
|
||||||
NumberDecrementStepper,
|
|
||||||
NumberIncrementStepper,
|
|
||||||
NumberInput,
|
|
||||||
NumberInputField,
|
NumberInputField,
|
||||||
NumberInputProps,
|
NumberInputProps,
|
||||||
NumberInputStepper,
|
NumberInputRoot,
|
||||||
} from '@chakra-ui/react';
|
} from '../ui/number-input';
|
||||||
|
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
|
||||||
import { UseFormidableReturn } from '@/components/form/Formidable';
|
|
||||||
|
|
||||||
export type FormNumberProps = Pick<
|
export type FormNumberProps = Pick<
|
||||||
NumberInputProps,
|
NumberInputProps,
|
||||||
'step' | 'defaultValue' | 'min' | 'max'
|
'step' | 'defaultValue' | 'min' | 'max'
|
||||||
> & {
|
> & {
|
||||||
form: UseFormidableReturn;
|
name: string;
|
||||||
variableName: string;
|
|
||||||
ref?: RefObject<any>;
|
ref?: RefObject<any>;
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@ -25,8 +21,7 @@ export type FormNumberProps = Pick<
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FormNumber = ({
|
export const FormNumber = ({
|
||||||
form,
|
name,
|
||||||
variableName,
|
|
||||||
ref,
|
ref,
|
||||||
placeholder,
|
placeholder,
|
||||||
step,
|
step,
|
||||||
@ -35,27 +30,21 @@ export const FormNumber = ({
|
|||||||
defaultValue,
|
defaultValue,
|
||||||
...rest
|
...rest
|
||||||
}: FormNumberProps) => {
|
}: FormNumberProps) => {
|
||||||
|
const { form, value, isModify, onChange, onRestore } =
|
||||||
|
useFormidableContextElement(name);
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup name={name} {...rest}>
|
||||||
isModify={form.isModify[variableName]}
|
<NumberInputRoot
|
||||||
onRestore={() => form.restoreValue({ [variableName]: true })}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<NumberInput
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
value={form.values[variableName]}
|
value={value}
|
||||||
onChange={(_, value) => form.setValues({ [variableName]: value })}
|
onValueChange={(e) => onChange(e.value)}
|
||||||
step={step}
|
step={step}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
>
|
>
|
||||||
<NumberInputField />
|
<NumberInputField />
|
||||||
<NumberInputStepper>
|
</NumberInputRoot>
|
||||||
<NumberIncrementStepper />
|
|
||||||
<NumberDecrementStepper />
|
|
||||||
</NumberInputStepper>
|
|
||||||
</NumberInput>
|
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
50
front/src/components/form/FormPassword.tsx
Normal file
50
front/src/components/form/FormPassword.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { RefObject, useState } from 'react';
|
||||||
|
|
||||||
|
import { chakra, Group, Input } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { FormGroup, FormGroupProps } from '@/components/form/FormGroup';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import { LuEye, LuEyeOff } from 'react-icons/lu';
|
||||||
|
import { useFormidableContextElement } from '../formidable';
|
||||||
|
|
||||||
|
export type FormInputProps = {
|
||||||
|
name: string;
|
||||||
|
ref?: RefObject<any>;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
isRequired?: boolean;
|
||||||
|
} & Omit<FormGroupProps, 'children'>;
|
||||||
|
|
||||||
|
export const FormPassword = ({
|
||||||
|
name,
|
||||||
|
ref,
|
||||||
|
placeholder,
|
||||||
|
...rest
|
||||||
|
}: FormInputProps) => {
|
||||||
|
const {value, onChange} = useFormidableContextElement(name);
|
||||||
|
const [showPassword, setShowPassword] = useState<boolean>(false);
|
||||||
|
function toggleVisible(): void {
|
||||||
|
setShowPassword((value) => ! value)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
name={name}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<chakra.div position="relative" width="full">
|
||||||
|
<Input
|
||||||
|
ref={ref}
|
||||||
|
type={showPassword? "text" : "password"}
|
||||||
|
name={name}
|
||||||
|
autoComplete={name}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
paddingRight="47px"
|
||||||
|
/>
|
||||||
|
<Button variant="ghost" onClick={toggleVisible} position="absolute" top="0" right="0" _hover={{bg:"#0000", shadow:"none", color:"black"}}>
|
||||||
|
{showPassword? <LuEye/> : <LuEyeOff/>}
|
||||||
|
</Button>
|
||||||
|
</chakra.div>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
@ -3,7 +3,9 @@ import { useState } from 'react';
|
|||||||
import { Box } from '@chakra-ui/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/formidable/FormidableConfig';
|
||||||
|
|
||||||
|
import { Formidable } from '../formidable';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Components/FormSelect',
|
title: 'Components/FormSelect',
|
||||||
@ -16,23 +18,24 @@ type BasicFormData = {
|
|||||||
export const Default = () => {
|
export const Default = () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelect
|
<FormSelect
|
||||||
label="Simple Title"
|
label="Simple Title"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
keyInputValue="id"
|
keyInputValue="id"
|
||||||
options={[{ id: 111 }, { id: 222 }, { id: 333 }, { id: 123 }]}
|
options={[{ id: 111 }, { id: 222 }, { id: 333 }, { id: 123 }]}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChangeKeys = () => {
|
export const ChangeKeys = () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelect
|
<FormSelect
|
||||||
label="Simple Title for (ChangeKeys)"
|
label="Simple Title for (ChangeKeys)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
keyInputKey="key"
|
keyInputKey="key"
|
||||||
keyInputValue="plop"
|
keyInputValue="plop"
|
||||||
options={[
|
options={[
|
||||||
@ -41,21 +44,23 @@ export const ChangeKeys = () => {
|
|||||||
{ key: 333, plop: 'third item' },
|
{ key: 333, plop: 'third item' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const ChangeName = () => {
|
export const ChangeName = () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelect
|
<FormSelect
|
||||||
label="Simple Title for (ChangeName)"
|
label="Simple Title for (ChangeName)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
options={[
|
options={[
|
||||||
{ id: 111, name: 'first Item' },
|
{ id: 111, name: 'first Item' },
|
||||||
{ id: 222, name: 'Second Item' },
|
{ id: 222, name: 'Second Item' },
|
||||||
{ id: 333, name: 'third item' },
|
{ id: 333, name: 'third item' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const AddableItem = () => {
|
export const AddableItem = () => {
|
||||||
@ -66,10 +71,10 @@ export const AddableItem = () => {
|
|||||||
{ id: 333, name: 'third item' },
|
{ id: 333, name: 'third item' },
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelect
|
<FormSelect
|
||||||
label="Simple Title for (ChangeName)"
|
label="Simple Title for (ChangeName)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
addNewItem={(data: string) => {
|
addNewItem={(data: string) => {
|
||||||
return new Promise((resolve, _rejects) => {
|
return new Promise((resolve, _rejects) => {
|
||||||
let upperId = 0;
|
let upperId = 0;
|
||||||
@ -87,6 +92,7 @@ export const AddableItem = () => {
|
|||||||
}}
|
}}
|
||||||
options={data}
|
options={data}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -94,11 +100,11 @@ export const DarkBackground = {
|
|||||||
render: () => {
|
render: () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<Box p="4" color="white" bg="gray.800">
|
<Box p="4" color="white" bg="gray.800">
|
||||||
<FormSelect
|
<FormSelect
|
||||||
label="Simple Title for (DarkBackground)"
|
label="Simple Title for (DarkBackground)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
options={[
|
options={[
|
||||||
{ id: 111, name: 'first Item' },
|
{ id: 111, name: 'first Item' },
|
||||||
{ id: 222, name: 'Second Item' },
|
{ id: 222, name: 'Second Item' },
|
||||||
@ -106,6 +112,7 @@ export const DarkBackground = {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
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 { SelectSingle } from '@/components/select/SelectSingle';
|
import { SelectSingle } from '@/components/select/SelectSingle';
|
||||||
|
|
||||||
|
import { useFormidableContextElement } from '../formidable';
|
||||||
|
|
||||||
export type FormSelectProps = {
|
export type FormSelectProps = {
|
||||||
// Generic Form input
|
|
||||||
form: UseFormidableReturn;
|
|
||||||
// Form: Name of the variable
|
// Form: Name of the variable
|
||||||
variableName: string;
|
name: string;
|
||||||
// Forward object reference
|
// Forward object reference
|
||||||
ref?: RefObject<any>;
|
ref?: RefObject<any>;
|
||||||
// Form: Label of the input
|
// Form: Label of the input
|
||||||
@ -32,8 +29,7 @@ export type FormSelectProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FormSelect = ({
|
export const FormSelect = ({
|
||||||
form,
|
name,
|
||||||
variableName,
|
|
||||||
ref,
|
ref,
|
||||||
placeholder,
|
placeholder,
|
||||||
options,
|
options,
|
||||||
@ -43,23 +39,23 @@ export const FormSelect = ({
|
|||||||
addNewItem,
|
addNewItem,
|
||||||
...rest
|
...rest
|
||||||
}: FormSelectProps) => {
|
}: FormSelectProps) => {
|
||||||
|
const { form, value, isModify, onChange, onRestore } =
|
||||||
|
useFormidableContextElement(name);
|
||||||
// if set add capability to add the search item
|
// if set add capability to add the search item
|
||||||
const onCreate = !addNewItem
|
const onCreate = !addNewItem
|
||||||
? undefined
|
? undefined
|
||||||
: (data: string) => {
|
: (data: string) => {
|
||||||
addNewItem(data).then((data: object) => form.setValues({ [variableName]: data[keyInputKey] }));
|
addNewItem(data).then((data: object) =>
|
||||||
|
form.setValues({ [name]: data[keyInputKey] })
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup name={name} {...rest}>
|
||||||
isModify={form.isModify[variableName]}
|
|
||||||
onRestore={() => form.restoreValue({ [variableName]: true })}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<SelectSingle
|
<SelectSingle
|
||||||
ref={ref}
|
ref={ref}
|
||||||
value={form.values[variableName]}
|
value={value}
|
||||||
options={options}
|
options={options}
|
||||||
onChange={(value) => form.setValues({ [variableName]: value })}
|
onChange={(value) => onChange(value)}
|
||||||
keyKey={keyInputKey}
|
keyKey={keyInputKey}
|
||||||
keyValue={keyInputValue}
|
keyValue={keyInputValue}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
|
@ -3,7 +3,9 @@ import { useState } from 'react';
|
|||||||
import { Box } from '@chakra-ui/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/formidable/FormidableConfig';
|
||||||
|
|
||||||
|
import { Formidable } from '../formidable';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Components/FormSelectMultipleMultiple',
|
title: 'Components/FormSelectMultipleMultiple',
|
||||||
@ -16,23 +18,24 @@ type BasicFormData = {
|
|||||||
export const Default = () => {
|
export const Default = () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelectMultiple
|
<FormSelectMultiple
|
||||||
label="Simple Title"
|
label="Simple Title"
|
||||||
form={form}
|
name={'data'}
|
||||||
variableName={'data'}
|
|
||||||
keyInputValue="id"
|
keyInputValue="id"
|
||||||
options={[{ id: 111 }, { id: 222 }, { id: 333 }, { id: 123 }]}
|
options={[{ id: 111 }, { id: 222 }, { id: 333 }, { id: 123 }]}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChangeKeys = () => {
|
export const ChangeKeys = () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelectMultiple
|
<FormSelectMultiple
|
||||||
label="Simple Title for (ChangeKeys)"
|
label="Simple Title for (ChangeKeys)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
keyInputKey="key"
|
keyInputKey="key"
|
||||||
keyInputValue="plop"
|
keyInputValue="plop"
|
||||||
options={[
|
options={[
|
||||||
@ -41,21 +44,23 @@ export const ChangeKeys = () => {
|
|||||||
{ key: 333, plop: 'third item' },
|
{ key: 333, plop: 'third item' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const ChangeName = () => {
|
export const ChangeName = () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelectMultiple
|
<FormSelectMultiple
|
||||||
label="Simple Title for (ChangeName)"
|
label="Simple Title for (ChangeName)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
options={[
|
options={[
|
||||||
{ id: 111, name: 'first Item' },
|
{ id: 111, name: 'first Item' },
|
||||||
{ id: 222, name: 'Second Item' },
|
{ id: 222, name: 'Second Item' },
|
||||||
{ id: 333, name: 'third item' },
|
{ id: 333, name: 'third item' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const AddableItem = () => {
|
export const AddableItem = () => {
|
||||||
@ -66,10 +71,10 @@ export const AddableItem = () => {
|
|||||||
{ id: 333, name: 'third item' },
|
{ id: 333, name: 'third item' },
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<FormSelectMultiple
|
<FormSelectMultiple
|
||||||
label="Simple Title for (ChangeName)"
|
label="Simple Title for (ChangeName)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
addNewItem={(data: string) => {
|
addNewItem={(data: string) => {
|
||||||
return new Promise((resolve, _rejects) => {
|
return new Promise((resolve, _rejects) => {
|
||||||
let upperId = 0;
|
let upperId = 0;
|
||||||
@ -87,6 +92,7 @@ export const AddableItem = () => {
|
|||||||
}}
|
}}
|
||||||
options={data}
|
options={data}
|
||||||
/>
|
/>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -94,11 +100,11 @@ export const DarkBackground = {
|
|||||||
render: () => {
|
render: () => {
|
||||||
const form = useFormidable<BasicFormData>({});
|
const form = useFormidable<BasicFormData>({});
|
||||||
return (
|
return (
|
||||||
|
<Formidable.From form={form}>
|
||||||
<Box p="4" color="white" bg="gray.800">
|
<Box p="4" color="white" bg="gray.800">
|
||||||
<FormSelectMultiple
|
<FormSelectMultiple
|
||||||
label="Simple Title for (DarkBackground)"
|
label="Simple Title for (DarkBackground)"
|
||||||
form={form}
|
name="data"
|
||||||
variableName={'data'}
|
|
||||||
options={[
|
options={[
|
||||||
{ id: 111, name: 'first Item' },
|
{ id: 111, name: 'first Item' },
|
||||||
{ id: 222, name: 'Second Item' },
|
{ id: 222, name: 'Second Item' },
|
||||||
@ -106,6 +112,7 @@ export const DarkBackground = {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Formidable.From>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
|
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
import { FormGroup } from '@/components/form/FormGroup';
|
||||||
import { UseFormidableReturn } from '@/components/form/Formidable';
|
|
||||||
import { SelectMultiple } from '@/components/select/SelectMultiple';
|
import { SelectMultiple } from '@/components/select/SelectMultiple';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useFormidableContext,
|
||||||
|
useFormidableContextElement,
|
||||||
|
} from '../formidable';
|
||||||
|
|
||||||
export type FormSelectMultipleProps = {
|
export type FormSelectMultipleProps = {
|
||||||
// Generic Form input
|
|
||||||
form: UseFormidableReturn;
|
|
||||||
// Form: Name of the variable
|
// Form: Name of the variable
|
||||||
variableName: string;
|
name: string;
|
||||||
// Forward object reference
|
// Forward object reference
|
||||||
ref?: RefObject<any>;
|
ref?: RefObject<any>;
|
||||||
// Form: Label of the input
|
// Form: Label of the input
|
||||||
@ -28,8 +30,7 @@ export type FormSelectMultipleProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FormSelectMultiple = ({
|
export const FormSelectMultiple = ({
|
||||||
form,
|
name,
|
||||||
variableName,
|
|
||||||
ref,
|
ref,
|
||||||
placeholder,
|
placeholder,
|
||||||
options,
|
options,
|
||||||
@ -38,23 +39,25 @@ export const FormSelectMultiple = ({
|
|||||||
addNewItem,
|
addNewItem,
|
||||||
...rest
|
...rest
|
||||||
}: FormSelectMultipleProps) => {
|
}: FormSelectMultipleProps) => {
|
||||||
|
const { form, value, isModify, onChange, onRestore } =
|
||||||
|
useFormidableContextElement(name);
|
||||||
// if set add capability to add the search item
|
// if set add capability to add the search item
|
||||||
const onCreate = !addNewItem
|
const onCreate = !addNewItem
|
||||||
? undefined
|
? undefined
|
||||||
: (data: string) => {
|
: (data: string) => {
|
||||||
addNewItem(data).then((data: object) => form.setValues({ [variableName]: [...(form.values[variableName] ?? []), data[keyInputKey]] }));
|
addNewItem(data).then((data: object) =>
|
||||||
|
form.setValues({
|
||||||
|
[name]: [...(form.values[name] ?? []), data[keyInputKey]],
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup name={name} {...rest}>
|
||||||
isModify={form.isModify[variableName]}
|
|
||||||
onRestore={() => form.restoreValue({ [variableName]: true })}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<SelectMultiple
|
<SelectMultiple
|
||||||
//ref={ref}
|
//ref={ref}
|
||||||
values={form.values[variableName]}
|
values={value}
|
||||||
options={options}
|
options={options}
|
||||||
onChange={(value) => form.setValues({ [variableName]: value })}
|
onChange={(value) => onChange(value)}
|
||||||
keyKey={keyInputKey}
|
keyKey={keyInputKey}
|
||||||
keyValue={keyInputValue}
|
keyValue={keyInputValue}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
|
@ -3,11 +3,11 @@ import { RefObject } from 'react';
|
|||||||
import { Textarea } from '@chakra-ui/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 { useFormidableContextElement } from '../formidable';
|
||||||
|
|
||||||
export type FormTextareaProps = {
|
export type FormTextareaProps = {
|
||||||
form: UseFormidableReturn;
|
name: string;
|
||||||
variableName: string;
|
|
||||||
ref?: RefObject<any>;
|
ref?: RefObject<any>;
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@ -15,22 +15,20 @@ export type FormTextareaProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FormTextarea = ({
|
export const FormTextarea = ({
|
||||||
form,
|
name,
|
||||||
variableName,
|
|
||||||
ref,
|
ref,
|
||||||
placeholder,
|
placeholder,
|
||||||
...rest
|
...rest
|
||||||
}: FormTextareaProps) => {
|
}: FormTextareaProps) => {
|
||||||
|
const { value, onChange } = useFormidableContextElement(name);
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup name={name} {...rest}>
|
||||||
isModify={form.isModify[variableName]}
|
|
||||||
onRestore={() => form.restoreValue({ [variableName]: true })}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
|
name={name}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
value={form.values[variableName]}
|
autoComplete={name}
|
||||||
onChange={(e) => form.setValues({ [variableName]: e.target.value })}
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
@ -1,66 +1,44 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { isArray, isNullOrUndefined, isObject } from '@/utils/validator';
|
import { isNullOrUndefined } from '@/utils/validator';
|
||||||
|
|
||||||
const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => {
|
import { getDifferences, hasAnyTrue } from './utils';
|
||||||
for (const key in obj) {
|
|
||||||
if (obj.hasOwnProperty(key) && obj[key] === true) {
|
export type FormidableDeltaConfig<TYPE> = {
|
||||||
return true;
|
omit?: string[]; // (keyof TYPE)[];
|
||||||
}
|
only?: string[]; // (keyof TYPE)[];
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDifferences(
|
export type FormidableConfig = {
|
||||||
obj1: object,
|
enableReset?: boolean;
|
||||||
obj2: object
|
enableModifyNotification?: boolean;
|
||||||
): { [key: string]: boolean } {
|
singleLineForm?: boolean;
|
||||||
// Create an empty object to store the differences
|
};
|
||||||
const result: { [key: string]: boolean } = {};
|
const initialFormConfig: Required<FormidableConfig> = {
|
||||||
// Recursive function to compare values
|
enableReset: true,
|
||||||
function compareValues(value1: any, value2: any): boolean {
|
enableModifyNotification: true,
|
||||||
// If both values are objects, compare their properties recursively
|
singleLineForm: false,
|
||||||
if (isObject(value1) && isObject(value2)) {
|
};
|
||||||
return hasAnyTrue(getDifferences(value1, value2));
|
|
||||||
}
|
|
||||||
// If both values are arrays, compare their elements
|
|
||||||
if (isArray(value1) && isArray(value2)) {
|
|
||||||
//console.log(`Check is array: ${JSON.stringify(value1)} =?= ${JSON.stringify(value2)}`);
|
|
||||||
if (value1.length !== value2.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < value1.length; i++) {
|
|
||||||
if (compareValues(value1[i], value2[i])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Otherwise, compare the values directly
|
|
||||||
//console.log(`compare : ${value1} =?= ${value2}`);
|
|
||||||
return value1 !== value2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all keys from both objects
|
|
||||||
const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
|
|
||||||
|
|
||||||
// Iterate over all keys
|
|
||||||
for (const key of allKeys) {
|
|
||||||
if (compareValues(obj1[key], obj2[key])) {
|
|
||||||
result[key] = true;
|
|
||||||
} else {
|
|
||||||
result[key] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useFormidable = <TYPE extends object = object>({
|
export const useFormidable = <TYPE extends object = object>({
|
||||||
initialValues = {} as TYPE,
|
initialValues = {} as TYPE,
|
||||||
|
configuration: inputConfiguration = initialFormConfig,
|
||||||
|
deltaConfig,
|
||||||
|
resolver = (_data: TYPE) => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
}: {
|
}: {
|
||||||
initialValues?: TYPE;
|
initialValues?: TYPE;
|
||||||
|
configuration?: FormidableConfig;
|
||||||
|
deltaConfig?: FormidableDeltaConfig<TYPE>;
|
||||||
|
resolver?: (data: any) => Record<string, string>;
|
||||||
}) => {
|
}) => {
|
||||||
|
const configuration: Required<FormidableConfig> = {
|
||||||
|
...initialFormConfig,
|
||||||
|
...inputConfiguration,
|
||||||
|
};
|
||||||
const [values, setValues] = useState<TYPE>({ ...initialValues } as TYPE);
|
const [values, setValues] = useState<TYPE>({ ...initialValues } as TYPE);
|
||||||
|
const [errors, setErrors] = useState<object>({});
|
||||||
const [initialData, setInitialData] = useState<TYPE>(initialValues);
|
const [initialData, setInitialData] = useState<TYPE>(initialValues);
|
||||||
const [isModify, setIsModify] = useState<{ [key: string]: boolean }>({});
|
const [isModify, setIsModify] = useState<{ [key: string]: boolean }>({});
|
||||||
const [isFormModified, setIsFormModified] = useState<boolean>(false);
|
const [isFormModified, setIsFormModified] = useState<boolean>(false);
|
||||||
@ -97,10 +75,11 @@ export const useFormidable = <TYPE extends object = object>({
|
|||||||
const ret = getDifferences(initialData, newValues);
|
const ret = getDifferences(initialData, newValues);
|
||||||
setIsModify(ret);
|
setIsModify(ret);
|
||||||
setIsFormModified(hasAnyTrue(ret));
|
setIsFormModified(hasAnyTrue(ret));
|
||||||
|
setErrors(resolver(newValues));
|
||||||
return newValues;
|
return newValues;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setValues, initialData]
|
[setValues, initialData, setErrors, setIsFormModified, setIsModify]
|
||||||
);
|
);
|
||||||
const restoreValue = useCallback(
|
const restoreValue = useCallback(
|
||||||
(data: object) => {
|
(data: object) => {
|
||||||
@ -133,7 +112,7 @@ export const useFormidable = <TYPE extends object = object>({
|
|||||||
[setValues, initialData, setIsFormModified, setIsModify]
|
[setValues, initialData, setIsFormModified, setIsModify]
|
||||||
);
|
);
|
||||||
const getDeltaData = useCallback(
|
const getDeltaData = useCallback(
|
||||||
({ omit = [], only }: { omit?: string[]; only?: string[] }) => {
|
({ omit = [], only }: FormidableDeltaConfig<TYPE> = {}) => {
|
||||||
const out = {};
|
const out = {};
|
||||||
Object.keys(isModify).forEach((key) => {
|
Object.keys(isModify).forEach((key) => {
|
||||||
if (omit.includes(key) || (only && !only.includes(key))) {
|
if (omit.includes(key) || (only && !only.includes(key))) {
|
||||||
@ -161,6 +140,9 @@ export const useFormidable = <TYPE extends object = object>({
|
|||||||
restoreValue,
|
restoreValue,
|
||||||
setValues: setValuesExternal,
|
setValues: setValuesExternal,
|
||||||
values,
|
values,
|
||||||
|
errors,
|
||||||
|
configuration,
|
||||||
|
deltaConfig,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
92
front/src/components/formidable/FormidableContext.tsx
Normal file
92
front/src/components/formidable/FormidableContext.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import {
|
||||||
|
ReactNode,
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { UseFormidableReturn } from './FormidableConfig';
|
||||||
|
|
||||||
|
export type FromContextProps = {
|
||||||
|
form: UseFormidableReturn;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formContext = createContext<FromContextProps>({
|
||||||
|
form: {
|
||||||
|
getDeltaData: ({}: { omit?: string[]; only?: string[] }) => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
isFormModified: false,
|
||||||
|
isModify: {},
|
||||||
|
restoreValues: () => {},
|
||||||
|
restoreValue: (_data: object) => {},
|
||||||
|
setValues: (_data: object) => {},
|
||||||
|
values: {},
|
||||||
|
errors: {},
|
||||||
|
configuration: {
|
||||||
|
enableReset: false,
|
||||||
|
enableModifyNotification: false,
|
||||||
|
singleLineForm: false,
|
||||||
|
},
|
||||||
|
deltaConfig: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useFormidableContext = () => {
|
||||||
|
const context = useContext(formContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useFormContext must be used within a FormProvider');
|
||||||
|
}
|
||||||
|
if (!context.form) {
|
||||||
|
throw new Error('useFormContext without defining a From');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
export const useFormidableContextElement = (name: string) => {
|
||||||
|
const { form } = useFormidableContext();
|
||||||
|
if (name === undefined) {
|
||||||
|
console.error(
|
||||||
|
"Can not request useFormidableContextElement with empty 'name'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const onChange = useCallback(
|
||||||
|
(value) => {
|
||||||
|
console.log(`new values: ${name}=>${value}`);
|
||||||
|
form.setValues({ [name]: value });
|
||||||
|
},
|
||||||
|
[name, form, form.setValues]
|
||||||
|
);
|
||||||
|
const onRestore = useCallback(() => {
|
||||||
|
console.log('Restore value : ' + name);
|
||||||
|
form.restoreValue({ [name]: true });
|
||||||
|
}, [name, form, form.restoreValue]);
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
value: form.values[name] || '',
|
||||||
|
error: form.errors[name],
|
||||||
|
isModify: form.isModify[name],
|
||||||
|
onChange,
|
||||||
|
onRestore,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormidableContextProps = {
|
||||||
|
form: UseFormidableReturn;
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormidableContext = ({
|
||||||
|
form,
|
||||||
|
children,
|
||||||
|
}: FormidableContextProps) => {
|
||||||
|
const memoContext = useMemo(
|
||||||
|
() => ({
|
||||||
|
form,
|
||||||
|
}),
|
||||||
|
[form]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<formContext.Provider value={memoContext}>{children}</formContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
43
front/src/components/formidable/FormidableForm.tsx
Normal file
43
front/src/components/formidable/FormidableForm.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { useFormidable } from './FormidableConfig';
|
||||||
|
import { FormidableContext } from './FormidableContext';
|
||||||
|
|
||||||
|
export interface FormidableFormProps<TYPE extends object = object> {
|
||||||
|
form: ReturnType<typeof useFormidable<TYPE>>;
|
||||||
|
children: ReactNode;
|
||||||
|
onSubmit?: (data: TYPE) => void;
|
||||||
|
onSubmitDelta?: (data: Partial<TYPE>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormidableForm = <TYPE extends object = object>({
|
||||||
|
onSubmit,
|
||||||
|
onSubmitDelta,
|
||||||
|
form,
|
||||||
|
children,
|
||||||
|
}: FormidableFormProps<TYPE>) => {
|
||||||
|
const handleSubmit = (event: React.FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const hasErrors = false; //Object.values(errors).some((err) => err);
|
||||||
|
if (!hasErrors) {
|
||||||
|
console.log(
|
||||||
|
`request From submit !!! ${JSON.stringify(form.values, null, 2)}`
|
||||||
|
);
|
||||||
|
if (onSubmit) {
|
||||||
|
onSubmit(form.values);
|
||||||
|
}
|
||||||
|
if (onSubmitDelta) {
|
||||||
|
onSubmitDelta(form.getDeltaData(form.deltaConfig));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<FormidableContext form={form}>
|
||||||
|
<Box as="form" onSubmit={handleSubmit}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</FormidableContext>
|
||||||
|
);
|
||||||
|
};
|
8
front/src/components/formidable/Fromidable.tsx
Normal file
8
front/src/components/formidable/Fromidable.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export {
|
||||||
|
type FormidableConfig as config,
|
||||||
|
useFormidable,
|
||||||
|
} from './FormidableConfig';
|
||||||
|
export {
|
||||||
|
FormidableForm as From,
|
||||||
|
type FormidableFormProps as FormProps,
|
||||||
|
} from './FormidableForm';
|
7
front/src/components/formidable/index.ts
Normal file
7
front/src/components/formidable/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export * as Formidable from './Fromidable';
|
||||||
|
export {
|
||||||
|
useFormidableContext,
|
||||||
|
useFormidableContextElement,
|
||||||
|
} from './FormidableContext';
|
||||||
|
export { useFormidable } from './FormidableConfig';
|
||||||
|
export { zodResolver } from './utils';
|
87
front/src/components/formidable/utils.ts
Normal file
87
front/src/components/formidable/utils.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { ZodError } from 'zod';
|
||||||
|
|
||||||
|
import { isArray, isNullOrUndefined, isObject } from '@/utils/validator';
|
||||||
|
|
||||||
|
export const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => {
|
||||||
|
for (const key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key) && obj[key] === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getDifferences(
|
||||||
|
obj1: object,
|
||||||
|
obj2: object
|
||||||
|
): { [key: string]: boolean } {
|
||||||
|
// Create an empty object to store the differences
|
||||||
|
const result: { [key: string]: boolean } = {};
|
||||||
|
// Recursive function to compare values
|
||||||
|
function compareValues(value1: any, value2: any): boolean {
|
||||||
|
// If both values are objects, compare their properties recursively
|
||||||
|
if (isObject(value1) && isObject(value2)) {
|
||||||
|
return hasAnyTrue(getDifferences(value1, value2));
|
||||||
|
}
|
||||||
|
// If both values are arrays, compare their elements
|
||||||
|
if (isArray(value1) && isArray(value2)) {
|
||||||
|
//console.log(`Check is array: ${JSON.stringify(value1)} =?= ${JSON.stringify(value2)}`);
|
||||||
|
if (value1.length !== value2.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < value1.length; i++) {
|
||||||
|
if (compareValues(value1[i], value2[i])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Otherwise, compare the values directly
|
||||||
|
//console.log(`compare : ${value1} =?= ${value2}`);
|
||||||
|
return value1 !== value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all keys from both objects
|
||||||
|
const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
|
||||||
|
|
||||||
|
// Iterate over all keys
|
||||||
|
for (const key of allKeys) {
|
||||||
|
if (compareValues(obj1[key], obj2[key])) {
|
||||||
|
result[key] = true;
|
||||||
|
} else {
|
||||||
|
result[key] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const zodResolver = (zodModel) => {
|
||||||
|
return (data: any) => {
|
||||||
|
try {
|
||||||
|
console.log(`check resolver of: ${JSON.stringify(data, null, 2)}`);
|
||||||
|
zodModel.parse(data);
|
||||||
|
return {};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
console.log(
|
||||||
|
`catch error with resolver: ${JSON.stringify(error, null, 2)}`
|
||||||
|
);
|
||||||
|
const formattedErrors = error.issues.reduce(
|
||||||
|
(acc, issue) => {
|
||||||
|
if (issue.path.length > 0) {
|
||||||
|
acc[issue.path[0]] = issue.message;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
console.log(`get errors: ${JSON.stringify(formattedErrors, null, 2)}`);
|
||||||
|
return formattedErrors;
|
||||||
|
}
|
||||||
|
// prevent zod error
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@ -23,7 +23,7 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => {
|
|||||||
data={dataGender?.covers}
|
data={dataGender?.covers}
|
||||||
size="100"
|
size="100"
|
||||||
height="full"
|
height="full"
|
||||||
iconEmpty={LuDisc3}
|
iconEmpty={<LuDisc3 />}
|
||||||
/>
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
@ -35,24 +35,24 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => {
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
as="span"
|
as="span"
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="20px"
|
fontSize="20px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={[1, 2]}
|
//TODO: noOfLines={[1, 2]}
|
||||||
>
|
>
|
||||||
{dataGender?.name}
|
{dataGender?.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
as="span"
|
as="span"
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="15px"
|
fontSize="15px"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
//TODO: noOfLines={1}
|
||||||
>
|
>
|
||||||
{countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'}
|
{countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -1,18 +1,6 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import { Flex, Text, useDisclosure } from '@chakra-ui/react';
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
Text,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
import {
|
||||||
MdAdminPanelSettings,
|
MdAdminPanelSettings,
|
||||||
MdDeleteForever,
|
MdDeleteForever,
|
||||||
@ -21,18 +9,27 @@ import {
|
|||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { Album, AlbumResource } from '@/back-api';
|
import { AlbumResource, AlbumWrite } from '@/back-api';
|
||||||
import { FormCovers } from '@/components/form/FormCovers';
|
import { FormCovers } from '@/components/form/FormCovers';
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
import { FormGroupShow } from '@/components/form/FormGroup';
|
||||||
import { FormInput } from '@/components/form/FormInput';
|
import { FormInput } from '@/components/form/FormInput';
|
||||||
import { FormTextarea } from '@/components/form/FormTextarea';
|
import { FormTextarea } from '@/components/form/FormTextarea';
|
||||||
import { useFormidable } from '@/components/form/Formidable';
|
|
||||||
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
||||||
|
import {
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogRoot,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import { useAlbumService, useSpecificAlbum } from '@/service/Album';
|
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 { Formidable, useFormidable } from '../formidable';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
|
||||||
export type AlbumEditPopUpProps = {};
|
export type AlbumEditPopUpProps = {};
|
||||||
|
|
||||||
export const AlbumEditPopUp = ({}: AlbumEditPopUpProps) => {
|
export const AlbumEditPopUp = ({}: AlbumEditPopUpProps) => {
|
||||||
@ -65,21 +62,21 @@ 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<AlbumWrite>({
|
||||||
initialValues: dataAlbum,
|
initialValues: dataAlbum,
|
||||||
|
deltaConfig: { omit: ['covers'] },
|
||||||
});
|
});
|
||||||
const onSave = async () => {
|
const onSave = async (deltaData: AlbumWrite) => {
|
||||||
if (isNullOrUndefined(albumIdInt)) {
|
if (isNullOrUndefined(albumIdInt)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dataThatNeedToBeUpdated = form.getDeltaData({ omit: ['covers'] });
|
console.log(`onSave = ${JSON.stringify(deltaData, null, 2)}`);
|
||||||
console.log(`onSave = ${JSON.stringify(dataThatNeedToBeUpdated, null, 2)}`);
|
|
||||||
store.update(
|
store.update(
|
||||||
AlbumResource.patch({
|
AlbumResource.patch({
|
||||||
restConfig: session.getRestConfig(),
|
restConfig: session.getRestConfig(),
|
||||||
data: dataThatNeedToBeUpdated,
|
data: deltaData,
|
||||||
params: {
|
params: {
|
||||||
id: albumIdInt,
|
id: albumIdInt,
|
||||||
},
|
},
|
||||||
@ -141,43 +138,44 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Modal
|
<DialogRoot
|
||||||
initialFocusRef={initialRef}
|
//initialFocusRef={initialRef}
|
||||||
finalFocusRef={finalRef}
|
//finalFocusRef={finalRef}
|
||||||
closeOnOverlayClick={false}
|
//closeOnOverlayClick={false}
|
||||||
onClose={onClose}
|
//onOpenChange={onClose}
|
||||||
isOpen={true}
|
open={true}
|
||||||
|
data-testid="album-edit-pop-up"
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
<DialogContent>
|
||||||
<ModalContent>
|
<Formidable.From form={form} onSubmitDelta={onSave}>
|
||||||
<ModalHeader>Edit Album</ModalHeader>
|
<DialogHeader>Edit Album</DialogHeader>
|
||||||
<ModalCloseButton ref={finalRef} />
|
{/* <DialogCloseButton ref={finalRef} /> */}
|
||||||
|
|
||||||
<ModalBody pb={6} gap="0px" paddingLeft="18px">
|
<DialogBody pb={6} gap="0px" paddingLeft="18px">
|
||||||
{admin && (
|
{admin && (
|
||||||
<>
|
<>
|
||||||
<FormGroup isRequired label="Id">
|
<FormGroupShow isRequired label="Id">
|
||||||
<Text>{dataAlbum?.id}</Text>
|
<Text>{dataAlbum?.id}</Text>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
{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)
|
||||||
on it.
|
depend on it.
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<FormGroup label="Action(s):">
|
<FormGroupShow label="Action(s):">
|
||||||
<Button
|
<Button
|
||||||
onClick={disclosure.onOpen}
|
onClick={disclosure.onOpen}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
variant="@danger"
|
colorPalette="@danger"
|
||||||
isDisabled={countTracksOfAnAlbum !== 0}
|
disabled={countTracksOfAnAlbum !== 0}
|
||||||
>
|
>
|
||||||
<MdDeleteForever /> Remove Media
|
<MdDeleteForever /> Remove Media
|
||||||
</Button>
|
</Button>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
<ConfirmPopUp
|
<ConfirmPopUp
|
||||||
disclosure={disclosure}
|
disclosure={disclosure}
|
||||||
title="Remove album"
|
title="Remove album"
|
||||||
@ -190,33 +188,23 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
|
|||||||
{!admin && (
|
{!admin && (
|
||||||
<>
|
<>
|
||||||
<FormInput
|
<FormInput
|
||||||
form={form}
|
name="name"
|
||||||
variableName="name"
|
|
||||||
isRequired
|
isRequired
|
||||||
label="Title"
|
label="Title"
|
||||||
ref={initialRef}
|
ref={initialRef}
|
||||||
/>
|
/>
|
||||||
<FormTextarea
|
<FormTextarea name="description" label="Description" />
|
||||||
form={form}
|
<FormInput name="publication" label="Publication" />
|
||||||
variableName="description"
|
|
||||||
label="Description"
|
|
||||||
/>
|
|
||||||
<FormInput
|
|
||||||
form={form}
|
|
||||||
variableName="publication"
|
|
||||||
label="Publication"
|
|
||||||
/>
|
|
||||||
<FormCovers
|
<FormCovers
|
||||||
form={form}
|
name="covers"
|
||||||
variableName="covers"
|
|
||||||
onFilesSelected={onFilesSelected}
|
onFilesSelected={onFilesSelected}
|
||||||
onUriSelected={onUriSelected}
|
onUriSelected={onUriSelected}
|
||||||
onRemove={onRemoveCover}
|
onRemove={onRemoveCover}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ModalBody>
|
</DialogBody>
|
||||||
<ModalFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setAdmin((value) => !value)}
|
onClick={() => setAdmin((value) => !value)}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
@ -234,13 +222,14 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{!admin && form.isFormModified && (
|
{!admin && form.isFormModified && (
|
||||||
<Button colorScheme="blue" mr={3} onClick={onSave}>
|
<Button colorScheme="blue" mr={3} type="submit">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
</ModalFooter>
|
</DialogFooter>
|
||||||
</ModalContent>
|
</Formidable.From>
|
||||||
</Modal>
|
</DialogContent>
|
||||||
|
</DialogRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,6 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import { Button, Flex, Text, useDisclosure } from '@chakra-ui/react';
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
Text,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
import {
|
||||||
MdAdminPanelSettings,
|
MdAdminPanelSettings,
|
||||||
MdDeleteForever,
|
MdDeleteForever,
|
||||||
@ -21,18 +9,26 @@ import {
|
|||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { Artist, ArtistResource } from '@/back-api';
|
import { ArtistResource, ArtistWrite } from '@/back-api';
|
||||||
import { FormCovers } from '@/components/form/FormCovers';
|
import { FormCovers } from '@/components/form/FormCovers';
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
|
||||||
import { FormInput } from '@/components/form/FormInput';
|
import { FormInput } from '@/components/form/FormInput';
|
||||||
import { FormTextarea } from '@/components/form/FormTextarea';
|
import { FormTextarea } from '@/components/form/FormTextarea';
|
||||||
import { useFormidable } from '@/components/form/Formidable';
|
|
||||||
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
||||||
|
import {
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogRoot,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import { useArtistService, useSpecificArtist } from '@/service/Artist';
|
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 { FormGroupShow } from '../form/FormGroup';
|
||||||
|
import { Formidable, useFormidable } from '../formidable';
|
||||||
|
|
||||||
export type ArtistEditPopUpProps = {};
|
export type ArtistEditPopUpProps = {};
|
||||||
|
|
||||||
export const ArtistEditPopUp = ({}: ArtistEditPopUpProps) => {
|
export const ArtistEditPopUp = ({}: ArtistEditPopUpProps) => {
|
||||||
@ -65,21 +61,21 @@ 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<ArtistWrite>({
|
||||||
initialValues: dataArtist,
|
initialValues: dataArtist,
|
||||||
|
deltaConfig: { omit: ['covers'] },
|
||||||
});
|
});
|
||||||
const onSave = async () => {
|
const onSave = async (dataDelta: ArtistWrite) => {
|
||||||
if (isNullOrUndefined(artistIdInt)) {
|
if (isNullOrUndefined(artistIdInt)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dataThatNeedToBeUpdated = form.getDeltaData({ omit: ['covers'] });
|
console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`);
|
||||||
console.log(`onSave = ${JSON.stringify(dataThatNeedToBeUpdated, null, 2)}`);
|
|
||||||
store.update(
|
store.update(
|
||||||
ArtistResource.patch({
|
ArtistResource.patch({
|
||||||
restConfig: session.getRestConfig(),
|
restConfig: session.getRestConfig(),
|
||||||
data: dataThatNeedToBeUpdated,
|
data: dataDelta,
|
||||||
params: {
|
params: {
|
||||||
id: artistIdInt,
|
id: artistIdInt,
|
||||||
},
|
},
|
||||||
@ -140,24 +136,26 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Modal
|
<DialogRoot
|
||||||
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 />
|
{/* <DialogOverlay /> */}
|
||||||
<ModalContent>
|
<DialogContent>
|
||||||
<ModalHeader>Edit Artist</ModalHeader>
|
<Formidable.From form={form} onSubmitDelta={onSave}>
|
||||||
<ModalCloseButton ref={finalRef} />
|
<DialogHeader>Edit Artist</DialogHeader>
|
||||||
|
{/* <DialogCloseButton ref={finalRef} /> */}
|
||||||
|
|
||||||
<ModalBody pb={6} gap="0px" paddingLeft="18px">
|
<DialogBody pb={6} gap="0px" paddingLeft="18px">
|
||||||
{admin && (
|
{admin && (
|
||||||
<>
|
<>
|
||||||
<FormGroup isRequired label="Id">
|
<FormGroupShow isRequired label="Id">
|
||||||
<Text>{dataArtist?.id}</Text>
|
<Text>{dataArtist?.id}</Text>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
{countTracksOnAnArtist !== 0 && (
|
{countTracksOnAnArtist !== 0 && (
|
||||||
<Flex paddingLeft="14px">
|
<Flex paddingLeft="14px">
|
||||||
<MdWarning color="red.600" />
|
<MdWarning color="red.600" />
|
||||||
@ -167,16 +165,16 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<FormGroup label="Action(s):">
|
<FormGroupShow label="Action(s):">
|
||||||
<Button
|
<Button
|
||||||
onClick={disclosure.onOpen}
|
onClick={disclosure.onOpen}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
variant="@danger"
|
colorPalette="@danger"
|
||||||
isDisabled={countTracksOnAnArtist !== 0}
|
disabled={countTracksOnAnArtist !== 0}
|
||||||
>
|
>
|
||||||
<MdDeleteForever /> Remove Media
|
<MdDeleteForever /> Remove Media
|
||||||
</Button>
|
</Button>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
<ConfirmPopUp
|
<ConfirmPopUp
|
||||||
disclosure={disclosure}
|
disclosure={disclosure}
|
||||||
title="Remove artist"
|
title="Remove artist"
|
||||||
@ -189,39 +187,30 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
|
|||||||
{!admin && (
|
{!admin && (
|
||||||
<>
|
<>
|
||||||
<FormInput
|
<FormInput
|
||||||
form={form}
|
name="name"
|
||||||
variableName="name"
|
|
||||||
isRequired
|
isRequired
|
||||||
label="Artist name"
|
label="Artist name"
|
||||||
ref={initialRef}
|
ref={initialRef}
|
||||||
/>
|
/>
|
||||||
<FormTextarea
|
<FormTextarea name="description" label="Description" />
|
||||||
form={form}
|
<FormInput name="firstName" label="First Name" />
|
||||||
variableName="description"
|
<FormInput name="surname" label="SurName" />
|
||||||
label="Description"
|
<FormInput name="birth" label="Birth date" />
|
||||||
/>
|
<FormInput name="death" label="Death date" />
|
||||||
<FormInput
|
|
||||||
form={form}
|
|
||||||
variableName="firstName"
|
|
||||||
label="First Name"
|
|
||||||
/>
|
|
||||||
<FormInput form={form} variableName="surname" label="SurName" />
|
|
||||||
<FormInput form={form} variableName="birth" label="Birth date" />
|
|
||||||
<FormInput form={form} variableName="death" label="Death date" />
|
|
||||||
<FormCovers
|
<FormCovers
|
||||||
form={form}
|
name="covers"
|
||||||
variableName="covers"
|
|
||||||
onFilesSelected={onFilesSelected}
|
onFilesSelected={onFilesSelected}
|
||||||
onUriSelected={onUriSelected}
|
onUriSelected={onUriSelected}
|
||||||
onRemove={onRemoveCover}
|
onRemove={onRemoveCover}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ModalBody>
|
</DialogBody>
|
||||||
<ModalFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setAdmin((value) => !value)}
|
onClick={() => setAdmin((value) => !value)}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
|
colorPalette={admin ? undefined : '@danger'}
|
||||||
>
|
>
|
||||||
{admin ? (
|
{admin ? (
|
||||||
<>
|
<>
|
||||||
@ -236,13 +225,14 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{!admin && form.isFormModified && (
|
{!admin && form.isFormModified && (
|
||||||
<Button colorScheme="blue" mr={3} onClick={onSave}>
|
<Button colorScheme="blue" mr={3} type="submit">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
</ModalFooter>
|
</DialogFooter>
|
||||||
</ModalContent>
|
</Formidable.From>
|
||||||
</Modal>
|
</DialogContent>
|
||||||
|
</DialogRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
import { Button, UseDisclosureReturn } from '@chakra-ui/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
DialogBody,
|
||||||
AlertDialogBody,
|
DialogContent,
|
||||||
AlertDialogContent,
|
DialogFooter,
|
||||||
AlertDialogFooter,
|
DialogHeader,
|
||||||
AlertDialogHeader,
|
DialogRoot,
|
||||||
AlertDialogOverlay,
|
} from '@/components/ui/dialog';
|
||||||
Button,
|
|
||||||
UseDisclosureReturn,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
|
|
||||||
export type ConfirmPopUpProps = {
|
export type ConfirmPopUpProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -30,31 +29,31 @@ export const ConfirmPopUp = ({
|
|||||||
onConfirm();
|
onConfirm();
|
||||||
disclosure.onClose();
|
disclosure.onClose();
|
||||||
};
|
};
|
||||||
const cancelRef = useRef(null);
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
return (
|
return (
|
||||||
<AlertDialog
|
<DialogRoot
|
||||||
isOpen={disclosure.isOpen}
|
role="alertdialog"
|
||||||
leastDestructiveRef={cancelRef}
|
open={disclosure.open}
|
||||||
onClose={disclosure.onClose}
|
//leastDestructiveRef={cancelRef}
|
||||||
|
onOpenChange={disclosure.onClose}
|
||||||
|
data-testid="confirm-pop-up"
|
||||||
>
|
>
|
||||||
<AlertDialogOverlay>
|
<DialogContent>
|
||||||
<AlertDialogContent>
|
<DialogHeader fontSize="lg" fontWeight="bold">
|
||||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
|
||||||
{title}
|
{title}
|
||||||
</AlertDialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<AlertDialogBody>{body}</AlertDialogBody>
|
<DialogBody>{body}</DialogBody>
|
||||||
|
|
||||||
<AlertDialogFooter>
|
<DialogFooter>
|
||||||
<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>
|
||||||
</AlertDialogFooter>
|
</DialogFooter>
|
||||||
</AlertDialogContent>
|
</DialogContent>
|
||||||
</AlertDialogOverlay>
|
</DialogRoot>
|
||||||
</AlertDialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,6 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import { Button, Flex, Text, useDisclosure } from '@chakra-ui/react';
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
Text,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import {
|
import {
|
||||||
MdAdminPanelSettings,
|
MdAdminPanelSettings,
|
||||||
MdDeleteForever,
|
MdDeleteForever,
|
||||||
@ -21,18 +9,26 @@ import {
|
|||||||
} 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 { GenderResource, GenderWrite } from '@/back-api';
|
||||||
import { FormCovers } from '@/components/form/FormCovers';
|
import { FormCovers } from '@/components/form/FormCovers';
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
|
||||||
import { FormInput } from '@/components/form/FormInput';
|
import { FormInput } from '@/components/form/FormInput';
|
||||||
import { FormTextarea } from '@/components/form/FormTextarea';
|
import { FormTextarea } from '@/components/form/FormTextarea';
|
||||||
import { useFormidable } from '@/components/form/Formidable';
|
|
||||||
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
||||||
|
import {
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogRoot,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import { useGenderService, useSpecificGender } from '@/service/Gender';
|
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 { FormGroupShow } from '../form/FormGroup';
|
||||||
|
import { Formidable, useFormidable } from '../formidable';
|
||||||
|
|
||||||
export type GenderEditPopUpProps = {};
|
export type GenderEditPopUpProps = {};
|
||||||
|
|
||||||
export const GenderEditPopUp = ({}: GenderEditPopUpProps) => {
|
export const GenderEditPopUp = ({}: GenderEditPopUpProps) => {
|
||||||
@ -65,21 +61,21 @@ 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<GenderWrite>({
|
||||||
initialValues: dataGender,
|
initialValues: dataGender,
|
||||||
|
deltaConfig: { omit: ['covers'] },
|
||||||
});
|
});
|
||||||
const onSave = async () => {
|
const onSave = async (dataDelta: GenderWrite) => {
|
||||||
if (isNullOrUndefined(genderIdInt)) {
|
if (isNullOrUndefined(genderIdInt)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dataThatNeedToBeUpdated = form.getDeltaData({ omit: ['covers'] });
|
console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`);
|
||||||
console.log(`onSave = ${JSON.stringify(dataThatNeedToBeUpdated, null, 2)}`);
|
|
||||||
store.update(
|
store.update(
|
||||||
GenderResource.patch({
|
GenderResource.patch({
|
||||||
restConfig: session.getRestConfig(),
|
restConfig: session.getRestConfig(),
|
||||||
data: dataThatNeedToBeUpdated,
|
data: dataDelta,
|
||||||
params: {
|
params: {
|
||||||
id: genderIdInt,
|
id: genderIdInt,
|
||||||
},
|
},
|
||||||
@ -139,43 +135,45 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Modal
|
<DialogRoot
|
||||||
initialFocusRef={initialRef}
|
//initialFocusRef={initialRef}
|
||||||
finalFocusRef={finalRef}
|
//finalFocusRef={finalRef}
|
||||||
closeOnOverlayClick={false}
|
//closeOnOverlayClick={false}
|
||||||
onClose={onClose}
|
onOpenChange={onClose}
|
||||||
isOpen={true}
|
open={true}
|
||||||
|
data-testid="gender-edit-pop-up"
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
{/* <DialogOverlay /> */}
|
||||||
<ModalContent>
|
<DialogContent>
|
||||||
<ModalHeader>Edit Gender</ModalHeader>
|
<Formidable.From form={form} onSubmitDelta={onSave}>
|
||||||
<ModalCloseButton ref={finalRef} />
|
<DialogHeader>Edit Gender</DialogHeader>
|
||||||
|
{/* <DialogCloseButton ref={finalRef} /> */}
|
||||||
|
|
||||||
<ModalBody pb={6} gap="0px" paddingLeft="18px">
|
<DialogBody pb={6} gap="0px" paddingLeft="18px">
|
||||||
{admin && (
|
{admin && (
|
||||||
<>
|
<>
|
||||||
<FormGroup isRequired label="Id">
|
<FormGroupShow isRequired label="Id">
|
||||||
<Text>{dataGender?.id}</Text>
|
<Text>{dataGender?.id}</Text>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
{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)
|
||||||
on it.
|
depend on it.
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<FormGroup label="Action(s):">
|
<FormGroupShow label="Action(s):">
|
||||||
<Button
|
<Button
|
||||||
onClick={disclosure.onOpen}
|
onClick={disclosure.onOpen}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
variant="@danger"
|
colorPalette="@danger"
|
||||||
isDisabled={countTracksOnAGender !== 0}
|
disabled={countTracksOnAGender !== 0}
|
||||||
>
|
>
|
||||||
<MdDeleteForever /> Remove gender
|
<MdDeleteForever /> Remove gender
|
||||||
</Button>
|
</Button>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
<ConfirmPopUp
|
<ConfirmPopUp
|
||||||
disclosure={disclosure}
|
disclosure={disclosure}
|
||||||
title="Remove gender"
|
title="Remove gender"
|
||||||
@ -188,28 +186,22 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => {
|
|||||||
{!admin && (
|
{!admin && (
|
||||||
<>
|
<>
|
||||||
<FormInput
|
<FormInput
|
||||||
form={form}
|
name="name"
|
||||||
variableName="name"
|
|
||||||
isRequired
|
isRequired
|
||||||
label="Gender name"
|
label="Gender name"
|
||||||
ref={initialRef}
|
ref={initialRef}
|
||||||
/>
|
/>
|
||||||
<FormTextarea
|
<FormTextarea name="description" label="Description" />
|
||||||
form={form}
|
|
||||||
variableName="description"
|
|
||||||
label="Description"
|
|
||||||
/>
|
|
||||||
<FormCovers
|
<FormCovers
|
||||||
form={form}
|
name="covers"
|
||||||
variableName="covers"
|
|
||||||
onFilesSelected={onFilesSelected}
|
onFilesSelected={onFilesSelected}
|
||||||
onUriSelected={onUriSelected}
|
onUriSelected={onUriSelected}
|
||||||
onRemove={onRemoveCover}
|
onRemove={onRemoveCover}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ModalBody>
|
</DialogBody>
|
||||||
<ModalFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setAdmin((value) => !value)}
|
onClick={() => setAdmin((value) => !value)}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
@ -227,13 +219,14 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{!admin && form.isFormModified && (
|
{!admin && form.isFormModified && (
|
||||||
<Button colorScheme="blue" mr={3} onClick={onSave}>
|
<Button colorScheme="blue" mr={3} type="submit">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
</ModalFooter>
|
</DialogFooter>
|
||||||
</ModalContent>
|
</Formidable.From>
|
||||||
</Modal>
|
</DialogContent>
|
||||||
|
</DialogRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,38 +1,14 @@
|
|||||||
import { ReactElement, useRef, useState } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
import { Button, Flex, Progress, Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
DialogBody,
|
||||||
Flex,
|
DialogContent,
|
||||||
Modal,
|
DialogFooter,
|
||||||
ModalBody,
|
DialogHeader,
|
||||||
ModalCloseButton,
|
DialogRoot,
|
||||||
ModalContent,
|
} from '@/components/ui/dialog';
|
||||||
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,22 +39,23 @@ 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
|
<DialogRoot
|
||||||
initialFocusRef={initialRef}
|
//initialFocusRef={initialRef}
|
||||||
finalFocusRef={finalRef}
|
//finalFocusRef={finalRef}
|
||||||
closeOnOverlayClick={false}
|
//closeOnOverlayClick={false}
|
||||||
onClose={onClose}
|
onOpenChange={onClose}
|
||||||
isOpen={true}
|
open={true}
|
||||||
|
data-testid="upload-progress-edit-pop-up"
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
{/* <DialogOverlay /> */}
|
||||||
<ModalContent>
|
<DialogContent>
|
||||||
<ModalHeader>{title}</ModalHeader>
|
<DialogHeader>{title}</DialogHeader>
|
||||||
<ModalCloseButton ref={finalRef} />
|
{/* <DialogCloseButton ref={finalRef} /> */}
|
||||||
|
|
||||||
<ModalBody pb={6} paddingLeft="18px">
|
<DialogBody pb={6} paddingLeft="18px">
|
||||||
<Flex direction="column" gap="10px">
|
<Flex direction="column" gap="10px">
|
||||||
{isFinished ? (
|
{isFinished ? (
|
||||||
<Text fontSize="20px" fontWeight="bold">
|
<Text fontSize="20px" fontWeight="bold">
|
||||||
@ -89,11 +66,11 @@ export const PopUpUploadProgress = ({
|
|||||||
[{index + 1}/{elements.length}] {elements[index]}
|
[{index + 1}/{elements.length}] {elements[index]}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Progress
|
<Progress.Root
|
||||||
colorScheme="green"
|
colorScheme="green"
|
||||||
hasStripe
|
striped
|
||||||
value={currentSize}
|
value={currentSize}
|
||||||
isAnimated
|
animated
|
||||||
max={totalSize}
|
max={totalSize}
|
||||||
height="24px"
|
height="24px"
|
||||||
/>
|
/>
|
||||||
@ -109,10 +86,10 @@ export const PopUpUploadProgress = ({
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalBody>
|
</DialogBody>
|
||||||
<ModalFooter>
|
<DialogFooter>
|
||||||
{isFinished ? (
|
{isFinished ? (
|
||||||
<Button onClick={onClose} variant="@success">
|
<Button onClick={onClose} colorPalette="green">
|
||||||
Ok
|
Ok
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
@ -120,8 +97,8 @@ export const PopUpUploadProgress = ({
|
|||||||
Abort
|
Abort
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ModalFooter>
|
</DialogFooter>
|
||||||
</ModalContent>
|
</DialogContent>
|
||||||
</Modal>
|
</DialogRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import { Button, Text, useDisclosure } from '@chakra-ui/react';
|
||||||
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';
|
||||||
|
|
||||||
import { Track, TrackResource } from '@/back-api';
|
import { TrackResource, TrackWrite } from '@/back-api';
|
||||||
import { FormGroup } from '@/components/form/FormGroup';
|
import { FormGroupShow } from '@/components/form/FormGroup';
|
||||||
import { FormInput } from '@/components/form/FormInput';
|
import { FormInput } from '@/components/form/FormInput';
|
||||||
import { FormNumber } from '@/components/form/FormNumber';
|
import { FormNumber } from '@/components/form/FormNumber';
|
||||||
import { FormSelect } from '@/components/form/FormSelect';
|
import { FormSelect } from '@/components/form/FormSelect';
|
||||||
import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
|
import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
|
||||||
import { FormTextarea } from '@/components/form/FormTextarea';
|
import { FormTextarea } from '@/components/form/FormTextarea';
|
||||||
import { useFormidable } from '@/components/form/Formidable';
|
|
||||||
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
|
||||||
|
import {
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogRoot,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import { useOrderedAlbums } from '@/service/Album';
|
import { useOrderedAlbums } from '@/service/Album';
|
||||||
import { useOrderedArtists } from '@/service/Artist';
|
import { useOrderedArtists } from '@/service/Artist';
|
||||||
import { useOrderedGenders } from '@/service/Gender';
|
import { useOrderedGenders } from '@/service/Gender';
|
||||||
@ -31,6 +26,8 @@ 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 { Formidable, useFormidable } from '../formidable';
|
||||||
|
|
||||||
export type TrackEditPopUpProps = {};
|
export type TrackEditPopUpProps = {};
|
||||||
|
|
||||||
export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
|
export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
|
||||||
@ -65,25 +62,21 @@ 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<TrackWrite>({
|
||||||
//onSubmit,
|
|
||||||
//onValuesChange,
|
|
||||||
initialValues: dataTrack,
|
initialValues: dataTrack,
|
||||||
//onValid: () => console.log('onValid'),
|
deltaConfig: { omit: ['covers'] },
|
||||||
//onInvalid: () => console.log('onInvalid'),
|
|
||||||
});
|
});
|
||||||
const onSave = async () => {
|
const onSave = async (dataDelta: TrackWrite) => {
|
||||||
if (isNullOrUndefined(trackIdInt)) {
|
if (isNullOrUndefined(trackIdInt)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dataThatNeedToBeUpdated = form.getDeltaData({ omit: ['covers'] });
|
console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`);
|
||||||
console.log(`onSave = ${JSON.stringify(dataThatNeedToBeUpdated, null, 2)}`);
|
|
||||||
store.update(
|
store.update(
|
||||||
TrackResource.patch({
|
TrackResource.patch({
|
||||||
restConfig: session.getRestConfig(),
|
restConfig: session.getRestConfig(),
|
||||||
data: dataThatNeedToBeUpdated,
|
data: dataDelta,
|
||||||
params: {
|
params: {
|
||||||
id: trackIdInt,
|
id: trackIdInt,
|
||||||
},
|
},
|
||||||
@ -91,36 +84,38 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Modal
|
<DialogRoot
|
||||||
initialFocusRef={initialRef}
|
//initialFocusRef={initialRef}
|
||||||
finalFocusRef={finalRef}
|
//finalFocusRef={finalRef}
|
||||||
closeOnOverlayClick={false}
|
//closeOnOverlayClick={false}
|
||||||
onClose={onClose}
|
onOpenChange={onClose}
|
||||||
isOpen={true}
|
open={true}
|
||||||
|
data-testid="track-edit-pop-up"
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
{/* <DialogOverlay /> */}
|
||||||
<ModalContent>
|
<DialogContent>
|
||||||
<ModalHeader>Edit Track</ModalHeader>
|
<Formidable.From form={form} onSubmitDelta={onSave}>
|
||||||
<ModalCloseButton ref={finalRef} />
|
<DialogHeader>Edit Track</DialogHeader>
|
||||||
|
{/* <DialogCloseButton ref={finalRef} /> */}
|
||||||
|
|
||||||
<ModalBody pb={6} gap="0px" paddingLeft="18px">
|
<DialogBody pb={6} gap="0px" paddingLeft="18px">
|
||||||
{admin && (
|
{admin && (
|
||||||
<>
|
<>
|
||||||
<FormGroup isRequired label="Id">
|
<FormGroupShow isRequired label="Id">
|
||||||
<Text>{dataTrack?.id}</Text>
|
<Text>{dataTrack?.id}</Text>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
<FormGroup label="Data Id">
|
<FormGroupShow label="Data Id">
|
||||||
<Text>{dataTrack?.dataId}</Text>
|
<Text>{dataTrack?.dataId}</Text>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
<FormGroup label="Action(s):">
|
<FormGroupShow label="Action(s):">
|
||||||
<Button
|
<Button
|
||||||
onClick={disclosure.onOpen}
|
onClick={disclosure.onOpen}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
variant="@danger"
|
colorPalette="@danger"
|
||||||
>
|
>
|
||||||
<MdDeleteForever /> Remove Media
|
<MdDeleteForever /> Remove Media
|
||||||
</Button>
|
</Button>
|
||||||
</FormGroup>
|
</FormGroupShow>
|
||||||
<ConfirmPopUp
|
<ConfirmPopUp
|
||||||
disclosure={disclosure}
|
disclosure={disclosure}
|
||||||
title="Remove track"
|
title="Remove track"
|
||||||
@ -133,48 +128,35 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
|
|||||||
{!admin && (
|
{!admin && (
|
||||||
<>
|
<>
|
||||||
<FormInput
|
<FormInput
|
||||||
form={form}
|
name="name"
|
||||||
variableName="name"
|
|
||||||
isRequired
|
isRequired
|
||||||
label="Title"
|
label="Title"
|
||||||
ref={initialRef}
|
ref={initialRef}
|
||||||
/>
|
/>
|
||||||
<FormTextarea
|
<FormTextarea name="description" label="Description" />
|
||||||
form={form}
|
|
||||||
variableName="description"
|
|
||||||
label="Description"
|
|
||||||
/>
|
|
||||||
<FormSelect
|
<FormSelect
|
||||||
form={form}
|
name="genderId"
|
||||||
variableName="genderId"
|
|
||||||
options={dataGenders}
|
options={dataGenders}
|
||||||
label="Gender"
|
label="Gender"
|
||||||
/>
|
/>
|
||||||
<FormSelectMultiple
|
<FormSelectMultiple
|
||||||
form={form}
|
name="artists"
|
||||||
variableName="artists"
|
|
||||||
options={dataArtist}
|
options={dataArtist}
|
||||||
label="Artist(s)"
|
label="Artist(s)"
|
||||||
/>
|
/>
|
||||||
<FormSelect
|
<FormSelect name="albumId" options={dataAlbums} label="Album" />
|
||||||
form={form}
|
|
||||||
variableName="albumId"
|
|
||||||
options={dataAlbums}
|
|
||||||
label="Album"
|
|
||||||
/>
|
|
||||||
<FormNumber
|
<FormNumber
|
||||||
form={form}
|
name="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>
|
</DialogBody>
|
||||||
<ModalFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setAdmin((value) => !value)}
|
onClick={() => setAdmin((value) => !value)}
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
@ -192,13 +174,14 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{!admin && form.isFormModified && (
|
{!admin && form.isFormModified && (
|
||||||
<Button colorScheme="blue" mr={3} onClick={onSave}>
|
<Button colorScheme="blue" mr={3} type="submit">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
</ModalFooter>
|
</DialogFooter>
|
||||||
</ModalContent>
|
</Formidable.From>
|
||||||
</Modal>
|
</DialogContent>
|
||||||
|
</DialogRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,6 @@
|
|||||||
import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import { Button, Flex, HStack, Input, Spinner, Tag } from '@chakra-ui/react';
|
||||||
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';
|
||||||
@ -111,22 +101,30 @@ export const SelectMultiple = ({
|
|||||||
return (
|
return (
|
||||||
<Flex direction="column" width="full" gap="0px">
|
<Flex direction="column" width="full" gap="0px">
|
||||||
{selectedOptions && (
|
{selectedOptions && (
|
||||||
<Wrap spacing="5px" justify="left" width="full" marginBottom="2px">
|
<HStack
|
||||||
{selectedOptions.map((data) => (
|
wrap="wrap"
|
||||||
<WrapItem key={data[keyKey]}>
|
gap="5px"
|
||||||
<Tag
|
justify="left"
|
||||||
size="md"
|
width="full"
|
||||||
key="md"
|
marginBottom="2px"
|
||||||
borderRadius="5px"
|
|
||||||
variant="solid"
|
|
||||||
backgroundColor="green.500"
|
|
||||||
>
|
>
|
||||||
<TagLabel>{data[keyValue] ?? `id=${data[keyKey]}`}</TagLabel>
|
{selectedOptions.map((data) => (
|
||||||
<TagCloseButton onClick={() => selectValue(data)} />
|
<Flex align="flex-start" key={data[keyKey]}>
|
||||||
</Tag>
|
<Tag.Root
|
||||||
</WrapItem>
|
size="xl"
|
||||||
|
borderRadius="5px"
|
||||||
|
variant="surface"
|
||||||
|
backgroundColor="green.800"
|
||||||
|
>
|
||||||
|
<Tag.Label>{data[keyValue] ?? `id=${data[keyKey]}`}</Tag.Label>
|
||||||
|
<Tag.CloseTrigger
|
||||||
|
boxSize="5"
|
||||||
|
onClick={() => selectValue(data)}
|
||||||
|
/>
|
||||||
|
</Tag.Root>
|
||||||
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</Wrap>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Flex>
|
<Flex>
|
||||||
@ -137,7 +135,13 @@ export const SelectMultiple = ({
|
|||||||
//onSubmit={onSubmit}
|
//onSubmit={onSubmit}
|
||||||
onFocus={() => setShowList(true)}
|
onFocus={() => setShowList(true)}
|
||||||
onBlur={() => setTimeout(() => setShowList(false), 200)}
|
onBlur={() => setTimeout(() => setShowList(false), 200)}
|
||||||
value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''}
|
value={
|
||||||
|
showList
|
||||||
|
? (currentSearch ?? '')
|
||||||
|
: hasSuggestion
|
||||||
|
? `suggest: ${currentSearch}`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
borderRadius="5px 0 0 5px"
|
borderRadius="5px 0 0 5px"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
@ -50,7 +50,9 @@ export const SelectSingle = ({
|
|||||||
onCreate ? suggestion : undefined
|
onCreate ? suggestion : undefined
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(`Update suggestion : ${onCreate} ${suggestion} ==> ${onCreate ? suggestion : undefined} .. ${onCreate && !isNullOrUndefined(suggestion) ? true : false}`);
|
console.log(
|
||||||
|
`Update suggestion : ${onCreate} ${suggestion} ==> ${onCreate ? suggestion : undefined} .. ${onCreate && !isNullOrUndefined(suggestion) ? true : false}`
|
||||||
|
);
|
||||||
setCurrentSearch(onCreate ? suggestion : undefined);
|
setCurrentSearch(onCreate ? suggestion : undefined);
|
||||||
setHasSuggestion(onCreate && !isNullOrUndefined(suggestion) ? true : false);
|
setHasSuggestion(onCreate && !isNullOrUndefined(suggestion) ? true : false);
|
||||||
}, [suggestion]);
|
}, [suggestion]);
|
||||||
@ -110,10 +112,13 @@ export const SelectSingle = ({
|
|||||||
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={
|
backgroundColor={
|
||||||
showList || !selectedOptions ? undefined : 'green.500'
|
showList || !selectedOptions ? undefined : 'green.800'
|
||||||
}
|
}
|
||||||
borderRadius="5px 0 0 5px"
|
borderRadius="5px 0 0 5px"
|
||||||
/>
|
/>
|
||||||
|
@ -23,9 +23,7 @@ export const DisplayTrack = ({
|
|||||||
data={track?.covers}
|
data={track?.covers}
|
||||||
size="50"
|
size="50"
|
||||||
height="full"
|
height="full"
|
||||||
iconEmpty={
|
iconEmpty={trackActive?.id === track.id ? <LuPlay /> : <LuMusic2 />}
|
||||||
trackActive?.id === track.id ? LuPlay : LuMusic2
|
|
||||||
}
|
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
@ -38,13 +36,13 @@ export const DisplayTrack = ({
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
as="span"
|
as="span"
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="20px"
|
fontSize="20px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={[1, 2]}
|
// TODO: noOfLines={[1, 2]}
|
||||||
marginY="auto"
|
marginY="auto"
|
||||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { Suspense } from 'react';
|
|
||||||
|
|
||||||
import { Flex, Text } from '@chakra-ui/react';
|
import { Flex, Text } from '@chakra-ui/react';
|
||||||
import { LuMusic2, LuPlay } from 'react-icons/lu';
|
import { LuMusic2, LuPlay } from 'react-icons/lu';
|
||||||
|
|
||||||
@ -26,14 +24,17 @@ 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="full"
|
||||||
|
height="full"
|
||||||
|
data-testid="display-track-full"
|
||||||
|
>
|
||||||
<Covers
|
<Covers
|
||||||
data={track?.covers}
|
data={track?.covers}
|
||||||
size="50"
|
size="60px"
|
||||||
marginY="auto"
|
marginY="auto"
|
||||||
iconEmpty={
|
iconEmpty={trackActive?.id === track.id ? <LuPlay /> : <LuMusic2 />}
|
||||||
trackActive?.id === track.id ? LuPlay : LuMusic2
|
|
||||||
}
|
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
@ -46,13 +47,13 @@ export const DisplayTrackFull = ({
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
as="span"
|
as="span"
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="20px"
|
fontSize="20px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
// TODO: noOfLines={1}
|
||||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||||
>
|
>
|
||||||
{track.name} {track.track && ` [${track.track}]`}
|
{track.name} {track.track && ` [${track.track}]`}
|
||||||
@ -60,53 +61,65 @@ export const DisplayTrackFull = ({
|
|||||||
{dataAlbum && (
|
{dataAlbum && (
|
||||||
<Text
|
<Text
|
||||||
as="span"
|
as="span"
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="15px"
|
fontSize="15px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
//noOfLines={1}
|
||||||
marginY="auto"
|
marginY="auto"
|
||||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||||
>
|
>
|
||||||
<Text as="span" fontWeight="normal">Album:</Text> {dataAlbum.name}
|
<Text as="span" fontWeight="normal">
|
||||||
|
Album:
|
||||||
|
</Text>{' '}
|
||||||
|
{dataAlbum.name}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{dataArtists && (
|
{dataArtists && (
|
||||||
<Text
|
<Text
|
||||||
as="span"
|
as="span"
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="15px"
|
fontSize="15px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
//noOfLines={1}
|
||||||
marginY="auto"
|
marginY="auto"
|
||||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||||
>
|
>
|
||||||
<Text as="span" fontWeight="normal">Artist(s):</Text> {dataArtists.map((data) => data.name).join(', ')}
|
<Text as="span" fontWeight="normal">
|
||||||
|
Artist(s):
|
||||||
|
</Text>{' '}
|
||||||
|
{dataArtists.map((data) => data.name).join(', ')}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{dataGender && (
|
{dataGender && (
|
||||||
<Text
|
<Text
|
||||||
as="span"
|
as="span"
|
||||||
align="left"
|
alignContent="left"
|
||||||
fontSize="15px"
|
fontSize="15px"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
marginRight="auto"
|
marginRight="auto"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
noOfLines={1}
|
//noOfLines={1}
|
||||||
marginY="auto"
|
marginY="auto"
|
||||||
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
color={trackActive?.id === track.id ? 'green.700' : undefined}
|
||||||
>
|
>
|
||||||
<Text as="span" fontWeight="normal">Gender:</Text> {dataGender.name}
|
<Text as="span" fontWeight="normal">
|
||||||
|
Gender:
|
||||||
|
</Text>{' '}
|
||||||
|
{dataGender.name}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<ContextMenu elements={contextMenu} />
|
<ContextMenu
|
||||||
|
elements={contextMenu}
|
||||||
|
data-testid="display-track-full_context-menu"
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
30
front/src/components/track/DisplayTrackFullId.tsx
Normal file
30
front/src/components/track/DisplayTrackFullId.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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 />;
|
||||||
|
}
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { Flex, Skeleton, SkeletonText } from '@chakra-ui/react';
|
import { Flex, Skeleton } from '@chakra-ui/react';
|
||||||
|
|
||||||
export const DisplayTrackSkeleton = () => {
|
export const DisplayTrackSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
@ -17,13 +17,13 @@ export const DisplayTrackSkeleton = () => {
|
|||||||
paddingLeft="5px"
|
paddingLeft="5px"
|
||||||
overflowX="hidden"
|
overflowX="hidden"
|
||||||
>
|
>
|
||||||
<SkeletonText
|
{/* <SkeletonText
|
||||||
skeletonHeight="20px"
|
skeletonHeight="20px"
|
||||||
noOfLines={1}
|
noOfLines={1}
|
||||||
spacing={0}
|
gap={0}
|
||||||
width="50%"
|
width="50%"
|
||||||
marginY="auto"
|
marginY="auto"
|
||||||
/>
|
/> */}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
75
front/src/components/ui/avatar.tsx
Normal file
75
front/src/components/ui/avatar.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import type { GroupProps, SlotRecipeProps } from '@chakra-ui/react';
|
||||||
|
import { Avatar as ChakraAvatar, Group } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>;
|
||||||
|
|
||||||
|
export interface AvatarProps extends ChakraAvatar.RootProps {
|
||||||
|
name?: string;
|
||||||
|
src?: string;
|
||||||
|
srcSet?: string;
|
||||||
|
loading?: ImageProps['loading'];
|
||||||
|
icon?: React.ReactElement;
|
||||||
|
fallback?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
|
||||||
|
function Avatar(props, ref) {
|
||||||
|
const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
|
||||||
|
props;
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Root ref={ref} {...rest}>
|
||||||
|
<AvatarFallback name={name} icon={icon}>
|
||||||
|
{fallback}
|
||||||
|
</AvatarFallback>
|
||||||
|
<ChakraAvatar.Image src={src} srcSet={srcSet} loading={loading} />
|
||||||
|
{children}
|
||||||
|
</ChakraAvatar.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
|
||||||
|
name?: string;
|
||||||
|
icon?: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
|
||||||
|
function AvatarFallback(props, ref) {
|
||||||
|
const { name, icon, children, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Fallback ref={ref} {...rest}>
|
||||||
|
{children}
|
||||||
|
{name != null && children == null && <>{getInitials(name)}</>}
|
||||||
|
{name == null && children == null && (
|
||||||
|
<ChakraAvatar.Icon asChild={!!icon}>{icon}</ChakraAvatar.Icon>
|
||||||
|
)}
|
||||||
|
</ChakraAvatar.Fallback>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function getInitials(name: string) {
|
||||||
|
const names = name.trim().split(' ');
|
||||||
|
const firstName = names[0] != null ? names[0] : '';
|
||||||
|
const lastName = names.length > 1 ? names[names.length - 1] : '';
|
||||||
|
return firstName && lastName
|
||||||
|
? `${firstName.charAt(0)}${lastName.charAt(0)}`
|
||||||
|
: firstName.charAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AvatarGroupProps extends GroupProps, SlotRecipeProps<'avatar'> {}
|
||||||
|
|
||||||
|
export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
|
||||||
|
function AvatarGroup(props, ref) {
|
||||||
|
const { size, variant, borderless, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
|
||||||
|
<Group gap="0" spaceX="-3" ref={ref} {...rest} />
|
||||||
|
</ChakraAvatar.PropsProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
41
front/src/components/ui/button.tsx
Normal file
41
front/src/components/ui/button.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import type { ButtonProps as ChakraButtonProps } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
AbsoluteCenter,
|
||||||
|
Button as ChakraButton,
|
||||||
|
Span,
|
||||||
|
Spinner,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
|
interface ButtonLoadingProps {
|
||||||
|
loading?: boolean;
|
||||||
|
loadingText?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
|
||||||
|
|
||||||
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
function Button(props, ref) {
|
||||||
|
const { loading, disabled, loadingText, children, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraButton disabled={loading || disabled} ref={ref} {...rest}>
|
||||||
|
{loading && !loadingText ? (
|
||||||
|
<>
|
||||||
|
<AbsoluteCenter display="inline-flex">
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
</AbsoluteCenter>
|
||||||
|
<Span opacity={0}>{children}</Span>
|
||||||
|
</>
|
||||||
|
) : loading && loadingText ? (
|
||||||
|
<>
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
{loadingText}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</ChakraButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
26
front/src/components/ui/checkbox.tsx
Normal file
26
front/src/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { Checkbox as ChakraCheckbox } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface CheckboxProps extends ChakraCheckbox.RootProps {
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
|
||||||
|
rootRef?: React.Ref<HTMLLabelElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
|
function Checkbox(props, ref) {
|
||||||
|
const { icon, children, inputProps, rootRef, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraCheckbox.Root ref={rootRef} {...rest}>
|
||||||
|
<ChakraCheckbox.HiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraCheckbox.Control>
|
||||||
|
{icon || <ChakraCheckbox.Indicator />}
|
||||||
|
</ChakraCheckbox.Control>
|
||||||
|
{children != null && (
|
||||||
|
<ChakraCheckbox.Label>{children}</ChakraCheckbox.Label>
|
||||||
|
)}
|
||||||
|
</ChakraCheckbox.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
18
front/src/components/ui/close-button.tsx
Normal file
18
front/src/components/ui/close-button.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import type { ButtonProps } from '@chakra-ui/react';
|
||||||
|
import { IconButton as ChakraIconButton } from '@chakra-ui/react';
|
||||||
|
import { LuX } from 'react-icons/lu';
|
||||||
|
|
||||||
|
export type CloseButtonProps = ButtonProps;
|
||||||
|
|
||||||
|
export const CloseButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
CloseButtonProps
|
||||||
|
>(function CloseButton(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraIconButton variant="ghost" aria-label="Close" ref={ref} {...props}>
|
||||||
|
{props.children ?? <LuX />}
|
||||||
|
</ChakraIconButton>
|
||||||
|
);
|
||||||
|
});
|
76
front/src/components/ui/color-mode.tsx
Normal file
76
front/src/components/ui/color-mode.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import type { IconButtonProps } from '@chakra-ui/react';
|
||||||
|
import { ClientOnly, IconButton, Skeleton } from '@chakra-ui/react';
|
||||||
|
import { ThemeProvider, useTheme } from 'next-themes';
|
||||||
|
import type { ThemeProviderProps } from 'next-themes';
|
||||||
|
import { LuMoon, LuSun } from 'react-icons/lu';
|
||||||
|
|
||||||
|
export interface ColorModeProviderProps extends ThemeProviderProps {}
|
||||||
|
|
||||||
|
export function ColorModeProvider(props: ColorModeProviderProps) {
|
||||||
|
return (
|
||||||
|
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ColorMode = 'light' | 'dark';
|
||||||
|
|
||||||
|
export interface UseColorModeReturn {
|
||||||
|
colorMode: ColorMode;
|
||||||
|
setColorMode: (colorMode: ColorMode) => void;
|
||||||
|
toggleColorMode: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColorMode(): UseColorModeReturn {
|
||||||
|
const { resolvedTheme, setTheme } = useTheme();
|
||||||
|
const toggleColorMode = () => {
|
||||||
|
setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
colorMode: resolvedTheme as ColorMode,
|
||||||
|
setColorMode: setTheme,
|
||||||
|
toggleColorMode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColorModeValue<T>(light: T, dark: T) {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
return colorMode === 'dark' ? dark : light;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorModeIcon() {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
return colorMode === 'dark' ? <LuMoon /> : <LuSun />;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColorModeButtonProps extends Omit<IconButtonProps, 'aria-label'> {}
|
||||||
|
|
||||||
|
export const ColorModeButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ColorModeButtonProps
|
||||||
|
>(function ColorModeButton(props, ref) {
|
||||||
|
const { toggleColorMode } = useColorMode();
|
||||||
|
return (
|
||||||
|
<ClientOnly fallback={<Skeleton boxSize="8" />}>
|
||||||
|
<IconButton
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
variant="ghost"
|
||||||
|
aria-label="Toggle color mode"
|
||||||
|
size="sm"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
css={{
|
||||||
|
_icon: {
|
||||||
|
width: '5',
|
||||||
|
height: '5',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ColorModeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</ClientOnly>
|
||||||
|
);
|
||||||
|
});
|
64
front/src/components/ui/dialog.tsx
Normal file
64
front/src/components/ui/dialog.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { Dialog as ChakraDialog, Portal } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { CloseButton } from './close-button';
|
||||||
|
|
||||||
|
interface DialogContentProps extends ChakraDialog.ContentProps {
|
||||||
|
portalled?: boolean;
|
||||||
|
portalRef?: React.RefObject<HTMLElement>;
|
||||||
|
backdrop?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DialogContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DialogContentProps
|
||||||
|
>(function DialogContent(props, ref) {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
portalled = true,
|
||||||
|
portalRef,
|
||||||
|
backdrop = true,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
{backdrop && <ChakraDialog.Backdrop />}
|
||||||
|
<ChakraDialog.Positioner>
|
||||||
|
<ChakraDialog.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDialog.Content>
|
||||||
|
</ChakraDialog.Positioner>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DialogCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDialog.CloseTriggerProps
|
||||||
|
>(function DialogCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDialog.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref}>
|
||||||
|
{props.children}
|
||||||
|
</CloseButton>
|
||||||
|
</ChakraDialog.CloseTrigger>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DialogRoot = ChakraDialog.Root;
|
||||||
|
export const DialogFooter = ChakraDialog.Footer;
|
||||||
|
export const DialogHeader = ChakraDialog.Header;
|
||||||
|
export const DialogBody = ChakraDialog.Body;
|
||||||
|
export const DialogBackdrop = ChakraDialog.Backdrop;
|
||||||
|
export const DialogTitle = ChakraDialog.Title;
|
||||||
|
export const DialogDescription = ChakraDialog.Description;
|
||||||
|
export const DialogTrigger = ChakraDialog.Trigger;
|
||||||
|
export const DialogActionTrigger = ChakraDialog.ActionTrigger;
|
54
front/src/components/ui/drawer.tsx
Normal file
54
front/src/components/ui/drawer.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { Drawer as ChakraDrawer, Portal } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { CloseButton } from './close-button';
|
||||||
|
|
||||||
|
interface DrawerContentProps extends ChakraDrawer.ContentProps {
|
||||||
|
portalled?: boolean;
|
||||||
|
portalRef?: React.RefObject<HTMLElement>;
|
||||||
|
offset?: ChakraDrawer.ContentProps['padding'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DrawerContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DrawerContentProps
|
||||||
|
>(function DrawerContent(props, ref) {
|
||||||
|
const { children, portalled = true, portalRef, offset, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraDrawer.Positioner padding={offset}>
|
||||||
|
<ChakraDrawer.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDrawer.Content>
|
||||||
|
</ChakraDrawer.Positioner>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DrawerCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDrawer.CloseTriggerProps
|
||||||
|
>(function DrawerCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDrawer.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref} />
|
||||||
|
</ChakraDrawer.CloseTrigger>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DrawerTrigger = ChakraDrawer.Trigger;
|
||||||
|
export const DrawerRoot = ChakraDrawer.Root;
|
||||||
|
export const DrawerFooter = ChakraDrawer.Footer;
|
||||||
|
export const DrawerHeader = ChakraDrawer.Header;
|
||||||
|
export const DrawerBody = ChakraDrawer.Body;
|
||||||
|
export const DrawerBackdrop = ChakraDrawer.Backdrop;
|
||||||
|
export const DrawerDescription = ChakraDrawer.Description;
|
||||||
|
export const DrawerTitle = ChakraDrawer.Title;
|
||||||
|
export const DrawerActionTrigger = ChakraDrawer.ActionTrigger;
|
34
front/src/components/ui/field.tsx
Normal file
34
front/src/components/ui/field.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { Field as ChakraField } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface FieldProps extends Omit<ChakraField.RootProps, 'label'> {
|
||||||
|
label?: React.ReactNode;
|
||||||
|
helperText?: React.ReactNode;
|
||||||
|
errorText?: React.ReactNode;
|
||||||
|
optionalText?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||||
|
function Field(props, ref) {
|
||||||
|
const { label, children, helperText, errorText, optionalText, ...rest } =
|
||||||
|
props;
|
||||||
|
return (
|
||||||
|
<ChakraField.Root ref={ref} {...rest}>
|
||||||
|
{label && (
|
||||||
|
<ChakraField.Label>
|
||||||
|
{label}
|
||||||
|
<ChakraField.RequiredIndicator fallback={optionalText} />
|
||||||
|
</ChakraField.Label>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
{helperText && (
|
||||||
|
<ChakraField.HelperText>{helperText}</ChakraField.HelperText>
|
||||||
|
)}
|
||||||
|
{errorText && (
|
||||||
|
<ChakraField.ErrorText>{errorText}</ChakraField.ErrorText>
|
||||||
|
)}
|
||||||
|
</ChakraField.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
54
front/src/components/ui/input-group.tsx
Normal file
54
front/src/components/ui/input-group.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import type { BoxProps, InputElementProps } from '@chakra-ui/react';
|
||||||
|
import { Group, InputElement } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface InputGroupProps extends BoxProps {
|
||||||
|
startElementProps?: InputElementProps;
|
||||||
|
endElementProps?: InputElementProps;
|
||||||
|
startElement?: React.ReactNode;
|
||||||
|
endElement?: React.ReactNode;
|
||||||
|
children: React.ReactElement<InputElementProps>;
|
||||||
|
startOffset?: InputElementProps['paddingStart'];
|
||||||
|
endOffset?: InputElementProps['paddingEnd'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||||
|
function InputGroup(props, ref) {
|
||||||
|
const {
|
||||||
|
startElement,
|
||||||
|
startElementProps,
|
||||||
|
endElement,
|
||||||
|
endElementProps,
|
||||||
|
children,
|
||||||
|
startOffset = '6px',
|
||||||
|
endOffset = '6px',
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const child =
|
||||||
|
React.Children.only<React.ReactElement<InputElementProps>>(children);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group ref={ref} {...rest}>
|
||||||
|
{startElement && (
|
||||||
|
<InputElement pointerEvents="none" {...startElementProps}>
|
||||||
|
{startElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
{React.cloneElement(child, {
|
||||||
|
...(startElement && {
|
||||||
|
ps: `calc(var(--input-height) - ${startOffset})`,
|
||||||
|
}),
|
||||||
|
...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
|
||||||
|
...children.props,
|
||||||
|
})}
|
||||||
|
{endElement && (
|
||||||
|
<InputElement placement="end" {...endElementProps}>
|
||||||
|
{endElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
111
front/src/components/ui/menu.tsx
Normal file
111
front/src/components/ui/menu.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { AbsoluteCenter, Menu as ChakraMenu, Portal } from '@chakra-ui/react';
|
||||||
|
import { LuCheck, LuChevronRight } from 'react-icons/lu';
|
||||||
|
|
||||||
|
interface MenuContentProps extends ChakraMenu.ContentProps {
|
||||||
|
portalled?: boolean;
|
||||||
|
portalRef?: React.RefObject<HTMLElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MenuContent = React.forwardRef<HTMLDivElement, MenuContentProps>(
|
||||||
|
function MenuContent(props, ref) {
|
||||||
|
const { portalled = true, portalRef, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraMenu.Positioner>
|
||||||
|
<ChakraMenu.Content ref={ref} {...rest} />
|
||||||
|
</ChakraMenu.Positioner>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MenuArrow = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraMenu.ArrowProps
|
||||||
|
>(function MenuArrow(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraMenu.Arrow ref={ref} {...props}>
|
||||||
|
<ChakraMenu.ArrowTip />
|
||||||
|
</ChakraMenu.Arrow>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MenuCheckboxItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraMenu.CheckboxItemProps
|
||||||
|
>(function MenuCheckboxItem(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraMenu.CheckboxItem ref={ref} {...props}>
|
||||||
|
<ChakraMenu.ItemIndicator hidden={false}>
|
||||||
|
<LuCheck />
|
||||||
|
</ChakraMenu.ItemIndicator>
|
||||||
|
{props.children}
|
||||||
|
</ChakraMenu.CheckboxItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MenuRadioItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraMenu.RadioItemProps
|
||||||
|
>(function MenuRadioItem(props, ref) {
|
||||||
|
const { children, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraMenu.RadioItem ps="8" ref={ref} {...rest}>
|
||||||
|
<AbsoluteCenter axis="horizontal" left="4" asChild>
|
||||||
|
<ChakraMenu.ItemIndicator>
|
||||||
|
<LuCheck />
|
||||||
|
</ChakraMenu.ItemIndicator>
|
||||||
|
</AbsoluteCenter>
|
||||||
|
<ChakraMenu.ItemText>{children}</ChakraMenu.ItemText>
|
||||||
|
</ChakraMenu.RadioItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MenuItemGroup = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraMenu.ItemGroupProps
|
||||||
|
>(function MenuItemGroup(props, ref) {
|
||||||
|
const { title, children, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraMenu.ItemGroup ref={ref} {...rest}>
|
||||||
|
{title && (
|
||||||
|
<ChakraMenu.ItemGroupLabel userSelect="none">
|
||||||
|
{title}
|
||||||
|
</ChakraMenu.ItemGroupLabel>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</ChakraMenu.ItemGroup>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface MenuTriggerItemProps extends ChakraMenu.ItemProps {
|
||||||
|
startIcon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MenuTriggerItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
MenuTriggerItemProps
|
||||||
|
>(function MenuTriggerItem(props, ref) {
|
||||||
|
const { startIcon, children, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraMenu.TriggerItem ref={ref} {...rest}>
|
||||||
|
{startIcon}
|
||||||
|
{children}
|
||||||
|
<LuChevronRight />
|
||||||
|
</ChakraMenu.TriggerItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup;
|
||||||
|
export const MenuContextTrigger = ChakraMenu.ContextTrigger;
|
||||||
|
export const MenuRoot = ChakraMenu.Root;
|
||||||
|
export const MenuSeparator = ChakraMenu.Separator;
|
||||||
|
|
||||||
|
export const MenuItem = ChakraMenu.Item;
|
||||||
|
export const MenuItemText = ChakraMenu.ItemText;
|
||||||
|
export const MenuItemCommand = ChakraMenu.ItemCommand;
|
||||||
|
export const MenuTrigger = ChakraMenu.Trigger;
|
25
front/src/components/ui/number-input.tsx
Normal file
25
front/src/components/ui/number-input.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { NumberInput as ChakraNumberInput } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface NumberInputProps extends ChakraNumberInput.RootProps {}
|
||||||
|
|
||||||
|
export const NumberInputRoot = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
NumberInputProps
|
||||||
|
>(function NumberInput(props, ref) {
|
||||||
|
const { children, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraNumberInput.Root ref={ref} variant="outline" {...rest}>
|
||||||
|
{children}
|
||||||
|
<ChakraNumberInput.Control>
|
||||||
|
<ChakraNumberInput.IncrementTrigger />
|
||||||
|
<ChakraNumberInput.DecrementTrigger />
|
||||||
|
</ChakraNumberInput.Control>
|
||||||
|
</ChakraNumberInput.Root>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NumberInputField = ChakraNumberInput.Input;
|
||||||
|
export const NumberInputScrubber = ChakraNumberInput.Scrubber;
|
||||||
|
export const NumberInputLabel = ChakraNumberInput.Label;
|
61
front/src/components/ui/popover.tsx
Normal file
61
front/src/components/ui/popover.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { Popover as ChakraPopover, Portal } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { CloseButton } from './close-button';
|
||||||
|
|
||||||
|
interface PopoverContentProps extends ChakraPopover.ContentProps {
|
||||||
|
portalled?: boolean;
|
||||||
|
portalRef?: React.RefObject<HTMLElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PopoverContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
PopoverContentProps
|
||||||
|
>(function PopoverContent(props, ref) {
|
||||||
|
const { portalled = true, portalRef, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraPopover.Positioner>
|
||||||
|
<ChakraPopover.Content ref={ref} {...rest} />
|
||||||
|
</ChakraPopover.Positioner>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PopoverArrow = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraPopover.ArrowProps
|
||||||
|
>(function PopoverArrow(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.Arrow {...props} ref={ref}>
|
||||||
|
<ChakraPopover.ArrowTip />
|
||||||
|
</ChakraPopover.Arrow>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PopoverCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraPopover.CloseTriggerProps
|
||||||
|
>(function PopoverCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="1"
|
||||||
|
insetEnd="1"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" />
|
||||||
|
</ChakraPopover.CloseTrigger>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PopoverTitle = ChakraPopover.Title;
|
||||||
|
export const PopoverDescription = ChakraPopover.Description;
|
||||||
|
export const PopoverFooter = ChakraPopover.Footer;
|
||||||
|
export const PopoverHeader = ChakraPopover.Header;
|
||||||
|
export const PopoverRoot = ChakraPopover.Root;
|
||||||
|
export const PopoverBody = ChakraPopover.Body;
|
||||||
|
export const PopoverTrigger = ChakraPopover.Trigger;
|
13
front/src/components/ui/provider.tsx
Normal file
13
front/src/components/ui/provider.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { ColorModeProvider, type ColorModeProviderProps } from './color-mode';
|
||||||
|
|
||||||
|
export function Provider(props: ColorModeProviderProps) {
|
||||||
|
return (
|
||||||
|
<ChakraProvider value={defaultSystem}>
|
||||||
|
<ColorModeProvider {...props} />
|
||||||
|
</ChakraProvider>
|
||||||
|
);
|
||||||
|
}
|
25
front/src/components/ui/radio.tsx
Normal file
25
front/src/components/ui/radio.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { RadioGroup as ChakraRadioGroup } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface RadioProps extends ChakraRadioGroup.ItemProps {
|
||||||
|
rootRef?: React.Ref<HTMLDivElement>;
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
||||||
|
function Radio(props, ref) {
|
||||||
|
const { children, inputProps, rootRef, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<ChakraRadioGroup.Item ref={rootRef} {...rest}>
|
||||||
|
<ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraRadioGroup.ItemIndicator />
|
||||||
|
{children && (
|
||||||
|
<ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
|
||||||
|
)}
|
||||||
|
</ChakraRadioGroup.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RadioGroup = ChakraRadioGroup.Root;
|
83
front/src/components/ui/slider.tsx
Normal file
83
front/src/components/ui/slider.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { Slider as ChakraSlider, For, HStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface SliderProps extends ChakraSlider.RootProps {
|
||||||
|
marks?: Array<number | { value: number; label: React.ReactNode }>;
|
||||||
|
label?: React.ReactNode;
|
||||||
|
showValue?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
||||||
|
function Slider(props, ref) {
|
||||||
|
const { marks: marksProp, label, showValue, ...rest } = props;
|
||||||
|
const value = props.defaultValue ?? props.value;
|
||||||
|
|
||||||
|
const marks = marksProp?.map((mark) => {
|
||||||
|
if (typeof mark === 'number') return { value: mark, label: undefined };
|
||||||
|
return mark;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMarkLabel = !!marks?.some((mark) => mark.label);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraSlider.Root ref={ref} thumbAlignment="center" {...rest}>
|
||||||
|
{label && !showValue && (
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
)}
|
||||||
|
{label && showValue && (
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
<ChakraSlider.ValueText />
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
<ChakraSlider.Control data-has-mark-label={hasMarkLabel || undefined}>
|
||||||
|
<ChakraSlider.Track>
|
||||||
|
<ChakraSlider.Range />
|
||||||
|
</ChakraSlider.Track>
|
||||||
|
<SliderThumbs value={value} />
|
||||||
|
<SliderMarks marks={marks} />
|
||||||
|
</ChakraSlider.Control>
|
||||||
|
</ChakraSlider.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function SliderThumbs(props: { value?: number[] }) {
|
||||||
|
const { value } = props;
|
||||||
|
return (
|
||||||
|
<For each={value}>
|
||||||
|
{(_, index) => (
|
||||||
|
<ChakraSlider.Thumb key={index} index={index}>
|
||||||
|
<ChakraSlider.HiddenInput />
|
||||||
|
</ChakraSlider.Thumb>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SliderMarksProps {
|
||||||
|
marks?: Array<number | { value: number; label: React.ReactNode }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
|
||||||
|
function SliderMarks(props, ref) {
|
||||||
|
const { marks } = props;
|
||||||
|
if (!marks?.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraSlider.MarkerGroup ref={ref}>
|
||||||
|
{marks.map((mark, index) => {
|
||||||
|
const value = typeof mark === 'number' ? mark : mark.value;
|
||||||
|
const label = typeof mark === 'number' ? undefined : mark.label;
|
||||||
|
return (
|
||||||
|
<ChakraSlider.Marker key={index} value={value}>
|
||||||
|
<ChakraSlider.MarkerIndicator />
|
||||||
|
{label}
|
||||||
|
</ChakraSlider.Marker>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ChakraSlider.MarkerGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
52
front/src/components/ui/toaster.tsx
Normal file
52
front/src/components/ui/toaster.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Toaster as ChakraToaster,
|
||||||
|
Portal,
|
||||||
|
Spinner,
|
||||||
|
Stack,
|
||||||
|
Toast,
|
||||||
|
createToaster,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { RestErrorResponse } from '@/back-api';
|
||||||
|
|
||||||
|
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>
|
||||||
|
<ChakraToaster toaster={toaster} insetInline={{ mdDown: '4' }}>
|
||||||
|
{(toast) => (
|
||||||
|
<Toast.Root width={{ md: 'sm' }}>
|
||||||
|
{toast.type === 'loading' ? (
|
||||||
|
<Spinner size="sm" color="blue.solid" />
|
||||||
|
) : (
|
||||||
|
<Toast.Indicator />
|
||||||
|
)}
|
||||||
|
<Stack gap="1" flex="1" maxWidth="100%">
|
||||||
|
{toast.title && <Toast.Title>{toast.title}</Toast.Title>}
|
||||||
|
{toast.description && (
|
||||||
|
<Toast.Description>{toast.description}</Toast.Description>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
{toast.action && (
|
||||||
|
<Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger>
|
||||||
|
)}
|
||||||
|
{toast.meta?.closable && <Toast.CloseTrigger />}
|
||||||
|
</Toast.Root>
|
||||||
|
)}
|
||||||
|
</ChakraToaster>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user