Compare commits
	
		
			25 Commits
		
	
	
		
			v1.0.4
			...
			feat_raw_r
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 979ec4b576 | |||
| 12223347d3 | |||
| f9019ec508 | |||
| d52052de90 | |||
| 91defa42c2 | |||
| 2812d21782 | |||
| eb5a366a12 | |||
| 6b801d250f | |||
| 01fad1b9d4 | |||
| 371bea79f9 | |||
| 35725e1320 | |||
| d6a8c7d23f | |||
| 3c604e9593 | |||
| 43d8108270 | |||
| 1a883193d0 | |||
| 78b1970ba9 | |||
| 746d5dff96 | |||
| 8780ea8e63 | |||
| 8911eed0fb | |||
| 1a3652472e | |||
| 2e62577103 | |||
| 653e77160b | |||
| 3898a6bf4f | |||
| 448cf1569b | |||
| d3e2b3e601 | 
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
								
							| @@ -40,6 +40,23 @@ cd front | ||||
| pnpm run unlink_kar_cw | ||||
| ``` | ||||
|  | ||||
| Tools in production mode | ||||
| ======================== | ||||
|  | ||||
| Changing the Log Level | ||||
| ---------------------- | ||||
|  | ||||
| In a production environment, you can adjust the log level to help diagnose bugs more effectively. | ||||
|  | ||||
| The available log levels are: | ||||
| | **Log Level Tag** | **org.kar.karusic** | **org.kar.archidata** | **other** | | ||||
| | ----------------- | ------------------- | --------------------- | --------- | | ||||
| | `prod`            | INFO                | INFO                  | INFO      | | ||||
| | `prod-debug`      | DEBUG               | INFO                  | INFO      | | ||||
| | `prod-trace`      | TRACE               | DEBUG                 | INFO      | | ||||
| | `prod-trace-full` | TRACE               | TRACE                 | INFO      | | ||||
| | `dev`             | TRACE               | DEBUG                 | INFO      | | ||||
|  | ||||
|  | ||||
| Manual set in production: | ||||
| ========================= | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| FROM maven:3-openjdk-18 AS build | ||||
| FROM maven:3-openjdk-23 AS build | ||||
|  | ||||
| COPY pom.xml /tmp/ | ||||
| COPY src /tmp/src/ | ||||
| COPY Formatter.xml /tmp/ | ||||
| WORKDIR /tmp/ | ||||
| RUN mvn clean compile assembly:single | ||||
|  | ||||
|   | ||||
							
								
								
									
										97
									
								
								back/pom.xml
									
									
									
									
									
								
							
							
						
						
									
										97
									
								
								back/pom.xml
									
									
									
									
									
								
							| @@ -3,11 +3,11 @@ | ||||
| 	<modelVersion>4.0.0</modelVersion> | ||||
| 	<groupId>org.kar</groupId> | ||||
| 	<artifactId>karusic</artifactId> | ||||
| 	<version>1.0.4</version> | ||||
| 	<version>1.1.1-SNAPSHOT</version> | ||||
| 	<properties> | ||||
| 		<maven.compiler.version>3.1</maven.compiler.version> | ||||
| 		<maven.compiler.source>21</maven.compiler.source> | ||||
| 		<maven.compiler.target>21</maven.compiler.target> | ||||
| 		<maven.compiler.version>3.13.0</maven.compiler.version> | ||||
| 		<maven.compiler.source>23</maven.compiler.source> | ||||
| 		<maven.compiler.target>23</maven.compiler.target> | ||||
| 		<maven.dependency.version>3.1.1</maven.dependency.version> | ||||
| 	</properties> | ||||
| 	<repositories> | ||||
| @@ -20,7 +20,7 @@ | ||||
| 		<dependency> | ||||
| 			<groupId>kangaroo-and-rabbit</groupId> | ||||
| 			<artifactId>archidata</artifactId> | ||||
| 			<version>0.20.4</version> | ||||
| 			<version>0.21.0</version> | ||||
| 		</dependency> | ||||
| 		<!-- Loopback of logger JDK logging API to SLF4J --> | ||||
| 		<dependency> | ||||
| @@ -39,10 +39,15 @@ | ||||
| 			<artifactId>xercesImpl</artifactId> | ||||
| 			<version>2.12.2</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>org.codehaus.janino</groupId> | ||||
| 			<artifactId>janino</artifactId> | ||||
| 			<version>3.1.9</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>com.fasterxml.jackson.datatype</groupId> | ||||
| 			<artifactId>jackson-datatype-jsr310</artifactId> | ||||
| 			<version>2.18.0-rc1</version> | ||||
| 			<version>2.18.1</version> | ||||
| 		</dependency> | ||||
| 		<!-- | ||||
| 		************************************************************  | ||||
| @@ -108,7 +113,7 @@ | ||||
| 			<plugin> | ||||
| 				<groupId>org.apache.maven.plugins</groupId> | ||||
| 				<artifactId>maven-source-plugin</artifactId> | ||||
| 				<version>3.2.1</version> | ||||
| 				<version>4.0.0-beta-1</version> | ||||
| 				<executions> | ||||
| 					<execution> | ||||
| 						<id>attach-sources</id> | ||||
| @@ -122,10 +127,12 @@ | ||||
| 			<plugin> | ||||
| 				<groupId>org.apache.maven.plugins</groupId> | ||||
| 				<artifactId>maven-surefire-plugin</artifactId> | ||||
| 				<version>3.0.0-M5</version> | ||||
| 				<version>3.2.5</version> | ||||
| 			</plugin> | ||||
| 			<plugin> | ||||
| 				<groupId>org.apache.maven.plugins</groupId> | ||||
| 				<artifactId>maven-assembly-plugin</artifactId> | ||||
| 				<version>3.7.1</version> | ||||
| 				<configuration> | ||||
| 					<archive> | ||||
| 						<manifest> | ||||
| @@ -137,81 +144,21 @@ | ||||
| 					</descriptorRefs> | ||||
| 				</configuration> | ||||
| 			</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 --> | ||||
| 			<plugin> | ||||
| 				<groupId>org.apache.maven.plugins</groupId> | ||||
| 				<artifactId>maven-javadoc-plugin</artifactId> | ||||
| 				<version>3.2.0</version> | ||||
| 				<version>3.3.0</version> | ||||
| 				<configuration> | ||||
| 					<show>private</show> | ||||
| 					<nohelp>true</nohelp> | ||||
| 				</configuration> | ||||
| 			</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 --> | ||||
| 			<plugin> | ||||
| 				<groupId>net.revelc.code.formatter</groupId> | ||||
| 				<artifactId>formatter-maven-plugin</artifactId> | ||||
| 				<version>2.23.0</version> | ||||
| 				<version>2.24.1</version> | ||||
| 				<configuration> | ||||
| 					<encoding>UTF-8</encoding> | ||||
| 					<lineEnding>LF</lineEnding> | ||||
| @@ -242,14 +189,6 @@ | ||||
| 				<configuration> | ||||
| 					<includeFilterFile>spotbugs-security-include.xml</includeFilterFile> | ||||
| 					<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> | ||||
| 			</plugin> | ||||
| 		</plugins> | ||||
| @@ -260,7 +199,7 @@ | ||||
| 			<plugin> | ||||
| 				<groupId>org.apache.maven.plugins</groupId> | ||||
| 				<artifactId>maven-javadoc-plugin</artifactId> | ||||
| 				<version>3.2.0</version> | ||||
| 				<version>3.3.0</version> | ||||
| 				<configuration> | ||||
| 					<show>public</show> | ||||
| 				</configuration> | ||||
|   | ||||
| @@ -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); | ||||
| 	// } | ||||
|  | ||||
| } | ||||
| @@ -25,14 +25,10 @@ public class Initialization extends MigrationSqlStep { | ||||
| 		return "Initialization"; | ||||
| 	} | ||||
|  | ||||
| 	public Initialization() { | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void generateStep() throws Exception { | ||||
| 		for (final Class<?> elem : CLASSES_BASE) { | ||||
| 			addClass(elem); | ||||
| 		for (final Class<?> clazz : CLASSES_BASE) { | ||||
| 			addClass(clazz); | ||||
| 		} | ||||
|  | ||||
| 		addAction((final DBAccess da) -> { | ||||
| @@ -63,6 +59,7 @@ public class Initialization extends MigrationSqlStep { | ||||
| 					new Gender(24L, "Bande Originale"), // | ||||
| 					new Gender(25L, "Variété Belge"), // | ||||
| 					new Gender(26L, "Gospel")); | ||||
| 			da.insertMultiple(data); | ||||
| 		}); | ||||
| 		// set start increment element to permit to add after default elements | ||||
| 		addAction(""" | ||||
|   | ||||
| @@ -1,18 +1,50 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <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"> | ||||
|                 <encoder> | ||||
|             <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> | ||||
|             --> | ||||
|                     <pattern>%green(%d{HH:mm:ss.SSS}) %highlight(%-5level) %-30((%file:%line\)): %msg%n</pattern> | ||||
|                 </encoder> | ||||
|             </appender> | ||||
|  | ||||
|     <root level="info"> | ||||
|             <logger name="org.kar.karusic" level="TRACE" /> | ||||
|             <logger name="org.kar.archidata" level="DEBUG" /> | ||||
|             <root level="INFO"> | ||||
|                 <appender-ref ref="CONSOLE" /> | ||||
|             </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> | ||||
| @@ -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) { | ||||
| 			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( // | ||||
| 				Album.class, // | ||||
| 				Artist.class, // | ||||
|   | ||||
| @@ -25,8 +25,6 @@ public class TestBase { | ||||
| 		ConfigureDb.configure(); | ||||
| 		LOGGER.info("configure server ..."); | ||||
| 		webInterface = new WebLauncherTest(); | ||||
| 		LOGGER.info("Clean previous table"); | ||||
|  | ||||
| 		LOGGER.info("Start REST (BEGIN)"); | ||||
| 		webInterface.process(); | ||||
| 		LOGGER.info("Start REST (DONE)"); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "display": "2025-01-06", | ||||
|   "version": "0.0.1-dev\n - 2025-01-06T00:49:52+01:00", | ||||
|   "display": "2025-01-14", | ||||
|   "version": "0.0.1-dev\n - 2025-01-14T20:19:20+01:00", | ||||
|   "commit": "0.0.1-dev\n", | ||||
|   "date": "2025-01-06T00:49:52+01:00" | ||||
|   "date": "2025-01-14T20:19:20+01:00" | ||||
| } | ||||
| @@ -12,7 +12,8 @@ | ||||
|     "node": ">=20" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "update_packages": "ncu --upgrade", | ||||
|     "update_packages": "ncu --target minor", | ||||
|     "upgrade_packages": "ncu --upgrade ", | ||||
|     "install_dependency": "pnpm install", | ||||
|     "test": "vitest run", | ||||
|     "test:watch": "vitest watch", | ||||
| @@ -28,46 +29,18 @@ | ||||
|     "*.{ts,tsx,js,jsx,json}": "prettier --write" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@chakra-ui/anatomy": "2.2.2", | ||||
|     "@chakra-ui/cli": "2.4.1", | ||||
|     "@chakra-ui/react": "2.8.2", | ||||
|     "@chakra-ui/theme-tools": "2.2.6", | ||||
|     "@dnd-kit/core": "6.3.1", | ||||
|     "@dnd-kit/modifiers": "9.0.0", | ||||
|     "@dnd-kit/sortable": "10.0.0", | ||||
|     "@dnd-kit/utilities": "3.2.2", | ||||
|     "@emotion/react": "11.14.0", | ||||
|     "@emotion/styled": "11.14.0", | ||||
|     "@formiz/core": "2.4.5", | ||||
|     "@formiz/validations": "2.0.1", | ||||
|     "allotment": "1.20.2", | ||||
|     "css-mediaquery": "0.1.2", | ||||
|     "dayjs": "1.11.13", | ||||
|     "history": "5.3.0", | ||||
|     "react": "18.3.1", | ||||
|     "react-color-palette": "7.3.0", | ||||
|     "react-currency-input-field": "3.9.0", | ||||
|     "react-custom-scrollbars": "4.2.1", | ||||
|     "react-day-picker": "9.5.0", | ||||
|     "react-dom": "18.3.1", | ||||
|     "react-error-boundary": "4.0.13", | ||||
|     "react-focus-lock": "2.13.2", | ||||
|     "react-icons": "5.3.0", | ||||
|     "react-popper": "2.3.0", | ||||
|     "react-router-dom": "6.26.2", | ||||
|     "react-error-boundary": "5.0.0", | ||||
|     "react-icons": "5.4.0", | ||||
|     "react-router-dom": "7.1.1", | ||||
|     "react-select": "5.9.0", | ||||
|     "react-simple-keyboard": "3.8.33", | ||||
|     "react-sticky-el": "2.1.1", | ||||
|     "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", | ||||
|     "zustand": "5.0.2" | ||||
|     "zustand": "5.0.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@chakra-ui/styled-system": "2.12.0", | ||||
|     "@playwright/test": "1.49.1", | ||||
|     "@storybook/addon-actions": "8.4.7", | ||||
|     "@storybook/addon-essentials": "8.4.7", | ||||
| @@ -81,30 +54,28 @@ | ||||
|     "@testing-library/user-event": "14.5.2", | ||||
|     "@trivago/prettier-plugin-sort-imports": "5.2.1", | ||||
|     "@types/jest": "29.5.14", | ||||
|     "@types/node": "22.10.5", | ||||
|     "@types/node": "22.10.6", | ||||
|     "@types/react": "18.3.8", | ||||
|     "@types/react-dom": "18.3.0", | ||||
|     "@types/react-sticky-el": "1.0.7", | ||||
|     "@typescript-eslint/eslint-plugin": "8.19.0", | ||||
|     "@typescript-eslint/parser": "8.19.0", | ||||
|     "@typescript-eslint/eslint-plugin": "8.20.0", | ||||
|     "@typescript-eslint/parser": "8.20.0", | ||||
|     "@vitejs/plugin-react": "4.3.4", | ||||
|     "eslint": "9.17.0", | ||||
|     "eslint": "9.18.0", | ||||
|     "eslint-plugin-codeceptjs": "1.3.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-storybook": "0.11.2", | ||||
|     "jest": "29.7.0", | ||||
|     "jest-environment-jsdom": "29.7.0", | ||||
|     "knip": "5.41.1", | ||||
|     "knip": "5.42.0", | ||||
|     "lint-staged": "15.3.0", | ||||
|     "npm-check-updates": "^17.1.13", | ||||
|     "prettier": "3.4.2", | ||||
|     "puppeteer": "23.11.1", | ||||
|     "react-is": "19.0.0", | ||||
|     "storybook": "8.4.7", | ||||
|     "ts-node": "10.9.2", | ||||
|     "typescript": "5.7.2", | ||||
|     "typescript": "5.7.3", | ||||
|     "vite": "6.0.7", | ||||
|     "vitest": "2.1.8" | ||||
|   } | ||||
|   | ||||
							
								
								
									
										3447
									
								
								front/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3447
									
								
								front/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,29 +1,13 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { ChakraProvider, Select } from '@chakra-ui/react'; | ||||
| import { | ||||
|   Box, | ||||
|   Button, | ||||
|   Modal, | ||||
|   ModalBody, | ||||
|   ModalCloseButton, | ||||
|   ModalContent, | ||||
|   ModalFooter, | ||||
|   ModalHeader, | ||||
|   ModalOverlay, | ||||
|   Stack, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { environment } from '@/environment'; | ||||
| import { App as SpaApp } from '@/scene/App'; | ||||
| import { USERS } from '@/service/session'; | ||||
| import theme from '@/theme'; | ||||
| import { hashLocalData } from '@/utils/sso'; | ||||
| import { Div, FullPage } from './ui'; | ||||
| import { useDisclosure } from './utils/disclosure'; | ||||
| import { useState } from 'react'; | ||||
| import { environment } from './environment'; | ||||
| import { hashLocalData } from './utils/sso'; | ||||
| import { USERS } from './service/session'; | ||||
|  | ||||
| const AppEnvHint = () => { | ||||
|   const modal = useDisclosure(); | ||||
|   const dialog = useDisclosure(); | ||||
|   const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER'); | ||||
|   //const setUser = useRightsStore((store) => store.setUser); | ||||
|   const buildEnv = | ||||
| @@ -40,7 +24,7 @@ const AppEnvHint = () => { | ||||
|     setSelectUserTest(selectedOption.target.value); | ||||
|   }; | ||||
|   const onClose = () => { | ||||
|     modal.onClose(); | ||||
|     dialog.onClose(); | ||||
|     if (selectUserTest == 'NO_USER') { | ||||
|       window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`; | ||||
|     } else { | ||||
| @@ -50,68 +34,82 @@ const AppEnvHint = () => { | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <Box | ||||
|         zIndex="100000" | ||||
|         position="fixed" | ||||
|         top="0" | ||||
|         insetStart="0" | ||||
|         insetEnd="0" | ||||
|         h="2px" | ||||
|         bg="warning.400" | ||||
|         as="button" | ||||
|         cursor="pointer" | ||||
|       <Div style={{ | ||||
|         zIndex: "100000", | ||||
|         position: "fixed", | ||||
|         top: "0", | ||||
|         height: "2px", | ||||
|         width: "100%", | ||||
|         background: "warning.400", | ||||
|         cursor: "pointer" | ||||
|       }} | ||||
|         data-test-id="devtools" | ||||
|         onClick={modal.onOpen} | ||||
|         onClick={dialog.onOpen} | ||||
|       > | ||||
|         <Text | ||||
|           position="fixed" | ||||
|           top="0" | ||||
|           insetStart="4" | ||||
|           bg="warning.400" | ||||
|           color="warning.900" | ||||
|           fontSize="0.6rem" | ||||
|           fontWeight="bold" | ||||
|           px="10px" | ||||
|           marginLeft="25%" | ||||
|           borderBottomStartRadius="sm" | ||||
|           borderBottomEndRadius="sm" | ||||
|           textTransform="uppercase" | ||||
|         <Div style={{ | ||||
|           position: "fixed", | ||||
|           top: "0", | ||||
|           background: "warning.400", | ||||
|           color: "warning.900", | ||||
|           fontSize: "0.6rem", | ||||
|           fontWeight: "bold", | ||||
|           paddingLeft: "10px", | ||||
|           paddingRight: "10px", | ||||
|           marginLeft: "25%", | ||||
|           borderRadius: "0 0 10px 10px", | ||||
|           textTransform: "uppercase", | ||||
|         }} | ||||
|         > | ||||
|           {envName.join(' : ')} | ||||
|         </Text> | ||||
|       </Box> | ||||
|       <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> | ||||
|         </Div> | ||||
|       </Div> | ||||
|       {/* <Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}> | ||||
|         <Dialog.Trigger asChild> | ||||
|           <Button> | ||||
|             {dialog.open ? "Close" : "Open"} Dialog | ||||
|           </Button> | ||||
|         </Dialog.Trigger> | ||||
|         <Portal> | ||||
|           <Dialog.Backdrop /> | ||||
|           <Dialog.Positioner> | ||||
|             <Dialog.Content> | ||||
|               <Dialog.Title>Outils développeurs</Dialog.Title> | ||||
|               <Dialog.Description> | ||||
|                 <HStack> | ||||
|                   <Text>User</Text> | ||||
|                   <Select.Root onChange={handleChange} collection={USERS_COLLECTION}> | ||||
|                     <Select.Trigger> | ||||
|                       <SelectValueText placeholder="Select test user" /> | ||||
|                     </Select.Trigger> | ||||
|                     <Select.Content> | ||||
|                       {USERS_COLLECTION.items.map((value) => ( | ||||
|                         <Select.Item item={value} key={value.value}> | ||||
|                           {value.label} | ||||
|                         </Select.Item> | ||||
|                       ))} | ||||
|               </Select> | ||||
|             </Stack> | ||||
|           </ModalBody> | ||||
|           <ModalFooter> | ||||
|                     </Select.Content> | ||||
|                   </Select.Root> | ||||
|                 </HStack> | ||||
|               </Dialog.Description> | ||||
|               <Dialog.CloseTrigger> | ||||
|                 <Button onClick={onClose}>Apply</Button> | ||||
|           </ModalFooter> | ||||
|         </ModalContent> | ||||
|       </Modal> | ||||
|               </Dialog.CloseTrigger> | ||||
|             </Dialog.Content> | ||||
|           </Dialog.Positioner> | ||||
|         </Portal> | ||||
|       </Dialog.Root> */} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| const App = () => { | ||||
|  | ||||
|   return ( | ||||
|     <ChakraProvider theme={theme}> | ||||
|     <FullPage data-test-id="Full-root-page"> | ||||
|       <AppEnvHint /> | ||||
|       <SpaApp /> | ||||
|     </ChakraProvider> | ||||
|       <SpaApp data-test-id="app" /> | ||||
|       {/* <Toaster /> */} | ||||
|     </FullPage> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -3,11 +3,11 @@ | ||||
|  */ | ||||
| import { z as zod } from "zod"; | ||||
|  | ||||
| import {ZodUUID} from "./uuid"; | ||||
| import {ZodObjectId} from "./object-id"; | ||||
| import {ZodInteger} from "./integer"; | ||||
|  | ||||
| export const ZodRestErrorResponse = zod.object({ | ||||
| 	uuid: ZodUUID.optional(), | ||||
| 	oid: ZodObjectId.optional(), | ||||
| 	name: zod.string(), | ||||
| 	message: zod.string(), | ||||
| 	time: zod.string(), | ||||
|   | ||||
| @@ -1,26 +1,13 @@ | ||||
| import { SyntheticEvent, useEffect, useRef, useState } from 'react'; | ||||
|  | ||||
|  | ||||
| import { | ||||
|   Box, | ||||
|   Button, | ||||
|   Flex, | ||||
|   IconButton, | ||||
|   Slider, | ||||
|   SliderFilledTrack, | ||||
|   SliderThumb, | ||||
|   SliderTrack, | ||||
|   Text, | ||||
|   position, | ||||
| } from '@chakra-ui/react'; | ||||
| import { | ||||
|   MdCheck, | ||||
|   MdFastForward, | ||||
|   MdFastRewind, | ||||
|   MdGraphicEq, | ||||
|   MdLooksOne, | ||||
|   MdNavigateBefore, | ||||
|   MdNavigateNext, | ||||
|   MdOutlinePlayArrow, | ||||
|   MdPause, | ||||
|   MdPlayArrow, | ||||
|   MdRepeat, | ||||
| @@ -35,8 +22,10 @@ import { useSpecificArtists } from '@/service/Artist'; | ||||
| import { useSpecificGender } from '@/service/Gender'; | ||||
| import { useSpecificTrack } from '@/service/Track'; | ||||
| import { DataUrlAccess } from '@/utils/data-url-access'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Icon } from './Icon'; | ||||
| import { Flex, Text } from '@/ui'; | ||||
|  | ||||
| export enum PlayMode { | ||||
|   PLAY_ONE, | ||||
| @@ -65,8 +54,7 @@ const formatTime = (time) => { | ||||
|   return '00:00'; | ||||
| }; | ||||
|  | ||||
| export const AudioPlayer = ({}: AudioPlayerProps) => { | ||||
|   const { mode } = useThemeMode(); | ||||
| export const AudioPlayer = ({ }: AudioPlayerProps) => { | ||||
|   const { playTrackList, trackOffset, previous, next, first } = | ||||
|     useActivePlaylistService(); | ||||
|   const audioRef = useRef<HTMLAudioElement>(null); | ||||
| @@ -89,14 +77,17 @@ export const AudioPlayer = ({}: AudioPlayerProps) => { | ||||
|         : '' | ||||
|     ); | ||||
|   }, [dataTrack, setMediaSource]); | ||||
|   const backColor = mode('back.100', 'back.800'); | ||||
|   const backColor = useColorThemeValue('back.100', 'back.800'); | ||||
|   const configButton = { | ||||
|     borderRadius: 'full', | ||||
|     backgroundColor: '#00000000', | ||||
|     background: '#00000000', | ||||
|     _hover: { | ||||
|       boxShadow: 'outline-over', | ||||
|       bgColor: 'brand.500', | ||||
|     }, | ||||
|     width: "50px", | ||||
|     height: "50px", | ||||
|     padding: "5px", | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
| @@ -202,7 +193,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => { | ||||
|     console.log(`onTimeUpdate ${audioRef.current.currentTime}`); | ||||
|     setTimeProgress(audioRef.current.currentTime); | ||||
|   }; | ||||
|   const onDurationChange = (event) => {}; | ||||
|   const onDurationChange = (event) => { }; | ||||
|   const onChangeStateToPlay = () => { | ||||
|     setIsPlaying(true); | ||||
|   }; | ||||
| @@ -213,131 +204,129 @@ export const AudioPlayer = ({}: AudioPlayerProps) => { | ||||
|     <> | ||||
|       {!isNullOrUndefined(trackOffset) && ( | ||||
|         <Flex | ||||
|           position="absolute" | ||||
|           height="150px" | ||||
|           minHeight="150px" | ||||
|           paddingY="5px" | ||||
|           paddingX="10px" | ||||
|           marginX="15px" | ||||
|           bottom={0} | ||||
|           left={0} | ||||
|           right={0} | ||||
|           zIndex={1000} | ||||
|           borderWidth="1px" | ||||
|           borderColor="brand.900" | ||||
|           bgColor={backColor} | ||||
|           borderTopRadius="10px" | ||||
|           style={{ | ||||
|             position: "absolute", | ||||
|             height: "150px", | ||||
|             minHeight: "150px", | ||||
|             padding: "5px 10px 5px 10px", | ||||
|             margin: "0 15px 0 15px", | ||||
|             bottom: 0, | ||||
|             left: 0, | ||||
|             right: 0, | ||||
|             zIndex: 1000, | ||||
|             borderWidth: "1px", | ||||
|             borderColor: "brand.900", | ||||
|             background: backColor, | ||||
|             borderRadius: "10px 10px 0 0", | ||||
|           }} | ||||
|           direction="column" | ||||
|         > | ||||
|           <Text | ||||
|             align="left" | ||||
|             fontSize="20px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             noOfLines={1} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|             }} | ||||
|           // noOfLines={1} | ||||
|           > | ||||
|             {dataTrack?.name ?? '???'} | ||||
|           </Text> | ||||
|           <Text | ||||
|             align="left" | ||||
|             fontSize="16px" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             noOfLines={1} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               //noOfLines:1 | ||||
|             }} | ||||
|           > | ||||
|             {dataArtists.map((data) => data.name).join(', ')} /{' '} | ||||
|             {dataAlbum && dataAlbum?.name} | ||||
|             {dataGender && ` / ${dataGender.name}`} | ||||
|           </Text> | ||||
|           <Box width="full" paddingX="15px"> | ||||
|             <Slider | ||||
|               aria-label="slider-ex-4" | ||||
|               defaultValue={0} | ||||
|               value={timeProgress} | ||||
|           <Flex style={{ width: "100%", padding: "0 15px 0 15px" }}> | ||||
|             <>TODO ... </> | ||||
|             {/* <Slider.Root | ||||
|               defaultValue={[0]} | ||||
|               value={[timeProgress]} | ||||
|               min={0} | ||||
|               max={duration} | ||||
|               step={0.1} | ||||
|               onChange={onSeek} | ||||
|               focusThumbOnChange={false} | ||||
|               variant="outline" | ||||
|             // focusThumbOnChange={false} | ||||
|             > | ||||
|               <SliderTrack bg="gray.200" height="10px" borderRadius="full"> | ||||
|                 <SliderFilledTrack bg="brand.600" /> | ||||
|                 {/ * <SliderFilledTrack bg="brand.600" /> * /} | ||||
|               </SliderTrack> | ||||
|               <SliderThumb boxSize={6}> | ||||
|                 <Box color="brand.600" as={MdGraphicEq} /> | ||||
|               </SliderThumb> | ||||
|             </Slider> | ||||
|           </Box> | ||||
|             </Slider.Root> */} | ||||
|           </Flex> | ||||
|           <Flex> | ||||
|             <Text | ||||
|               align="left" | ||||
|               fontSize="16px" | ||||
|               userSelect="none" | ||||
|               marginRight="auto" | ||||
|               overflow="hidden" | ||||
|               noOfLines={1} | ||||
|               style={{ | ||||
|                 alignContent: "left", | ||||
|                 userSelect: "none", | ||||
|                 marginRight: "auto", | ||||
|                 overflow: "hidden", | ||||
|                 // noOfLines={1}, | ||||
|               }} | ||||
|             > | ||||
|               {formatTime(timeProgress)} | ||||
|             </Text> | ||||
|             <Text align="left" fontSize="16px" userSelect="none"> | ||||
|             <Text fontSize="16px" style={{ alignContent: "left", userSelect: "none" }}> | ||||
|               {formatTime(duration)} | ||||
|             </Text> | ||||
|           </Flex> | ||||
|           <Flex gap="5px"> | ||||
|           {/* <Flex gap="5px"> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'Play'} | ||||
|               icon={ | ||||
|                 isPlaying ? ( | ||||
|               onClick={onPlay} | ||||
|             > | ||||
|               {isPlaying ? ( | ||||
|                 <MdPause size="30px" /> | ||||
|               ) : ( | ||||
|                 <MdPlayArrow size="30px" /> | ||||
|                 ) | ||||
|               } | ||||
|               onClick={onPlay} | ||||
|             /> | ||||
|               )} | ||||
|             </IconButton> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'Stop'} | ||||
|               icon={<MdStop size="30px" />} | ||||
|               onClick={onStop} | ||||
|             /> | ||||
|             ><MdStop size="30px" /></IconButton> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'Previous track'} | ||||
|               icon={<MdNavigateBefore size="30px" />} | ||||
|               onClick={onNavigatePrevious} | ||||
|               marginLeft="auto" | ||||
|             /> | ||||
|             ><MdNavigateBefore size="30px" /> </IconButton> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'jump 15sec in past'} | ||||
|               icon={<MdFastRewind size="30px" />} | ||||
|               onClick={onFastRewind} | ||||
|             /> | ||||
|             ><MdFastRewind size="30px" /></IconButton> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'jump 15sec in future'} | ||||
|               icon={<MdFastForward size="30px" />} | ||||
|               onClick={onFastForward} | ||||
|             /> | ||||
|             ><MdFastForward style={{ width: "100%", height: "100%" }} /></IconButton> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'Next track'} | ||||
|               icon={<MdNavigateNext size="30px" />} | ||||
|               marginRight="auto" | ||||
|               onClick={onNavigateNext} | ||||
|             /> | ||||
|             ><MdNavigateNext style={{ width: "100%", height: "100%" }} /></IconButton> | ||||
|             <IconButton | ||||
|               {...configButton} | ||||
|               aria-label={'continue to the end'} | ||||
|               icon={playModeIcon[playingMode]} | ||||
|               onClick={onTypePlay} | ||||
|             /> | ||||
|           </Flex> | ||||
|             >{playModeIcon[playingMode]}</IconButton> | ||||
|           </Flex> */} | ||||
|         </Flex> | ||||
|       )} | ||||
|  | ||||
|   | ||||
| @@ -1,16 +1,14 @@ | ||||
| import { ReactElement, useEffect, useState } from 'react'; | ||||
|  | ||||
| import { As, Box, BoxProps, Flex, StyleProps } from '@chakra-ui/react'; | ||||
| import { Image } from '@chakra-ui/react'; | ||||
| import { CSSProperties, ReactElement, useEffect, useState } from 'react'; | ||||
|  | ||||
| import { DataUrlAccess } from '@/utils/data-url-access'; | ||||
| import { Icon } from './Icon'; | ||||
| import { ObjectId } from '@/back-api'; | ||||
| import { DivProps, Flex } from '@/ui'; | ||||
|  | ||||
| export type CoversProps = BoxProps & { | ||||
| export type CoversProps = Omit<DivProps, "iconEmpty"> & { | ||||
|   data?: ObjectId[]; | ||||
|   size?: StyleProps["width"]; | ||||
|   iconEmpty?: As; | ||||
|   size?: CSSProperties["width"]; | ||||
|   iconEmpty?: ReactElement; | ||||
|   slideshow?: boolean; | ||||
| }; | ||||
|  | ||||
| @@ -45,26 +43,34 @@ export const Covers = ({ | ||||
|       return <Icon icon={iconEmpty} sizeIcon={size} />; | ||||
|     } else { | ||||
|       return ( | ||||
|         <Box | ||||
|           width={size} | ||||
|           height={size} | ||||
|           minHeight={size} | ||||
|           minWidth={size} | ||||
|           borderColor="blue" | ||||
|           borderWidth="1px" | ||||
|           margin="auto" | ||||
|         <Flex | ||||
|           style={{ | ||||
|             width: size, | ||||
|             height: size, | ||||
|             minHeight: size, | ||||
|             minWidth: size, | ||||
|             borderColor: "blue", | ||||
|             borderWidth: "1px", | ||||
|             margin: "auto", | ||||
|           }} | ||||
|           {...rest} | ||||
|         ></Box> | ||||
|         ></Flex> | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|   if (slideshow === false || data.length === 1) { | ||||
|     const url = DataUrlAccess.getThumbnailUrl(data[0]); | ||||
|     return <Image loading="lazy" src={url} maxWidth={size} boxSize={size} {...rest} />; | ||||
|     return <></>;//<image loading="lazy" src={url} maxWidth={size} boxSize={size} /*{...rest}*/ />; | ||||
|   } | ||||
|   const urlCurrent = DataUrlAccess.getThumbnailUrl(data[currentImageIndex]); | ||||
|   const urlPrevious = DataUrlAccess.getThumbnailUrl(data[previousImageIndex]); | ||||
|   return <Flex 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 | ||||
|       src={urlPrevious} | ||||
|       loading="lazy" | ||||
| @@ -89,5 +95,5 @@ export const Covers = ({ | ||||
|       opacity={topOpacity} | ||||
|       zIndex={2} | ||||
|     /> | ||||
|   </Flex> | ||||
|   </Flex>*/ | ||||
| }; | ||||
|   | ||||
| @@ -1,13 +1,15 @@ | ||||
| import { Box } from '@chakra-ui/react'; | ||||
| import { Flex } from "@/ui"; | ||||
|  | ||||
| export const EmptyEnd = () => { | ||||
|   return ( | ||||
|     <Box | ||||
|       width="full" | ||||
|       height="25%" | ||||
|       minHeight="250px" | ||||
|     <Flex | ||||
|       style={{ | ||||
|         width: "100%", | ||||
|         height: "25%", | ||||
|         minHeight: "250px", | ||||
|       }} | ||||
|     // borderWidth="1px" | ||||
|     // borderColor="red" | ||||
|     ></Box> | ||||
|     ></Flex> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,42 +1,42 @@ | ||||
| import { | ||||
|     As, | ||||
|     Box, | ||||
|     BoxProps, | ||||
|     Icon as ChakraIcon, | ||||
|     IconProps as ChakraIconProps, | ||||
|     Flex, | ||||
|     FlexProps, | ||||
|     forwardRef, | ||||
|     LayoutProps, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { Flex, FlexProps } from '@/ui'; | ||||
| import { forwardRef, ReactNode } from 'react'; | ||||
|  | ||||
| export type IconProps = FlexProps & { | ||||
|     icon: As; | ||||
|     icon: ReactNode; | ||||
|     color?: string; | ||||
|     sizeIcon?: LayoutProps['width']; | ||||
|     sizeIcon?: FlexProps['width']; | ||||
| }; | ||||
|  | ||||
| export const Icon = forwardRef<IconProps, 'span'>( | ||||
|     ({ icon: IconEl, color, sizeIcon = '1em', ...rest }, ref) => { | ||||
| export const Icon = forwardRef<HTMLDivElement, IconProps>( | ||||
|     ({ icon: IconEl, color, sizeIcon = '1em', style, ...rest }, ref) => { | ||||
|         return ( | ||||
|             <Flex flex="none" | ||||
|                 minWidth={sizeIcon} | ||||
|                 minHeight={sizeIcon} | ||||
|                 maxWidth={sizeIcon} | ||||
|                 maxHeight={sizeIcon} | ||||
|             <Flex | ||||
|                 align="center" | ||||
|                 padding="1px" | ||||
|                 ref={ref} | ||||
|                 style={{ | ||||
|                     flex: "none", | ||||
|                     minWidth: sizeIcon, | ||||
|                     minHeight: sizeIcon, | ||||
|                     maxWidth: sizeIcon, | ||||
|                     maxHeight: sizeIcon, | ||||
|                     padding: "1px", | ||||
|                     ...style | ||||
|                 }} | ||||
|                 {...rest}> | ||||
|                 <Box | ||||
|                     marginX="auto" | ||||
|                     as={IconEl} | ||||
|                     width="100%" | ||||
|                     minWidth="100%" | ||||
|                     height="100%" | ||||
|                     color={color} | ||||
|                 /> | ||||
|                 <Flex | ||||
|                     style={{ | ||||
|                         margin: "0 auto 0 auto", | ||||
|                         width: "100%", | ||||
|                         minWidth: "100%", | ||||
|                         height: "100%", | ||||
|                         color: color, | ||||
|                     }} | ||||
|                 > | ||||
|                     {IconEl} | ||||
|                 </Flex> | ||||
|             </Flex> | ||||
|         ); | ||||
|     } | ||||
| ); | ||||
|  | ||||
| Icon.displayName = 'Icon'; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import React, { ReactNode, useEffect } from 'react'; | ||||
|  | ||||
| import { Flex, Image } from '@chakra-ui/react'; | ||||
| import { useLocation } from 'react-router-dom'; | ||||
|  | ||||
| import background from '@/assets/images/ikon.svg'; | ||||
| import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Image } from '@/ui'; | ||||
|  | ||||
| export type LayoutProps = React.PropsWithChildren<unknown> & { | ||||
|   topBar?: ReactNode; | ||||
| @@ -20,30 +20,34 @@ export const PageLayout = ({ children }: LayoutProps) => { | ||||
|   return ( | ||||
|     <> | ||||
|       <Flex | ||||
|         minH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         position="absolute" | ||||
|         top={TOP_BAR_HEIGHT} | ||||
|         bottom={0} | ||||
|         left={0} | ||||
|         right={0} | ||||
|         minWidth="300px" | ||||
|         zIndex={-1} | ||||
|         style={{ | ||||
|           position: "absolute", | ||||
|           minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           top: TOP_BAR_HEIGHT, | ||||
|           bottom: 0, | ||||
|           left: 0, | ||||
|           right: 0, | ||||
|           minWidth: "300px", | ||||
|           zIndex: -1, | ||||
|           background: "back.800", | ||||
|         }} | ||||
|       > | ||||
|         <Image src={background} boxSize="90%" margin="auto" opacity="30%" /> | ||||
|         <Image src={background} boxSize="90%" style={{ margin: "auto", opacity: "30%" }} /> | ||||
|       </Flex> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         overflowX="auto" | ||||
|         overflowY="auto" | ||||
|         minH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`} | ||||
|         position="absolute" | ||||
|         top={TOP_BAR_HEIGHT} | ||||
|         bottom={0} | ||||
|         left={0} | ||||
|         right={0} | ||||
|         minWidth="300px" | ||||
|         style={{ | ||||
|           overflow: "auto", | ||||
|           minHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           maxHeight: `calc(100vh - ${TOP_BAR_HEIGHT})`, | ||||
|           position: "absolute", | ||||
|           top: TOP_BAR_HEIGHT, | ||||
|           bottom: 0, | ||||
|           left: 0, | ||||
|           right: 0, | ||||
|           minWidth: "300px", | ||||
|         }} | ||||
|       > | ||||
|         {children} | ||||
|       </Flex> | ||||
|   | ||||
| @@ -1,19 +1,20 @@ | ||||
| import React, { ReactNode, useEffect } from 'react'; | ||||
| import { CSSProperties, useEffect } from 'react'; | ||||
|  | ||||
| import { Flex, FlexProps } from '@chakra-ui/react'; | ||||
| import { useLocation } from 'react-router-dom'; | ||||
|  | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { colors } from '@/theme/foundations/colors'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { basicColor } from '@/theme/colors'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Flex, FlexProps } from '@/ui'; | ||||
|  | ||||
| export type LayoutProps = FlexProps & { | ||||
|   children: ReactNode; | ||||
|   width?: CSSProperties['width']; | ||||
| }; | ||||
|  | ||||
| export const PageLayoutInfoCenter = ({ | ||||
|   children, | ||||
|   width = '25%', | ||||
|   width = "75%", | ||||
|   style, | ||||
|   ...rest | ||||
| }: LayoutProps) => { | ||||
|   const { pathname } = useLocation(); | ||||
| @@ -22,19 +23,21 @@ export const PageLayoutInfoCenter = ({ | ||||
|     window.scrollTo(0, 0); | ||||
|   }, [pathname]); | ||||
|  | ||||
|   const { mode } = useThemeMode(); | ||||
|   return ( | ||||
|     <PageLayout> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         margin="auto" | ||||
|         minWidth={width} | ||||
|         border="back.900" | ||||
|         borderWidth="1px" | ||||
|         borderRadius="8px" | ||||
|         padding="10px" | ||||
|         boxShadow={'0px 0px 16px ' + colors.back[900]} | ||||
|         backgroundColor={mode('#FFFFFF', '#000000')} | ||||
|         style={{ | ||||
|           margin: "auto", | ||||
|           width, | ||||
|           border: "back.900", | ||||
|           borderWidth: "1px", | ||||
|           borderRadius: "8px", | ||||
|           padding: "10px", | ||||
|           boxShadow: '0px 0px 16px ' + basicColor.back[900], | ||||
|           background: useColorThemeValue('#FFFFFF', '#000000'), | ||||
|           ...style | ||||
|         }} | ||||
|         {...rest} | ||||
|       > | ||||
|         {children} | ||||
|   | ||||
| @@ -1,10 +1,5 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Input, | ||||
|   InputGroup, | ||||
|   InputLeftElement, | ||||
| } from '@chakra-ui/react'; | ||||
| import { MdSearch } from 'react-icons/md'; | ||||
|  | ||||
| export type SearchInputProps = { | ||||
| @@ -43,17 +38,15 @@ export const SearchInput = ({ | ||||
|       onSubmitValue(inputData); | ||||
|     } | ||||
|   } | ||||
|   return ( | ||||
|     <InputGroup maxWidth="200px" marginLeft="auto" {...searchInputProperty}> | ||||
|       <InputLeftElement pointerEvents="none"> | ||||
|         <MdSearch color="gray.300" /> | ||||
|       </InputLeftElement> | ||||
|       <Input | ||||
|         onFocus={onFocusKeep} | ||||
|         onBlur={() => setTimeout(() => onFocusLost(), 200)} | ||||
|         onChange={onChange} | ||||
|         onSubmit={onSubmit} | ||||
|       /> | ||||
|     </InputGroup> | ||||
|   return (<></> | ||||
|     //<Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}> | ||||
|     // <MdSearch color="gray.300" /> | ||||
|     // <Input | ||||
|     //   onFocus={onFocusKeep} | ||||
|     //   onBlur={() => setTimeout(() => onFocusLost(), 200)} | ||||
|     //   onChange={onChange} | ||||
|     //   onSubmit={onSubmit} | ||||
|     // /> | ||||
|     //</Group> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,49 +1,31 @@ | ||||
| import { ReactNode } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Drawer, | ||||
|   DrawerBody, | ||||
|   DrawerContent, | ||||
|   DrawerHeader, | ||||
|   DrawerOverlay, | ||||
|   Flex, | ||||
|   HStack, | ||||
|   IconButton, | ||||
|   Menu, | ||||
|   MenuButton, | ||||
|   MenuItem, | ||||
|   MenuList, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   LuAlignJustify, | ||||
|   LuArrowBigLeft, | ||||
|   LuArrowUpSquare, | ||||
|   LuHelpCircle, | ||||
|   LuHome, | ||||
|   LuLogIn, | ||||
|   LuLogOut, | ||||
|   LuMoon, | ||||
|   LuPlusCircle, | ||||
|   LuSettings, | ||||
|   LuSun, | ||||
|   LuUserCircle, | ||||
| } from 'react-icons/lu'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { SessionState } from '@/service/SessionState'; | ||||
| import { colors } from '@/theme/foundations/colors'; | ||||
| import { basicColor } from '@/theme/colors'; | ||||
| import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useSessionService } from '@/service/session'; | ||||
| import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md'; | ||||
| import { useColorThemeValue, useTheme } from '@/theme/ThemeContext'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const TOP_BAR_HEIGHT = '50px'; | ||||
|  | ||||
| export const BUTTON_TOP_BAR_PROPERTY = { | ||||
|   variant: '@menu', | ||||
|   theme: '@menu', | ||||
|   height: TOP_BAR_HEIGHT, | ||||
| }; | ||||
|  | ||||
| @@ -53,11 +35,11 @@ export type TopBarProps = { | ||||
| }; | ||||
|  | ||||
| export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|   const { mode, colorMode, toggleColorMode } = useThemeMode(); | ||||
|   const { theme, toggleTheme } = useTheme(); | ||||
|   const { clearToken } = useSessionService(); | ||||
|  | ||||
|   const { session } = useServiceContext(); | ||||
|   const backColor = mode('back.100', 'back.800'); | ||||
|   const backColor = useColorThemeValue('back.100', 'back.800'); | ||||
|   const drawerDisclose = useDisclosure(); | ||||
|   const onChangeTheme = () => { | ||||
|     drawerDisclose.onOpen(); | ||||
| @@ -81,6 +63,9 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|   const onSelectHome = () => { | ||||
|     navigate('/'); | ||||
|   }; | ||||
|   const onSelectOnAir = () => { | ||||
|     navigate('/on-air'); | ||||
|   }; | ||||
|   const onHelp = () => { | ||||
|     navigate('/help'); | ||||
|   }; | ||||
| @@ -89,22 +74,24 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|   }; | ||||
|   return ( | ||||
|     <Flex | ||||
|       position="absolute" | ||||
|       top={0} | ||||
|       left={0} | ||||
|       right={0} | ||||
|       height={TOP_BAR_HEIGHT} | ||||
|       alignItems="center" | ||||
|       justifyContent="space-between" | ||||
|       backgroundColor={backColor} | ||||
|       gap="2" | ||||
|       px="2" | ||||
|       boxShadow={'0px 2px 4px ' + colors.back[900]} | ||||
|       zIndex={200} | ||||
|       align="center" | ||||
|       justify="space-between" | ||||
|       style={{ | ||||
|         position: "absolute", | ||||
|         top: 0, | ||||
|         left: 0, | ||||
|         right: 0, | ||||
|         height: TOP_BAR_HEIGHT, | ||||
|         background: backColor, | ||||
|         padding: "0 2 0 2", | ||||
|         boxShadow: `0px 2px 4px ${basicColor.back[900]}`, | ||||
|         zIndex: 200, | ||||
|       }} | ||||
|     > | ||||
|       <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}> | ||||
|         <LuAlignJustify /> | ||||
|         <Text paddingLeft="3px" fontWeight="bold"> | ||||
|         <Text style={{ padding: "0 0 0 3px" }} fontWeight="bold"> | ||||
|           Karusic | ||||
|         </Text> | ||||
|       </Button> | ||||
| @@ -112,80 +99,85 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|         <Text | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           textTransform="uppercase" | ||||
|           marginRight="auto" | ||||
|           userSelect="none" | ||||
|           style={{ | ||||
|             textTransform: "uppercase", | ||||
|             marginRight: "auto", | ||||
|             userSelect: "none", | ||||
|           }} | ||||
|         > | ||||
|           {title} | ||||
|         </Text> | ||||
|       )} | ||||
|       {children} | ||||
|       <Flex right="0"> | ||||
|       <Flex style={{ right: 0 }}> | ||||
|         {session?.state !== SessionState.CONNECTED && ( | ||||
|           <> | ||||
|             <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onSignIn}> | ||||
|             <Button {...BUTTON_TOP_BAR_PROPERTY}  /*{...THEME.Button.primary}*/ onClick={onSignIn}> | ||||
|               <LuLogIn /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold"> | ||||
|               <Text style={{ paddingLeft: "0 0 0 3px" }} fontWeight="bold"> | ||||
|                 Sign-in | ||||
|               </Text> | ||||
|             </Button> | ||||
|             <Button | ||||
|               {...BUTTON_TOP_BAR_PROPERTY} | ||||
|               onClick={onSignUp} | ||||
|               disabled={true} | ||||
|             // disabled={true} | ||||
|             > | ||||
|               <LuPlusCircle /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold"> | ||||
|               <MdMore /> | ||||
|               <Text style={{ padding: "0 0 0 3px" }} fontWeight="bold"> | ||||
|                 Sign-up | ||||
|               </Text> | ||||
|             </Button> | ||||
|           </> | ||||
|         )} | ||||
|         {session?.state === SessionState.CONNECTED && ( | ||||
|           <Menu> | ||||
|             <MenuButton | ||||
|         {/* {session?.state === SessionState.CONNECTED && ( | ||||
|           <Menu.Root> | ||||
|             <Menu.Trigger asChild> | ||||
|               <IconButton | ||||
|                 as={IconButton} | ||||
|                 aria-label="Options" | ||||
|               icon={<LuUserCircle />} | ||||
|                 {...BUTTON_TOP_BAR_PROPERTY} | ||||
|                 width={TOP_BAR_HEIGHT} | ||||
|             /> | ||||
|             <MenuList> | ||||
|               <MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}> | ||||
|                 Sign in as {session?.login ?? 'Fail'} | ||||
|               </MenuItem> | ||||
|               <MenuItem icon={<LuSettings />} onClick={onSettings}>Settings</MenuItem> | ||||
|               <MenuItem icon={<LuHelpCircle />} onClick={onHelp}>Help</MenuItem> | ||||
|               <MenuItem icon={<LuLogOut />} onClick={onSignOut}> | ||||
|                 Sign-out | ||||
|               </MenuItem> | ||||
|               ><MdSupervisedUserCircle /></IconButton> | ||||
|             </Menu.Trigger> | ||||
|             <Menu.Content> | ||||
|               <Menu.Item value="user" valueText="user" _hover={{}} color={useColorModeValue('brand.800', 'brand.200')}> | ||||
|                 <MdSupervisedUserCircle /> | ||||
|                 <Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box> | ||||
|               </Menu.Item> | ||||
|               <Menu.Item value="Settings" valueText="Settings" onClick={onSettings}><LuSettings />Settings</Menu.Item> | ||||
|               <Menu.Item value="Help" valueText="Help" onClick={onHelp}><MdHelp /> Help</Menu.Item> | ||||
|               <Menu.Item value="Sign-out" valueText="Sign-out" onClick={onSignOut}> | ||||
|                 <LuLogOut /> Sign-out | ||||
|               </Menu.Item> | ||||
|               {colorMode === 'light' ? ( | ||||
|                 <MenuItem icon={<LuMoon />} onClick={toggleColorMode}> | ||||
|                   Set dark mode | ||||
|                 </MenuItem> | ||||
|                 <Menu.Item value="set-dark" valueText="set-dark" onClick={toggleColorMode}> | ||||
|                   <LuMoon /> Set dark mode | ||||
|                 </Menu.Item> | ||||
|               ) : ( | ||||
|                 <MenuItem icon={<LuSun />} onClick={toggleColorMode}> | ||||
|                   Set light mode | ||||
|                 </MenuItem> | ||||
|               )} | ||||
|             </MenuList> | ||||
|           </Menu> | ||||
|                 <Menu.Item value="set-light" valueText="set-light" onClick={toggleColorMode}> | ||||
|                   <LuSun /> Set light mode | ||||
|                 </Menu.Item> | ||||
|               )} | ||||
|             </Menu.Content> | ||||
|           </Menu.Root> | ||||
|         )} */} | ||||
|       </Flex> | ||||
|       <Drawer | ||||
|         placement="left" | ||||
|         onClose={drawerDisclose.onClose} | ||||
|         isOpen={drawerDisclose.isOpen} | ||||
|       {/* <Drawer.Root | ||||
|         placement="start" | ||||
|         onOpenChange={drawerDisclose.onClose} | ||||
|         open={drawerDisclose.open} | ||||
|         data-testid="top-bar_drawer-root" | ||||
|       > | ||||
|         <DrawerOverlay /> | ||||
|         <DrawerContent> | ||||
|           <DrawerHeader | ||||
|         <Drawer.Content | ||||
|           data-test-id="top-bar_drawer-content"> | ||||
|           <Drawer.Header | ||||
|             paddingY="auto" | ||||
|             as="button" | ||||
|             onClick={drawerDisclose.onClose} | ||||
|             boxShadow={'0px 2px 4px ' + colors.back[900]} | ||||
|             backgroundColor={backColor} | ||||
|             color={mode('brand.900', 'brand.50')} | ||||
|             background={backColor} | ||||
|             color={useColorModeValue('brand.900', 'brand.50')} | ||||
|             textTransform="uppercase" | ||||
|           > | ||||
|             <HStack height={TOP_BAR_HEIGHT}> | ||||
| @@ -194,15 +186,15 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|                 Karusic | ||||
|               </Text> | ||||
|             </HStack> | ||||
|           </DrawerHeader> | ||||
|           <DrawerBody paddingX="0px"> | ||||
|           </Drawer.Header> | ||||
|           <Drawer.Body paddingX="0px"> | ||||
|             <Button | ||||
|               background="#00000000" | ||||
|               borderRadius="0px" | ||||
|               onClick={onSelectHome} | ||||
|               width="full" | ||||
|               width="100%" | ||||
|             > | ||||
|               <LuHome /> | ||||
|               <MdHome /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> | ||||
|                 Home | ||||
|               </Text> | ||||
| @@ -211,17 +203,29 @@ export const TopBar = ({ title, children }: TopBarProps) => { | ||||
|             <Button | ||||
|               background="#00000000" | ||||
|               borderRadius="0px" | ||||
|               onClick={onSelectAdd} | ||||
|               width="full" | ||||
|               onClick={onSelectOnAir} | ||||
|               width="100%" | ||||
|             > | ||||
|               <LuArrowUpSquare /> | ||||
|               <MdOutlinePlaylistPlay /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> | ||||
|                 On air | ||||
|               </Text> | ||||
|             </Button> | ||||
|             <hr /> | ||||
|             <Button | ||||
|               background="#00000000" | ||||
|               borderRadius="0px" | ||||
|               onClick={onSelectAdd} | ||||
|               width="100%" | ||||
|             > | ||||
|               <MdOutlineUploadFile /> | ||||
|               <Text paddingLeft="3px" fontWeight="bold" marginRight="auto"> | ||||
|                 Add Media | ||||
|               </Text> | ||||
|             </Button> | ||||
|           </DrawerBody> | ||||
|         </DrawerContent> | ||||
|       </Drawer> | ||||
|           </Drawer.Body> | ||||
|         </Drawer.Content> | ||||
|       </Drawer.Root> */} | ||||
|     </Flex> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| import { Flex, Text } from '@chakra-ui/react'; | ||||
|  | ||||
| import { LuDisc3 } from 'react-icons/lu'; | ||||
|  | ||||
| import { Album } from '@/back-api'; | ||||
| import { Covers } from '@/components/Cover'; | ||||
| import { useCountTracksWithAlbumId } from '@/service/Track'; | ||||
| import { BASE_WRAP_ICON_SIZE } from '@/constants/genericSpacing'; | ||||
| import { Flex, Text } from '@/ui'; | ||||
| import { Span } from '@/ui/Span'; | ||||
|  | ||||
| export type DisplayAlbumProps = { | ||||
|   dataAlbum?: Album; | ||||
| @@ -13,18 +15,19 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => { | ||||
|   const { countTracksOfAnAlbum } = useCountTracksWithAlbumId(dataAlbum?.id); | ||||
|   if (!dataAlbum) { | ||||
|     return ( | ||||
|       <Flex direction="row" width="full" height="full"> | ||||
|       <Flex direction="row" width="100%" height="full"> | ||||
|         Fail to retrieve Album Data. | ||||
|       </Flex> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|     <Flex direction="row" width="100%" height="full" | ||||
|       data-testid="display-album_flex"> | ||||
|       <Covers | ||||
|         data={dataAlbum?.covers} | ||||
|         size={BASE_WRAP_ICON_SIZE} | ||||
|         flex={1} | ||||
|         iconEmpty={LuDisc3} | ||||
|       // TODO: iconEmpty={LuDisc3} | ||||
|       /> | ||||
|       <Flex | ||||
|         direction="column" | ||||
| @@ -32,32 +35,37 @@ export const DisplayAlbum = ({ dataAlbum }: DisplayAlbumProps) => { | ||||
|         //maxWidth="150px" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         flex={1} | ||||
|         style={{ | ||||
|           marginRight: "auto", | ||||
|           overflow: "hidden", | ||||
|           flex: 1, | ||||
|         }} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|           align="left" | ||||
|         <Span | ||||
|           // align="left" | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           noOfLines={[1, 2]} | ||||
|           style={{ | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|           }} | ||||
|         // noOfLines={[1, 2]} | ||||
|         > | ||||
|           {dataAlbum?.name} | ||||
|         </Text> | ||||
|         <Text | ||||
|           as="span" | ||||
|           align="left" | ||||
|         </Span> | ||||
|         <Span | ||||
|           // align="left" | ||||
|           fontSize="15px" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           noOfLines={1} | ||||
|           style={{ | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|           }} | ||||
|         // noOfLines={1} | ||||
|         > | ||||
|           {countTracksOfAnAlbum} track{countTracksOfAnAlbum >= 1 && 's'} | ||||
|         </Text> | ||||
|         </Span> | ||||
|       </Flex> | ||||
|     </Flex> | ||||
|   ); | ||||
|   | ||||
| @@ -6,5 +6,6 @@ export type DisplayAlbumIdProps = { | ||||
| }; | ||||
| export const DisplayAlbumId = ({ id }: DisplayAlbumIdProps) => { | ||||
|   const { dataAlbum } = useSpecificAlbum(id); | ||||
|   return <DisplayAlbum dataAlbum={dataAlbum} />; | ||||
|   return <DisplayAlbum dataAlbum={dataAlbum} | ||||
|     data-testid="display-album-id" />; | ||||
| }; | ||||
|   | ||||
| @@ -1,14 +1,7 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   IconButton, | ||||
|   Menu, | ||||
|   MenuButton, | ||||
|   MenuItem, | ||||
|   MenuList, | ||||
| } from '@chakra-ui/react'; | ||||
| import { LuMenu } from 'react-icons/lu'; | ||||
|  | ||||
|  | ||||
| export type MenuElement = { | ||||
|   name: string; | ||||
|   onClick: () => void; | ||||
| @@ -19,24 +12,28 @@ export type ContextMenuProps = { | ||||
| }; | ||||
|  | ||||
| export const ContextMenu = ({ elements }: ContextMenuProps) => { | ||||
|   if (!elements) { | ||||
|   // if (!elements) { | ||||
|   return <></>; | ||||
|   } | ||||
|   return ( | ||||
|     <Menu> | ||||
|       <MenuButton | ||||
|         as={IconButton} | ||||
|         aria-label="Options" | ||||
|         icon={<LuMenu />} | ||||
|         marginY="auto" | ||||
|       /> | ||||
|       <MenuList> | ||||
|         {elements?.map((data) => ( | ||||
|           <MenuItem key={data.name} onClick={data.onClick}> | ||||
|             {data.name} | ||||
|           </MenuItem> | ||||
|         ))} | ||||
|       </MenuList> | ||||
|     </Menu> | ||||
|   ); | ||||
|   // } | ||||
|   // return ( | ||||
|   //   <Menu.Root | ||||
|   //     data-testid="context-menu"> | ||||
|   //     <Menu.Trigger asChild | ||||
|   //       data-testid="context-menu_trigger"> | ||||
|   //       {/* This is very stupid, we need to set as span to prevent a button in button... WTF */} | ||||
|   //       <Button {...THEME.Button.primary} > | ||||
|   //         <LuMenu /> | ||||
|   //       </Button> | ||||
|   //     </Menu.Trigger> | ||||
|   //     <Menu.Content | ||||
|   //       data-testid="context-menu_content"> | ||||
|   //       {elements?.map((data) => ( | ||||
|   //         <Menu.Item key={data.name} value={data.name} onClick={data.onClick} | ||||
|   //           data-testid="context-menu_item"> | ||||
|   //           {data.name} | ||||
|   //         </Menu.Item> | ||||
|   //       ))} | ||||
|   //     </Menu.Content> | ||||
|   //   </Menu.Root > | ||||
|   //); | ||||
| }; | ||||
|   | ||||
| @@ -3,14 +3,7 @@ import { | ||||
|   RefObject, | ||||
| } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Box, | ||||
|   BoxProps, | ||||
|   Center, | ||||
|   Image, | ||||
|   Wrap, | ||||
|   WrapItem, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   MdHighlightOff, | ||||
|   MdUploadFile, | ||||
| @@ -19,6 +12,7 @@ import { | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
| import { DataUrlAccess } from '@/utils/data-url-access'; | ||||
| import { Flex, FlexProps } from '@/ui'; | ||||
|  | ||||
| export type DragNdropProps = { | ||||
|   onFilesSelected?: (file: File[]) => void; | ||||
| @@ -58,7 +52,8 @@ export const DragNdrop = ({ | ||||
|       onUriSelected(listUri); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return <></>; | ||||
|   /* | ||||
|     return ( | ||||
|       <Box | ||||
|         width={width} | ||||
| @@ -89,9 +84,10 @@ export const DragNdrop = ({ | ||||
|         </label> | ||||
|       </Box> | ||||
|     ); | ||||
|     */ | ||||
| }; | ||||
|  | ||||
| export type CenterIconProps = BoxProps & { | ||||
| export type CenterIconProps = FlexProps & { | ||||
|   icon: any; | ||||
|   sizeIcon?: string; | ||||
| }; | ||||
| @@ -99,11 +95,13 @@ export type CenterIconProps = BoxProps & { | ||||
| export const CenterIcon = ({ | ||||
|   icon: IconEl, | ||||
|   sizeIcon = '15px', | ||||
|   style, | ||||
|   ...rest | ||||
| }: CenterIconProps) => { | ||||
|   return ( | ||||
|     <Box position="relative" w={sizeIcon} h={sizeIcon} flex="none" {...rest}> | ||||
|       <Box | ||||
|     <Flex style={{ position: "relative", width: sizeIcon, height: sizeIcon, flex: "none", ...style }} | ||||
|       {...rest}> | ||||
|       {/*<Flex | ||||
|         as={IconEl} | ||||
|         w={sizeIcon} | ||||
|         h={sizeIcon} | ||||
| @@ -111,8 +109,8 @@ export const CenterIcon = ({ | ||||
|         top="50%" | ||||
|         left="50%" | ||||
|         transform="translate(-50%, -50%)" | ||||
|       /> | ||||
|     </Box> | ||||
|       />*/} | ||||
|     </Flex> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| @@ -143,10 +141,10 @@ export const FormCovers = ({ | ||||
|       isModify={form.isModify[variableName]} | ||||
|       onRestore={() => form.restoreValue({ [variableName]: true })} | ||||
|       {...rest} | ||||
|     > | ||||
|       <Wrap width="full"> | ||||
|     > <></> | ||||
|       {/* <HStack wrap="wrap" width="100%"> | ||||
|         {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="absolute"> | ||||
|                 <CenterIcon | ||||
| @@ -161,17 +159,17 @@ export const FormCovers = ({ | ||||
|               </Box> | ||||
|               <Image loading="lazy" src={data} boxSize="full" /> | ||||
|             </Box> | ||||
|           </WrapItem> | ||||
|           </Flex> | ||||
|         ))} | ||||
|         <WrapItem key="data"> | ||||
|         <Flex align="flex-start" key="data"> | ||||
|           <DragNdrop | ||||
|             height="125px" | ||||
|             width="125px" | ||||
|             onFilesSelected={onFilesSelected} | ||||
|             onUriSelected={onUriSelected} | ||||
|           /> | ||||
|         </WrapItem> | ||||
|       </Wrap> | ||||
|         </Flex> | ||||
|       </HStack> */} | ||||
|     </FormGroup> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { Flex, Text } from '@/ui'; | ||||
| import { Span } from '@/ui/Span'; | ||||
| import { ReactNode } from 'react'; | ||||
|  | ||||
| import { Flex, Text } from '@chakra-ui/react'; | ||||
| import { MdErrorOutline, MdHelpOutline, MdRefresh } from 'react-icons/md'; | ||||
|  | ||||
| export type FormGroupProps = { | ||||
| @@ -23,20 +24,22 @@ export const FormGroup = ({ | ||||
|   onRestore, | ||||
| }: FormGroupProps) => ( | ||||
|   <Flex | ||||
|     borderLeftWidth="3px" | ||||
|     borderLeftColor={error ? 'red' : isModify ? 'blue' : '#00000000'} | ||||
|     style={{ | ||||
|       borderLeftWidth: "3px", | ||||
|       borderLeftColor: error ? 'red' : isModify ? 'blue' : '#00000000', | ||||
|     }} | ||||
|     paddingLeft="7px" | ||||
|     paddingY="4px" | ||||
|     padding="0 4px" | ||||
|     direction="column" | ||||
|   > | ||||
|     <Flex direction="row" width="full" gap="52px"> | ||||
|     <Flex direction="row" width="100%" gap="52px"> | ||||
|       {!!label && ( | ||||
|         <Text marginRight="auto" fontWeight="bold"> | ||||
|         <Text style={{ marginRight: "auto" }} fontWeight="bold"> | ||||
|           {label}{' '} | ||||
|           {isRequired && ( | ||||
|             <Text as="span" color="red.600"> | ||||
|             <Span color="red.600"> | ||||
|               * | ||||
|             </Text> | ||||
|             </Span> | ||||
|           )} | ||||
|         </Text> | ||||
|       )} | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { Input } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
| import { Input } from '@/ui'; | ||||
|  | ||||
| export type FormInputProps = { | ||||
|   form: UseFormidableReturn; | ||||
|   | ||||
| @@ -1,19 +1,11 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { | ||||
|   NumberDecrementStepper, | ||||
|   NumberIncrementStepper, | ||||
|   NumberInput, | ||||
|   NumberInputField, | ||||
|   NumberInputProps, | ||||
|   NumberInputStepper, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
|  | ||||
| export type FormNumberProps = Pick< | ||||
|   NumberInputProps, | ||||
|   NumberInput.RootProps, | ||||
|   'step' | 'defaultValue' | 'min' | 'max' | ||||
| > & { | ||||
|   form: UseFormidableReturn; | ||||
| @@ -41,21 +33,18 @@ export const FormNumber = ({ | ||||
|       onRestore={() => form.restoreValue({ [variableName]: true })} | ||||
|       {...rest} | ||||
|     > | ||||
|       <NumberInput | ||||
|       <></> | ||||
|       {/* <NumberInput.Root | ||||
|         ref={ref} | ||||
|         value={form.values[variableName]} | ||||
|         onChange={(_, value) => form.setValues({ [variableName]: value })} | ||||
|         onValueChange={(value) => form.setValues({ [variableName]: value })} | ||||
|         step={step} | ||||
|         defaultValue={defaultValue} | ||||
|         min={min} | ||||
|         max={max} | ||||
|       > | ||||
|         <NumberInputField /> | ||||
|         <NumberInputStepper> | ||||
|           <NumberIncrementStepper /> | ||||
|           <NumberDecrementStepper /> | ||||
|         </NumberInputStepper> | ||||
|       </NumberInput> | ||||
|         <NumberInput.Input /> | ||||
|       </NumberInput.Root> */} | ||||
|     </FormGroup> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Box } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormSelect } from '@/components/form/FormSelect'; | ||||
| import { useFormidable } from '@/components/form/Formidable'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export default { | ||||
|   title: 'Components/FormSelect', | ||||
| @@ -94,7 +94,7 @@ export const DarkBackground = { | ||||
|   render: () => { | ||||
|     const form = useFormidable<BasicFormData>({}); | ||||
|     return ( | ||||
|       <Box p="4" color="white" bg="gray.800"> | ||||
|       <Flex style={{ padding: "4", color: "white", background: "gray.800" }}> | ||||
|         <FormSelect | ||||
|           label="Simple Title for (DarkBackground)" | ||||
|           form={form} | ||||
| @@ -105,7 +105,7 @@ export const DarkBackground = { | ||||
|             { id: 333, name: 'third item' }, | ||||
|           ]} | ||||
|         /> | ||||
|       </Box> | ||||
|       </Flex> | ||||
|     ); | ||||
|   }, | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { Text } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Box } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormSelectMultiple } from '@/components/form/FormSelectMultiple'; | ||||
| import { useFormidable } from '@/components/form/Formidable'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export default { | ||||
|   title: 'Components/FormSelectMultipleMultiple', | ||||
| @@ -94,7 +94,7 @@ export const DarkBackground = { | ||||
|   render: () => { | ||||
|     const form = useFormidable<BasicFormData>({}); | ||||
|     return ( | ||||
|       <Box p="4" color="white" bg="gray.800"> | ||||
|       <Flex style={{ padding: "4", color: "white", background: "gray.800" }}> | ||||
|         <FormSelectMultiple | ||||
|           label="Simple Title for (DarkBackground)" | ||||
|           form={form} | ||||
| @@ -105,7 +105,7 @@ export const DarkBackground = { | ||||
|             { id: 333, name: 'third item' }, | ||||
|           ]} | ||||
|         /> | ||||
|       </Box> | ||||
|       </Flex> | ||||
|     ); | ||||
|   }, | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { RefObject } from 'react'; | ||||
|  | ||||
| import { Textarea } from '@chakra-ui/react'; | ||||
|  | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { UseFormidableReturn } from '@/components/form/Formidable'; | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import { Flex, Text } from '@chakra-ui/react'; | ||||
|  | ||||
| import { LuDisc3 } from 'react-icons/lu'; | ||||
|  | ||||
| import { Gender } from '@/back-api'; | ||||
| import { Covers } from '@/components/Cover'; | ||||
| import { useCountTracksOfAGender } from '@/service/Track'; | ||||
| import { Flex, Span, Text } from '@/ui'; | ||||
|  | ||||
| export type DisplayGenderProps = { | ||||
|   dataGender?: Gender; | ||||
| @@ -12,18 +13,18 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => { | ||||
|   const { countTracksOnAGender } = useCountTracksOfAGender(dataGender?.id); | ||||
|   if (!dataGender) { | ||||
|     return ( | ||||
|       <Flex direction="row" width="full" height="full"> | ||||
|       <Flex direction="row" width="100%" height="full"> | ||||
|         Fail to retrieve Gender Data. | ||||
|       </Flex> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|     <Flex direction="row" width="100%" height="full"> | ||||
|       <Covers | ||||
|         data={dataGender?.covers} | ||||
|         size="100" | ||||
|         height="full" | ||||
|         iconEmpty={LuDisc3} | ||||
|       //TODO: iconEmpty={LuDisc3} | ||||
|       /> | ||||
|       <Flex | ||||
|         direction="column" | ||||
| @@ -31,31 +32,36 @@ export const DisplayGender = ({ dataGender }: DisplayGenderProps) => { | ||||
|         maxWidth="150px" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         style={{ | ||||
|           overflowX: "hidden" | ||||
|         }} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|           align="left" | ||||
|         <Span | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           noOfLines={[1, 2]} | ||||
|           style={{ | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             alignContent: "left", | ||||
|           }} | ||||
|  | ||||
|         //TODO: noOfLines={[1, 2]} | ||||
|         > | ||||
|           {dataGender?.name} | ||||
|         </Text> | ||||
|         <Text | ||||
|           as="span" | ||||
|           align="left" | ||||
|         </Span> | ||||
|         <Span | ||||
|           fontSize="15px" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           noOfLines={1} | ||||
|           style={{ | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             alignContent: "left", | ||||
|           }} | ||||
|         //TODO: noOfLines={1} | ||||
|         > | ||||
|           {countTracksOnAGender} track{countTracksOnAGender >= 1 && 's'} | ||||
|         </Text> | ||||
|         </Span> | ||||
|       </Flex> | ||||
|     </Flex> | ||||
|   ); | ||||
|   | ||||
| @@ -1,18 +1,5 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Flex, | ||||
|   Modal, | ||||
|   ModalBody, | ||||
|   ModalCloseButton, | ||||
|   ModalContent, | ||||
|   ModalFooter, | ||||
|   ModalHeader, | ||||
|   ModalOverlay, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
| import { | ||||
|   MdAdminPanelSettings, | ||||
|   MdDeleteForever, | ||||
| @@ -32,6 +19,7 @@ import { useAlbumService, useSpecificAlbum } from '@/service/Album'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useCountTracksWithAlbumId } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type AlbumEditPopUpProps = {}; | ||||
|  | ||||
| @@ -65,8 +53,8 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => { | ||||
|     ); | ||||
|     onClose(); | ||||
|   }; | ||||
|   const initialRef = useRef(null); | ||||
|   const finalRef = useRef(null); | ||||
|   const initialRef = useRef<HTMLButtonElement>(null); | ||||
|   const finalRef = useRef<HTMLButtonElement>(null); | ||||
|   const form = useFormidable<Album>({ | ||||
|     initialValues: dataAlbum, | ||||
|   }); | ||||
| @@ -140,107 +128,110 @@ export const AlbumEditPopUp = ({ }: AlbumEditPopUpProps) => { | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <Modal | ||||
|       initialFocusRef={initialRef} | ||||
|       finalFocusRef={finalRef} | ||||
|       closeOnOverlayClick={false} | ||||
|       onClose={onClose} | ||||
|       isOpen={true} | ||||
|     > | ||||
|       <ModalOverlay /> | ||||
|       <ModalContent> | ||||
|         <ModalHeader>Edit Album</ModalHeader> | ||||
|         <ModalCloseButton ref={finalRef} /> | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="album-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     {/* <DialogCloseTrigger /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>Edit Album</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <ModalBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataAlbum?.id}</Text> | ||||
|               </FormGroup> | ||||
|               {countTracksOfAnAlbum !== 0 && ( | ||||
|                 <Flex paddingLeft="14px"> | ||||
|                   <MdWarning color="red.600" /> | ||||
|                   <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|                     Can not remove album {countTracksOfAnAlbum} track(s) depend | ||||
|                     on it. | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               )} | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   variant="@danger" | ||||
|                   isDisabled={countTracksOfAnAlbum !== 0} | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove Media | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove album" | ||||
|                 body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Title" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="publication" | ||||
|                 label="Publication" | ||||
|               /> | ||||
|               <FormCovers | ||||
|                 form={form} | ||||
|                 variableName="covers" | ||||
|                 onFilesSelected={onFilesSelected} | ||||
|                 onUriSelected={onUriSelected} | ||||
|                 onRemove={onRemoveCover} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </ModalBody> | ||||
|         <ModalFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </ModalFooter> | ||||
|       </ModalContent> | ||||
|     </Modal> | ||||
|   ); | ||||
|   //       <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|   //         {admin && ( | ||||
|   //           <> | ||||
|   //             <FormGroup isRequired label="Id"> | ||||
|   //               <Text>{dataAlbum?.id}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             {countTracksOfAnAlbum !== 0 && ( | ||||
|   //               <Flex paddingLeft="14px"> | ||||
|   //                 <MdWarning color="red.600" /> | ||||
|   //                 <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|   //                   Can not remove album {countTracksOfAnAlbum} track(s) depend | ||||
|   //                   on it. | ||||
|   //                 </Text> | ||||
|   //               </Flex> | ||||
|   //             )} | ||||
|   //             <FormGroup label="Action(s):"> | ||||
|   //               <Button | ||||
|   //                 onClick={disclosure.onOpen} | ||||
|   //                 marginRight="auto" | ||||
|   //                 theme="@danger" | ||||
|   //                 disabled={countTracksOfAnAlbum !== 0} | ||||
|   //               > | ||||
|   //                 <MdDeleteForever /> Remove Media | ||||
|   //               </Button> | ||||
|   //             </FormGroup> | ||||
|   //             <ConfirmPopUp | ||||
|   //               disclosure={disclosure} | ||||
|   //               title="Remove album" | ||||
|   //               body={`Remove Album [${dataAlbum?.id}] ${dataAlbum?.name}`} | ||||
|   //               confirmTitle="Remove" | ||||
|   //               onConfirm={onRemove} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //         {!admin && ( | ||||
|   //           <> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="name" | ||||
|   //               isRequired | ||||
|   //               label="Title" | ||||
|   //               ref={initialRef} | ||||
|   //             /> | ||||
|   //             <FormTextarea | ||||
|   //               form={form} | ||||
|   //               variableName="description" | ||||
|   //               label="Description" | ||||
|   //             /> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="publication" | ||||
|   //               label="Publication" | ||||
|   //             /> | ||||
|   //             <FormCovers | ||||
|   //               form={form} | ||||
|   //               variableName="covers" | ||||
|   //               onFilesSelected={onFilesSelected} | ||||
|   //               onUriSelected={onUriSelected} | ||||
|   //               onRemove={onRemoveCover} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button | ||||
|   //           onClick={() => setAdmin((value) => !value)} | ||||
|   //           marginRight="auto" | ||||
|   //         > | ||||
|   //           {admin ? ( | ||||
|   //             <> | ||||
|   //               <MdEdit /> | ||||
|   //               Edit | ||||
|   //             </> | ||||
|   //           ) : ( | ||||
|   //             <> | ||||
|   //               <MdAdminPanelSettings /> | ||||
|   //               Admin | ||||
|   //             </> | ||||
|   //           )} | ||||
|   //         </Button> | ||||
|   //         {!admin && form.isFormModified && ( | ||||
|   //           <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|   //             Save | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //         <Button onClick={onClose}>Cancel</Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,18 +1,6 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Flex, | ||||
|   Modal, | ||||
|   ModalBody, | ||||
|   ModalCloseButton, | ||||
|   ModalContent, | ||||
|   ModalFooter, | ||||
|   ModalHeader, | ||||
|   ModalOverlay, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   MdAdminPanelSettings, | ||||
|   MdDeleteForever, | ||||
| @@ -32,6 +20,7 @@ import { useArtistService, useSpecificArtist } from '@/service/Artist'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useCountTracksOfAnArtist } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type ArtistEditPopUpProps = {}; | ||||
|  | ||||
| @@ -65,8 +54,8 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => { | ||||
|     ); | ||||
|     onClose(); | ||||
|   }; | ||||
|   const initialRef = useRef(null); | ||||
|   const finalRef = useRef(null); | ||||
|   const initialRef = useRef<HTMLButtonElement>(null); | ||||
|   const finalRef = useRef<HTMLButtonElement>(null); | ||||
|   const form = useFormidable<Artist>({ | ||||
|     initialValues: dataArtist, | ||||
|   }); | ||||
| @@ -140,109 +129,110 @@ export const ArtistEditPopUp = ({ }: ArtistEditPopUpProps) => { | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <Modal | ||||
|       initialFocusRef={initialRef} | ||||
|       finalFocusRef={finalRef} | ||||
|       closeOnOverlayClick={false} | ||||
|       onClose={onClose} | ||||
|       isOpen={true} | ||||
|     > | ||||
|       <ModalOverlay /> | ||||
|       <ModalContent> | ||||
|         <ModalHeader>Edit Artist</ModalHeader> | ||||
|         <ModalCloseButton ref={finalRef} /> | ||||
|     // <Dialog.Root | ||||
|     //   //initialFocusRef={initialRef} | ||||
|     //   //finalFocusRef={finalRef} | ||||
|     //   //closeOnOverlayClick={false} | ||||
|     //   onOpenChange={onClose} | ||||
|     //   open={true} | ||||
|     //   data-testid="artist-edit-pop-up" | ||||
|     // > | ||||
|     //   {/* <DialogOverlay /> */} | ||||
|     //   <Dialog.Content> | ||||
|     //     <Dialog.Header>Edit Artist</Dialog.Header> | ||||
|     //     {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <ModalBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataArtist?.id}</Text> | ||||
|               </FormGroup> | ||||
|               {countTracksOnAnArtist !== 0 && ( | ||||
|                 <Flex paddingLeft="14px"> | ||||
|                   <MdWarning color="red.600" /> | ||||
|                   <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|                     Can not remove artist {countTracksOnAnArtist} track(s) | ||||
|                     depend on it. | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               )} | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   variant="@danger" | ||||
|                   isDisabled={countTracksOnAnArtist !== 0} | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove Media | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove artist" | ||||
|                 body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Artist name" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="firstName" | ||||
|                 label="First Name" | ||||
|               /> | ||||
|               <FormInput form={form} variableName="surname" label="SurName" /> | ||||
|               <FormInput form={form} variableName="birth" label="Birth date" /> | ||||
|               <FormInput form={form} variableName="death" label="Death date" /> | ||||
|               <FormCovers | ||||
|                 form={form} | ||||
|                 variableName="covers" | ||||
|                 onFilesSelected={onFilesSelected} | ||||
|                 onUriSelected={onUriSelected} | ||||
|                 onRemove={onRemoveCover} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </ModalBody> | ||||
|         <ModalFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </ModalFooter> | ||||
|       </ModalContent> | ||||
|     </Modal> | ||||
|   ); | ||||
|     //     <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|     //       {admin && ( | ||||
|     //         <> | ||||
|     //           <FormGroup isRequired label="Id"> | ||||
|     //             <Text>{dataArtist?.id}</Text> | ||||
|     //           </FormGroup> | ||||
|     //           {countTracksOnAnArtist !== 0 && ( | ||||
|     //             <Flex paddingLeft="14px"> | ||||
|     //               <MdWarning color="red.600" /> | ||||
|     //               <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|     //                 Can not remove artist {countTracksOnAnArtist} track(s) | ||||
|     //                 depend on it. | ||||
|     //               </Text> | ||||
|     //             </Flex> | ||||
|     //           )} | ||||
|     //           <FormGroup label="Action(s):"> | ||||
|     //             <Button | ||||
|     //               onClick={disclosure.onOpen} | ||||
|     //               marginRight="auto" | ||||
|     //               theme="@danger" | ||||
|     //               disabled={countTracksOnAnArtist !== 0} | ||||
|     //             > | ||||
|     //               <MdDeleteForever /> Remove Media | ||||
|     //             </Button> | ||||
|     //           </FormGroup> | ||||
|     //           <ConfirmPopUp | ||||
|     //             disclosure={disclosure} | ||||
|     //             title="Remove artist" | ||||
|     //             body={`Remove Artist [${dataArtist?.id}] ${dataArtist?.name}`} | ||||
|     //             confirmTitle="Remove" | ||||
|     //             onConfirm={onRemove} | ||||
|     //           /> | ||||
|     //         </> | ||||
|     //       )} | ||||
|     //       {!admin && ( | ||||
|     //         <> | ||||
|     //           <FormInput | ||||
|     //             form={form} | ||||
|     //             variableName="name" | ||||
|     //             isRequired | ||||
|     //             label="Artist name" | ||||
|     //             ref={initialRef} | ||||
|     //           /> | ||||
|     //           <FormTextarea | ||||
|     //             form={form} | ||||
|     //             variableName="description" | ||||
|     //             label="Description" | ||||
|     //           /> | ||||
|     //           <FormInput | ||||
|     //             form={form} | ||||
|     //             variableName="firstName" | ||||
|     //             label="First Name" | ||||
|     //           /> | ||||
|     //           <FormInput form={form} variableName="surname" label="SurName" /> | ||||
|     //           <FormInput form={form} variableName="birth" label="Birth date" /> | ||||
|     //           <FormInput form={form} variableName="death" label="Death date" /> | ||||
|     //           <FormCovers | ||||
|     //             form={form} | ||||
|     //             variableName="covers" | ||||
|     //             onFilesSelected={onFilesSelected} | ||||
|     //             onUriSelected={onUriSelected} | ||||
|     //             onRemove={onRemoveCover} | ||||
|     //           /> | ||||
|     //         </> | ||||
|     //       )} | ||||
|     //     </Dialog.Body> | ||||
|     //     <Dialog.Footer> | ||||
|     //       <Button | ||||
|     //         onClick={() => setAdmin((value) => !value)} | ||||
|     //         marginRight="auto" | ||||
|     //       > | ||||
|     //         {admin ? ( | ||||
|     //           <> | ||||
|     //             <MdEdit /> | ||||
|     //             Edit | ||||
|     //           </> | ||||
|     //         ) : ( | ||||
|     //           <> | ||||
|     //             <MdAdminPanelSettings /> | ||||
|     //             Admin | ||||
|     //           </> | ||||
|     //         )} | ||||
|     //       </Button> | ||||
|     //       {!admin && form.isFormModified && ( | ||||
|     //         <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|     //           Save | ||||
|     //         </Button> | ||||
|     //       )} | ||||
|     //       <Button onClick={onClose}>Cancel</Button> | ||||
|     //     </Dialog.Footer> | ||||
|     //   </Dialog.Content> | ||||
|     // </Dialog.Root> | ||||
|     <></>); | ||||
| }; | ||||
|   | ||||
| @@ -1,15 +1,7 @@ | ||||
| import { UseDisclosureReturn } from '@/utils/disclosure'; | ||||
| import { useRef } from 'react'; | ||||
|  | ||||
| import { | ||||
|   AlertDialog, | ||||
|   AlertDialogBody, | ||||
|   AlertDialogContent, | ||||
|   AlertDialogFooter, | ||||
|   AlertDialogHeader, | ||||
|   AlertDialogOverlay, | ||||
|   Button, | ||||
|   UseDisclosureReturn, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
|  | ||||
| export type ConfirmPopUpProps = { | ||||
|   title: string; | ||||
| @@ -30,31 +22,29 @@ export const ConfirmPopUp = ({ | ||||
|     onConfirm(); | ||||
|     disclosure.onClose(); | ||||
|   }; | ||||
|   const cancelRef = useRef(null); | ||||
|   return ( | ||||
|     <AlertDialog | ||||
|       isOpen={disclosure.isOpen} | ||||
|       leastDestructiveRef={cancelRef} | ||||
|       onClose={disclosure.onClose} | ||||
|     > | ||||
|       <AlertDialogOverlay> | ||||
|         <AlertDialogContent> | ||||
|           <AlertDialogHeader fontSize="lg" fontWeight="bold"> | ||||
|             {title} | ||||
|           </AlertDialogHeader> | ||||
|  | ||||
|           <AlertDialogBody>{body}</AlertDialogBody> | ||||
|  | ||||
|           <AlertDialogFooter> | ||||
|             <Button onClick={disclosure.onClose} ref={cancelRef}> | ||||
|               Cancel | ||||
|             </Button> | ||||
|             <Button colorScheme="red" onClick={onClickConfirm} ml={3}> | ||||
|               {confirmTitle} | ||||
|             </Button> | ||||
|           </AlertDialogFooter> | ||||
|         </AlertDialogContent> | ||||
|       </AlertDialogOverlay> | ||||
|     </AlertDialog> | ||||
|   ); | ||||
|   const cancelRef = useRef<HTMLButtonElement>(null); | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root role="alertdialog" | ||||
|   //     open={disclosure.open} | ||||
|   //     //leastDestructiveRef={cancelRef} | ||||
|   //     onOpenChange={disclosure.onClose} | ||||
|   //     data-testid="confirm-pop-up" | ||||
|   //   > | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header fontSize="lg" fontWeight="bold"> | ||||
|   //         {title} | ||||
|   //       </Dialog.Header> | ||||
|   //       <Dialog.Body>{body}</Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button onClick={disclosure.onClose} ref={cancelRef}> | ||||
|   //           Cancel | ||||
|   //         </Button> | ||||
|   //         <Button colorScheme="red" onClick={onClickConfirm} ml={3}> | ||||
|   //           {confirmTitle} | ||||
|   //         </Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,24 +1,13 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Flex, | ||||
|   Modal, | ||||
|   ModalBody, | ||||
|   ModalCloseButton, | ||||
|   ModalContent, | ||||
|   ModalFooter, | ||||
|   ModalHeader, | ||||
|   ModalOverlay, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   MdAdminPanelSettings, | ||||
|   MdDeleteForever, | ||||
|   MdEdit, | ||||
|   MdWarning, | ||||
| } from 'react-icons/md'; | ||||
|  | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| import { Gender, GenderResource } from '@/back-api'; | ||||
| @@ -32,6 +21,7 @@ import { useGenderService, useSpecificGender } from '@/service/Gender'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useCountTracksOfAGender } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type GenderEditPopUpProps = {}; | ||||
|  | ||||
| @@ -65,8 +55,8 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => { | ||||
|     ); | ||||
|     onClose(); | ||||
|   }; | ||||
|   const initialRef = useRef(null); | ||||
|   const finalRef = useRef(null); | ||||
|   const initialRef = useRef<HTMLButtonElement>(null); | ||||
|   const finalRef = useRef<HTMLButtonElement>(null); | ||||
|   const form = useFormidable<Gender>({ | ||||
|     initialValues: dataGender, | ||||
|   }); | ||||
| @@ -138,102 +128,104 @@ export const GenderEditPopUp = ({ }: GenderEditPopUpProps) => { | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <Modal | ||||
|       initialFocusRef={initialRef} | ||||
|       finalFocusRef={finalRef} | ||||
|       closeOnOverlayClick={false} | ||||
|       onClose={onClose} | ||||
|       isOpen={true} | ||||
|     > | ||||
|       <ModalOverlay /> | ||||
|       <ModalContent> | ||||
|         <ModalHeader>Edit Gender</ModalHeader> | ||||
|         <ModalCloseButton ref={finalRef} /> | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="gender-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>Edit Gender</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <ModalBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataGender?.id}</Text> | ||||
|               </FormGroup> | ||||
|               {countTracksOnAGender !== 0 && ( | ||||
|                 <Flex paddingLeft="14px"> | ||||
|                   <MdWarning color="red.600" /> | ||||
|                   <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|                     Can not remove gender {countTracksOnAGender} track(s) depend | ||||
|                     on it. | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               )} | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   variant="@danger" | ||||
|                   isDisabled={countTracksOnAGender !== 0} | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove gender | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove gender" | ||||
|                 body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Gender name" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormCovers | ||||
|                 form={form} | ||||
|                 variableName="covers" | ||||
|                 onFilesSelected={onFilesSelected} | ||||
|                 onUriSelected={onUriSelected} | ||||
|                 onRemove={onRemoveCover} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </ModalBody> | ||||
|         <ModalFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </ModalFooter> | ||||
|       </ModalContent> | ||||
|     </Modal> | ||||
|   ); | ||||
|   //       <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|   //         {admin && ( | ||||
|   //           <> | ||||
|   //             <FormGroup isRequired label="Id"> | ||||
|   //               <Text>{dataGender?.id}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             {countTracksOnAGender !== 0 && ( | ||||
|   //               <Flex paddingLeft="14px"> | ||||
|   //                 <MdWarning color="red.600" /> | ||||
|   //                 <Text paddingLeft="6px" color="red.600" fontWeight="bold"> | ||||
|   //                   Can not remove gender {countTracksOnAGender} track(s) depend | ||||
|   //                   on it. | ||||
|   //                 </Text> | ||||
|   //               </Flex> | ||||
|   //             )} | ||||
|   //             <FormGroup label="Action(s):"> | ||||
|   //               <Button | ||||
|   //                 onClick={disclosure.onOpen} | ||||
|   //                 marginRight="auto" | ||||
|   //                 theme="@danger" | ||||
|   //                 disabled={countTracksOnAGender !== 0} | ||||
|   //               > | ||||
|   //                 <MdDeleteForever /> Remove gender | ||||
|   //               </Button> | ||||
|   //             </FormGroup> | ||||
|   //             <ConfirmPopUp | ||||
|   //               disclosure={disclosure} | ||||
|   //               title="Remove gender" | ||||
|   //               body={`Remove gender [${dataGender?.id}] ${dataGender?.name}`} | ||||
|   //               confirmTitle="Remove" | ||||
|   //               onConfirm={onRemove} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //         {!admin && ( | ||||
|   //           <> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="name" | ||||
|   //               isRequired | ||||
|   //               label="Gender name" | ||||
|   //               ref={initialRef} | ||||
|   //             /> | ||||
|   //             <FormTextarea | ||||
|   //               form={form} | ||||
|   //               variableName="description" | ||||
|   //               label="Description" | ||||
|   //             /> | ||||
|   //             <FormCovers | ||||
|   //               form={form} | ||||
|   //               variableName="covers" | ||||
|   //               onFilesSelected={onFilesSelected} | ||||
|   //               onUriSelected={onUriSelected} | ||||
|   //               onRemove={onRemoveCover} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button | ||||
|   //           onClick={() => setAdmin((value) => !value)} | ||||
|   //           marginRight="auto" | ||||
|   //         > | ||||
|   //           {admin ? ( | ||||
|   //             <> | ||||
|   //               <MdEdit /> | ||||
|   //               Edit | ||||
|   //             </> | ||||
|   //           ) : ( | ||||
|   //             <> | ||||
|   //               <MdAdminPanelSettings /> | ||||
|   //               Admin | ||||
|   //             </> | ||||
|   //           )} | ||||
|   //         </Button> | ||||
|   //         {!admin && form.isFormModified && ( | ||||
|   //           <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|   //             Save | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //         <Button onClick={onClose}>Cancel</Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,38 +1,5 @@ | ||||
| import { ReactElement, useRef, useState } from 'react'; | ||||
| import { useRef } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Flex, | ||||
|   Modal, | ||||
|   ModalBody, | ||||
|   ModalCloseButton, | ||||
|   ModalContent, | ||||
|   ModalFooter, | ||||
|   ModalHeader, | ||||
|   ModalOverlay, | ||||
|   Progress, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
| import { | ||||
|   MdAdminPanelSettings, | ||||
|   MdDeleteForever, | ||||
|   MdEdit, | ||||
|   MdWarning, | ||||
| } from 'react-icons/md'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| import { Artist, ArtistResource } from '@/back-api'; | ||||
| import { FormCovers } from '@/components/form/FormCovers'; | ||||
| import { FormGroup } from '@/components/form/FormGroup'; | ||||
| import { FormInput } from '@/components/form/FormInput'; | ||||
| import { FormTextarea } from '@/components/form/FormTextarea'; | ||||
| import { useFormidable } from '@/components/form/Formidable'; | ||||
| import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp'; | ||||
| import { useArtistService, useSpecificArtist } from '@/service/Artist'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useCountTracksOfAnArtist } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
|  | ||||
| export type PopUpUploadProgressProps = { | ||||
|   title: string; | ||||
| @@ -63,65 +30,68 @@ export const PopUpUploadProgress = ({ | ||||
|   title, | ||||
|   totalSize, | ||||
| }: PopUpUploadProgressProps) => { | ||||
|   const initialRef = useRef(null); | ||||
|   const finalRef = useRef(null); | ||||
|   return ( | ||||
|     <Modal | ||||
|       initialFocusRef={initialRef} | ||||
|       finalFocusRef={finalRef} | ||||
|       closeOnOverlayClick={false} | ||||
|       onClose={onClose} | ||||
|       isOpen={true} | ||||
|     > | ||||
|       <ModalOverlay /> | ||||
|       <ModalContent> | ||||
|         <ModalHeader>{title}</ModalHeader> | ||||
|         <ModalCloseButton ref={finalRef} /> | ||||
|   const initialRef = useRef<HTMLButtonElement>(null); | ||||
|   const finalRef = useRef<HTMLButtonElement>(null); | ||||
|   return <></>; | ||||
|  | ||||
|         <ModalBody pb={6} paddingLeft="18px"> | ||||
|           <Flex direction="column" gap="10px"> | ||||
|             {isFinished ? ( | ||||
|               <Text fontSize="20px" fontWeight="bold"> | ||||
|                 All {elements.length} element have been sent | ||||
|               </Text> | ||||
|             ) : ( | ||||
|               <Text fontSize="20px" fontWeight="bold"> | ||||
|                 [{index + 1}/{elements.length}] {elements[index]} | ||||
|               </Text> | ||||
|             )} | ||||
|             <Progress | ||||
|               colorScheme="green" | ||||
|               hasStripe | ||||
|               value={currentSize} | ||||
|               isAnimated | ||||
|               max={totalSize} | ||||
|               height="24px" | ||||
|             /> | ||||
|             <Flex> | ||||
|               <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text> | ||||
|               <Text marginLeft="auto"> | ||||
|                 {totalSize.toLocaleString('fr-FR')} Bytes | ||||
|               </Text> | ||||
|             </Flex> | ||||
|             {error && ( | ||||
|               <Text fontWeight="bold" color="darkred"> | ||||
|                 {error} | ||||
|               </Text> | ||||
|             )} | ||||
|           </Flex> | ||||
|         </ModalBody> | ||||
|         <ModalFooter> | ||||
|           {isFinished ? ( | ||||
|             <Button onClick={onClose} variant="@success"> | ||||
|               Ok | ||||
|             </Button> | ||||
|           ) : ( | ||||
|             <Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}> | ||||
|               Abort | ||||
|             </Button> | ||||
|           )} | ||||
|         </ModalFooter> | ||||
|       </ModalContent> | ||||
|     </Modal> | ||||
|   ); | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="upload-progress-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>{title}</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|   //       <Dialog.Body pb={6} paddingLeft="18px"> | ||||
|   //         <Flex direction="column" gap="10px"> | ||||
|   //           {isFinished ? ( | ||||
|   //             <Text fontSize="20px" fontWeight="bold"> | ||||
|   //               All {elements.length} element have been sent | ||||
|   //             </Text> | ||||
|   //           ) : ( | ||||
|   //             <Text fontSize="20px" fontWeight="bold"> | ||||
|   //               [{index + 1}/{elements.length}] {elements[index]} | ||||
|   //             </Text> | ||||
|   //           )} | ||||
|   //           <Progress.Root | ||||
|   //             colorScheme="green" | ||||
|   //             striped | ||||
|   //             value={currentSize} | ||||
|   //             animated | ||||
|   //             max={totalSize} | ||||
|   //             height="24px" | ||||
|   //           /> | ||||
|   //           <Flex> | ||||
|   //             <Text>{currentSize.toLocaleString('fr-FR')} Bytes</Text> | ||||
|   //             <Text marginLeft="auto"> | ||||
|   //               {totalSize.toLocaleString('fr-FR')} Bytes | ||||
|   //             </Text> | ||||
|   //           </Flex> | ||||
|   //           {error && ( | ||||
|   //             <Text fontWeight="bold" color="darkred"> | ||||
|   //               {error} | ||||
|   //             </Text> | ||||
|   //           )} | ||||
|   //         </Flex> | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         {isFinished ? ( | ||||
|   //           <Button onClick={onClose} theme="@success"> | ||||
|   //             Ok | ||||
|   //           </Button> | ||||
|   //         ) : ( | ||||
|   //           <Button colorScheme="red" mr={3} onClick={onAbort} ref={initialRef}> | ||||
|   //             Abort | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,17 +1,6 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Modal, | ||||
|   ModalBody, | ||||
|   ModalCloseButton, | ||||
|   ModalContent, | ||||
|   ModalFooter, | ||||
|   ModalHeader, | ||||
|   ModalOverlay, | ||||
|   Text, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| @@ -30,10 +19,11 @@ import { useOrderedGenders } from '@/service/Gender'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useSpecificTrack, useTrackService } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
|  | ||||
| export type TrackEditPopUpProps = {}; | ||||
|  | ||||
| export const TrackEditPopUp = ({}: TrackEditPopUpProps) => { | ||||
| export const TrackEditPopUp = ({ }: TrackEditPopUpProps) => { | ||||
|   const { trackId } = useParams(); | ||||
|   const trackIdInt = isNullOrUndefined(trackId) | ||||
|     ? undefined | ||||
| @@ -65,8 +55,8 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => { | ||||
|     ); | ||||
|     onClose(); | ||||
|   }; | ||||
|   const initialRef = useRef(null); | ||||
|   const finalRef = useRef(null); | ||||
|   const initialRef = useRef<HTMLButtonElement>(null); | ||||
|   const finalRef = useRef<HTMLButtonElement>(null); | ||||
|   const form = useFormidable<Track>({ | ||||
|     //onSubmit, | ||||
|     //onValuesChange, | ||||
| @@ -90,115 +80,117 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => { | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|   return ( | ||||
|     <Modal | ||||
|       initialFocusRef={initialRef} | ||||
|       finalFocusRef={finalRef} | ||||
|       closeOnOverlayClick={false} | ||||
|       onClose={onClose} | ||||
|       isOpen={true} | ||||
|     > | ||||
|       <ModalOverlay /> | ||||
|       <ModalContent> | ||||
|         <ModalHeader>Edit Track</ModalHeader> | ||||
|         <ModalCloseButton ref={finalRef} /> | ||||
|   return <></>; | ||||
|   // return ( | ||||
|   //   <Dialog.Root | ||||
|   //     //initialFocusRef={initialRef} | ||||
|   //     //finalFocusRef={finalRef} | ||||
|   //     //closeOnOverlayClick={false} | ||||
|   //     onOpenChange={onClose} | ||||
|   //     open={true} | ||||
|   //     data-testid="track-edit-pop-up" | ||||
|   //   > | ||||
|   //     {/* <DialogOverlay /> */} | ||||
|   //     <Dialog.Content> | ||||
|   //       <Dialog.Header>Edit Track</Dialog.Header> | ||||
|   //       {/* <DialogCloseButton ref={finalRef} /> */} | ||||
|  | ||||
|         <ModalBody pb={6} gap="0px" paddingLeft="18px"> | ||||
|           {admin && ( | ||||
|             <> | ||||
|               <FormGroup isRequired label="Id"> | ||||
|                 <Text>{dataTrack?.id}</Text> | ||||
|               </FormGroup> | ||||
|               <FormGroup label="Data Id"> | ||||
|                 <Text>{dataTrack?.dataId}</Text> | ||||
|               </FormGroup> | ||||
|               <FormGroup label="Action(s):"> | ||||
|                 <Button | ||||
|                   onClick={disclosure.onOpen} | ||||
|                   marginRight="auto" | ||||
|                   variant="@danger" | ||||
|                 > | ||||
|                   <MdDeleteForever /> Remove Media | ||||
|                 </Button> | ||||
|               </FormGroup> | ||||
|               <ConfirmPopUp | ||||
|                 disclosure={disclosure} | ||||
|                 title="Remove track" | ||||
|                 body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`} | ||||
|                 confirmTitle="Remove" | ||||
|                 onConfirm={onRemove} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|           {!admin && ( | ||||
|             <> | ||||
|               <FormInput | ||||
|                 form={form} | ||||
|                 variableName="name" | ||||
|                 isRequired | ||||
|                 label="Title" | ||||
|                 ref={initialRef} | ||||
|               /> | ||||
|               <FormTextarea | ||||
|                 form={form} | ||||
|                 variableName="description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <FormSelect | ||||
|                 form={form} | ||||
|                 variableName="genderId" | ||||
|                 options={dataGenders} | ||||
|                 label="Gender" | ||||
|               /> | ||||
|               <FormSelectMultiple | ||||
|                 form={form} | ||||
|                 variableName="artists" | ||||
|                 options={dataArtist} | ||||
|                 label="Artist(s)" | ||||
|               /> | ||||
|               <FormSelect | ||||
|                 form={form} | ||||
|                 variableName="albumId" | ||||
|                 options={dataAlbums} | ||||
|                 label="Album" | ||||
|               /> | ||||
|               <FormNumber | ||||
|                 form={form} | ||||
|                 variableName="track" | ||||
|                 label="Track n°" | ||||
|                 step={1} | ||||
|                 defaultValue={0} | ||||
|                 min={0} | ||||
|                 max={1000} | ||||
|               /> | ||||
|             </> | ||||
|           )} | ||||
|         </ModalBody> | ||||
|         <ModalFooter> | ||||
|           <Button | ||||
|             onClick={() => setAdmin((value) => !value)} | ||||
|             marginRight="auto" | ||||
|           > | ||||
|             {admin ? ( | ||||
|               <> | ||||
|                 <MdEdit /> | ||||
|                 Edit | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <MdAdminPanelSettings /> | ||||
|                 Admin | ||||
|               </> | ||||
|             )} | ||||
|           </Button> | ||||
|           {!admin && form.isFormModified && ( | ||||
|             <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|               Save | ||||
|             </Button> | ||||
|           )} | ||||
|           <Button onClick={onClose}>Cancel</Button> | ||||
|         </ModalFooter> | ||||
|       </ModalContent> | ||||
|     </Modal> | ||||
|   ); | ||||
|   //       <Dialog.Body pb={6} gap="0px" paddingLeft="18px"> | ||||
|   //         {admin && ( | ||||
|   //           <> | ||||
|   //             <FormGroup isRequired label="Id"> | ||||
|   //               <Text>{dataTrack?.id}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             <FormGroup label="Data Id"> | ||||
|   //               <Text>{dataTrack?.dataId}</Text> | ||||
|   //             </FormGroup> | ||||
|   //             <FormGroup label="Action(s):"> | ||||
|   //               <Button | ||||
|   //                 onClick={disclosure.onOpen} | ||||
|   //                 marginRight="auto" | ||||
|   //                 theme="@danger" | ||||
|   //               > | ||||
|   //                 <MdDeleteForever /> Remove Media | ||||
|   //               </Button> | ||||
|   //             </FormGroup> | ||||
|   //             <ConfirmPopUp | ||||
|   //               disclosure={disclosure} | ||||
|   //               title="Remove track" | ||||
|   //               body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`} | ||||
|   //               confirmTitle="Remove" | ||||
|   //               onConfirm={onRemove} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //         {!admin && ( | ||||
|   //           <> | ||||
|   //             <FormInput | ||||
|   //               form={form} | ||||
|   //               variableName="name" | ||||
|   //               isRequired | ||||
|   //               label="Title" | ||||
|   //               ref={initialRef} | ||||
|   //             /> | ||||
|   //             <FormTextarea | ||||
|   //               form={form} | ||||
|   //               variableName="description" | ||||
|   //               label="Description" | ||||
|   //             /> | ||||
|   //             <FormSelect | ||||
|   //               form={form} | ||||
|   //               variableName="genderId" | ||||
|   //               options={dataGenders} | ||||
|   //               label="Gender" | ||||
|   //             /> | ||||
|   //             <FormSelectMultiple | ||||
|   //               form={form} | ||||
|   //               variableName="artists" | ||||
|   //               options={dataArtist} | ||||
|   //               label="Artist(s)" | ||||
|   //             /> | ||||
|   //             <FormSelect | ||||
|   //               form={form} | ||||
|   //               variableName="albumId" | ||||
|   //               options={dataAlbums} | ||||
|   //               label="Album" | ||||
|   //             /> | ||||
|   //             <FormNumber | ||||
|   //               form={form} | ||||
|   //               variableName="track" | ||||
|   //               label="Track n°" | ||||
|   //               step={1} | ||||
|   //               //defaultValue={0} | ||||
|   //               min={0} | ||||
|   //               max={1000} | ||||
|   //             /> | ||||
|   //           </> | ||||
|   //         )} | ||||
|   //       </Dialog.Body> | ||||
|   //       <Dialog.Footer> | ||||
|   //         <Button | ||||
|   //           onClick={() => setAdmin((value) => !value)} | ||||
|   //           marginRight="auto" | ||||
|   //         > | ||||
|   //           {admin ? ( | ||||
|   //             <> | ||||
|   //               <MdEdit /> | ||||
|   //               Edit | ||||
|   //             </> | ||||
|   //           ) : ( | ||||
|   //             <> | ||||
|   //               <MdAdminPanelSettings /> | ||||
|   //               Admin | ||||
|   //             </> | ||||
|   //           )} | ||||
|   //         </Button> | ||||
|   //         {!admin && form.isFormModified && ( | ||||
|   //           <Button colorScheme="blue" mr={3} onClick={onSave}> | ||||
|   //             Save | ||||
|   //           </Button> | ||||
|   //         )} | ||||
|   //         <Button onClick={onClose}>Cancel</Button> | ||||
|   //       </Dialog.Footer> | ||||
|   //     </Dialog.Content> | ||||
|   //   </Dialog.Root> | ||||
|   // ); | ||||
| }; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { useEffect, useRef } from 'react'; | ||||
|  | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { MdAdd } from 'react-icons/md'; | ||||
|  | ||||
| import { isNullOrUndefined, isNumber } from '@/utils/validator'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export type SelectListModel = { | ||||
|   id: any; | ||||
| @@ -65,56 +65,65 @@ export const SelectList = ({ | ||||
|     } | ||||
|   }, []); | ||||
|   return ( | ||||
|     <Box position="relative"> | ||||
|     <Flex style={{ position: "relative" }}> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         position="absolute" | ||||
|         border="1px" | ||||
|         borderColor="black" | ||||
|         backgroundColor="gray.700" | ||||
|         overflowY="auto" | ||||
|         overflowX="hidden" | ||||
|         maxHeight="300px" | ||||
|         zIndex={300} | ||||
|         transform="translateY(1px)" | ||||
|         style={{ | ||||
|           width: "100%", | ||||
|           position: "absolute", | ||||
|           border: "1px", | ||||
|           borderColor: "black", | ||||
|           background: "gray.700", | ||||
|           overflowY: "auto", | ||||
|           overflowX: "hidden", | ||||
|           maxHeight: "300px", | ||||
|           zIndex: 300, | ||||
|           transform: "translateY(1px)", | ||||
|         }} | ||||
|       > | ||||
|         {displayedValue.length === 0 && ( | ||||
|           <Text marginX="auto" color="red.500" fontWeight="bold" marginY="10px"> | ||||
|           <Text fontWeight="bold" color="red.500" style={{ margin: "0 auto" }}> | ||||
|             ... No element found... | ||||
|           </Text> | ||||
|         )} | ||||
|         {displayedValue.map((data) => ( | ||||
|           <Button | ||||
|             key={data.id} | ||||
|             marginY="1px" | ||||
|             borderRadius="0px" | ||||
|             autoFocus={false} | ||||
|             backgroundColor={data.isSelected ? 'green.800' : '0x00000000'} | ||||
|             _hover={{ backgroundColor: 'gray.400' }} | ||||
|             style={{ | ||||
|               margin: "1px 0", | ||||
|               borderRadius: "0px", | ||||
|               //autoFocus: false, | ||||
|               background: data.isSelected ? 'green.800' : '0x00000000', | ||||
|               //_hover={ background: 'gray.400' }, | ||||
|             }} | ||||
|             onClick={() => onSelectValue(data)} | ||||
|             ref={data.isSelected ? scrollToRef : undefined} | ||||
|           //ref={data.isSelected ? scrollToRef : undefined} | ||||
|           > | ||||
|             <Text marginRight="auto" autoFocus={false}> | ||||
|             <Text style={{ margin: "0 auto", }} /*autoFocus={false}*/> | ||||
|               {data.name} | ||||
|             </Text> | ||||
|           </Button> | ||||
|         ))} | ||||
|         {onCreate && search && search.length > 0 && ( | ||||
|           <Button | ||||
|             marginY="1px" | ||||
|             borderRadius="0px" | ||||
|             autoFocus={false} | ||||
|             _hover={{ backgroundColor: 'gray.400' }} | ||||
|             style={{ | ||||
|               margin: "1px 0", | ||||
|               borderRadius: "0px", | ||||
|               //autoFocus:false, | ||||
|               //_hover={ background: 'gray.400' } | ||||
|             }} | ||||
|             onClick={() => onCreate(search)} | ||||
|           > | ||||
|             <Flex marginRight="auto"> | ||||
|             <Flex | ||||
|               style={{ | ||||
|                 margin: "0 auto", | ||||
|               }}> | ||||
|               <MdAdd /> | ||||
|               <Text autoFocus={false}>Create '{search}'</Text> | ||||
|               <Text /*autoFocus={false}*/>Create '{search}'</Text> | ||||
|             </Flex> | ||||
|           </Button> | ||||
|         )} | ||||
|       </Flex> | ||||
|     </Box> | ||||
|     </Flex> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,20 +1,10 @@ | ||||
| import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Flex, | ||||
|   Input, | ||||
|   Spinner, | ||||
|   Tag, | ||||
|   TagCloseButton, | ||||
|   TagLabel, | ||||
|   Wrap, | ||||
|   WrapItem, | ||||
| } from '@chakra-ui/react'; | ||||
| import { MdEdit, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md'; | ||||
|  | ||||
| import { SelectList, SelectListModel } from '@/components/select/SelectList'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button, Flex, HStack, Input } from '@/ui'; | ||||
|  | ||||
| export type SelectMultipleProps = { | ||||
|   options?: object[]; | ||||
| @@ -85,7 +75,7 @@ export const SelectMultiple = ({ | ||||
|     } | ||||
|   }; | ||||
|   if (!options) { | ||||
|     return <Spinner />; | ||||
|     return <></>;// TODO: <Spinner />; | ||||
|   } | ||||
|   const onChangeInput = (value: string): void => { | ||||
|     setHasSuggestion(false); | ||||
| @@ -109,42 +99,47 @@ export const SelectMultiple = ({ | ||||
|     }; | ||||
|  | ||||
|   return ( | ||||
|     <Flex direction="column" width="full" gap="0px"> | ||||
|     <Flex direction="column" width="100%" gap="0px"> | ||||
|       {selectedOptions && ( | ||||
|         <Wrap spacing="5px" justify="left" width="full" marginBottom="2px"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing="5px"*/ justifyContent: "left", width: "100%", marginBottom: "2px" }}> | ||||
|           {selectedOptions.map((data) => ( | ||||
|             <WrapItem key={data[keyKey]}> | ||||
|               <Tag | ||||
|             <Flex align="flex-start" key={data[keyKey]}> | ||||
|               {/* <Tag.Root | ||||
|                 size="md" | ||||
|                 key="md" | ||||
|                 borderRadius="5px" | ||||
|                 variant="solid" | ||||
|                 backgroundColor="green.500" | ||||
|                 background="green.500" | ||||
|               > | ||||
|                 <TagLabel>{data[keyValue] ?? `id=${data[keyKey]}`}</TagLabel> | ||||
|                 <TagCloseButton onClick={() => selectValue(data)} /> | ||||
|               </Tag> | ||||
|             </WrapItem> | ||||
|                 <Tag.Label>{data[keyValue] ?? `id=${data[keyKey]}`}</Tag.Label> | ||||
|                 <Tag.CloseTrigger onClick={() => selectValue(data)} /> | ||||
|               </Tag.Root> */} | ||||
|             </Flex> | ||||
|           ))} | ||||
|         </Wrap> | ||||
|         </HStack> | ||||
|       )} | ||||
|  | ||||
|       <Flex> | ||||
|         <Input | ||||
|           ref={refFocus} | ||||
|           width="full" | ||||
|           style={{ | ||||
|             width: "100%", | ||||
|             borderRadius: "5px 0 0 5px", | ||||
|           }} | ||||
|           onChange={(e) => onChangeInput(e.target.value)} | ||||
|           //onSubmit={onSubmit} | ||||
|           onFocus={() => setShowList(true)} | ||||
|           onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           // TODO: onFocus={() => setShowList(true)} | ||||
|           // TODO: onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''} | ||||
|           borderRadius="5px 0 0 5px" | ||||
|  | ||||
|         /> | ||||
|         <Button | ||||
|           onClick={onOpenClose} | ||||
|           variant="outline" | ||||
|           borderRadius="0 5px 5px 0" | ||||
|           borderWidth="1px 1px 1px 0" | ||||
|           //variant="outline" | ||||
|           style={{ | ||||
|             borderRadius: "0 5px 5px 0", | ||||
|             borderWidth: "1px 1px 1px 0", | ||||
|           }} | ||||
|         > | ||||
|           {showList ? ( | ||||
|             <MdKeyboardArrowUp color="gray.300" /> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { RefObject, useEffect, useMemo, useRef, useState } from 'react'; | ||||
|  | ||||
| import { Button, Flex, Input, Spinner } from '@chakra-ui/react'; | ||||
| import { | ||||
|   MdClose, | ||||
|   MdEdit, | ||||
| @@ -10,6 +9,7 @@ import { | ||||
|  | ||||
| import { SelectList, SelectListModel } from '@/components/select/SelectList'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button, Flex, Input } from '@/ui'; | ||||
|  | ||||
| export type SelectSingleProps = { | ||||
|   options?: object[]; | ||||
| @@ -70,7 +70,7 @@ export const SelectSingle = ({ | ||||
|     } | ||||
|   }; | ||||
|   if (!transformedOption) { | ||||
|     return <Spinner />; | ||||
|     return <></>; // TODO: <Spinner />; | ||||
|   } | ||||
|   function onChangeInput(value: string): void { | ||||
|     setHasSuggestion(false); | ||||
| @@ -101,29 +101,32 @@ export const SelectSingle = ({ | ||||
|     }; | ||||
|  | ||||
|   return ( | ||||
|     <Flex direction="column" width="full" gap="0px"> | ||||
|     <Flex direction="column" width="100%" gap="0px"> | ||||
|       <Flex> | ||||
|         <Input | ||||
|           ref={refFocus} | ||||
|           width="full" | ||||
|           style={{ | ||||
|             width: "100%", | ||||
|             background: showList || !selectedOptions ? undefined : 'green.500', | ||||
|             borderRadius: "5px 0 0 5px", | ||||
|           }} | ||||
|           onChange={(e) => onChangeInput(e.target.value)} | ||||
|           onFocus={() => setShowList(true)} | ||||
|           onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           //onFocus={() => setShowList(true)} | ||||
|           //onBlur={() => setTimeout(() => setShowList(false), 200)} | ||||
|           value={ | ||||
|             showList ? (currentSearch ?? '') : (selectedOptions?.name ?? (hasSuggestion ? `suggest: ${currentSearch}` : '')) | ||||
|           } | ||||
|           backgroundColor={ | ||||
|             showList || !selectedOptions ? undefined : 'green.500' | ||||
|           } | ||||
|           borderRadius="5px 0 0 5px" | ||||
|         /> | ||||
|         <Button | ||||
|           onClick={onRemoveItem} | ||||
|           variant="outline" | ||||
|           borderRadius="0 5px 5px 0" | ||||
|           borderWidth="1px 1px 1px 0" | ||||
|           // TODO: variant="outline" | ||||
|           style={{ | ||||
|             borderRadius: "0 5px 5px 0", | ||||
|             borderWidth: "1px 1px 1px 0", | ||||
|           }} | ||||
|         > | ||||
|           {selectedOptions ? ( | ||||
|           { | ||||
|             selectedOptions ? ( | ||||
|               <MdClose color="gray.300" /> | ||||
|             ) : showList ? ( | ||||
|               <MdKeyboardArrowUp color="gray.300" /> | ||||
| @@ -134,7 +137,8 @@ export const SelectSingle = ({ | ||||
|             )} | ||||
|         </Button> | ||||
|       </Flex> | ||||
|       {showList && ( | ||||
|       { | ||||
|         showList && ( | ||||
|           <SelectList | ||||
|             options={transformedOption} | ||||
|             selected={selectedOptions ? [selectedOptions] : []} | ||||
| @@ -142,7 +146,8 @@ export const SelectSingle = ({ | ||||
|             onSelectValue={selectValue} | ||||
|             onCreate={createNewItem} | ||||
|           /> | ||||
|       )} | ||||
|     </Flex> | ||||
|         ) | ||||
|       } | ||||
|     </Flex > | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import { Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuMusic2, LuPlay } from 'react-icons/lu'; | ||||
|  | ||||
|  | ||||
| import { Track } from '@/back-api'; | ||||
| import { Covers } from '@/components/Cover'; | ||||
| import { ContextMenu, MenuElement } from '@/components/contextMenu/ContextMenu'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { Flex, Span } from '@/ui'; | ||||
|  | ||||
| export type DisplayTrackProps = { | ||||
|   track: Track; | ||||
| @@ -18,38 +18,41 @@ export const DisplayTrack = ({ | ||||
| }: DisplayTrackProps) => { | ||||
|   const { trackActive } = useActivePlaylistService(); | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|     <Flex direction="row" width="100%" height="full"> | ||||
|       <Covers | ||||
|         data={track?.covers} | ||||
|         size="50" | ||||
|         height="full" | ||||
|         iconEmpty={ | ||||
|         /* TODO: iconEmpty={ | ||||
|           trackActive?.id === track.id ? LuPlay : LuMusic2 | ||||
|         } | ||||
|         } */ | ||||
|         onClick={onClick} | ||||
|       /> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         width="100%" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         style={{ | ||||
|           overflowX: "hidden", | ||||
|         }} | ||||
|         onClick={onClick} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|           align="left" | ||||
|         <Span | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           noOfLines={[1, 2]} | ||||
|           marginY="auto" | ||||
|           color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|           style={{ | ||||
|             alignContent: "left", | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             // TODO: noOfLines={[1, 2]} | ||||
|             margin: "auto 0", | ||||
|           }} | ||||
|         > | ||||
|           [{track.track}] {track.name} | ||||
|         </Text> | ||||
|         </Span> | ||||
|       </Flex> | ||||
|       <ContextMenu elements={contextMenu} /> | ||||
|     </Flex> | ||||
|   | ||||
| @@ -1,7 +1,4 @@ | ||||
| import { Suspense } from 'react'; | ||||
|  | ||||
| import { Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuMusic2, LuPlay } from 'react-icons/lu'; | ||||
|  | ||||
| import { Track } from '@/back-api'; | ||||
| import { Covers } from '@/components/Cover'; | ||||
| @@ -10,6 +7,7 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificAlbum } from '@/service/Album'; | ||||
| import { useSpecificArtists } from '@/service/Artist'; | ||||
| import { useSpecificGender } from '@/service/Gender'; | ||||
| import { Flex, Span } from '@/ui'; | ||||
|  | ||||
| export type DisplayTrackProps = { | ||||
|   track: Track; | ||||
| @@ -26,87 +24,98 @@ export const DisplayTrackFull = ({ | ||||
|   const { dataGender } = useSpecificGender(track?.genderId); | ||||
|   const { dataArtists } = useSpecificArtists(track?.artists); | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|     <Flex direction="row" width="100%" height="full" | ||||
|       data-testid="display-track-full"> | ||||
|       <Covers | ||||
|         data={track?.covers} | ||||
|         size="50" | ||||
|         marginY="auto" | ||||
|         iconEmpty={ | ||||
|         /* TODO: iconEmpty={ | ||||
|           trackActive?.id === track.id ? LuPlay : LuMusic2 | ||||
|         } | ||||
|         } */ | ||||
|         onClick={onClick} | ||||
|       /> | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         width="100%" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         onClick={onClick} | ||||
|         style={{ | ||||
|           overflowX: "hidden", | ||||
|         }} | ||||
|       > | ||||
|         <Text | ||||
|           as="span" | ||||
|           align="left" | ||||
|         <Span | ||||
|           fontSize="20px" | ||||
|           fontWeight="bold" | ||||
|           userSelect="none" | ||||
|           marginRight="auto" | ||||
|           overflow="hidden" | ||||
|           noOfLines={1} | ||||
|           color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|           style={{ | ||||
|             alignContent: "left", | ||||
|             userSelect: "none", | ||||
|             marginRight: "auto", | ||||
|             overflow: "hidden", | ||||
|             // TODO: noOfLines={1} | ||||
|             margin: "auto 0", | ||||
|           }} | ||||
|         > | ||||
|           {track.name} {track.track && ` [${track.track}]`} | ||||
|         </Text> | ||||
|         </Span> | ||||
|         {dataAlbum && ( | ||||
|           <Text | ||||
|             as="span" | ||||
|             align="left" | ||||
|           <Span | ||||
|             fontSize="15px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             noOfLines={1} | ||||
|             marginY="auto" | ||||
|             //noOfLines={1} | ||||
|             color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               // TODO: noOfLines={[1, 2]} | ||||
|               margin: "auto 0", | ||||
|             }} | ||||
|           > | ||||
|             <Text as="span" fontWeight="normal">Album:</Text> {dataAlbum.name} | ||||
|           </Text> | ||||
|             <Span fontWeight="normal">Album:</Span> {dataAlbum.name} | ||||
|           </Span> | ||||
|         )} | ||||
|         {dataArtists && ( | ||||
|           <Text | ||||
|             as="span" | ||||
|             align="left" | ||||
|           <Span | ||||
|             fontSize="15px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             noOfLines={1} | ||||
|             marginY="auto" | ||||
|             color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               // TODO: noOfLines={[1, 2]} | ||||
|               margin: "auto 0", | ||||
|             }} | ||||
|           > | ||||
|             <Text as="span" fontWeight="normal">Artist(s):</Text> {dataArtists.map((data) => data.name).join(', ')} | ||||
|           </Text> | ||||
|             <Span fontWeight="normal">Artist(s):</Span> {dataArtists.map((data) => data.name).join(', ')} | ||||
|           </Span> | ||||
|         )} | ||||
|         {dataGender && ( | ||||
|           <Text | ||||
|             as="span" | ||||
|             align="left" | ||||
|           <Span | ||||
|             fontSize="15px" | ||||
|             fontWeight="bold" | ||||
|             userSelect="none" | ||||
|             marginRight="auto" | ||||
|             overflow="hidden" | ||||
|             noOfLines={1} | ||||
|             marginY="auto" | ||||
|             color={trackActive?.id === track.id ? 'green.700' : undefined} | ||||
|             style={{ | ||||
|               alignContent: "left", | ||||
|               userSelect: "none", | ||||
|               marginRight: "auto", | ||||
|               overflow: "hidden", | ||||
|               // TODO: noOfLines={[1, 2]} | ||||
|               margin: "auto 0", | ||||
|             }} | ||||
|           > | ||||
|             <Text as="span" fontWeight="normal">Gender:</Text> {dataGender.name} | ||||
|           </Text> | ||||
|             <Span fontWeight="normal">Gender:</Span> {dataGender.name} | ||||
|           </Span> | ||||
|         )} | ||||
|       </Flex> | ||||
|       <ContextMenu elements={contextMenu} /> | ||||
|       <ContextMenu elements={contextMenu} | ||||
|         data-testid="display-track-full_context-menu" /> | ||||
|     </Flex> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										27
									
								
								front/src/components/track/DisplayTrackFullId.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								front/src/components/track/DisplayTrackFullId.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import { Track } from '@/back-api'; | ||||
| import { MenuElement } from '@/components/contextMenu/ContextMenu'; | ||||
| import { useSpecificTrack } from '@/service/Track'; | ||||
| import { DisplayTrackFull } from './DisplayTrackFull'; | ||||
| import { DisplayTrackSkeleton } from './DisplayTrackSkeleton'; | ||||
|  | ||||
| export type DisplayTrackProps = { | ||||
|   trackId: Track["id"]; | ||||
|   onClick?: () => void; | ||||
|   contextMenu?: MenuElement[]; | ||||
| }; | ||||
| export const DisplayTrackFullId = ({ | ||||
|   trackId, | ||||
|   onClick, | ||||
|   contextMenu, | ||||
| }: DisplayTrackProps) => { | ||||
|   const { dataTrack } = useSpecificTrack(trackId); | ||||
|   if (dataTrack) { | ||||
|     return ( | ||||
|       <DisplayTrackFull track={dataTrack} onClick={onClick} contextMenu={contextMenu} /> | ||||
|     ); | ||||
|   } else { | ||||
|     return ( | ||||
|       <DisplayTrackSkeleton /> | ||||
|     ); | ||||
|   } | ||||
| }; | ||||
| @@ -1,29 +1,32 @@ | ||||
| import { Flex, Skeleton, SkeletonText } from '@chakra-ui/react'; | ||||
| import { Flex } from "@/ui"; | ||||
|  | ||||
|  | ||||
| export const DisplayTrackSkeleton = () => { | ||||
|   return ( | ||||
|     <Flex direction="row" width="full" height="full"> | ||||
|       <Skeleton | ||||
|     <Flex direction="row" width="100%" height="full"> | ||||
|       {/* <Skeleton | ||||
|         borderRadius="0px" | ||||
|         height="50" | ||||
|         width="50" | ||||
|         minWidth="50" | ||||
|         minHeight="50" | ||||
|       /> | ||||
|       /> */} | ||||
|       <Flex | ||||
|         direction="column" | ||||
|         width="full" | ||||
|         width="100%" | ||||
|         height="full" | ||||
|         paddingLeft="5px" | ||||
|         overflowX="hidden" | ||||
|         style={{ | ||||
|           overflowX: "hidden" | ||||
|         }} | ||||
|       > | ||||
|         <SkeletonText | ||||
|         {/* <SkeletonText | ||||
|           skeletonHeight="20px" | ||||
|           noOfLines={1} | ||||
|           spacing={0} | ||||
|           width="50%" | ||||
|           marginY="auto" | ||||
|         /> | ||||
|         /> */} | ||||
|       </Flex> | ||||
|     </Flex> | ||||
|   ); | ||||
|   | ||||
							
								
								
									
										0
									
								
								front/src/components/ui/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								front/src/components/ui/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										39
									
								
								front/src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								front/src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| "use client" | ||||
|  | ||||
| import { RestErrorResponse } from "@/back-api"; | ||||
| import { HStack } from "@/ui"; | ||||
|  | ||||
| // export const toaster = createToaster({ | ||||
| //   placement: "bottom-end", | ||||
| //   pauseOnPageIdle: true, | ||||
| // }) | ||||
|  | ||||
| // export const toasterAPIError = (error: RestErrorResponse) => { | ||||
| //   toaster.create({ | ||||
| //     title: `[${error.status}] ${error.statusMessage}`, | ||||
| //     description: error.message, | ||||
| //   }); | ||||
| // }; | ||||
|  | ||||
| // export const Toaster = () => { | ||||
| //   return ( | ||||
| //     <Portal> | ||||
| //       <ArkToaster toaster={toaster}> | ||||
| //         {(toast) => ( | ||||
| //           <Toast.Root> | ||||
| //             <HStack spacing="1" style={{ width: "100%" }}> | ||||
| //               {toast.title && <Toast.Title>{toast.title}</Toast.Title>} | ||||
| //               {toast.description && ( | ||||
| //                 <Toast.Description>{toast.description}</Toast.Description> | ||||
| //               )} | ||||
| //             </HStack> | ||||
| //             {toast.action && ( | ||||
| //               <Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger> | ||||
| //             )} | ||||
| //             {toast.meta?.closable && <Toast.CloseTrigger />} | ||||
| //           </Toast.Root> | ||||
| //         )} | ||||
| //       </ArkToaster> | ||||
| //     </Portal> | ||||
| //   ) | ||||
| // } | ||||
| @@ -1,28 +0,0 @@ | ||||
| import dayjs from 'dayjs'; | ||||
| import 'dayjs/locale/fr'; | ||||
| import advancedFormat from 'dayjs/plugin/advancedFormat'; | ||||
| import customParseFormat from 'dayjs/plugin/customParseFormat'; | ||||
| import dayOfYear from 'dayjs/plugin/dayOfYear'; | ||||
| import duration from 'dayjs/plugin/duration'; | ||||
| import isBetween from 'dayjs/plugin/isBetween'; | ||||
| import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; | ||||
| import isToday from 'dayjs/plugin/isToday'; | ||||
| import isTomorrow from 'dayjs/plugin/isTomorrow'; | ||||
| import isYesterday from 'dayjs/plugin/isYesterday'; | ||||
| import quarterOfYear from 'dayjs/plugin/quarterOfYear'; | ||||
| import relativeTime from 'dayjs/plugin/relativeTime'; | ||||
| import weekOfYear from 'dayjs/plugin/weekOfYear'; | ||||
|  | ||||
| dayjs.locale('fr'); | ||||
| dayjs.extend(relativeTime); | ||||
| dayjs.extend(customParseFormat); | ||||
| dayjs.extend(weekOfYear); | ||||
| dayjs.extend(isSameOrAfter); | ||||
| dayjs.extend(isToday); | ||||
| dayjs.extend(isTomorrow); | ||||
| dayjs.extend(isYesterday); | ||||
| dayjs.extend(dayOfYear); | ||||
| dayjs.extend(isBetween); | ||||
| dayjs.extend(advancedFormat); | ||||
| dayjs.extend(quarterOfYear); | ||||
| dayjs.extend(duration); | ||||
| @@ -1,2 +0,0 @@ | ||||
| import './axios'; | ||||
| import './dayjs'; | ||||
| @@ -1,4 +1,4 @@ | ||||
| export const BASE_WRAP_SPACING = { base: "5px", md: "10px", lg: "20px" }; | ||||
| export const BASE_WRAP_WIDTH = { base: "90%", md: "45%", lg: "270px" }; | ||||
| export const BASE_WRAP_HEIGHT = { base: "75px", lg: "120px" }; | ||||
| export const BASE_WRAP_ICON_SIZE = { base: "50px", lg: "100px" }; | ||||
| export const BASE_WRAP_SPACING = "5px";   // { base: "5px", md: "10px", lg: "20px" }; | ||||
| export const BASE_WRAP_WIDTH = "90%";     // { base: "90%", md: "45%", lg: "270px" }; | ||||
| export const BASE_WRAP_HEIGHT = "75px";   // { base: "75px", lg: "120px" }; | ||||
| export const BASE_WRAP_ICON_SIZE = "50px";// { base: "50px", lg: "100px" }; | ||||
| @@ -25,9 +25,9 @@ const environment_back_prod: Environment = { | ||||
|     karso: `${serverSSOAddress}/karso/api`, | ||||
|   }, | ||||
|   ssoSite: `${serverSSOAddress}/karso/`, | ||||
|   ssoSignIn: `${serverSSOAddress}/karso/signin/karusic-dev/`, | ||||
|   ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`, | ||||
|   ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`, | ||||
|   ssoSignIn: `${serverSSOAddress}/karso/signin/karusic/`, | ||||
|   ssoSignUp: `${serverSSOAddress}/karso/signup/karusic/`, | ||||
|   ssoSignOut: `${serverSSOAddress}/karso/signout/karusic/`, | ||||
|   tokenStoredInPermanentStorage: false, | ||||
| }; | ||||
|  | ||||
| @@ -45,7 +45,7 @@ const environment_local: Environment = { | ||||
|   ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`, | ||||
|   ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`, | ||||
|   tokenStoredInPermanentStorage: false, | ||||
|   //replaceDataToRealServer: true, | ||||
|   replaceDataToRealServer: true, | ||||
| }; | ||||
|  | ||||
| const environment_full_local: Environment = { | ||||
| @@ -80,7 +80,6 @@ const environment_hybrid: Environment = { | ||||
|   tokenStoredInPermanentStorage: false, | ||||
| }; | ||||
|  | ||||
| export const environment = environment_local; | ||||
|  | ||||
| /** | ||||
|  * Check if the current environment is for development | ||||
| @@ -90,6 +89,9 @@ export const isDevelopmentEnvironment = () => { | ||||
|   return import.meta.env.MODE === 'development'; | ||||
| }; | ||||
|  | ||||
| export const environment = isDevelopmentEnvironment() ? environment_local : environment_back_prod; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * get the current REST api URL. Depend on the VITE_API_BASE_URL env variable. | ||||
|  * @returns The URL with http(s)://*** | ||||
|   | ||||
| @@ -1,26 +1,27 @@ | ||||
| import { Box, Button, Center, Heading, Text } from '@chakra-ui/react'; | ||||
|  | ||||
| import { MdControlCamera } from 'react-icons/md'; | ||||
|  | ||||
| import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Link, Text } from '@/ui'; | ||||
|  | ||||
| export const Error401 = () => { | ||||
|   return ( | ||||
|     <> | ||||
|       <TopBar /> | ||||
|       <PageLayoutInfoCenter padding="25px"> | ||||
|         <Center> | ||||
|       <PageLayoutInfoCenter padding="25px" width="75%"> | ||||
|         <Flex align="center"> | ||||
|           <MdControlCamera size="250px" color="red.600" /> | ||||
|         </Center> | ||||
|         <Box textAlign="center"> | ||||
|           <Heading>Erreur 401</Heading> | ||||
|         </Flex> | ||||
|         <Flex style={{ textAlign: "center" }}> | ||||
|           <Text fontSize="px">Erreur 401</Text> | ||||
|           <Text color="red.600"> | ||||
|             Vous n'êtes pas autorisé a accéder a ce contenu. | ||||
|           </Text> | ||||
|           <Button as="a" variant="link" href="/"> | ||||
|           <Link href="/"> | ||||
|             Retour à l'accueil | ||||
|           </Button> | ||||
|         </Box> | ||||
|           </Link> | ||||
|         </Flex> | ||||
|       </PageLayoutInfoCenter> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,24 +1,26 @@ | ||||
| import { Box, Button, Center, Heading, Text } from '@chakra-ui/react'; | ||||
| import { MdDangerous } from 'react-icons/md'; | ||||
| import { MdControlCamera } from 'react-icons/md'; | ||||
|  | ||||
| import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Link, Text } from '@/ui'; | ||||
|  | ||||
| export const Error403 = () => { | ||||
|   return ( | ||||
|     <> | ||||
|       <TopBar /> | ||||
|       <PageLayoutInfoCenter padding="25px"> | ||||
|         <Center> | ||||
|           <MdDangerous size="250px" color="orange.600" /> | ||||
|         </Center> | ||||
|         <Box textAlign="center"> | ||||
|           <Heading>Erreur 401</Heading> | ||||
|           <Text color="orange.600">Cette page vous est interdite</Text> | ||||
|           <Button as="a" variant="link" href="/"> | ||||
|       <PageLayoutInfoCenter padding="25px" width="75%"> | ||||
|         <Flex align="center"> | ||||
|           <MdControlCamera size="250px" color="red.600" /> | ||||
|         </Flex> | ||||
|         <Flex style={{ textAlign: "center" }}> | ||||
|           <Text fontSize="px">Erreur 403</Text> | ||||
|           <Text color="red.600"> | ||||
|             Cette page vous est interdite. | ||||
|           </Text> | ||||
|           <Link href="/"> | ||||
|             Retour à l'accueil | ||||
|           </Button> | ||||
|         </Box> | ||||
|           </Link> | ||||
|         </Flex> | ||||
|       </PageLayoutInfoCenter> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,26 +1,26 @@ | ||||
| import { Box, Button, Center, Heading, Text } from '@chakra-ui/react'; | ||||
| import { MdSignpost } from 'react-icons/md'; | ||||
| import { MdControlCamera } from 'react-icons/md'; | ||||
|  | ||||
| import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { Flex, Link, Text } from '@/ui'; | ||||
|  | ||||
| export const Error404 = () => { | ||||
|   return ( | ||||
|     <> | ||||
|       <TopBar /> | ||||
|       <PageLayoutInfoCenter padding="25px"> | ||||
|         <Center> | ||||
|           <MdSignpost size="250px" /> | ||||
|         </Center> | ||||
|         <Box textAlign="center"> | ||||
|           <Heading>Erreur 404</Heading> | ||||
|           <Text color="gray.600"> | ||||
|             Cette page n'existe plus ou l'URL a changé | ||||
|       <PageLayoutInfoCenter padding="25px" width="75%"> | ||||
|         <Flex align="center"> | ||||
|           <MdControlCamera size="250px" color="red.600" /> | ||||
|         </Flex> | ||||
|         <Flex style={{ textAlign: "center" }}> | ||||
|           <Text fontSize="px">Erreur 404</Text> | ||||
|           <Text color="red.600"> | ||||
|             Cette page n'existe plus ou l'URL a changé. | ||||
|           </Text> | ||||
|           <Button as="a" variant="link" href="/"> | ||||
|           <Link href="/"> | ||||
|             Retour à l'accueil | ||||
|           </Button> | ||||
|         </Box> | ||||
|           </Link> | ||||
|         </Flex> | ||||
|       </PageLayoutInfoCenter> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,48 +1,44 @@ | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
| import { useDisclosure } from '@/utils/disclosure'; | ||||
| import React, { FC } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Alert, | ||||
|   AlertDescription, | ||||
|   AlertIcon, | ||||
|   AlertTitle, | ||||
|   Box, | ||||
|   Button, | ||||
|   Collapse, | ||||
|   useDisclosure, | ||||
| } from '@chakra-ui/react'; | ||||
|  | ||||
| import { | ||||
|   FallbackProps, | ||||
|   ErrorBoundary as ReactErrorBoundary, | ||||
| } from 'react-error-boundary'; | ||||
| import { LuChevronDown, LuChevronUp } from 'react-icons/lu'; | ||||
| import { LuChevronUp, LuChevronDown } from 'react-icons/lu'; | ||||
|  | ||||
| const ErrorFallback = ({ error }: FallbackProps) => { | ||||
|   const { isOpen, onToggle } = useDisclosure(); | ||||
|   const { open, onToggle } = useDisclosure(); | ||||
|   return ( | ||||
|     <Box p="4" m="auto"> | ||||
|       <Alert status="error" borderRadius="md"> | ||||
|         <AlertIcon /> | ||||
|         <Box flex="1"> | ||||
|     <Flex direction="column" style={{ padding: 4, margin: "auto", background: "red.500" }}> | ||||
|       <Text color='red'>An unexpected error has occurred.</Text> | ||||
|       <Text>Message: {error.message}</Text> | ||||
|       {/* | ||||
|       <AlertRoot status="error" borderRadius="md"> | ||||
|         <Flex style={{ flex: "1" }}> | ||||
|           <AlertTitle>An unexpected error has occurred.</AlertTitle> | ||||
|           <AlertDescription display="block" lineHeight="1.4"> | ||||
|             <Button | ||||
|               variant="link" | ||||
|               //theme="@secondary" | ||||
|               color="red.800" | ||||
|               size="sm" | ||||
|               rightIcon={isOpen ? <LuChevronUp /> : <LuChevronDown />} | ||||
|               //size="sm" | ||||
|               onClick={onToggle} | ||||
|             > | ||||
|               Show details | ||||
|               Show details {open ? <LuChevronUp /> : <LuChevronDown />} | ||||
|             </Button> | ||||
|             <Collapse in={isOpen} animateOpacity> | ||||
|               <Box mt={4} fontFamily="monospace"> | ||||
|             <Collapsible.Root open={open}> | ||||
|               <Collapsible.Content> | ||||
|                 <Flex mt={4} fontFamily="monospace"> | ||||
|                   {error.message} | ||||
|               </Box> | ||||
|             </Collapse> | ||||
|                 </Flex> | ||||
|               </Collapsible.Content> | ||||
|             </Collapsible.Root> | ||||
|           </AlertDescription> | ||||
|         </Box> | ||||
|       </Alert> | ||||
|     </Box> | ||||
|         </Flex> | ||||
|       </AlertRoot> */} | ||||
|     </Flex > | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,15 @@ | ||||
| import { createIcon } from '@chakra-ui/react'; | ||||
|  | ||||
| export const DoubleArrowIcon = createIcon({ | ||||
|   displayName: 'DoubleArrowIcon', | ||||
|   viewBox: '0 0 24 24', | ||||
|   path: ( | ||||
|     <path | ||||
|       fillRule="evenodd" | ||||
|       clipRule="evenodd" | ||||
|       d="M1.293 12.207a1 1 0 0 1 0-1.414l6.364-6.364A1 1 0 0 1 9.07 5.843L4.414 10.5h15.172l-4.657-4.657a1 1 0 0 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 0 1-1.414-1.414l4.657-4.657H4.414l4.657 4.657a1 1 0 1 1-1.414 1.414l-6.364-6.364Z" | ||||
|       fill="currentColor" | ||||
|     /> | ||||
|   ), | ||||
| }); | ||||
|  | ||||
| export const DoubleArrowIcon = <></>; | ||||
| // createIcon({ | ||||
| //   displayName: 'DoubleArrowIcon', | ||||
| //   viewBox: '0 0 24 24', | ||||
| //   path: ( | ||||
| //     <path | ||||
| //       fillRule="evenodd" | ||||
| //       clipRule="evenodd" | ||||
| //       d="M1.293 12.207a1 1 0 0 1 0-1.414l6.364-6.364A1 1 0 0 1 9.07 5.843L4.414 10.5h15.172l-4.657-4.657a1 1 0 0 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 0 1-1.414-1.414l4.657-4.657H4.414l4.657 4.657a1 1 0 1 1-1.414 1.414l-6.364-6.364Z" | ||||
| //       fill="currentColor" | ||||
| //     /> | ||||
| //   ), | ||||
| // }); | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| import { StrictMode } from 'react'; | ||||
|  | ||||
| import ReactDOM from 'react-dom/client'; | ||||
|  | ||||
| import App from '@/App'; | ||||
|  | ||||
| import { ThemeProvider } from './theme/ThemeContext'; | ||||
| import { StrictMode } from 'react'; | ||||
| // Render the app | ||||
| const rootElement = document.getElementById('root'); | ||||
| if (rootElement && !rootElement.innerHTML) { | ||||
|   const root = ReactDOM.createRoot(rootElement); | ||||
|   root.render( | ||||
|     <StrictMode> | ||||
|       <ThemeProvider> | ||||
|         <App /> | ||||
|       </ThemeProvider> | ||||
|     </StrictMode> | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { AudioPlayer } from '@/components/AudioPlayer'; | ||||
| import { ErrorBoundary } from '@/errors/ErrorBoundary'; | ||||
| import { AppRoutes } from '@/scene/AppRoutes'; | ||||
| import { Text } from '@/ui'; | ||||
| import { ServiceContextProvider } from '@/service/ServiceContext'; | ||||
|  | ||||
| export const App = () => { | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import { TrackRoutes } from '@/scene/track/TrackRoutes'; | ||||
| import { useHasRight } from '@/service/session'; | ||||
| import { SettingsPage } from './home/SettingsPage'; | ||||
| import { AddPage } from './home/AddPage'; | ||||
| import { OnAirPage } from './onAir/OnAirPage'; | ||||
|  | ||||
| export const AppRoutes = () => { | ||||
|   const { isReadable } = useHasRight('user'); | ||||
| @@ -34,6 +35,7 @@ export const AppRoutes = () => { | ||||
|             <Route path="help" element={<HelpPage />} /> | ||||
|             <Route path="settings" element={<SettingsPage />} /> | ||||
|             <Route path="add" element={<AddPage />} /> | ||||
|             <Route path="on-air/*" element={<OnAirPage />} /> | ||||
|             <Route path="artist/*" element={<ArtistRoutes />} /> | ||||
|             <Route path="album/*" element={<AlbumRoutes />} /> | ||||
|             <Route path="gender/*" element={<GenderRoutes />} /> | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuDisc3 } from 'react-icons/lu'; | ||||
|  | ||||
| import { MdEdit } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| @@ -10,18 +9,17 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp'; | ||||
| import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; | ||||
| import { DisplayTrack } from '@/components/track/DisplayTrack'; | ||||
| import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificAlbum } from '@/service/Album'; | ||||
| import { useTracksOfAnAlbum } from '@/service/Track'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const AlbumDetailPage = () => { | ||||
|   const { albumId } = useParams(); | ||||
|   const albumIdInt = albumId ? parseInt(albumId, 10) : undefined; | ||||
|   const { mode } = useThemeMode(); | ||||
|   const { playInList } = useActivePlaylistService(); | ||||
|   const { dataAlbum } = useSpecificAlbum(albumIdInt); | ||||
|   const { tracksOnAnAlbum } = useTracksOfAnAlbum(albumIdInt); | ||||
| @@ -48,7 +46,7 @@ export const AlbumDetailPage = () => { | ||||
|     return ( | ||||
|       <> | ||||
|         <TopBar title="Album detail" /> | ||||
|         <PageLayoutInfoCenter> | ||||
|         <PageLayoutInfoCenter width="75%"> | ||||
|           Fail to load artist id: {albumId} | ||||
|         </PageLayoutInfoCenter> | ||||
|       </> | ||||
| @@ -66,20 +64,23 @@ export const AlbumDetailPage = () => { | ||||
|           <MdEdit /> | ||||
|         </Button> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|       <PageLayout | ||||
|         data-testid="Album-detail-page_layout"> | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|         > | ||||
|           <Covers | ||||
|             data={dataAlbum?.covers} | ||||
|             iconEmpty={LuDisc3} | ||||
|             // TODO: iconEmpty={LuDisc3} | ||||
|             slideshow | ||||
|           /> | ||||
|           <Flex direction="column" width="80%" marginRight="auto"> | ||||
|           <Flex direction="column" style={{ width: "80%", marginRight: "auto" }}> | ||||
|             <Text fontSize="24px" fontWeight="bold"> | ||||
|               {dataAlbum?.name} | ||||
|             </Text> | ||||
| @@ -94,24 +95,27 @@ export const AlbumDetailPage = () => { | ||||
|  | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap={BASE_WRAP_SPACING} | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|           gap={BASE_WRAP_SPACING} style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|           data-test-id="Album-detail-page_flex-list" | ||||
|         > | ||||
|           {tracksOnAnAlbum?.map((data) => ( | ||||
|             <Box | ||||
|               minWidth="100%" | ||||
|               //height="60px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Flex | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 //height="60px" | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrackFull | ||||
| @@ -126,8 +130,9 @@ export const AlbumDetailPage = () => { | ||||
|                   }, | ||||
|                   { name: 'Add Playlist', onClick: () => { } }, | ||||
|                 ]} | ||||
|                 data-testid="Album-detail-page_display-detail" | ||||
|               /> | ||||
|             </Box> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Wrap, WrapItem } from '@chakra-ui/react'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { EmptyEnd } from '@/components/EmptyEnd'; | ||||
| @@ -10,13 +9,13 @@ import { SearchInput } from '@/components/SearchInput'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { DisplayAlbum } from '@/components/album/DisplayAlbum'; | ||||
| import { useOrderedAlbums } from '@/service/Album'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { BASE_WRAP_SPACING, BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_WIDTH, BASE_WRAP_HEIGHT } from '@/constants/genericSpacing'; | ||||
| import { Button, Flex, HStack } from '@/ui'; | ||||
|  | ||||
| export const AlbumsPage = () => { | ||||
|   const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); | ||||
|   const { isLoading, dataAlbums } = useOrderedAlbums(filterTitle); | ||||
|   const { mode } = useThemeMode(); | ||||
|   const navigate = useNavigate(); | ||||
|   const onSelectItem = (albumId: number) => { | ||||
|     navigate(`/album/${albumId}`); | ||||
| @@ -26,7 +25,7 @@ export const AlbumsPage = () => { | ||||
|     return ( | ||||
|       <> | ||||
|         <TopBar title="All Albums" /> | ||||
|         <PageLayoutInfoCenter>No Album available</PageLayoutInfoCenter> | ||||
|         <PageLayoutInfoCenter width="75%">No Album available</PageLayoutInfoCenter> | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
| @@ -37,29 +36,31 @@ export const AlbumsPage = () => { | ||||
|         <SearchInput onChange={setFilterTitle} /> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|         <Wrap spacing={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}> | ||||
|           {dataAlbums.map((data) => ( | ||||
|             <WrapItem | ||||
|               width={BASE_WRAP_WIDTH} | ||||
|               height={BASE_WRAP_HEIGHT} | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Button | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 width: BASE_WRAP_WIDTH, | ||||
|                 height: BASE_WRAP_HEIGHT, | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover={ | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|                 alignContent: "flex-start", | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data.id)} | ||||
|             > | ||||
|               <DisplayAlbum dataAlbum={data} /> | ||||
|             </WrapItem> | ||||
|             </Button> | ||||
|           ))} | ||||
|         </Wrap> | ||||
|         </HStack> | ||||
|         <EmptyEnd /> | ||||
|       </PageLayout> | ||||
|       </PageLayout > | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuDisc3, LuUser } from 'react-icons/lu'; | ||||
| import { MdEdit, MdPerson } from 'react-icons/md'; | ||||
|  | ||||
| import { MdEdit } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| import { Covers } from '@/components/Cover'; | ||||
| @@ -15,13 +14,13 @@ import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificAlbum } from '@/service/Album'; | ||||
| import { useSpecificArtist } from '@/service/Artist'; | ||||
| import { useTracksOfAnAlbum } from '@/service/Track'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const ArtistAlbumDetailPage = () => { | ||||
|   const { artistId, albumId } = useParams(); | ||||
|   const artistIdInt = artistId ? parseInt(artistId, 10) : undefined; | ||||
|   const albumIdInt = albumId ? parseInt(albumId, 10) : undefined; | ||||
|   const { mode } = useThemeMode(); | ||||
|   const { playInList } = useActivePlaylistService(); | ||||
|   const { dataArtist } = useSpecificArtist(artistIdInt); | ||||
|   const { dataAlbum } = useSpecificAlbum(albumIdInt); | ||||
| @@ -61,14 +60,19 @@ export const ArtistAlbumDetailPage = () => { | ||||
|           <> | ||||
|             <Button | ||||
|               {...BUTTON_TOP_BAR_PROPERTY} | ||||
|               marginRight="auto" | ||||
|  | ||||
|               style={{ | ||||
|                 marginRight: "auto" | ||||
|               }} | ||||
|               onClick={() => navigate(`/artist/${dataArtist.id}`)} | ||||
|             > | ||||
|               <Covers | ||||
|                 data={dataArtist?.covers} | ||||
|                 size="35px" | ||||
|                 borderRadius="full" | ||||
|                 iconEmpty={MdPerson} | ||||
|                 style={{ | ||||
|                   borderRadius: "full", | ||||
|                 }} | ||||
|               // TODO: iconEmpty={MdPerson} | ||||
|               /> | ||||
|               <Text fontSize="24px" fontWeight="bold"> | ||||
|                 {dataArtist?.name} | ||||
| @@ -88,17 +92,20 @@ export const ArtistAlbumDetailPage = () => { | ||||
|       <PageLayout> | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|           gap="10px" | ||||
|         > | ||||
|           <Covers | ||||
|             data={dataAlbum?.covers} | ||||
|             iconEmpty={LuDisc3} | ||||
|             // TODO: iconEmpty={LuDisc3} | ||||
|             slideshow | ||||
|           /> | ||||
|           <Flex direction="column" width="80%" marginRight="auto"> | ||||
|           <Flex direction="column" | ||||
|             style={{ width: "80%", marginRight: "auto" }}> | ||||
|             <Text fontSize="24px" fontWeight="bold"> | ||||
|               {dataAlbum?.name} | ||||
|             </Text> | ||||
| @@ -114,23 +121,28 @@ export const ArtistAlbumDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap="20px" | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|  | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {tracksOnAnAlbum?.map((data) => ( | ||||
|             <Box | ||||
|               minWidth="100%" | ||||
|               height="60px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Flex | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|  | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 height: "60px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrack | ||||
| @@ -148,7 +160,7 @@ export const ArtistAlbumDetailPage = () => { | ||||
|                   { name: 'Add Playlist', onClick: () => { } }, | ||||
|                 ]} | ||||
|               /> | ||||
|             </Box> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Button, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react'; | ||||
| import { LuUser } from 'react-icons/lu'; | ||||
|  | ||||
| import { MdEdit, MdGroup } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| @@ -12,13 +11,13 @@ import { DisplayAlbumId } from '@/components/album/DisplayAlbumId'; | ||||
| import { ArtistEditPopUp } from '@/components/popup/ArtistEditPopUp'; | ||||
| import { useSpecificArtist } from '@/service/Artist'; | ||||
| import { useAlbumIdsOfAnArtist } from '@/service/Track'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { Button, Flex, HStack, Text } from '@/ui'; | ||||
|  | ||||
| export const ArtistDetailPage = () => { | ||||
|   const { artistId } = useParams(); | ||||
|   const artistIdInt = artistId ? parseInt(artistId, 10) : undefined; | ||||
|   const { mode } = useThemeMode(); | ||||
|   const navigate = useNavigate(); | ||||
|   const onSelectItem = (albumId: number) => { | ||||
|     navigate(`/artist/${artistIdInt}/album/${albumId}`); | ||||
| @@ -42,7 +41,7 @@ export const ArtistDetailPage = () => { | ||||
|         <> | ||||
|           <Button | ||||
|             {...BUTTON_TOP_BAR_PROPERTY} | ||||
|             marginRight="auto" | ||||
|             style={{ marginRight: "auto" }} | ||||
|             onClick={() => navigate(`/artist/all`)} | ||||
|           > | ||||
|             <MdGroup height="full" /> | ||||
| @@ -65,13 +64,13 @@ export const ArtistDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           margin="0 auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|         > | ||||
|           <Covers | ||||
|             data={dataArtist?.covers} | ||||
|             iconEmpty={LuUser} | ||||
|             // TODO: iconEmpty={LuUser} | ||||
|             slideshow | ||||
|           /> | ||||
|           <Flex direction="column" width="80%" marginRight="auto"> | ||||
| @@ -89,27 +88,29 @@ export const ArtistDetailPage = () => { | ||||
|           </Flex> | ||||
|         </Flex> | ||||
|  | ||||
|         <Wrap spacing={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", justifyContent: "center" }}> | ||||
|           {albumIdsOfAnArtist?.map((data) => ( | ||||
|             <WrapItem | ||||
|               width={BASE_WRAP_WIDTH} | ||||
|               height={BASE_WRAP_HEIGHT} | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Button | ||||
|               key={data} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 alignContent: "flex-start", | ||||
|                 width: BASE_WRAP_WIDTH, | ||||
|                 height: BASE_WRAP_HEIGHT, | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover={ | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <DisplayAlbumId id={data} key={data} /> | ||||
|             </WrapItem> | ||||
|             </Button> | ||||
|           ))} | ||||
|         </Wrap> | ||||
|         </HStack> | ||||
|         <EmptyEnd /> | ||||
|         <Routes> | ||||
|           <Route path="edit-artist/:artistId" element={<ArtistEditPopUp />} /> | ||||
|   | ||||
| @@ -1,56 +1,91 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react'; | ||||
| import { LuUser } from 'react-icons/lu'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { Artist } from '@/back-api'; | ||||
| import { Artist, Track } from '@/back-api'; | ||||
| import { Covers } from '@/components/Cover'; | ||||
| import { EmptyEnd } from '@/components/EmptyEnd'; | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { SearchInput } from '@/components/SearchInput'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { useArtistService, useOrderedArtists } from '@/service/Artist'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_SPACING, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { useOrderedArtists } from '@/service/Artist'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_HEIGHT, BASE_WRAP_ICON_SIZE, BASE_WRAP_WIDTH } from '@/constants/genericSpacing'; | ||||
| import { MdOutlineForkRight } from 'react-icons/md'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { shuffleArray } from '@/utils/arrayTools'; | ||||
| import { useTrackService } from '@/service/Track'; | ||||
| import { DataTools, TypeCheck } from '@/utils/data-tools'; | ||||
| import { Button, Flex, HStack, Span, Text } from '@/ui'; | ||||
| const LIMIT_RANDOM_VALUES = 25; | ||||
|  | ||||
| export const ArtistsPage = () => { | ||||
|   const { mode } = useThemeMode(); | ||||
|   const [filterName, setFilterName] = useState<string | undefined>(undefined); | ||||
|   const navigate = useNavigate(); | ||||
|   const { playInList } = useActivePlaylistService(); | ||||
|   const onSelectItem = (data: Artist) => { | ||||
|     navigate(`/artist/${data.id}/`); | ||||
|   }; | ||||
|   const { dataArtist } = useOrderedArtists(filterName); | ||||
|   const { store: trackStore } = useTrackService(); | ||||
|   const onRandomPlay = () => { | ||||
|     const data = shuffleArray(dataArtist) | ||||
|     const playingList: number[] = []; | ||||
|     for (let i = 0; i < Math.min(data.length, LIMIT_RANDOM_VALUES); i++) { | ||||
|       const selectedArtist: Artist = data[i]; | ||||
|       const listArtistTracks: Track[] = DataTools.getsWhere( | ||||
|         trackStore.data, | ||||
|         [ | ||||
|           { | ||||
|             check: TypeCheck.CONTAINS, | ||||
|             key: 'artists', | ||||
|             value: selectedArtist.id, | ||||
|           }, | ||||
|         ], | ||||
|         [] | ||||
|       ); | ||||
|       if (listArtistTracks.length === 0) { | ||||
|         continue; | ||||
|       } | ||||
|       playingList.push(listArtistTracks[Math.floor(Math.random() * listArtistTracks.length)].id); | ||||
|     } | ||||
|     playInList(0, playingList) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <TopBar title="All artists"> | ||||
|         <SearchInput onChange={setFilterName} /> | ||||
|         <Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onRandomPlay}> | ||||
|           <MdOutlineForkRight /> | ||||
|         </Button> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|         <Wrap spacing={BASE_WRAP_SPACING} marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ flexWrap: "wrap", /*spacing={BASE_WRAP_SPACING}*/ margin: "0 auto", padding: "20px", alignContent: "center" }}> | ||||
|           {dataArtist?.map((data) => ( | ||||
|             <WrapItem | ||||
|               width={BASE_WRAP_WIDTH} | ||||
|               height={BASE_WRAP_HEIGHT} | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Button | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 width: BASE_WRAP_WIDTH, | ||||
|                 height: BASE_WRAP_HEIGHT, | ||||
|                 alignContent: "flex-start", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover:{ | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <Flex direction="row" width="full" height="full"> | ||||
|               <Flex direction="row" width="100%" height="full"> | ||||
|                 <Covers | ||||
|                   data={data.covers} | ||||
|                   size={BASE_WRAP_ICON_SIZE} | ||||
|                   height="full" | ||||
|                   iconEmpty={LuUser} | ||||
|                   style={{ height: "100%" }} | ||||
|                 // iconEmpty={LuUser} | ||||
|                 /> | ||||
|                 <Flex | ||||
|                   direction="column" | ||||
| @@ -58,25 +93,30 @@ export const ArtistsPage = () => { | ||||
|                   maxWidth="150px" | ||||
|                   height="full" | ||||
|                   paddingLeft="5px" | ||||
|                   overflowX="hidden" | ||||
|                   style={{ overflowX: "hidden" }} | ||||
|                 > | ||||
|                   <Text | ||||
|                     as="span" | ||||
|                     align="left" | ||||
|                   /*align="left"*/ | ||||
|                   /*noOfLines={[1, 2]}*/ | ||||
|                   > | ||||
|                     <Span | ||||
|                       fontSize="20px" | ||||
|                       fontWeight="bold" | ||||
|                       userSelect="none" | ||||
|                     marginRight="auto" | ||||
|                     overflow="hidden" | ||||
|                     noOfLines={[1, 2]} | ||||
|                       style={{ | ||||
|                         marginRight: "auto", | ||||
|                         overflow: "hidden", | ||||
|                       }} | ||||
|                     > | ||||
|                       {data.name} | ||||
|                     </Span> | ||||
|  | ||||
|                   </Text> | ||||
|                 </Flex> | ||||
|               </Flex> | ||||
|             </WrapItem> | ||||
|             </Button> | ||||
|           ))} | ||||
|         </Wrap> | ||||
|         </HStack> | ||||
|         <EmptyEnd /> | ||||
|       </PageLayout> | ||||
|     </> | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Box, Button, Flex, Text } from '@chakra-ui/react'; | ||||
| import { LuDisc3 } from 'react-icons/lu'; | ||||
|  | ||||
| import { MdEdit } from 'react-icons/md'; | ||||
| import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| @@ -10,18 +9,17 @@ import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { GenderEditPopUp } from '@/components/popup/GenderEditPopUp'; | ||||
| import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; | ||||
| import { DisplayTrack } from '@/components/track/DisplayTrack'; | ||||
| import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useSpecificGender } from '@/service/Gender'; | ||||
| import { useTracksOfAGender } from '@/service/Track'; | ||||
| //import { useTracksOfAGender } from '@/service/Track'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Button, Flex, Text } from '@/ui'; | ||||
|  | ||||
| export const GenderDetailPage = () => { | ||||
|   const { genderId } = useParams(); | ||||
|   const genderIdInt = genderId ? parseInt(genderId, 10) : undefined; | ||||
|   const { mode } = useThemeMode(); | ||||
|   const { playInList } = useActivePlaylistService(); | ||||
|   const { dataGender } = useSpecificGender(genderIdInt); | ||||
|   const { tracksOnAGender } = useTracksOfAGender(genderIdInt); | ||||
| @@ -69,17 +67,19 @@ export const GenderDetailPage = () => { | ||||
|       <PageLayout> | ||||
|         <Flex | ||||
|           direction="row" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|         > | ||||
|           <Covers | ||||
|             data={dataGender?.covers} | ||||
|             iconEmpty={LuDisc3} | ||||
|             // TODO: iconEmpty={LuDisc3} | ||||
|             slideshow | ||||
|           /> | ||||
|           <Flex direction="column" width="80%" marginRight="auto"> | ||||
|           <Flex direction="column" style={{ width: "80%", marginRight: "auto" }}> | ||||
|             <Text fontSize="24px" fontWeight="bold"> | ||||
|               {dataGender?.name} | ||||
|             </Text> | ||||
| @@ -92,23 +92,26 @@ export const GenderDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap="20px" | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {tracksOnAGender?.map((data) => ( | ||||
|             <Box | ||||
|               minWidth="100%" | ||||
|               //height="60px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Flex | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 //height="60px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrackFull | ||||
| @@ -124,7 +127,7 @@ export const GenderDetailPage = () => { | ||||
|                   { name: 'Add Playlist', onClick: () => { } }, | ||||
|                 ]} | ||||
|               /> | ||||
|             </Box> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { Wrap, WrapItem } from '@chakra-ui/react'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { EmptyEnd } from '@/components/EmptyEnd'; | ||||
| @@ -10,12 +9,12 @@ import { SearchInput } from '@/components/SearchInput'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { DisplayGender } from '@/components/gender/DisplayGender'; | ||||
| import { useOrderedGenders } from '@/service/Gender'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Flex, HStack } from '@/ui'; | ||||
|  | ||||
| export const GendersPage = () => { | ||||
|   const [filterTitle, setFilterTitle] = useState<string | undefined>(undefined); | ||||
|   const { isLoading, dataGenders } = useOrderedGenders(filterTitle); | ||||
|   const { mode } = useThemeMode(); | ||||
|   const navigate = useNavigate(); | ||||
|   const onSelectItem = (genderId: number) => { | ||||
|     navigate(`/gender/${genderId}`); | ||||
| @@ -36,27 +35,31 @@ export const GendersPage = () => { | ||||
|         <SearchInput onChange={setFilterTitle} /> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|         <Wrap spacing="20px" marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ | ||||
|           // wrap: "wrap", | ||||
|           /*spacing="20px"*/ margin: "0 auto 0 auto", padding: "20px", justifyContent: "center" | ||||
|         }}> | ||||
|           {dataGenders.map((data) => ( | ||||
|             <WrapItem | ||||
|               width="270px" | ||||
|               height="120px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Flex align="flex-start" | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|               style={{ | ||||
|                 width: "270px", | ||||
|                 height: "120px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 //background:{useColorModeValue('#FFFFFF88', '#00000088')}, | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data.id)} | ||||
|             > | ||||
|               <DisplayGender dataGender={data} /> | ||||
|             </WrapItem> | ||||
|             </Flex> | ||||
|           ))} | ||||
|         </Wrap> | ||||
|         </HStack> | ||||
|         <EmptyEnd /> | ||||
|       </PageLayout> | ||||
|     </> | ||||
|   | ||||
| @@ -1,18 +1,5 @@ | ||||
| import { useCallback, useState } from 'react'; | ||||
|  | ||||
| import { | ||||
|   Button, | ||||
|   Flex, | ||||
|   Input, | ||||
|   Table, | ||||
|   TableContainer, | ||||
|   Tbody, | ||||
|   Td, | ||||
|   Text, | ||||
|   Th, | ||||
|   Thead, | ||||
|   Tr, | ||||
| } from '@chakra-ui/react'; | ||||
| import { LuTrash } from 'react-icons/lu'; | ||||
| import { MdCloudUpload } from 'react-icons/md'; | ||||
|  | ||||
| @@ -38,6 +25,7 @@ import { useGenderService, useOrderedGenders } from '@/service/Gender'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
| import { useTrackService } from '@/service/Track'; | ||||
| import { isNullOrUndefined } from '@/utils/validator'; | ||||
| import { Button, Flex, Table, Text } from '@/ui'; | ||||
|  | ||||
| export class ElementList { | ||||
|   constructor( | ||||
| @@ -127,7 +115,7 @@ export const AddPage = () => { | ||||
|     updateNeedSend(); | ||||
|   }; | ||||
|  | ||||
|   const removeElementFromList = (data: FileParsedElement, value: any): void => { | ||||
|   const removeElementFromList = (data: FileParsedElement): void => { | ||||
|     const parsedElementTmp = [...parsedElement]; | ||||
|     for (let iii = 0; iii < parsedElementTmp.length; iii++) { | ||||
|       if (parsedElementTmp[iii] === data) { | ||||
| @@ -426,15 +414,17 @@ export const AddPage = () => { | ||||
|       <PageLayout> | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           width="80%" | ||||
|           marginX="auto" | ||||
|           padding="10px" | ||||
|           gap="10px" | ||||
|           style={{ | ||||
|             width: "80%", | ||||
|             margin: "0 auto 0 auto", | ||||
|             padding: "10px", | ||||
|           }} | ||||
|         > | ||||
|           <Flex direction="column" width="full"> | ||||
|           <Flex direction="column" style={{ width: "100%" }}> | ||||
|             <Flex> | ||||
|               <Text flex={1}>format:</Text> | ||||
|               <Text flex={4}> | ||||
|               <Text>format:</Text> | ||||
|               <Text> | ||||
|                 The format of the media permit to automatic find meta-data: | ||||
|                 <br /> | ||||
|                 Artist~album#idTrack-my name of my media.webm | ||||
| @@ -443,15 +433,15 @@ export const AddPage = () => { | ||||
|               </Text> | ||||
|             </Flex> | ||||
|             <Flex> | ||||
|               <Text flex={1}>Media:</Text> | ||||
|               <Input | ||||
|               <Text>Media:</Text> | ||||
|               {/* <Input | ||||
|                 flex={4} | ||||
|                 type="file" | ||||
|                 placeholder="Select a media file" | ||||
|                 accept=".webm" | ||||
|                 multiple | ||||
|                 onChange={onChangeFile} | ||||
|               /> | ||||
|               /> */} | ||||
|             </Flex> | ||||
|           </Flex> | ||||
|           {parsedElement && parsedElement.length !== 0 && ( | ||||
| @@ -483,102 +473,99 @@ export const AddPage = () => { | ||||
|                 addNewItem={addNewAlbum} | ||||
|                 suggestion={suggestedAlbum} | ||||
|               /> | ||||
|               <TableContainer> | ||||
|                 <Table | ||||
|                   variant="striped" | ||||
|                   colorScheme="teal" | ||||
|               <Table.Root | ||||
|                 background="gray.700" | ||||
|               > | ||||
|                   <Thead> | ||||
|                     <Tr> | ||||
|                       <Th>track ID</Th> | ||||
|                       <Th width="full">Title</Th> | ||||
|                       <Th>actions</Th> | ||||
|                     </Tr> | ||||
|                   </Thead> | ||||
|                   <Tbody> | ||||
|                 <Table.Header> | ||||
|                   <Table.Row> | ||||
|                     <Table.ColumnHeader>track ID</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader style={{ width: "100%" }}>Title</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader>actions</Table.ColumnHeader> | ||||
|                   </Table.Row> | ||||
|                 </Table.Header> | ||||
|                 <Table.Body> | ||||
|                   {parsedElement.map((data) => ( | ||||
|                       <Tr key={data.uniqueId}> | ||||
|                         <Td> | ||||
|                           <Input | ||||
|                     <Table.Row key={data.uniqueId}> | ||||
|                       <Table.Cell> | ||||
|                         {/* <Input | ||||
|                           type="number" | ||||
|                           pattern="[0-9]{0-4}" | ||||
|                           placeholder="e?" | ||||
|                           value={data.trackId} | ||||
|                           onChange={(e) => onTrackId(data, e.target.value)} | ||||
|                             backgroundColor={ | ||||
|                           background={ | ||||
|                             data.trackIdDetected === true | ||||
|                               ? 'darkred' | ||||
|                               : undefined | ||||
|                           } | ||||
|                           /> | ||||
|                         </Td> | ||||
|                         <Td> | ||||
|                           <Input | ||||
|                         /> */} | ||||
|                       </Table.Cell> | ||||
|                       <Table.Cell> | ||||
|                         {/* <Input | ||||
|                           type="text" | ||||
|                           placeholder="Name of the Media" | ||||
|                           value={data.title} | ||||
|                           onChange={(e) => onTitle(data, e.target.value)} | ||||
|                             backgroundColor={ | ||||
|                           background={ | ||||
|                             data.title === '' ? 'darkred' : undefined | ||||
|                           } | ||||
|                           /> | ||||
|                         /> */} | ||||
|                         {data.nameDetected === true && ( | ||||
|                           <> | ||||
|                             <br /> | ||||
|                               <Text as="span" color="@danger"> | ||||
|                             <Text style={{ background: "red" }}> | ||||
|                               ^^^This title already exist !!! | ||||
|                             </Text> | ||||
|                           </> | ||||
|                         )} | ||||
|                         </Td> | ||||
|                         <Td> | ||||
|                       </Table.Cell> | ||||
|                       <Table.Cell> | ||||
|                         <Button | ||||
|                             onClick={(e) => | ||||
|                               removeElementFromList(data, e.target) | ||||
|                           onClick={() => | ||||
|                             removeElementFromList(data) | ||||
|                           } | ||||
|                         > | ||||
|                           <LuTrash /> Remove | ||||
|                         </Button> | ||||
|                         </Td> | ||||
|                       </Tr> | ||||
|                       </Table.Cell> | ||||
|                     </Table.Row> | ||||
|                   ))} | ||||
|                   </Tbody> | ||||
|                 </Table> | ||||
|                 <Flex marginY="15px"> | ||||
|                 </Table.Body> | ||||
|               </Table.Root> | ||||
|               <Flex style={{ margin: "15px 0 15px 0" }}> | ||||
|                 <Button | ||||
|                     variant="@primary" | ||||
|                   //theme="@primary" | ||||
|                   onClick={sendFile} | ||||
|                     disabled={!needSend} | ||||
|                     marginLeft="auto" | ||||
|                     marginRight="30px" | ||||
|                   style={{ | ||||
|                     //disabled:!needSend, | ||||
|                     margin: "0 auto 0 30px" | ||||
|                   }} | ||||
|                 > | ||||
|                   <MdCloudUpload /> Upload | ||||
|                 </Button> | ||||
|               </Flex> | ||||
|               </TableContainer> | ||||
|             </> | ||||
|           )} | ||||
|  | ||||
|           {listFileInBdd && ( | ||||
|             <> | ||||
|               <TableContainer> | ||||
|                 <Table | ||||
|                   variant="striped" | ||||
|                   colorScheme="teal" | ||||
|                   background="gray.700" | ||||
|             <Table.Root | ||||
|               //fontPalette="striped" | ||||
|               //colorScheme="teal" | ||||
|               style={{ | ||||
|                 background: "gray.700" | ||||
|               }} | ||||
|             > | ||||
|                   <Thead> | ||||
|                     <Tr> | ||||
|                       <Th>track ID</Th> | ||||
|                       <Th width="full">Title</Th> | ||||
|                       <Th>actions</Th> | ||||
|                     </Tr> | ||||
|                   </Thead> | ||||
|                   <Tbody> | ||||
|               <Table.Header> | ||||
|                 <Table.Row> | ||||
|                   <Table.ColumnHeader>track ID</Table.ColumnHeader> | ||||
|                   <Table.ColumnHeader style={{ width: "100%" }}>Title</Table.ColumnHeader> | ||||
|                   <Table.ColumnHeader>actions</Table.ColumnHeader> | ||||
|                 </Table.Row> | ||||
|               </Table.Header> | ||||
|               <Table.Body> | ||||
|                 {listFileInBdd.map((data) => ( | ||||
|                       <Tr> | ||||
|                         <Td> | ||||
|                   <Table.Row> | ||||
|                     <Table.Cell> | ||||
|                       <Text | ||||
|                         color={ | ||||
|                           data.episodeDetected === true ? 'red' : undefined | ||||
| @@ -586,8 +573,8 @@ export const AddPage = () => { | ||||
|                       > | ||||
|                         {data.trackId} | ||||
|                       </Text> | ||||
|                         </Td> | ||||
|                         <Td> | ||||
|                     </Table.Cell> | ||||
|                     <Table.Cell> | ||||
|                       <Text | ||||
|                         color={ | ||||
|                           data.nameDetected === true ? 'red' : undefined | ||||
| @@ -595,41 +582,37 @@ export const AddPage = () => { | ||||
|                       > | ||||
|                         {data.title} | ||||
|                       </Text> | ||||
|                         </Td> | ||||
|                         <Td></Td> | ||||
|                       </Tr> | ||||
|                     </Table.Cell> | ||||
|                     <Table.Cell></Table.Cell> | ||||
|                   </Table.Row> | ||||
|                 ))} | ||||
|                   </Tbody> | ||||
|                 </Table> | ||||
|               </TableContainer> | ||||
|             </> | ||||
|               </Table.Body> | ||||
|             </Table.Root> | ||||
|           )} | ||||
|  | ||||
|           {parsedFailedElement && ( | ||||
|             <> | ||||
|               <Text fontSize="30px">Rejected:</Text> | ||||
|               <TableContainer> | ||||
|                 <Table | ||||
|                   variant="striped" | ||||
|                   colorScheme="teal" | ||||
|                   background="gray.700" | ||||
|               <Table.Root | ||||
|                 //colorPalette="striped" | ||||
|                 //colorScheme="teal" | ||||
|                 style={{ background: "gray.700" }} | ||||
|               > | ||||
|                   <Thead> | ||||
|                     <Tr> | ||||
|                       <Th maxWidth="80%">file</Th> | ||||
|                       <Th>Reason</Th> | ||||
|                     </Tr> | ||||
|                   </Thead> | ||||
|                   <Tbody> | ||||
|                 <Table.Header> | ||||
|                   <Table.Row> | ||||
|                     <Table.ColumnHeader style={{ maxWidth: "80%" }}>file</Table.ColumnHeader> | ||||
|                     <Table.ColumnHeader>Reason</Table.ColumnHeader> | ||||
|                   </Table.Row> | ||||
|                 </Table.Header> | ||||
|                 <Table.Body> | ||||
|                   {parsedFailedElement.map((data) => ( | ||||
|                       <Tr key={data.uniqueId}> | ||||
|                         <Td>{data.file.name}</Td> | ||||
|                         <Td>{data.reason}</Td> | ||||
|                       </Tr> | ||||
|                     <Table.Row key={data.uniqueId}> | ||||
|                       <Table.Cell>{data.file.name}</Table.Cell> | ||||
|                       <Table.Cell>{data.reason}</Table.Cell> | ||||
|                     </Table.Row> | ||||
|                   ))} | ||||
|                   </Tbody> | ||||
|                 </Table> | ||||
|               </TableContainer> | ||||
|                 </Table.Body> | ||||
|               </Table.Root> | ||||
|             </> | ||||
|           )} | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,13 +1,14 @@ | ||||
| import { ReactElement } from 'react'; | ||||
|  | ||||
| import { Center, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react'; | ||||
| import { LuCrown, LuDisc3, LuEar, LuFileAudio } from 'react-icons/lu'; | ||||
| import { MdGroup } from 'react-icons/md'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
|  | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Div, Flex, HStack, Text } from '@/ui'; | ||||
|  | ||||
| type HomeListType = { | ||||
|   id: number; | ||||
| @@ -19,37 +20,36 @@ const homeList: HomeListType[] = [ | ||||
|   { | ||||
|     id: 1, | ||||
|     name: 'Genders', | ||||
|     icon: <LuCrown size="60%" height="full" />, | ||||
|     icon: <LuCrown style={{ width: "100px", height: "100px" }} />, | ||||
|     to: 'gender', | ||||
|   }, | ||||
|   { | ||||
|     id: 2, | ||||
|     name: 'Artists', | ||||
|     icon: <MdGroup size="60%" height="full" />, | ||||
|     icon: <MdGroup style={{ width: "100px", height: "100px" }} />, | ||||
|     to: 'artist', | ||||
|   }, | ||||
|   { | ||||
|     id: 3, | ||||
|     name: 'Albums', | ||||
|     icon: <LuDisc3 size="60%" height="full" />, | ||||
|     icon: <LuDisc3 style={{ width: "100px", height: "100px" }} />, | ||||
|     to: 'album', | ||||
|   }, | ||||
|   { | ||||
|     id: 4, | ||||
|     name: 'Tracks', | ||||
|     icon: <LuFileAudio size="60%" height="full" />, | ||||
|     icon: <LuFileAudio style={{ width: "100px", height: "100px" }} />, | ||||
|     to: 'track', | ||||
|   }, | ||||
|   { | ||||
|     id: 5, | ||||
|     name: 'Playlists', | ||||
|     icon: <LuEar size="60%" height="full" />, | ||||
|     icon: <LuEar style={{ width: "100px", height: "100px" }} />, | ||||
|     to: 'playlists', | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| export const HomePage = () => { | ||||
|   const { mode } = useThemeMode(); | ||||
|   const navigate = useNavigate(); | ||||
|   const onSelectItem = (data: HomeListType) => { | ||||
|     navigate(data.to); | ||||
| @@ -58,40 +58,56 @@ export const HomePage = () => { | ||||
|     <> | ||||
|       <TopBar title="Home" /> | ||||
|       <PageLayout> | ||||
|         <Wrap spacing="20px" marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack style={{ | ||||
|           flexWrap: "wrap", | ||||
|           /*spacing:"20px",*/ | ||||
|           margin: "0 auto", | ||||
|           padding: "20px", | ||||
|           alignContent: "center" | ||||
|         }}> | ||||
|           {homeList.map((data) => ( | ||||
|             <WrapItem | ||||
|               width="200px" | ||||
|               height="190px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|             <Flex align="flex-start" | ||||
|               margin="auto" | ||||
|               key={data.id} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               style={{ | ||||
|                 width: "200px", | ||||
|                 height: "190px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|               }} | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|                 background: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <Flex direction="column" width="full" height="full"> | ||||
|                 <Center height="full">{data.icon}</Center> | ||||
|                 <Center> | ||||
|               <Flex | ||||
|                 direction="column" | ||||
|                 style={{ | ||||
|                   width: "100%", | ||||
|                   margin: "auto" | ||||
|                 }}> | ||||
|                 <Div style={{ margin: "auto", height: "100%", color: "black.50" }}>{data.icon}</Div> | ||||
|                 <Div style={{ margin: "auto", height: "100%" }}> | ||||
|                   <Text | ||||
|                     fontSize="25px" | ||||
|                     fontWeight="bold" | ||||
|                     textTransform="uppercase" | ||||
|                     userSelect="none" | ||||
|                     color="black.50" | ||||
|                     style={{ | ||||
|                       textTransform: "uppercase", | ||||
|                       userSelect: "none", | ||||
|                     }} | ||||
|                   > | ||||
|                     {data.name} | ||||
|                   </Text> | ||||
|                 </Center> | ||||
|                 </Div> | ||||
|               </Flex> | ||||
|             </Flex> | ||||
|             </WrapItem> | ||||
|           ))} | ||||
|         </Wrap> | ||||
|       </PageLayout> | ||||
|         </HStack> | ||||
|       </PageLayout > | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										128
									
								
								front/src/scene/onAir/OnAirPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								front/src/scene/onAir/OnAirPage.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| import { Route, Routes, useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { EmptyEnd } from '@/components/EmptyEnd'; | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { AlbumEditPopUp } from '@/components/popup/AlbumEditPopUp'; | ||||
| import { TrackEditPopUp } from '@/components/popup/TrackEditPopUp'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { BASE_WRAP_SPACING } from '@/constants/genericSpacing'; | ||||
| import { DisplayTrackFullId } from '@/components/track/DisplayTrackFullId'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export const OnAirPage = () => { | ||||
|   const { playInList } = useActivePlaylistService(); | ||||
|   const { playTrackList, trackOffset, setNewPlaylist } = useActivePlaylistService(); | ||||
|   const navigate = useNavigate(); | ||||
|   const onSelectItem = (trackId: number) => { | ||||
|     let currentPlay = 0; | ||||
|     if (!playTrackList) { | ||||
|       console.log('Fail to get list of on air...'); | ||||
|       return; | ||||
|     } | ||||
|     for (let iii = 0; iii < playTrackList.length; iii++) { | ||||
|       if (playTrackList[iii] === trackId) { | ||||
|         currentPlay = iii; | ||||
|       } | ||||
|     } | ||||
|     playInList(currentPlay, playTrackList); | ||||
|   }; | ||||
|   const removeTrack = (trackId: number) => { | ||||
|     let elementToRemoveOffset = 0; | ||||
|     if (!playTrackList) { | ||||
|       console.log('Fail to remove element of on air...'); | ||||
|       return; | ||||
|     } | ||||
|     const newList: number[] = []; | ||||
|     for (let iii = 0; iii < playTrackList.length; iii++) { | ||||
|       if (playTrackList[iii] === trackId) { | ||||
|         elementToRemoveOffset = iii; | ||||
|       } else { | ||||
|         newList.push(playTrackList[iii]) | ||||
|       } | ||||
|     } | ||||
|     let newPlayed = trackOffset; | ||||
|     if (newPlayed != undefined) { | ||||
|       if (elementToRemoveOffset < newPlayed) { | ||||
|         newPlayed = newPlayed - 1; | ||||
|       } | ||||
|       playInList(newPlayed, newList); | ||||
|     } else { | ||||
|       setNewPlaylist(newList); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   console.log(`playTrackList = ${JSON.stringify(playTrackList, null, 2)}`); | ||||
|   if (!playTrackList) { | ||||
|     return ( | ||||
|       <> | ||||
|         <TopBar title="Album detail" /> | ||||
|         <PageLayoutInfoCenter> | ||||
|           No data is currently playing... | ||||
|         </PageLayoutInfoCenter> | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <> | ||||
|       <TopBar title="On Air ..."> | ||||
|       </TopBar> | ||||
|       <PageLayout> | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap={BASE_WRAP_SPACING} | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {!playTrackList && <>No playing</>} | ||||
|           {playTrackList && playTrackList?.map((data) => ( | ||||
|             <Flex | ||||
|               key={data} | ||||
|               style={{ | ||||
|                 minWidth: "100%", | ||||
|                 //height:"60px", | ||||
|                 border: "1px", | ||||
|                 borderColor: "brand.900", | ||||
|                 background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                 padding: "5px", | ||||
|                 // _hover: { | ||||
|                 //   boxShadow: 'outline-over', | ||||
|                 //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                 // } | ||||
|               }} | ||||
|             > | ||||
|               <DisplayTrackFullId | ||||
|                 trackId={data} | ||||
|                 onClick={() => onSelectItem(data)} | ||||
|                 contextMenu={[ | ||||
|                   { | ||||
|                     name: 'Edit', | ||||
|                     onClick: () => { | ||||
|                       navigate(`edit-track/${data}`); | ||||
|                     }, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: 'Remove from playlist', | ||||
|                     onClick: () => { | ||||
|                       removeTrack(data); | ||||
|                     }, | ||||
|                   }, | ||||
|                 ]} | ||||
|               /> | ||||
|             </Flex> | ||||
|           ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|         <Routes> | ||||
|           <Route path="edit-track/:trackId" element={<TrackEditPopUp />} /> | ||||
|           <Route path="edit-album/:albumId" element={<AlbumEditPopUp />} /> | ||||
|         </Routes> | ||||
|       </PageLayout> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { useEffect } from 'react'; | ||||
|  | ||||
| import { Center, Heading, Image, Text } from '@chakra-ui/react'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
|  | ||||
| import avatar_generic from '@/assets/images/avatar_generic.svg'; | ||||
| @@ -11,6 +10,8 @@ import { SessionState } from '@/service/SessionState'; | ||||
| import { useSessionService } from '@/service/session'; | ||||
| import { b64_to_utf8 } from '@/utils/sso'; | ||||
|  | ||||
| import { Flex, Text, Image } from '@/ui'; | ||||
|  | ||||
| export const SSOPage = () => { | ||||
|   const { data, keepConnected, token } = useParams(); | ||||
|   console.log(`- data: ${data}`); | ||||
| @@ -47,13 +48,11 @@ export const SSOPage = () => { | ||||
|     <> | ||||
|       <TopBar /> | ||||
|       <PageLayoutInfoCenter width="35%" gap="15px"> | ||||
|         <Center w="full"> | ||||
|           <Heading size="xl">LOGIN (after SSO) </Heading> | ||||
|         </Center> | ||||
|         <Text>LOGIN (after SSO)</Text> | ||||
|  | ||||
|         <Center w="full"> | ||||
|           <Image src={avatar_generic} boxSize="150px" borderRadius="full" /> | ||||
|         </Center> | ||||
|         <Flex width="100%"> | ||||
|           <Image src={avatar_generic} boxSize="150px" style={{ borderRadius: "full" }} /> | ||||
|         </Flex> | ||||
|         {token === '__CANCEL__' && ( | ||||
|           <Text> | ||||
|             <b>ERROR: </b> Request cancel of connection ! | ||||
|   | ||||
| @@ -1,12 +1,8 @@ | ||||
| import { ReactElement } from 'react'; | ||||
|  | ||||
| import { Flex, Text, Wrap, WrapItem } from '@chakra-ui/react'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
|  | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { DataTools, TypeCheck } from '@/utils/data-tools'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
|  | ||||
| export const alphabet = [ | ||||
|   'a', | ||||
| @@ -39,7 +35,6 @@ export const alphabet = [ | ||||
| const possibilities = [...alphabet, '^^']; | ||||
|  | ||||
| export const TrackSelectionPage = () => { | ||||
|   const { mode } = useThemeMode(); | ||||
|   const navigate = useNavigate(); | ||||
|   const onSelectItem = (data: string) => { | ||||
|     navigate(`/track/${data}`); | ||||
| @@ -48,24 +43,24 @@ export const TrackSelectionPage = () => { | ||||
|     <> | ||||
|       <TopBar title="All Tracks" /> | ||||
|       <PageLayout> | ||||
|         <Wrap spacing="20px" marginX="auto" padding="20px" justify="center"> | ||||
|         <HStack wrap="wrap" /*spacing="20px"*/ marginX="auto" padding="20px" justify="center"> | ||||
|           {possibilities.map((data) => ( | ||||
|             <WrapItem | ||||
|             <Flex align="flex-start" | ||||
|               width="75px" | ||||
|               height="75px" | ||||
|               border="1px" | ||||
|               borderColor="brand.900" | ||||
|               backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|               background={useColorThemeValue('#FFFFFF88', '#00000088')} | ||||
|               key={data} | ||||
|               padding="5px" | ||||
|               as="button" | ||||
|               _hover={{ | ||||
|                 boxShadow: 'outline-over', | ||||
|                 bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|                 bgColor: useColorThemeValue('#FFFFFFF7', '#000000F7'), | ||||
|               }} | ||||
|               onClick={() => onSelectItem(data)} | ||||
|             > | ||||
|               <Flex direction="column" width="full" height="full"> | ||||
|               <Flex direction="column" width="100%" height="full"> | ||||
|                 <Text | ||||
|                   margin="auto" | ||||
|                   fontSize="25px" | ||||
| @@ -76,9 +71,9 @@ export const TrackSelectionPage = () => { | ||||
|                   {data.toUpperCase()} | ||||
|                 </Text> | ||||
|               </Flex> | ||||
|             </WrapItem> | ||||
|             </Flex> | ||||
|           ))} | ||||
|         </Wrap> | ||||
|         </HStack> | ||||
|       </PageLayout> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,22 +1,18 @@ | ||||
| import { ReactElement } from 'react'; | ||||
|  | ||||
| import { Box, Flex, Text, Wrap, WrapItem } from '@chakra-ui/react'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { useParams } from 'react-router-dom'; | ||||
|  | ||||
| import { EmptyEnd } from '@/components/EmptyEnd'; | ||||
| import { PageLayout } from '@/components/Layout/PageLayout'; | ||||
| import { TopBar } from '@/components/TopBar/TopBar'; | ||||
| import { DisplayTrack } from '@/components/track/DisplayTrack'; | ||||
| import { DisplayTrackFull } from '@/components/track/DisplayTrackFull'; | ||||
| import { DisplayTrackSkeleton } from '@/components/track/DisplayTrackSkeleton'; | ||||
| import { alphabet } from '@/scene/track/TrackSelectionPage'; | ||||
| import { useActivePlaylistService } from '@/service/ActivePlaylist'; | ||||
| import { useTrackService, useTracksWithStartName } from '@/service/Track'; | ||||
| import { useThemeMode } from '@/utils/theme-tools'; | ||||
| import { useTracksWithStartName } from '@/service/Track'; | ||||
| import { useColorThemeValue } from '@/theme/ThemeContext'; | ||||
| import { Flex } from '@/ui'; | ||||
|  | ||||
| export const TracksStartLetterDetailPage = () => { | ||||
|   const { startLetter } = useParams(); | ||||
|   const { mode } = useThemeMode(); | ||||
|   const { isLoading, tracksStartsWith } = useTracksWithStartName( | ||||
|     startLetter !== '^^' | ||||
|       ? startLetter | ||||
| @@ -52,50 +48,55 @@ export const TracksStartLetterDetailPage = () => { | ||||
|         <Flex | ||||
|           direction="column" | ||||
|           gap="20px" | ||||
|           marginX="auto" | ||||
|           padding="20px" | ||||
|           width="80%" | ||||
|           style={{ | ||||
|             margin: "0 auto", | ||||
|             padding: "20px", | ||||
|             width: "80%", | ||||
|           }} | ||||
|         > | ||||
|           {isLoading && | ||||
|             [1, 2, 3, 4]?.map((data) => ( | ||||
|               <Box | ||||
|                 minWidth="100%" | ||||
|                 //height="60px" | ||||
|                 border="1px" | ||||
|                 borderColor="brand.900" | ||||
|                 backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|               <Flex | ||||
|                 key={data} | ||||
|                 padding="5px" | ||||
|                 as="button" | ||||
|                 _hover={{ | ||||
|                   boxShadow: 'outline-over', | ||||
|                   bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|                 style={{ | ||||
|                   minWidth: "100%", | ||||
|                   //height:"60px", | ||||
|                   border: "1px", | ||||
|                   borderColor: "brand.900", | ||||
|                   background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|                   padding: "5px", | ||||
|                   // _hover: { | ||||
|                   //   boxShadow: 'outline-over', | ||||
|                   //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                   // } | ||||
|                 }} | ||||
|               > | ||||
|                 <DisplayTrackSkeleton /> | ||||
|               </Box> | ||||
|               </Flex> | ||||
|             ))} | ||||
|           {!isLoading && | ||||
|             tracksStartsWith?.map((data) => ( | ||||
|               <Box | ||||
|                 minWidth="100%" | ||||
|                 //height="60px" | ||||
|                 border="1px" | ||||
|                 borderColor="brand.900" | ||||
|                 backgroundColor={mode('#FFFFFF88', '#00000088')} | ||||
|               <Flex | ||||
|                 key={data.id} | ||||
|                 padding="5px" | ||||
|                 as="button" | ||||
|                 _hover={{ | ||||
|                   boxShadow: 'outline-over', | ||||
|                   bgColor: mode('#FFFFFFF7', '#000000F7'), | ||||
|                 style={{ | ||||
|                   minWidth: "100%", | ||||
|                   //height:"60px", | ||||
|                   border: "1px", | ||||
|                   borderColor: "brand.900", | ||||
|                   background: useColorThemeValue('#FFFFFF88', '#00000088'), | ||||
|  | ||||
|                   padding: "5px", | ||||
|                   // _hover: { | ||||
|                   //   boxShadow: 'outline-over', | ||||
|                   //   bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'), | ||||
|                   // } | ||||
|                 }} | ||||
|               > | ||||
|                 <DisplayTrackFull | ||||
|                   track={data} | ||||
|                   onClick={() => onSelectItem(data.id)} | ||||
|                 /> | ||||
|               </Box> | ||||
|               </Flex> | ||||
|             ))} | ||||
|           <EmptyEnd /> | ||||
|         </Flex> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { useEffect, useMemo, useState } from 'react'; | ||||
| import { useMemo } from 'react'; | ||||
|  | ||||
| import { Album, AlbumResource } from '@/back-api'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | ||||
| import { useMemo } from 'react'; | ||||
|  | ||||
| import { Track, TrackResource } from '@/back-api'; | ||||
| import { useServiceContext } from '@/service/ServiceContext'; | ||||
|   | ||||
| @@ -9,6 +9,10 @@ import { parseToken } from '@/utils/sso'; | ||||
|  | ||||
| const TOKEN_KEY = 'karusic-token-key-storage'; | ||||
|  | ||||
| export const USERS_COLLECTION = [ | ||||
|   { label: "admin", value: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E" }, | ||||
|   { label: "NO_USER", value: "svelte" }, | ||||
| ]; | ||||
| export const USERS = { | ||||
|   admin: | ||||
|     'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E', | ||||
|   | ||||
| @@ -1,14 +1,11 @@ | ||||
| import { ReactElement, ReactNode } from 'react'; | ||||
|  | ||||
| import { ChakraProvider } from '@chakra-ui/react'; | ||||
| import { RenderOptions, render } from '@testing-library/react'; | ||||
| import { BrowserRouter } from 'react-router-dom'; | ||||
|  | ||||
| import theme from '@/theme'; | ||||
|  | ||||
| const CustomWrapper = ({ children }: { children: ReactNode }) => { | ||||
|   return ( | ||||
|     <ChakraProvider theme={theme}> | ||||
|     <ChakraProvider value={defaultSystem}> | ||||
|       <BrowserRouter>{children}</BrowserRouter> | ||||
|     </ChakraProvider> | ||||
|   ); | ||||
|   | ||||
							
								
								
									
										202
									
								
								front/src/theme/ThemeContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								front/src/theme/ThemeContext.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
| import { createContext, useContext, useState, useEffect, CSSProperties, ReactNode, useCallback } from "react"; | ||||
| import { basicColor } from "./colors"; | ||||
|  | ||||
| export type ApplyThemeProperty = { | ||||
|   componentType?: String; | ||||
|   // define the format of the shape | ||||
|   shape?: String; | ||||
|   // Define the color model used | ||||
|   palette?: String; | ||||
| } | ||||
|  | ||||
| export type ThemeContextProps = { | ||||
|   theme: string; | ||||
|   themeList: string[]; | ||||
|   setTheme: (string) => void; | ||||
|   toggleTheme: () => void; | ||||
|   convertStyle: (style: CSSProperties) => CSSProperties, | ||||
|   applyTheme: (style: CSSProperties, theme: ApplyThemeProperty) => CSSProperties, | ||||
| } | ||||
|  | ||||
|  | ||||
| const ThemeContext = createContext<ThemeContextProps>({ | ||||
|   theme: "error", | ||||
|   themeList: ["error"], | ||||
|   setTheme: (_: string) => { | ||||
|     console.error("Request setTheme without context"); | ||||
|   }, | ||||
|   toggleTheme: () => { | ||||
|     console.error("Request toggleTheme without context"); | ||||
|   }, | ||||
|   convertStyle: (style: CSSProperties): CSSProperties => { | ||||
|     console.error("Request convertStyle without context"); | ||||
|     return style; | ||||
|   }, | ||||
|   applyTheme: (style: CSSProperties, _theme: ApplyThemeProperty) => { | ||||
|     console.error("Request applyTheme without context"); | ||||
|     return style; | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| const themes = { | ||||
|   baseColors: { | ||||
|     ...basicColor | ||||
|   }, | ||||
|   themeColors: { | ||||
|     light: { | ||||
|       primary: "#007bff", | ||||
|       secondary: "#6c757d", | ||||
|       background: "#ffffff", | ||||
|       text: "#000000", | ||||
|     }, | ||||
|     dark: { | ||||
|       primary: "#375a7f", | ||||
|       secondary: "#444444", | ||||
|       background: "#181818", | ||||
|       text: "#ffffff", | ||||
|     }, | ||||
|   }, | ||||
|   base: { | ||||
|     Button: { | ||||
|       border: '1px solid red', | ||||
|     }, | ||||
|     Flex: { | ||||
|  | ||||
|     }, | ||||
|   }, | ||||
|   shape: { | ||||
|     outline: { | ||||
|       border: '1px solid transparent', | ||||
|     }, | ||||
|   }, | ||||
|   palette: { | ||||
|     primary: { | ||||
|       borderColor: "green" | ||||
|     }, | ||||
|     secondary: { | ||||
|  | ||||
|     }, | ||||
|     danger: { | ||||
|  | ||||
|     }, | ||||
|     success: { | ||||
|  | ||||
|     } | ||||
|  | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| export function ThemeProvider({ children, themeList = ["light", "dark"] }: { children: ReactNode, themeList?: string[] }) { | ||||
|   const [theme, setTheme] = useState<string>(themeList[1]); | ||||
|   const [basicsColor, setBasicsColor] = useState<{ [key: string]: string }>({}); | ||||
|  | ||||
|   // update the global CSS wen theme is updated: | ||||
|   useEffect(() => { | ||||
|     // generates the colors: | ||||
|     const baseColor = themes.baseColors; | ||||
|     const newBasicsColor: { [key: string]: string } = {} | ||||
|     Object.entries(baseColor).forEach(([tableColorName, colorValues]) => { | ||||
|       Object.entries(colorValues).forEach(([key, value]) => { | ||||
|         const colorName = `${tableColorName}.${key}`; | ||||
|         newBasicsColor[colorName] = value; | ||||
|       }); | ||||
|     }); | ||||
|     setBasicsColor(newBasicsColor); | ||||
|  | ||||
|     const root = document.documentElement; | ||||
|     //const currentColorTheme = themes.baseColors[theme]; | ||||
|     Object.entries(newBasicsColor).forEach(([key, value]) => { | ||||
|       const cssKeyValue = `--${key.replace(".", "-")}`; | ||||
|       console.log(` generate CSS color: ${cssKeyValue}:${value}`); | ||||
|       root.style.setProperty(cssKeyValue, value); | ||||
|     }); | ||||
|   }, [theme, setBasicsColor]); | ||||
|  | ||||
|   const toggleTheme = useCallback(() => { | ||||
|     setTheme((previous) => { | ||||
|       if (themeList.length <= 1) { | ||||
|         return previous; | ||||
|       } | ||||
|       for (let iii = 0; iii < themeList.length - 1; iii++) { | ||||
|         if (themeList[iii] == theme) { | ||||
|           return themeList[iii + 1] | ||||
|         } | ||||
|       } | ||||
|       return themeList[0]; | ||||
|     }) | ||||
|   }, [setTheme]); | ||||
|  | ||||
|   const convertElementStyle = useCallback((style: CSSProperties, key: string) => { | ||||
|     const value = style[key]; | ||||
|     if (typeof value !== "string") { | ||||
|       return; | ||||
|     } | ||||
|     // console.log(`request convert value: ${value}`); | ||||
|     if (basicsColor[value]) { | ||||
|       // console.log(`convert value: ${value} in ${basicsColor[value]}`); | ||||
|       style[key] = basicsColor[value]; | ||||
|     } | ||||
|   }, [basicsColor]); | ||||
|  | ||||
|   const convertStyle = useCallback((style: CSSProperties): CSSProperties => { | ||||
|     //console.log(`plop: ${theme}`); | ||||
|     if (!style) { | ||||
|       return style; | ||||
|     } | ||||
|     const out = { ...style } | ||||
|     convertElementStyle(out, "background"); | ||||
|     convertElementStyle(out, "backgroundColor"); | ||||
|     convertElementStyle(out, "borderColor"); | ||||
|     convertElementStyle(out, "color"); | ||||
|     return out; | ||||
|   }, [convertElementStyle]); | ||||
|  | ||||
|   const applyTheme = useCallback((style: CSSProperties, selectedTheme?: ApplyThemeProperty): CSSProperties => { | ||||
|     let out = style; | ||||
|     console.log(`apply style ... ${JSON.stringify(selectedTheme, null, 2)}`); | ||||
|     // Apply the basic theme of the component: | ||||
|     if (selectedTheme?.componentType) { | ||||
|       console.log(`detect component type theme ... ${selectedTheme?.componentType}`); | ||||
|       const base = themes?.base[selectedTheme.componentType]; | ||||
|       console.log(`    base = ${JSON.stringify(base, null, 2)}`); | ||||
|       if (base) { | ||||
|         out = { ...base, ...out }; | ||||
|       } | ||||
|     } | ||||
|     // Apply the variant selected: | ||||
|     if (selectedTheme?.shape) { | ||||
|       const shape = themes?.shape[selectedTheme.shape]; | ||||
|       if (shape) { | ||||
|         out = { ...shape, ...out }; | ||||
|       } | ||||
|     } | ||||
|     // Apply the palette selected: | ||||
|     if (selectedTheme?.palette) { | ||||
|       const palette = themes?.shape[selectedTheme.palette]; | ||||
|       if (palette) { | ||||
|         const palette2 = palette[theme]; | ||||
|         if (palette2) { | ||||
|           out = { ...palette2, ...out }; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     return convertStyle(out); | ||||
|   }, [convertStyle]); | ||||
|  | ||||
|   return ( | ||||
|     <ThemeContext.Provider value={{ theme, themeList, setTheme, toggleTheme, convertStyle, applyTheme }}> | ||||
|       {children} | ||||
|     </ThemeContext.Provider> | ||||
|   ); | ||||
| }; | ||||
| export const useTheme = () => useContext(ThemeContext); | ||||
|  | ||||
| export function useColorThemeValue<T>(firstTheme: T, secondTheme: T) { | ||||
|   const { theme, themeList } = useTheme() | ||||
|   //console.log(`use theme =${theme} ==> ${theme === themeList[0] ? firstTheme : secondTheme}`); | ||||
|   return theme === themeList[0] ? firstTheme : secondTheme | ||||
| } | ||||
| @@ -1,44 +1,4 @@ | ||||
| type ThemeModel = { | ||||
|   50: string; | ||||
|   100: string; | ||||
|   200: string; | ||||
|   300: string; | ||||
|   400: string; | ||||
|   500: string; | ||||
|   600: string; | ||||
|   700: string; | ||||
|   800: string; | ||||
|   900: string; | ||||
| }; | ||||
| const isLightMode = false; | ||||
| const reverseColor = (data: ThemeModel) => { | ||||
|   return { | ||||
|     50: data[900], | ||||
|     100: data[800], | ||||
|     200: data[700], | ||||
|     300: data[600], | ||||
|     400: data[500], | ||||
|     500: data[400], | ||||
|     600: data[300], | ||||
|     700: data[200], | ||||
|     800: data[100], | ||||
|     900: data[50], | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const back = { | ||||
|   50: '#ebf4fa', | ||||
|   100: '#d1dbe0', | ||||
|   200: '#b6c2c9', | ||||
|   300: '#99aab4', | ||||
|   400: '#7c939e', | ||||
|   500: '#637985', | ||||
|   600: '#4d5e67', | ||||
|   700: '#37444a', | ||||
|   800: '#1f292e', | ||||
|   900: '#020f12', | ||||
| }; | ||||
| 
 | ||||
| // Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
 | ||||
| const brand = { | ||||
|   50: '#e3edff', | ||||
|   100: '#b6c9fd', | ||||
| @@ -51,7 +11,8 @@ const brand = { | ||||
|   800: '#02164a', | ||||
|   900: '#00071e', | ||||
| }; | ||||
| const normalText = { | ||||
| 
 | ||||
| const black = { | ||||
|   50: '#f2f2f2', | ||||
|   100: '#d9d9d9', | ||||
|   200: '#bfbfbf', | ||||
| @@ -88,7 +49,6 @@ const blue = { | ||||
|   800: '#1e40af', | ||||
|   900: '#1e3a8a', | ||||
| }; | ||||
| 
 | ||||
| const orange = { | ||||
|   50: '#fff7ed', | ||||
|   100: '#ffedd5', | ||||
| @@ -113,15 +73,54 @@ const red = { | ||||
|   800: '#991b1b', | ||||
|   900: '#7f1d1d', | ||||
| }; | ||||
| const yellow = { | ||||
|   50: '#ffffda', | ||||
|   100: '#ffffad', | ||||
|   200: '#ffff7d', | ||||
|   300: '#ffff4b', | ||||
|   400: '#ffff1a', | ||||
|   500: '#e5e600', | ||||
|   600: '#b2b300', | ||||
|   700: '#7f8000', | ||||
|   800: '#4c4d00', | ||||
|   900: '#191b00', | ||||
| }; | ||||
| const purple = { | ||||
|   50: '#ffe3ff', | ||||
|   100: '#ffb2ff', | ||||
|   200: '#ff80ff', | ||||
|   300: '#fe4efe', | ||||
|   400: '#fe20fe', | ||||
|   500: '#e50ce4', | ||||
|   600: '#b204b1', | ||||
|   700: '#80007f', | ||||
|   800: '#4e004d', | ||||
|   900: '#1d001c', | ||||
| }; | ||||
| 
 | ||||
| export const colors = { | ||||
|   // Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
 | ||||
| const cyan = { | ||||
|   50: '#d6ffff', | ||||
|   100: '#aaffff', | ||||
|   200: '#7affff', | ||||
|   300: '#47ffff', | ||||
|   400: '#1affff', | ||||
|   500: '#00e5e6', | ||||
|   600: '#00b2b3', | ||||
|   700: '#008081', | ||||
|   800: '#004d4e', | ||||
|   900: '#001b1d', | ||||
| } | ||||
| 
 | ||||
|   brand: brand, | ||||
|   back: back, | ||||
|   text: normalText, | ||||
|   /// ????
 | ||||
|   success: green, | ||||
|   error: red, | ||||
| export const basicColor = { | ||||
|   brand, | ||||
|   green, | ||||
|   red, | ||||
|   orange, | ||||
|   black, | ||||
|   blue, | ||||
|   yellow, | ||||
|   purple, | ||||
|   cyan, | ||||
|   back: black, | ||||
|   warning: orange, | ||||
| } as const; | ||||
| } | ||||
| @@ -1,196 +0,0 @@ | ||||
| import { border, defineStyleConfig, keyframes } from '@chakra-ui/react'; | ||||
| import { isAccessible, mode, transparentize } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| import { shadows } from '@/theme/foundations/shadows'; | ||||
|  | ||||
| const shimmer = keyframes` | ||||
|   100% { | ||||
|     transform: translateX(100%); | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const customVariant = ({ | ||||
|   theme, | ||||
|   bg, | ||||
|   bgHover = bg, | ||||
|   bgActive = bgHover, | ||||
|   color, | ||||
|   colorHover = color, | ||||
|   boxColorFocus = '#0x000000', | ||||
|   boxShadowHover = 'outline-over', | ||||
| }) => { | ||||
|   const isColorAccessible = isAccessible(color, bg, { | ||||
|     size: 'large', | ||||
|     level: 'AA', | ||||
|   })(theme); | ||||
|  | ||||
|   return { | ||||
|     bg, | ||||
|     color: isColorAccessible ? color : 'black', | ||||
|     border: '1px solid #00000000', | ||||
|     _focus: { | ||||
|       //border: `1px solid ${boxColorFocus}`, | ||||
|       border: `1px solid #000000`, | ||||
|     }, | ||||
|     _hover: { | ||||
|       bg: bgHover, | ||||
|       color: isColorAccessible ? colorHover : 'black', | ||||
|       boxShadow: boxShadowHover, | ||||
|       _disabled: { | ||||
|         bg, | ||||
|         boxShadow: 'none', | ||||
|       }, | ||||
|     }, | ||||
|     _active: { bg: bgActive }, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export default defineStyleConfig({ | ||||
|   variants: { | ||||
|     // Custom variants | ||||
|     '@primary': (props) => | ||||
|       customVariant({ | ||||
|         theme: props.theme, | ||||
|         bg: mode('brand.600', 'brand.300')(props), | ||||
|         bgHover: mode('brand.700', 'brand.400')(props), | ||||
|         bgActive: mode('brand.600', 'brand.300')(props), | ||||
|         color: mode('white', 'brand.900')(props), | ||||
|         boxColorFocus: mode('brand.100', 'brand.600')(props), | ||||
|       }), | ||||
|     '@secondary': (props) => | ||||
|       customVariant({ | ||||
|         theme: props.theme, | ||||
|         bg: mode('brand.100', 'brand.900')(props), | ||||
|         bgHover: mode('brand.200', 'brand.800')(props), | ||||
|         bgActive: mode('brand.300', 'brand.700')(props), | ||||
|         color: mode('brand.700', 'brand.50')(props), | ||||
|         colorHover: mode('brand.800', 'brand.100')(props), | ||||
|         boxColorFocus: mode('brand.900', 'brand.300')(props), | ||||
|       }), | ||||
|     '@danger': (props) => | ||||
|       customVariant({ | ||||
|         theme: props.theme, | ||||
|         bg: mode('error.600', 'error.600')(props), | ||||
|         bgHover: mode('error.700', 'error.500')(props), | ||||
|         bgActive: mode('error.600', 'error.500')(props), | ||||
|         color: mode('white', 'error.900')(props), | ||||
|         boxColorFocus: mode('error.900', 'error.500')(props), | ||||
|       }), | ||||
|     '@success': (props) => | ||||
|       customVariant({ | ||||
|         theme: props.theme, | ||||
|         bg: mode('green.300', 'green.300')(props), | ||||
|         bgHover: mode('green.400', 'green.400')(props), | ||||
|         bgActive: mode('green.500', 'green.400')(props), | ||||
|         color: mode('white', 'green.900')(props), | ||||
|         boxColorFocus: mode('green.900', 'green.400')(props), | ||||
|       }), | ||||
|  | ||||
|     '@progress': (props) => ({ | ||||
|       ...customVariant({ | ||||
|         theme: props.theme, | ||||
|         bg: mode(`${props.colorScheme}.500`, `${props.colorScheme}.300`)(props), | ||||
|         bgHover: mode( | ||||
|           `${props.colorScheme}.600`, | ||||
|           `${props.colorScheme}.400` | ||||
|         )(props), | ||||
|         bgActive: mode( | ||||
|           `${props.colorScheme}.700`, | ||||
|           `${props.colorScheme}.500` | ||||
|         )(props), | ||||
|         color: mode('white', `${props.colorScheme}.900`)(props), | ||||
|         boxColorFocus: mode( | ||||
|           `${props.colorScheme}.900`, | ||||
|           `${props.colorScheme}.600` | ||||
|         )(props), | ||||
|       }), | ||||
|       overflow: 'hidden', | ||||
|  | ||||
|       _after: !props.isLoading | ||||
|         ? { | ||||
|             position: 'absolute', | ||||
|             top: 0, | ||||
|             right: 0, | ||||
|             bottom: 0, | ||||
|             left: 0, | ||||
|             transform: 'translateX(-100%)', | ||||
|             bgGradient: `linear(90deg, ${transparentize( | ||||
|               `${props.colorScheme}.100`, | ||||
|               0 | ||||
|             )(props.theme)} 0, ${transparentize( | ||||
|               `${props.colorScheme}.100`, | ||||
|               0.2 | ||||
|             )(props.theme)} 20%, ${transparentize( | ||||
|               `${props.colorScheme}.100`, | ||||
|               0.5 | ||||
|             )(props.theme)} 60%, ${transparentize( | ||||
|               `${props.colorScheme}.100`, | ||||
|               0 | ||||
|             )(props.theme)})`, | ||||
|             animation: `${shimmer} 3s infinite`, | ||||
|             content: '""', | ||||
|           } | ||||
|         : undefined, | ||||
|     }), | ||||
|     '@menu': (props) => ({ | ||||
|       bg: mode('back.100', 'back.800')(props), | ||||
|       color: mode('brand.900', 'brand.100')(props), | ||||
|       borderRadius: 0, | ||||
|       border: 0, | ||||
|       _hover: { background: mode('back.300', 'back.600')(props) }, | ||||
|       _focus: { border: 'none' }, | ||||
|       fontSize: '20px', | ||||
|       textTransform: 'uppercase', | ||||
|     }), | ||||
|  | ||||
|     // Default variants | ||||
|     solid: (props) => ({ | ||||
|       bg: | ||||
|         props.colorScheme === 'gray' | ||||
|           ? mode('gray.100', 'whiteAlpha.100')(props) | ||||
|           : `${props.colorScheme}.600`, | ||||
|       _hover: { | ||||
|         bg: | ||||
|           props.colorScheme === 'gray' | ||||
|             ? mode('gray.200', 'whiteAlpha.200')(props) | ||||
|             : `${props.colorScheme}.700`, | ||||
|       }, | ||||
|       _focus: { | ||||
|         boxShadow: `outline-${props.colorScheme}`, | ||||
|       }, | ||||
|     }), | ||||
|     light: (props) => ({ | ||||
|       bg: | ||||
|         props.colorScheme === 'gray' | ||||
|           ? mode('gray.100', 'whiteAlpha.100')(props) | ||||
|           : `${props.colorScheme}.100`, | ||||
|       color: | ||||
|         props.colorScheme === 'gray' | ||||
|           ? mode('gray.700', 'whiteAlpha.700')(props) | ||||
|           : `${props.colorScheme}.700`, | ||||
|       _hover: { | ||||
|         bg: | ||||
|           props.colorScheme === 'gray' | ||||
|             ? mode('gray.200', 'whiteAlpha.200')(props) | ||||
|             : `${props.colorScheme}.200`, | ||||
|         color: | ||||
|           props.colorScheme === 'gray' | ||||
|             ? mode('gray.800', 'whiteAlpha.800')(props) | ||||
|             : `${props.colorScheme}.800`, | ||||
|       }, | ||||
|       _focus: { | ||||
|         boxShadow: `outline-${props.colorScheme}`, | ||||
|       }, | ||||
|     }), | ||||
|     ghost: (props) => ({ | ||||
|       bg: transparentize(`${props.colorScheme}.50`, 0.05)(props.theme), | ||||
|       borderRadius: 'full', | ||||
|       _hover: { | ||||
|         bg: transparentize(`${props.colorScheme}.50`, 0.15)(props.theme), | ||||
|       }, | ||||
|       _focus: { | ||||
|         boxShadow: 'none', | ||||
|       }, | ||||
|     }), | ||||
|   }, | ||||
| }); | ||||
| @@ -1,19 +0,0 @@ | ||||
| import { defineStyleConfig } from '@chakra-ui/react'; | ||||
| import { mode } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
|  | ||||
| const flexTheme = defineStyleConfig({ | ||||
|   variants: { | ||||
|     '@menu': (props) => ({ | ||||
|       bg: mode('back.100', 'back.800')(props), | ||||
|       color: mode('brand.900', 'brand.100')(props), | ||||
|       borderRadius: 0, | ||||
|       border: 0, | ||||
|       _hover: { background: mode('back.300', 'back.600')(props) }, | ||||
|       _focus: { border: 'none' }, | ||||
|       fontSize: '20px', | ||||
|     }), | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| export default flexTheme; | ||||
| @@ -1,25 +0,0 @@ | ||||
| import { getColor, mode } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| export default { | ||||
|   variants: { | ||||
|     outline: (props) => { | ||||
|       const focusBorderColor = getColor( | ||||
|         props.theme, | ||||
|         props.focusBorderColor | ||||
|           ? props.focusBorderColor | ||||
|           : mode('brand.500', 'brand.300')(props) | ||||
|       ); | ||||
|       return { | ||||
|         field: { | ||||
|           bg: mode('gray.50', 'whiteAlpha.50')(props), | ||||
|           borderColor: mode('gray.200', 'whiteAlpha.100')(props), | ||||
|           color: mode('gray.800', 'gray.50')(props), | ||||
|           _focus: { | ||||
|             borderColor: focusBorderColor, | ||||
|             boxShadow: `0 0 0 1px ${focusBorderColor}`, | ||||
|           }, | ||||
|         }, | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| @@ -1,33 +0,0 @@ | ||||
| import { numberInputAnatomy } from '@chakra-ui/anatomy'; | ||||
| import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; | ||||
| import { getColor, mode } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| const { definePartsStyle, defineMultiStyleConfig } = | ||||
|   createMultiStyleConfigHelpers(numberInputAnatomy.keys); | ||||
|  | ||||
| const baseStyle = definePartsStyle((props) => { | ||||
|   const focusBorderColor = getColor( | ||||
|     props.theme, | ||||
|     props.focusBorderColor | ||||
|       ? props.focusBorderColor | ||||
|       : mode('brand.500', 'brand.300')(props) | ||||
|   ); | ||||
|  | ||||
|   return { | ||||
|     field: { | ||||
|       border: 0, | ||||
|       _focusVisible: { | ||||
|         borderColor: focusBorderColor, | ||||
|         boxShadow: `0 0 0 1px ${focusBorderColor}`, | ||||
|         ring: '1px', | ||||
|         ringColor: focusBorderColor, | ||||
|         ringOffset: '1px', | ||||
|         ringOffsetColor: focusBorderColor, | ||||
|       }, | ||||
|     }, | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| export default defineMultiStyleConfig({ | ||||
|   baseStyle, | ||||
| }); | ||||
| @@ -1,24 +0,0 @@ | ||||
| import { getColor, mode } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| export default { | ||||
|   variants: { | ||||
|     outline: (props) => { | ||||
|       const focusBorderColor = getColor( | ||||
|         props.theme, | ||||
|         props.focusBorderColor | ||||
|           ? props.focusBorderColor | ||||
|           : mode('brand.500', 'brand.300')(props) | ||||
|       ); | ||||
|       return { | ||||
|         field: { | ||||
|           bg: mode('gray.50', 'whiteAlpha.50')(props), | ||||
|           borderColor: mode('blackAlpha.100', 'whiteAlpha.100')(props), | ||||
|           _focus: { | ||||
|             borderColor: focusBorderColor, | ||||
|             boxShadow: `0 0 0 1px ${focusBorderColor}`, | ||||
|           }, | ||||
|         }, | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| @@ -1,22 +0,0 @@ | ||||
| import { getColor, mode } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| export default { | ||||
|   variants: { | ||||
|     outline: (props) => { | ||||
|       const focusBorderColor = getColor( | ||||
|         props.theme, | ||||
|         props.focusBorderColor | ||||
|           ? props.focusBorderColor | ||||
|           : mode('brand.500', 'brand.300')(props) | ||||
|       ); | ||||
|       return { | ||||
|         bg: mode('gray.50', 'whiteAlpha.50')(props), | ||||
|         borderColor: mode('blackAlpha.100', 'whiteAlpha.100')(props), | ||||
|         _focus: { | ||||
|           borderColor: focusBorderColor, | ||||
|           boxShadow: `0 0 0 1px ${focusBorderColor}`, | ||||
|         }, | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| @@ -1,9 +0,0 @@ | ||||
| import { colors } from './colors'; | ||||
| import { shadows } from './shadows'; | ||||
|  | ||||
| const foundations = { | ||||
|   colors, | ||||
|   shadows, | ||||
| }; | ||||
|  | ||||
| export default foundations; | ||||
| @@ -1,26 +0,0 @@ | ||||
| import { transparentize } from '@chakra-ui/theme-tools'; | ||||
|  | ||||
| import { colors } from './colors'; | ||||
|  | ||||
| const createOutline = (colorScheme = 'gray') => | ||||
|   `0 0 0 3px ${transparentize(`${colorScheme}.500`, 0.3)({ colors })}`; | ||||
|  | ||||
| export const shadows = { | ||||
|   outline: createOutline('brand'), | ||||
|   'outline-brand': '0 0 0 1px brand.900', | ||||
|   'outline-gray': createOutline('gray'), | ||||
|   'outline-over': `4px 4px 5px #00000088`, | ||||
|   'outline-darkgray': `0 0 0 3px ${transparentize( | ||||
|     'gray.500', | ||||
|     0.8 | ||||
|   )({ colors })}`, | ||||
|   'outline-success': createOutline('success'), | ||||
|   'outline-warning': createOutline('warning'), | ||||
|   'outline-error': createOutline('error'), | ||||
|   'outline-doing': createOutline('doing'), | ||||
|   'outline-paused': createOutline('paused'), | ||||
|   layout: '0 0 24px 1px rgba(0, 0, 0, 0.05)', | ||||
|   smooth: 'inset 0px 0px 16px rgba(0, 0, 0, 0.05)', | ||||
|   // smooth-light is used for dark backgrounds | ||||
|   'smooth-light': 'inset 0px 0px 16px rgba(255, 255, 255, 0.1)', | ||||
| }; | ||||
| @@ -1 +0,0 @@ | ||||
| export { theme as default } from './theme'; | ||||
							
								
								
									
										103
									
								
								front/src/theme/recipes/button.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								front/src/theme/recipes/button.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
|  | ||||
| // https://medium.com/@a.heydari.dev/simplifying-chakra-ui-v3-recipes-vs-chakra-factory-a-developers-perspective-4020b62f1b4d | ||||
|  | ||||
| // const shimmer = keyframes` | ||||
| //   100% { | ||||
| //     transform: translateX(100%); | ||||
| //   } | ||||
| // `; | ||||
|  | ||||
| export const customVariant = ({ bg, bgHover, bgActive, color, colorHover, boxShadowHover }) => { | ||||
|   return { | ||||
|     bg, | ||||
|     color, | ||||
|     border: '1px solid transparent', | ||||
|     _focus: { | ||||
|       border: '1px solid', | ||||
|       borderColor: 'black', | ||||
|     }, | ||||
|     _hover: { | ||||
|       bg: bgHover, | ||||
|       color: colorHover, | ||||
|       boxShadow: boxShadowHover, | ||||
|       _disabled: { | ||||
|         bg, | ||||
|         boxShadow: 'none', | ||||
|       }, | ||||
|     }, | ||||
|     _active: { | ||||
|       bg: bgActive, | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| const buttonTheme = { | ||||
|   primary: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|       bgHover: { _light: 'brand.700', _dark: 'brand.400' }, | ||||
|       bgActive: { _light: 'brand.600', _dark: 'brand.300' }, | ||||
|       color: { _light: 'white', _dark: 'brand.900' }, | ||||
|       colorHover: { _light: 'brand.800', _dark: 'brand.100' }, | ||||
|       boxShadowHover: 'outline-over' | ||||
|     }), | ||||
|   secondary: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'brand.100', _dark: 'brand.900' }, | ||||
|       bgHover: { _light: 'brand.200', _dark: 'brand.800' }, | ||||
|       bgActive: { _light: 'brand.300', _dark: 'brand.700' }, | ||||
|       color: { _light: 'brand.700', _dark: 'brand.50' }, | ||||
|       colorHover: { _light: 'brand.800', _dark: 'brand.100' }, | ||||
|       boxShadowHover: 'outline-over', | ||||
|     }), | ||||
|   danger: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'error.600', _dark: 'error.600' }, | ||||
|       bgHover: { _light: 'error.700', _dark: 'error.500' }, | ||||
|       bgActive: { _light: 'error.600', _dark: 'error.500' }, | ||||
|       color: { _light: 'white', _dark: 'error.900' }, | ||||
|       colorHover: { _light: 'error.700', _dark: 'error.900' }, | ||||
|       boxShadowHover: 'outline-over', | ||||
|     }), | ||||
|   success: | ||||
|     customVariant({ | ||||
|       bg: { _light: 'green.300', _dark: 'green.300' }, | ||||
|       bgHover: { _light: 'green.400', _dark: 'green.400' }, | ||||
|       bgActive: { _light: 'green.500', _dark: 'green.400' }, | ||||
|       color: { _light: 'white', _dark: 'green.900' }, | ||||
|       colorHover: { _light: 'green.500', _dark: 'green.900' }, | ||||
|       boxShadowHover: 'outline-over', | ||||
|     }), | ||||
|   progress: | ||||
|   { | ||||
|     bg: { _light: `brand.500`, _dark: `brand.300` }, | ||||
|     overflow: 'hidden', | ||||
|     /* | ||||
|     _after: !props.isLoading | ||||
|       ? { | ||||
|         content: '""', | ||||
|         position: 'absolute', | ||||
|         top: 0, | ||||
|         right: 0, | ||||
|         bottom: 0, | ||||
|         left: 0, | ||||
|         transform: 'translateX(-100%)', | ||||
|         bgGradient: `linear(90deg, brand.100/0 0%, brand.100/2 20%, brand.100/5 60%, v.100/0`, | ||||
|       } | ||||
|       : undefined, | ||||
|       */ | ||||
|   }, | ||||
|  | ||||
|   menu: { | ||||
|     bg: 'back.100', | ||||
|     color: 'brand.900', | ||||
|     borderRadius: 0, | ||||
|     border: 0, | ||||
|     _hover: { background: 'back.300' }, | ||||
|     _focus: { border: 'none' }, | ||||
|     fontSize: '20px', | ||||
|     textTransform: 'uppercase', | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default buttonTheme; | ||||
							
								
								
									
										29
									
								
								front/src/theme/recipes/drawer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								front/src/theme/recipes/drawer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
|  | ||||
|  | ||||
| const drawerRecipe = { | ||||
|     base: { | ||||
|         bg: { _light: 'white', _dark: 'gray.800' }, | ||||
|         color: { _light: 'gray.900', _dark: 'whiteAlpha.900' }, | ||||
|         boxShadow: { _light: 'lg', _dark: 'dark-lg' }, | ||||
|         padding: 4, | ||||
|         borderRadius: 'md', | ||||
|     }, | ||||
|     variants: { | ||||
|         variant: { | ||||
|             solid: { | ||||
|                 bg: { _light: 'blue.500', _dark: 'blue.300' }, | ||||
|                 color: 'white', | ||||
|             }, | ||||
|             outline: { | ||||
|                 border: '1px solid', | ||||
|                 borderColor: { _light: 'gray.300', _dark: 'whiteAlpha.300' }, | ||||
|                 bg: { _light: 'white', _dark: 'gray.900' }, | ||||
|             }, | ||||
|         } | ||||
|     }, | ||||
|     defaultVariants: { | ||||
|         variant: 'solid', | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default drawerRecipe; | ||||
							
								
								
									
										20
									
								
								front/src/theme/recipes/flex.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								front/src/theme/recipes/flex.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
| const flexTheme = { | ||||
|   variants: { | ||||
|     variant: { | ||||
|       '@menu': { | ||||
|         bg: { _light: 'back.100', _dark: 'back.800' }, | ||||
|         color: { _light: 'brand.900', _dark: 'brand.100' }, | ||||
|         borderRadius: 0, | ||||
|         border: 0, | ||||
|         _hover: { background: { _light: 'back.300', _dark: 'back.600' } }, | ||||
|         _focus: { border: 'none' }, | ||||
|         fontSize: '20px', | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default flexTheme; | ||||
| @@ -2,11 +2,12 @@ export { default as Badge } from './badge'; | ||||
| export { default as Button } from './button'; | ||||
| export { default as Checkbox } from './checkbox'; | ||||
| export { default as Input } from './input'; | ||||
| export { default as NumberInput } from './numberInput'; | ||||
| //export { default as NumberInput } from './numberInput.ts_';
 | ||||
| export { default as Popover } from './popover'; | ||||
| export { default as Radio } from './radio'; | ||||
| export { default as Select } from './select'; | ||||
| export { default as Switch } from './switch'; | ||||
| export { default as Textarea } from './textarea'; | ||||
| export { default as Modal } from './modal'; | ||||
| //export { default as Modal } from './modal';
 | ||||
| export { default as Flex } from './flex'; | ||||
| export { default as Drawer } from './drawer'; | ||||
							
								
								
									
										20
									
								
								front/src/theme/recipes/input.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								front/src/theme/recipes/input.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
|  | ||||
| const inputTheme = { | ||||
|   variants: { | ||||
|     variant: { | ||||
|       outline: { | ||||
|         field: { | ||||
|           bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' }, | ||||
|           borderColor: { _light: 'gray.200', _dark: 'whiteAlpha.100' }, | ||||
|           color: { _light: 'gray.800', _dark: 'gray.50' }, | ||||
|           _focus: { | ||||
|             borderColor: { _light: 'gray.800', _dark: 'gray.50' }, | ||||
|             boxShadow: `0 0 0 1px gray.800`, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default inputTheme; | ||||
| @@ -1,5 +1,3 @@ | ||||
| import { modalAnatomy as parts } from '@chakra-ui/anatomy'; | ||||
| import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; | ||||
| 
 | ||||
| const { definePartsStyle, defineMultiStyleConfig } = | ||||
|   createMultiStyleConfigHelpers(parts.keys); | ||||
							
								
								
									
										19
									
								
								front/src/theme/recipes/select.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								front/src/theme/recipes/select.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
|  | ||||
| const selectTheme = { | ||||
|   variants: { | ||||
|     variant: { | ||||
|       outline: { | ||||
|         field: { | ||||
|           bg: { _light: 'gray.50', _dark: 'whiteAlpha.50' }, | ||||
|           borderColor: { _light: 'blackAlpha.100', _dark: 'whiteAlpha.100' }, | ||||
|           _focus: { | ||||
|             borderColor: { _light: 'brand.500', _dark: 'brand.300' }, | ||||
|             boxShadow: `0 0 0 1px black`, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default selectTheme; | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user