aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/.eslintrc.js (renamed from FrontEnd/.eslintrc.cjs)0
-rw-r--r--FrontEnd/package.json5
-rw-r--r--FrontEnd/pnpm-lock.yaml9
-rw-r--r--FrontEnd/src/common.ts10
-rw-r--r--FrontEnd/src/index.css3
-rw-r--r--FrontEnd/src/index.tsx7
-rw-r--r--FrontEnd/src/palette.ts167
-rw-r--r--FrontEnd/src/views/common/alert/alert.css2
-rw-r--r--FrontEnd/src/views/common/button/Button.css10
-rw-r--r--FrontEnd/src/views/common/button/Button.tsx5
-rw-r--r--FrontEnd/src/views/common/button/FlatButton.tsx5
-rw-r--r--FrontEnd/src/views/common/button/IconButton.tsx4
-rw-r--r--FrontEnd/src/views/common/button/LoadingButton.tsx5
-rw-r--r--FrontEnd/src/views/common/index.css216
-rw-r--r--FrontEnd/src/views/common/theme.css244
-rw-r--r--FrontEnd/tools/palette.ts271
-rw-r--r--FrontEnd/tools/theme-generator.ts403
-rw-r--r--FrontEnd/tools/tsconfig.json4
18 files changed, 687 insertions, 683 deletions
diff --git a/FrontEnd/.eslintrc.cjs b/FrontEnd/.eslintrc.js
index 6fcccd3e..6fcccd3e 100644
--- a/FrontEnd/.eslintrc.cjs
+++ b/FrontEnd/.eslintrc.js
diff --git a/FrontEnd/package.json b/FrontEnd/package.json
index 850543ee..268e9636 100644
--- a/FrontEnd/package.json
+++ b/FrontEnd/package.json
@@ -1,8 +1,6 @@
{
"name": "timeline",
"version": "0.4.0",
- "private": true,
- "type": "module",
"source": "index.html",
"scripts": {
"start": "parcel --port 5678",
@@ -43,6 +41,7 @@
"devDependencies": {
"@parcel/packager-raw-url": "2.9.3",
"@parcel/transformer-webmanifest": "2.9.3",
+ "@tsconfig/node20": "^1.0.2",
"@types/color": "^3.0.3",
"@types/lodash": "^4.14.195",
"@types/marked": "^5.0.0",
@@ -64,7 +63,7 @@
"parcel": "latest",
"prettier": "^3.0.0",
"process": "^0.11.10",
- "ts-node": "latest",
+ "ts-node": "^10.9.1",
"typescript": "^5.1.6"
}
} \ No newline at end of file
diff --git a/FrontEnd/pnpm-lock.yaml b/FrontEnd/pnpm-lock.yaml
index 0ee3d598..e57ef2f4 100644
--- a/FrontEnd/pnpm-lock.yaml
+++ b/FrontEnd/pnpm-lock.yaml
@@ -88,6 +88,9 @@ devDependencies:
'@parcel/transformer-webmanifest':
specifier: 2.9.3
version: 2.9.3(@parcel/core@2.9.3)
+ '@tsconfig/node20':
+ specifier: ^1.0.2
+ version: 1.0.2
'@types/color':
specifier: ^3.0.3
version: 3.0.3
@@ -152,7 +155,7 @@ devDependencies:
specifier: ^0.11.10
version: 0.11.10
ts-node:
- specifier: latest
+ specifier: ^10.9.1
version: 10.9.1(@types/node@20.4.1)(typescript@5.1.6)
typescript:
specifier: ^5.1.6
@@ -1396,6 +1399,10 @@ packages:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
dev: true
+ /@tsconfig/node20@1.0.2:
+ resolution: {integrity: sha512-pw0MmECiSTbBfIlT0x3iQLuJ8s3i2mwYoGxJ3vzqTNMdc4nO2VeqfCOQ/doGFa8iyPlqmBd98/5pBctWz7uN2A==}
+ dev: true
+
/@types/color-convert@2.0.0:
resolution: {integrity: sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==}
dependencies:
diff --git a/FrontEnd/src/common.ts b/FrontEnd/src/common.ts
index 965f9933..6dcd2a9e 100644
--- a/FrontEnd/src/common.ts
+++ b/FrontEnd/src/common.ts
@@ -8,3 +8,13 @@ export const highlightTimelineUsername = "crupest";
export type { I18nText } from "./i18n";
export { c, convertI18nText } from "./i18n";
export { default as useC } from "./utilities/hooks/use-c";
+
+export const themeColors = [
+ "primary",
+ "secondary",
+ "tertiary",
+ "danger",
+ "success",
+] as const;
+
+export type ThemeColor = (typeof themeColors)[number];
diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css
index 419ccb8c..2faecfae 100644
--- a/FrontEnd/src/index.css
+++ b/FrontEnd/src/index.css
@@ -75,6 +75,7 @@ i {
.markdown-container {
white-space: initial;
}
+
.markdown-container img {
max-height: 200px;
max-width: 100%;
@@ -82,4 +83,4 @@ i {
a {
text-decoration: none;
-}
+} \ No newline at end of file
diff --git a/FrontEnd/src/index.tsx b/FrontEnd/src/index.tsx
index ba61d357..64d39cd5 100644
--- a/FrontEnd/src/index.tsx
+++ b/FrontEnd/src/index.tsx
@@ -1,14 +1,9 @@
-import "regenerator-runtime";
-import "core-js/modules/es.promise";
-import "core-js/modules/es.array.iterator";
-
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import "./i18n";
-import "./palette";
import App from "./App";
@@ -18,5 +13,5 @@ const root = createRoot(container!);
root.render(
<StrictMode>
<App />
- </StrictMode>
+ </StrictMode>,
);
diff --git a/FrontEnd/src/palette.ts b/FrontEnd/src/palette.ts
deleted file mode 100644
index d06f9b19..00000000
--- a/FrontEnd/src/palette.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import Color from "color";
-import { BehaviorSubject, Observable } from "rxjs";
-
-import refreshAnimation from "./utilities/refreshAnimation";
-
-function lightenBy(color: Color, ratio: number): Color {
- const lightness = color.lightness();
- return color.lightness(lightness + (100 - lightness) * ratio);
-}
-
-function darkenBy(color: Color, ratio: number): Color {
- const lightness = color.lightness();
- return color.lightness(lightness - lightness * ratio);
-}
-
-export interface PaletteColor {
- color: string;
- l1: string;
- l2: string;
- l3: string;
- d1: string;
- d2: string;
- d3: string;
- f1: string;
- f2: string;
- f3: string;
- r1: string;
- r2: string;
- r3: string;
- t: string;
- t1: string;
- t2: string;
- t3: string;
- [key: string]: string;
-}
-
-const paletteColorList = [
- "primary",
- "primary-enhance",
- "secondary",
- "danger",
- "success",
-] as const;
-
-export type PaletteColorType = (typeof paletteColorList)[number];
-
-export type Palette = Record<PaletteColorType, PaletteColor>;
-
-export function generatePaletteColor(color: string): PaletteColor {
- const c = Color(color);
- const light = c.lightness() > 60;
- const l1 = lightenBy(c, 0.1).rgb().toString();
- const l2 = lightenBy(c, 0.2).rgb().toString();
- const l3 = lightenBy(c, 0.3).rgb().toString();
- const d1 = darkenBy(c, 0.1).rgb().toString();
- const d2 = darkenBy(c, 0.2).rgb().toString();
- const d3 = darkenBy(c, 0.3).rgb().toString();
- const f1 = light ? l1 : d1;
- const f2 = light ? l2 : d2;
- const f3 = light ? l3 : d3;
- const r1 = light ? d1 : l1;
- const r2 = light ? d2 : l2;
- const r3 = light ? d3 : l3;
- const _t = light ? Color("black") : Color("white");
- const t = _t.rgb().toString();
- const _b = light ? lightenBy : darkenBy;
- const t1 = _b(_t, 0.1).rgb().toString();
- const t2 = _b(_t, 0.2).rgb().toString();
- const t3 = _b(_t, 0.3).rgb().toString();
-
- return {
- color: c.rgb().toString(),
- l1,
- l2,
- l3,
- d1,
- d2,
- d3,
- f1,
- f2,
- f3,
- r1,
- r2,
- r3,
- t,
- t1,
- t2,
- t3,
- };
-}
-
-export function generatePalette(options: {
- primary: string;
- primaryEnhance?: string;
- secondary?: string;
-}): Palette {
- const { primary, primaryEnhance, secondary } = options;
- const p = Color(primary);
- const pe =
- primaryEnhance == null
- ? lightenBy(p, 0.3).saturate(0.3)
- : Color(primaryEnhance);
- const s = secondary == null ? Color("gray") : Color(secondary);
-
- return {
- primary: generatePaletteColor(p.toString()),
- "primary-enhance": generatePaletteColor(pe.toString()),
- secondary: generatePaletteColor(s.toString()),
- danger: generatePaletteColor("red"),
- success: generatePaletteColor("green"),
- };
-}
-
-export function generatePaletteCSS(palette: Palette): string {
- const colors: [string, string][] = [];
- for (const colorType of paletteColorList) {
- const paletteColor = palette[colorType];
- for (const variant in paletteColor) {
- let key = `--cru-${colorType}`;
- if (variant !== "color") key += `-${variant}`;
- key += "-color";
- colors.push([key, paletteColor[variant]]);
- }
- }
-
- return `:root {${colors
- .map(([key, color]) => `${key} : ${color};`)
- .join("")}}`;
-}
-
-const paletteSubject: BehaviorSubject<Palette | null> =
- new BehaviorSubject<Palette | null>(
- generatePalette({ primary: "rgb(0, 123, 255)" })
- );
-
-export const palette$: Observable<Palette | null> =
- paletteSubject.asObservable();
-
-palette$.subscribe((palette) => {
- const styleTagId = "timeline-palette-css";
- if (palette != null) {
- let styleTag = document.getElementById(styleTagId);
- if (styleTag == null) {
- styleTag = document.createElement("style");
- styleTag.id = styleTagId;
- document.head.append(styleTag);
- }
- styleTag.innerHTML = generatePaletteCSS(palette);
- } else {
- const styleTag = document.getElementById(styleTagId);
- if (styleTag != null) {
- styleTag.parentElement?.removeChild(styleTag);
- }
- }
-
- refreshAnimation();
-});
-
-export function setPalette(palette: Palette): () => void {
- const old = paletteSubject.value;
-
- paletteSubject.next(palette);
-
- return () => {
- paletteSubject.next(old);
- };
-}
diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css
index fc15e3cb..83e1af28 100644
--- a/FrontEnd/src/views/common/alert/alert.css
+++ b/FrontEnd/src/views/common/alert/alert.css
@@ -7,7 +7,7 @@
border-radius: 5px;
border: var(--cru-theme-color) 1px solid;
color: var(--cru-theme-t-color);
- background-color: var(--cru-theme-r1-color);
+ background-color: var(--cru-theme-b1-color);
display: flex;
overflow: hidden;
diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css
index c34176f6..406d70d2 100644
--- a/FrontEnd/src/views/common/button/Button.css
+++ b/FrontEnd/src/views/common/button/Button.css
@@ -1,5 +1,5 @@
.cru-button:not(.outline) {
- color: var(--cru-theme-t-color);
+ color: var(--cru-text-color);
cursor: pointer;
padding: 0.2em 0.5em;
border-radius: 0.2em;
@@ -17,7 +17,7 @@
}
.cru-button:not(.outline):disabled {
- background-color: var(--cru-disable-color);
+ background-color: var(--cru-disabled-color);
cursor: auto;
}
@@ -44,8 +44,8 @@
}
.cru-button.outline:disabled {
- color: var(--cru-disable-color);
- border-color: var(--cru-disable-color);
+ color: var(--cru-disabled-color);
+ border-color: var(--cru-disabled-color);
background-color: white;
cursor: auto;
-}
+} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx
index be605328..53f41bc1 100644
--- a/FrontEnd/src/views/common/button/Button.tsx
+++ b/FrontEnd/src/views/common/button/Button.tsx
@@ -1,13 +1,12 @@
import { ComponentPropsWithoutRef, Ref } from "react";
import classNames from "classnames";
-import { I18nText, useC } from "@/common";
-import { PaletteColorType } from "@/palette";
+import { I18nText, useC, ThemeColor } from "@/common";
import "./Button.css";
interface ButtonProps extends ComponentPropsWithoutRef<"button"> {
- color?: PaletteColorType;
+ color?: ThemeColor;
text?: I18nText;
outline?: boolean;
buttonRef?: Ref<HTMLButtonElement> | null;
diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx
index 49912b68..a5354670 100644
--- a/FrontEnd/src/views/common/button/FlatButton.tsx
+++ b/FrontEnd/src/views/common/button/FlatButton.tsx
@@ -1,13 +1,12 @@
import { ComponentPropsWithoutRef, Ref } from "react";
import classNames from "classnames";
-import { I18nText, useC } from "@/common";
-import { PaletteColorType } from "@/palette";
+import { I18nText, useC, ThemeColor } from "@/common";
import "./FlatButton.css";
interface FlatButtonProps extends ComponentPropsWithoutRef<"button"> {
- color?: PaletteColorType;
+ color?: ThemeColor;
text?: I18nText;
buttonRef?: Ref<HTMLButtonElement> | null;
}
diff --git a/FrontEnd/src/views/common/button/IconButton.tsx b/FrontEnd/src/views/common/button/IconButton.tsx
index 652a8b09..0ff9541a 100644
--- a/FrontEnd/src/views/common/button/IconButton.tsx
+++ b/FrontEnd/src/views/common/button/IconButton.tsx
@@ -1,13 +1,13 @@
import { ComponentPropsWithoutRef } from "react";
import classNames from "classnames";
-import { PaletteColorType } from "@/palette";
+import { ThemeColor } from "@/common";
import "./IconButton.css";
interface IconButtonProps extends ComponentPropsWithoutRef<"i"> {
icon: string;
- color?: PaletteColorType;
+ color?: ThemeColor;
large?: boolean;
}
diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx
index fceaec27..c4cafc0c 100644
--- a/FrontEnd/src/views/common/button/LoadingButton.tsx
+++ b/FrontEnd/src/views/common/button/LoadingButton.tsx
@@ -2,13 +2,12 @@ import * as React from "react";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
-import { convertI18nText, I18nText } from "@/common";
-import { PaletteColorType } from "@/palette";
+import { convertI18nText, I18nText, ThemeColor } from "@/common";
import Spinner from "../Spinner";
interface LoadingButtonProps extends React.ComponentPropsWithoutRef<"button"> {
- color?: PaletteColorType;
+ color?: ThemeColor;
text?: I18nText;
loading?: boolean;
}
diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css
index 111a3ec0..06f2556b 100644
--- a/FrontEnd/src/views/common/index.css
+++ b/FrontEnd/src/views/common/index.css
@@ -1,218 +1,4 @@
-:root {
- --cru-background-color: #f8f9fa;
- --cru-background-1-color: #e9ecef;
- --cru-background-2-color: #dee2e6;
-
- --cru-disable-color: #ced4da;
-
- /*
- --cru-primary-color: rgb(0, 123, 255);
- --cru-primary-l1-color: rgb(26, 136, 255);
- --cru-primary-l2-color: rgb(51, 149, 255);
- --cru-primary-l3-color: rgb(77, 163, 255);
- --cru-primary-d1-color: rgb(0, 111, 230);
- --cru-primary-d2-color: rgb(0, 98, 204);
- --cru-primary-d3-color: rgb(0, 86, 179);
- --cru-primary-f1-color: rgb(0, 111, 230);
- --cru-primary-f2-color: rgb(0, 98, 204);
- --cru-primary-f3-color: rgb(0, 86, 179);
- --cru-primary-r1-color: rgb(26, 136, 255);
- --cru-primary-r2-color: rgb(51, 149, 255);
- --cru-primary-r3-color: rgb(77, 163, 255);
- --cru-primary-t-color: rgb(255, 255, 255);
- --cru-primary-t1-color: rgb(230, 230, 230);
- --cru-primary-t2-color: rgb(204, 204, 204);
- --cru-primary-t3-color: rgb(179, 179, 179);
- --cru-primary-enhance-color: rgb(77, 163, 255);
- --cru-primary-enhance-l1-color: rgb(94, 172, 255);
- --cru-primary-enhance-l2-color: rgb(112, 181, 255);
- --cru-primary-enhance-l3-color: rgb(130, 190, 255);
- --cru-primary-enhance-d1-color: rgb(43, 145, 255);
- --cru-primary-enhance-d2-color: rgb(10, 128, 255);
- --cru-primary-enhance-d3-color: rgb(0, 112, 232);
- --cru-primary-enhance-f1-color: rgb(94, 172, 255);
- --cru-primary-enhance-f2-color: rgb(112, 181, 255);
- --cru-primary-enhance-f3-color: rgb(130, 190, 255);
- --cru-primary-enhance-r1-color: rgb(43, 145, 255);
- --cru-primary-enhance-r2-color: rgb(10, 128, 255);
- --cru-primary-enhance-r3-color: rgb(0, 112, 232);
- --cru-primary-enhance-t-color: rgb(0, 0, 0);
- --cru-primary-enhance-t1-color: rgb(26, 26, 26);
- --cru-primary-enhance-t2-color: rgb(51, 51, 51);
- --cru-primary-enhance-t3-color: rgb(77, 77, 77);
- --cru-secondary-color: rgb(128, 128, 128);
- --cru-secondary-l1-color: rgb(141, 141, 141);
- --cru-secondary-l2-color: rgb(153, 153, 153);
- --cru-secondary-l3-color: rgb(166, 166, 166);
- --cru-secondary-d1-color: rgb(115, 115, 115);
- --cru-secondary-d2-color: rgb(102, 102, 102);
- --cru-secondary-d3-color: rgb(90, 90, 90);
- --cru-secondary-f1-color: rgb(115, 115, 115);
- --cru-secondary-f2-color: rgb(102, 102, 102);
- --cru-secondary-f3-color: rgb(90, 90, 90);
- --cru-secondary-r1-color: rgb(141, 141, 141);
- --cru-secondary-r2-color: rgb(153, 153, 153);
- --cru-secondary-r3-color: rgb(166, 166, 166);
- --cru-secondary-t-color: rgb(255, 255, 255);
- --cru-secondary-t1-color: rgb(230, 230, 230);
- --cru-secondary-t2-color: rgb(204, 204, 204);
- --cru-secondary-t3-color: rgb(179, 179, 179);
- --cru-danger-color: rgb(255, 0, 0);
- --cru-danger-l1-color: rgb(255, 26, 26);
- --cru-danger-l2-color: rgb(255, 51, 51);
- --cru-danger-l3-color: rgb(255, 77, 77);
- --cru-danger-d1-color: rgb(230, 0, 0);
- --cru-danger-d2-color: rgb(204, 0, 0);
- --cru-danger-d3-color: rgb(179, 0, 0);
- --cru-danger-f1-color: rgb(230, 0, 0);
- --cru-danger-f2-color: rgb(204, 0, 0);
- --cru-danger-f3-color: rgb(179, 0, 0);
- --cru-danger-r1-color: rgb(255, 26, 26);
- --cru-danger-r2-color: rgb(255, 51, 51);
- --cru-danger-r3-color: rgb(255, 77, 77);
- --cru-danger-t-color: rgb(255, 255, 255);
- --cru-danger-t1-color: rgb(230, 230, 230);
- --cru-danger-t2-color: rgb(204, 204, 204);
- --cru-danger-t3-color: rgb(179, 179, 179);
- --cru-success-color: rgb(0, 128, 0);
- --cru-success-l1-color: rgb(0, 166, 0);
- --cru-success-l2-color: rgb(0, 204, 0);
- --cru-success-l3-color: rgb(0, 243, 0);
- --cru-success-d1-color: rgb(0, 115, 0);
- --cru-success-d2-color: rgb(0, 102, 0);
- --cru-success-d3-color: rgb(0, 90, 0);
- --cru-success-f1-color: rgb(0, 115, 0);
- --cru-success-f2-color: rgb(0, 102, 0);
- --cru-success-f3-color: rgb(0, 90, 0);
- --cru-success-r1-color: rgb(0, 166, 0);
- --cru-success-r2-color: rgb(0, 204, 0);
- --cru-success-r3-color: rgb(0, 243, 0);
- --cru-success-t-color: rgb(255, 255, 255);
- --cru-success-t1-color: rgb(230, 230, 230);
- --cru-success-t2-color: rgb(204, 204, 204);
- --cru-success-t3-color: rgb(179, 179, 179);
- */
-}
-
-.cru-primary {
- --cru-theme-color: var(--cru-primary-color);
- --cru-theme-l1-color: var(--cru-primary-l1-color);
- --cru-theme-l2-color: var(--cru-primary-l2-color);
- --cru-theme-l3-color: var(--cru-primary-l3-color);
- --cru-theme-d1-color: var(--cru-primary-d1-color);
- --cru-theme-d2-color: var(--cru-primary-d2-color);
- --cru-theme-d3-color: var(--cru-primary-d3-color);
- --cru-theme-f1-color: var(--cru-primary-f1-color);
- --cru-theme-f2-color: var(--cru-primary-f2-color);
- --cru-theme-f3-color: var(--cru-primary-f3-color);
- --cru-theme-r1-color: var(--cru-primary-r1-color);
- --cru-theme-r2-color: var(--cru-primary-r2-color);
- --cru-theme-r3-color: var(--cru-primary-r3-color);
- --cru-theme-t-color: var(--cru-primary-t-color);
- --cru-theme-t1-color: var(--cru-primary-t1-color);
- --cru-theme-t2-color: var(--cru-primary-t2-color);
- --cru-theme-t3-color: var(--cru-primary-t3-color);
-}
-
-.cru-primary-enhance {
- --cru-theme-color: var(--cru-primary-enhance-color);
- --cru-theme-l1-color: var(--cru-primary-enhance-l1-color);
- --cru-theme-l2-color: var(--cru-primary-enhance-l2-color);
- --cru-theme-l3-color: var(--cru-primary-enhance-l3-color);
- --cru-theme-d1-color: var(--cru-primary-enhance-d1-color);
- --cru-theme-d2-color: var(--cru-primary-enhance-d2-color);
- --cru-theme-d3-color: var(--cru-primary-enhance-d3-color);
- --cru-theme-f1-color: var(--cru-primary-enhance-f1-color);
- --cru-theme-f2-color: var(--cru-primary-enhance-f2-color);
- --cru-theme-f3-color: var(--cru-primary-enhance-f3-color);
- --cru-theme-r1-color: var(--cru-primary-enhance-r1-color);
- --cru-theme-r2-color: var(--cru-primary-enhance-r2-color);
- --cru-theme-r3-color: var(--cru-primary-enhance-r3-color);
- --cru-theme-t-color: var(--cru-primary-enhance-t-color);
- --cru-theme-t1-color: var(--cru-primary-enhance-t1-color);
- --cru-theme-t2-color: var(--cru-primary-enhance-t2-color);
- --cru-theme-t3-color: var(--cru-primary-enhance-t3-color);
-}
-
-.cru-secondary {
- --cru-theme-color: var(--cru-secondary-color);
- --cru-theme-l1-color: var(--cru-secondary-l1-color);
- --cru-theme-l2-color: var(--cru-secondary-l2-color);
- --cru-theme-l3-color: var(--cru-secondary-l3-color);
- --cru-theme-d1-color: var(--cru-secondary-d1-color);
- --cru-theme-d2-color: var(--cru-secondary-d2-color);
- --cru-theme-d3-color: var(--cru-secondary-d3-color);
- --cru-theme-f1-color: var(--cru-secondary-f1-color);
- --cru-theme-f2-color: var(--cru-secondary-f2-color);
- --cru-theme-f3-color: var(--cru-secondary-f3-color);
- --cru-theme-r1-color: var(--cru-secondary-r1-color);
- --cru-theme-r2-color: var(--cru-secondary-r2-color);
- --cru-theme-r3-color: var(--cru-secondary-r3-color);
- --cru-theme-t-color: var(--cru-secondary-t-color);
- --cru-theme-t1-color: var(--cru-secondary-t1-color);
- --cru-theme-t2-color: var(--cru-secondary-t2-color);
- --cru-theme-t3-color: var(--cru-secondary-t3-color);
-}
-
-.cru-success {
- --cru-theme-color: var(--cru-success-color);
- --cru-theme-l1-color: var(--cru-success-l1-color);
- --cru-theme-l2-color: var(--cru-success-l2-color);
- --cru-theme-l3-color: var(--cru-success-l3-color);
- --cru-theme-d1-color: var(--cru-success-d1-color);
- --cru-theme-d2-color: var(--cru-success-d2-color);
- --cru-theme-d3-color: var(--cru-success-d3-color);
- --cru-theme-f1-color: var(--cru-success-f1-color);
- --cru-theme-f2-color: var(--cru-success-f2-color);
- --cru-theme-f3-color: var(--cru-success-f3-color);
- --cru-theme-r1-color: var(--cru-success-r1-color);
- --cru-theme-r2-color: var(--cru-success-r2-color);
- --cru-theme-r3-color: var(--cru-success-r3-color);
- --cru-theme-t-color: var(--cru-success-t-color);
- --cru-theme-t1-color: var(--cru-success-t1-color);
- --cru-theme-t2-color: var(--cru-success-t2-color);
- --cru-theme-t3-color: var(--cru-success-t3-color);
-}
-
-.cru-danger {
- --cru-theme-color: var(--cru-danger-color);
- --cru-theme-l1-color: var(--cru-danger-l1-color);
- --cru-theme-l2-color: var(--cru-danger-l2-color);
- --cru-theme-l3-color: var(--cru-danger-l3-color);
- --cru-theme-d1-color: var(--cru-danger-d1-color);
- --cru-theme-d2-color: var(--cru-danger-d2-color);
- --cru-theme-d3-color: var(--cru-danger-d3-color);
- --cru-theme-f1-color: var(--cru-danger-f1-color);
- --cru-theme-f2-color: var(--cru-danger-f2-color);
- --cru-theme-f3-color: var(--cru-danger-f3-color);
- --cru-theme-r1-color: var(--cru-danger-r1-color);
- --cru-theme-r2-color: var(--cru-danger-r2-color);
- --cru-theme-r3-color: var(--cru-danger-r3-color);
- --cru-theme-t-color: var(--cru-danger-t-color);
- --cru-theme-t1-color: var(--cru-danger-t1-color);
- --cru-theme-t2-color: var(--cru-danger-t2-color);
- --cru-theme-t3-color: var(--cru-danger-t3-color);
-}
-
-.cru-color-primary {
- color: var(--cru-primary-color);
-}
-
-.cru-color-primary-enhance {
- color: var(--cru-primary-enhance-color);
-}
-
-.cru-color-secondary {
- color: var(--cru-secondary-color);
-}
-
-.cru-color-success {
- color: var(--cru-success-color);
-}
-
-.cru-color-danger {
- color: var(--cru-danger-color);
-}
+@import "./theme.css";
.cru-text-center {
text-align: center;
diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css
new file mode 100644
index 00000000..816bd3d1
--- /dev/null
+++ b/FrontEnd/src/views/common/theme.css
@@ -0,0 +1,244 @@
+/* Generated by theme-generator.ts */
+
+:root {
+ --cru-primary-color: hsl(210 100% 50%);
+ --cru-primary-l1-color: hsl(210 100% 60%);
+ --cru-primary-l2-color: hsl(210 100% 70%);
+ --cru-primary-l3-color: hsl(210 100% 80%);
+ --cru-primary-d1-color: hsl(210 100% 40%);
+ --cru-primary-d2-color: hsl(210 100% 30%);
+ --cru-primary-d3-color: hsl(210 100% 20%);
+ --cru-primary-f1-color: hsl(210 100% 60%);
+ --cru-primary-f2-color: hsl(210 100% 70%);
+ --cru-primary-f3-color: hsl(210 100% 80%);
+ --cru-primary-b1-color: hsl(210 100% 40%);
+ --cru-primary-b2-color: hsl(210 100% 30%);
+ --cru-primary-b3-color: hsl(210 100% 20%);
+ --cru-secondary-color: hsl(40 100% 50%);
+ --cru-secondary-l1-color: hsl(40 100% 60%);
+ --cru-secondary-l2-color: hsl(40 100% 70%);
+ --cru-secondary-l3-color: hsl(40 100% 80%);
+ --cru-secondary-d1-color: hsl(40 100% 40%);
+ --cru-secondary-d2-color: hsl(40 100% 30%);
+ --cru-secondary-d3-color: hsl(40 100% 20%);
+ --cru-secondary-f1-color: hsl(40 100% 60%);
+ --cru-secondary-f2-color: hsl(40 100% 70%);
+ --cru-secondary-f3-color: hsl(40 100% 80%);
+ --cru-secondary-b1-color: hsl(40 100% 40%);
+ --cru-secondary-b2-color: hsl(40 100% 30%);
+ --cru-secondary-b3-color: hsl(40 100% 20%);
+ --cru-tertiary-color: hsl(160 100% 50%);
+ --cru-tertiary-l1-color: hsl(160 100% 60%);
+ --cru-tertiary-l2-color: hsl(160 100% 70%);
+ --cru-tertiary-l3-color: hsl(160 100% 80%);
+ --cru-tertiary-d1-color: hsl(160 100% 40%);
+ --cru-tertiary-d2-color: hsl(160 100% 30%);
+ --cru-tertiary-d3-color: hsl(160 100% 20%);
+ --cru-tertiary-f1-color: hsl(160 100% 60%);
+ --cru-tertiary-f2-color: hsl(160 100% 70%);
+ --cru-tertiary-f3-color: hsl(160 100% 80%);
+ --cru-tertiary-b1-color: hsl(160 100% 40%);
+ --cru-tertiary-b2-color: hsl(160 100% 30%);
+ --cru-tertiary-b3-color: hsl(160 100% 20%);
+ --cru-danger-color: hsl(0 100% 50%);
+ --cru-danger-l1-color: hsl(0 100% 60%);
+ --cru-danger-l2-color: hsl(0 100% 70%);
+ --cru-danger-l3-color: hsl(0 100% 80%);
+ --cru-danger-d1-color: hsl(0 100% 40%);
+ --cru-danger-d2-color: hsl(0 100% 30%);
+ --cru-danger-d3-color: hsl(0 100% 20%);
+ --cru-danger-f1-color: hsl(0 100% 60%);
+ --cru-danger-f2-color: hsl(0 100% 70%);
+ --cru-danger-f3-color: hsl(0 100% 80%);
+ --cru-danger-b1-color: hsl(0 100% 40%);
+ --cru-danger-b2-color: hsl(0 100% 30%);
+ --cru-danger-b3-color: hsl(0 100% 20%);
+ --cru-success-color: hsl(120 100% 50%);
+ --cru-success-l1-color: hsl(120 100% 60%);
+ --cru-success-l2-color: hsl(120 100% 70%);
+ --cru-success-l3-color: hsl(120 100% 80%);
+ --cru-success-d1-color: hsl(120 100% 40%);
+ --cru-success-d2-color: hsl(120 100% 30%);
+ --cru-success-d3-color: hsl(120 100% 20%);
+ --cru-success-f1-color: hsl(120 100% 60%);
+ --cru-success-f2-color: hsl(120 100% 70%);
+ --cru-success-f3-color: hsl(120 100% 80%);
+ --cru-success-b1-color: hsl(120 100% 40%);
+ --cru-success-b2-color: hsl(120 100% 30%);
+ --cru-success-b3-color: hsl(120 100% 20%);
+ --cru-text-color: hsl(0 0% 0%);
+ --cru-text-1-color: hsl(0 0% 10%);
+ --cru-text-2-color: hsl(0 0% 20%);
+ --cru-text-3-color: hsl(0 0% 30%);
+ --cru-bg-color: hsl(0 0% 100%);
+ --cru-bg-1-color: hsl(0 0% 90%);
+ --cru-bg-2-color: hsl(0 0% 80%);
+ --cru-bg-3-color: hsl(0 0% 70%);
+ --cru-disabled-color: hsl(0 0% 75%);
+ --cru-disabled-1-color: hsl(0 0% 65%);
+ --cru-disabled-2-color: hsl(0 0% 55%);
+ --cru-disabled-3-color: hsl(0 0% 45%);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --cru-primary-color: hsl(210 100% 50%);
+ --cru-primary-l1-color: hsl(210 100% 60%);
+ --cru-primary-l2-color: hsl(210 100% 70%);
+ --cru-primary-l3-color: hsl(210 100% 80%);
+ --cru-primary-d1-color: hsl(210 100% 40%);
+ --cru-primary-d2-color: hsl(210 100% 30%);
+ --cru-primary-d3-color: hsl(210 100% 20%);
+ --cru-primary-f1-color: hsl(210 100% 40%);
+ --cru-primary-f2-color: hsl(210 100% 30%);
+ --cru-primary-f3-color: hsl(210 100% 20%);
+ --cru-primary-b1-color: hsl(210 100% 60%);
+ --cru-primary-b2-color: hsl(210 100% 70%);
+ --cru-primary-b3-color: hsl(210 100% 80%);
+ --cru-secondary-color: hsl(40 100% 50%);
+ --cru-secondary-l1-color: hsl(40 100% 60%);
+ --cru-secondary-l2-color: hsl(40 100% 70%);
+ --cru-secondary-l3-color: hsl(40 100% 80%);
+ --cru-secondary-d1-color: hsl(40 100% 40%);
+ --cru-secondary-d2-color: hsl(40 100% 30%);
+ --cru-secondary-d3-color: hsl(40 100% 20%);
+ --cru-secondary-f1-color: hsl(40 100% 40%);
+ --cru-secondary-f2-color: hsl(40 100% 30%);
+ --cru-secondary-f3-color: hsl(40 100% 20%);
+ --cru-secondary-b1-color: hsl(40 100% 60%);
+ --cru-secondary-b2-color: hsl(40 100% 70%);
+ --cru-secondary-b3-color: hsl(40 100% 80%);
+ --cru-tertiary-color: hsl(160 100% 50%);
+ --cru-tertiary-l1-color: hsl(160 100% 60%);
+ --cru-tertiary-l2-color: hsl(160 100% 70%);
+ --cru-tertiary-l3-color: hsl(160 100% 80%);
+ --cru-tertiary-d1-color: hsl(160 100% 40%);
+ --cru-tertiary-d2-color: hsl(160 100% 30%);
+ --cru-tertiary-d3-color: hsl(160 100% 20%);
+ --cru-tertiary-f1-color: hsl(160 100% 40%);
+ --cru-tertiary-f2-color: hsl(160 100% 30%);
+ --cru-tertiary-f3-color: hsl(160 100% 20%);
+ --cru-tertiary-b1-color: hsl(160 100% 60%);
+ --cru-tertiary-b2-color: hsl(160 100% 70%);
+ --cru-tertiary-b3-color: hsl(160 100% 80%);
+ --cru-danger-color: hsl(0 100% 50%);
+ --cru-danger-l1-color: hsl(0 100% 60%);
+ --cru-danger-l2-color: hsl(0 100% 70%);
+ --cru-danger-l3-color: hsl(0 100% 80%);
+ --cru-danger-d1-color: hsl(0 100% 40%);
+ --cru-danger-d2-color: hsl(0 100% 30%);
+ --cru-danger-d3-color: hsl(0 100% 20%);
+ --cru-danger-f1-color: hsl(0 100% 40%);
+ --cru-danger-f2-color: hsl(0 100% 30%);
+ --cru-danger-f3-color: hsl(0 100% 20%);
+ --cru-danger-b1-color: hsl(0 100% 60%);
+ --cru-danger-b2-color: hsl(0 100% 70%);
+ --cru-danger-b3-color: hsl(0 100% 80%);
+ --cru-success-color: hsl(120 100% 50%);
+ --cru-success-l1-color: hsl(120 100% 60%);
+ --cru-success-l2-color: hsl(120 100% 70%);
+ --cru-success-l3-color: hsl(120 100% 80%);
+ --cru-success-d1-color: hsl(120 100% 40%);
+ --cru-success-d2-color: hsl(120 100% 30%);
+ --cru-success-d3-color: hsl(120 100% 20%);
+ --cru-success-f1-color: hsl(120 100% 40%);
+ --cru-success-f2-color: hsl(120 100% 30%);
+ --cru-success-f3-color: hsl(120 100% 20%);
+ --cru-success-b1-color: hsl(120 100% 60%);
+ --cru-success-b2-color: hsl(120 100% 70%);
+ --cru-success-b3-color: hsl(120 100% 80%);
+ --cru-text-color: hsl(0 0% 100%);
+ --cru-text-1-color: hsl(0 0% 90%);
+ --cru-text-2-color: hsl(0 0% 80%);
+ --cru-text-3-color: hsl(0 0% 70%);
+ --cru-bg-color: hsl(0 0% 0%);
+ --cru-bg-1-color: hsl(0 0% 10%);
+ --cru-bg-2-color: hsl(0 0% 20%);
+ --cru-bg-3-color: hsl(0 0% 30%);
+ --cru-disabled-color: hsl(0 0% 25%);
+ --cru-disabled-1-color: hsl(0 0% 35%);
+ --cru-disabled-2-color: hsl(0 0% 45%);
+ --cru-disabled-3-color: hsl(0 0% 55%);
+ }
+}
+
+.cru-primary {
+ --cru-theme-color: var(--cru-primary-color);
+ --cru-theme-l1-color: var(--cru-primary-l1-color);
+ --cru-theme-l2-color: var(--cru-primary-l2-color);
+ --cru-theme-l3-color: var(--cru-primary-l3-color);
+ --cru-theme-d1-color: var(--cru-primary-d1-color);
+ --cru-theme-d2-color: var(--cru-primary-d2-color);
+ --cru-theme-d3-color: var(--cru-primary-d3-color);
+ --cru-theme-f1-color: var(--cru-primary-f1-color);
+ --cru-theme-f2-color: var(--cru-primary-f2-color);
+ --cru-theme-f3-color: var(--cru-primary-f3-color);
+ --cru-theme-b1-color: var(--cru-primary-b1-color);
+ --cru-theme-b2-color: var(--cru-primary-b2-color);
+ --cru-theme-b3-color: var(--cru-primary-b3-color);
+}
+
+.cru-secondary {
+ --cru-theme-color: var(--cru-secondary-color);
+ --cru-theme-l1-color: var(--cru-secondary-l1-color);
+ --cru-theme-l2-color: var(--cru-secondary-l2-color);
+ --cru-theme-l3-color: var(--cru-secondary-l3-color);
+ --cru-theme-d1-color: var(--cru-secondary-d1-color);
+ --cru-theme-d2-color: var(--cru-secondary-d2-color);
+ --cru-theme-d3-color: var(--cru-secondary-d3-color);
+ --cru-theme-f1-color: var(--cru-secondary-f1-color);
+ --cru-theme-f2-color: var(--cru-secondary-f2-color);
+ --cru-theme-f3-color: var(--cru-secondary-f3-color);
+ --cru-theme-b1-color: var(--cru-secondary-b1-color);
+ --cru-theme-b2-color: var(--cru-secondary-b2-color);
+ --cru-theme-b3-color: var(--cru-secondary-b3-color);
+}
+
+.cru-tertiary {
+ --cru-theme-color: var(--cru-tertiary-color);
+ --cru-theme-l1-color: var(--cru-tertiary-l1-color);
+ --cru-theme-l2-color: var(--cru-tertiary-l2-color);
+ --cru-theme-l3-color: var(--cru-tertiary-l3-color);
+ --cru-theme-d1-color: var(--cru-tertiary-d1-color);
+ --cru-theme-d2-color: var(--cru-tertiary-d2-color);
+ --cru-theme-d3-color: var(--cru-tertiary-d3-color);
+ --cru-theme-f1-color: var(--cru-tertiary-f1-color);
+ --cru-theme-f2-color: var(--cru-tertiary-f2-color);
+ --cru-theme-f3-color: var(--cru-tertiary-f3-color);
+ --cru-theme-b1-color: var(--cru-tertiary-b1-color);
+ --cru-theme-b2-color: var(--cru-tertiary-b2-color);
+ --cru-theme-b3-color: var(--cru-tertiary-b3-color);
+}
+
+.cru-danger {
+ --cru-theme-color: var(--cru-danger-color);
+ --cru-theme-l1-color: var(--cru-danger-l1-color);
+ --cru-theme-l2-color: var(--cru-danger-l2-color);
+ --cru-theme-l3-color: var(--cru-danger-l3-color);
+ --cru-theme-d1-color: var(--cru-danger-d1-color);
+ --cru-theme-d2-color: var(--cru-danger-d2-color);
+ --cru-theme-d3-color: var(--cru-danger-d3-color);
+ --cru-theme-f1-color: var(--cru-danger-f1-color);
+ --cru-theme-f2-color: var(--cru-danger-f2-color);
+ --cru-theme-f3-color: var(--cru-danger-f3-color);
+ --cru-theme-b1-color: var(--cru-danger-b1-color);
+ --cru-theme-b2-color: var(--cru-danger-b2-color);
+ --cru-theme-b3-color: var(--cru-danger-b3-color);
+}
+
+.cru-success {
+ --cru-theme-color: var(--cru-success-color);
+ --cru-theme-l1-color: var(--cru-success-l1-color);
+ --cru-theme-l2-color: var(--cru-success-l2-color);
+ --cru-theme-l3-color: var(--cru-success-l3-color);
+ --cru-theme-d1-color: var(--cru-success-d1-color);
+ --cru-theme-d2-color: var(--cru-success-d2-color);
+ --cru-theme-d3-color: var(--cru-success-d3-color);
+ --cru-theme-f1-color: var(--cru-success-f1-color);
+ --cru-theme-f2-color: var(--cru-success-f2-color);
+ --cru-theme-f3-color: var(--cru-success-f3-color);
+ --cru-theme-b1-color: var(--cru-success-b1-color);
+ --cru-theme-b2-color: var(--cru-success-b2-color);
+ --cru-theme-b3-color: var(--cru-success-b3-color);
+}
+
diff --git a/FrontEnd/tools/palette.ts b/FrontEnd/tools/palette.ts
deleted file mode 100644
index 7b047ce1..00000000
--- a/FrontEnd/tools/palette.ts
+++ /dev/null
@@ -1,271 +0,0 @@
-#!/usr/bin/env ts-node
-
-/**
- * Color variable name scheme:
- * has variant: --[prefix]-[name]-[variant]-color: [color];
- * no variant: --[prefix]-[name]-color: [color];
- * Variant scheme:
- * [variant-prefix][level]
- * eg. --cru-primary-color: [color]; --cru-primary-l1-color: [color];
- */
-
-class HslColor {
- constructor(
- public h: number,
- public s: number,
- public l: number,
- ) {}
-
- lighter(level: number): HslColor {
- return new HslColor(this.h, this.s, this.l + level * 10);
- }
-
- darker(level: number): HslColor {
- return new HslColor(this.h, this.s, this.l - level * 10);
- }
-
- toString(): string {
- return `hsl(${this.h} ${this.s}% ${this.l}%)`;
- }
-}
-
-class CssVarColor {
- constructor(
- public name: string,
- public cssVar: string,
- ) {}
-
- toString(): string {
- return `var(--${this.cssVar})`;
- }
-}
-
-class VariantColor {
- constructor(
- public color: HslColor,
- public variant?: string | null,
- ) {}
-
- toCssString(prefix: string, name: string): string {
- const variantPart = this.variant == null ? "" : `-${this.variant}`;
- return `--${prefix}-${name}${variantPart}-color: ${this.color.toString()};`;
- }
-}
-
-type LightnessVariantType = "lighter" | "darker";
-
-interface LightnessVariant {
- prefix: string;
- type: LightnessVariantType;
-}
-
-function generateLightnessVariantColors(
- baseColor: HslColor,
- lightnessVariant: LightnessVariant,
- levels: number,
-): VariantColor[] {
- const result: VariantColor[] = [];
-
- for (let i = 1; i <= levels; i++) {
- const color =
- lightnessVariant.type === "lighter"
- ? baseColor.lighter(i)
- : baseColor.darker(i);
- const colorVariant = `${lightnessVariant.prefix}${i}`;
- result.push(new VariantColor(color, colorVariant));
- }
-
- return result;
-}
-
-type ColorMode = "light" | "dark";
-
-const themeVariantPrefixes = ["l", "d", "f", "b"] as const;
-
-type LightnessVariants = {
- prefix: (typeof themeVariantPrefixes)[number];
- type: LightnessVariantType;
-}[];
-
-function generateThemeColorLightnessVariants(
- mode: ColorMode,
-): LightnessVariants {
- return [
- {
- prefix: "l",
- type: "lighter",
- },
- {
- prefix: "d",
- type: "darker",
- },
- {
- prefix: "f",
- type: mode === "light" ? "lighter" : "darker",
- },
- {
- prefix: "b",
- type: mode === "light" ? "darker" : "lighter",
- },
- ];
-}
-
-class ColorGroup {
- constructor(
- public name: string,
- public baseColor: HslColor,
- ) {}
-
- generateVariantColors(
- lightnessVariants: LightnessVariant[],
- levels = 3,
- ): VariantColor[] {
- const result: VariantColor[] = [new VariantColor(this.baseColor)];
-
- for (const lightnessVariant of lightnessVariants) {
- result.push(
- ...generateLightnessVariantColors(
- this.baseColor,
- lightnessVariant,
- levels,
- ),
- );
- }
-
- return result;
- }
-}
-
-class ThemeColorGroup extends ColorGroup {
- constructor(name: string, baseColor: HslColor) {
- super(name, baseColor);
- }
-
- generateColors(mode: ColorMode): VariantColor[] {
- return super.generateVariantColors(
- generateThemeColorLightnessVariants(mode),
- );
- }
-
- generateCss(prefix: string, mode: ColorMode): string {
- return this.generateColors(mode)
- .map((c) => c.toCssString(prefix, this.name))
- .join("\n");
- }
-}
-
-class VarColorGroup {
- constructor(
- public name: string,
- public varName: string,
- ) {}
-
- generateCss(
- prefix: string,
- variantPrefixes = themeVariantPrefixes,
- levels = 3,
- ): string {
- const vs: string[] = [];
- for (const v of variantPrefixes) {
- for (let l = 1; l <= levels; l++) {
- vs.push(`${v}${l}`);
- }
- }
- let result = vs
- .map(
- (v) =>
- `--${prefix}-${this.name}-${v}-color: var(--${prefix}-${this.varName}-${v}-color);`,
- )
- .join("\n");
- result = `--${prefix}-${this.name}-color: var(--${prefix}-${this.varName}-color);\n${result}`;
- return result;
- }
-}
-
-const themeColorNames = [
- "primary",
- "secondary",
- "tertiary",
- "danger",
- "success",
-] as const;
-
-type ThemeColorNames = (typeof themeColorNames)[number];
-
-type ThemeColors = {
- [key in ThemeColorNames]: HslColor;
-};
-
-// Config region begin
-const prefix = "cru";
-
-const themeColors: ThemeColors = {
- primary: new HslColor(210, 100, 50),
- secondary: new HslColor(40, 100, 50),
- tertiary: new HslColor(160, 100, 50),
- danger: new HslColor(0, 100, 50),
- success: new HslColor(120, 100, 50),
-};
-
-// Config region end
-
-let output = "";
-
-function indentText(
- text: string,
- level: number,
- indentWidth = 2,
- appendNewlines = 1,
-): string {
- const lines = text.split("\n");
- const indent = " ".repeat(level * indentWidth);
- return (
- lines
- .map((line) => (line.length === 0 ? "" : `${indent}${line}`))
- .join("\n") + "\n".repeat(appendNewlines)
- );
-}
-
-function print(text: string, indent = 0, appendNewlines = 1) {
- output += indentText(text, indent, 2, appendNewlines);
-}
-
-function generateThemeColorCss(mode: ColorMode): string {
- let output = "";
- for (const name of themeColorNames) {
- const colorGroup = new ThemeColorGroup(name, themeColors[name]);
- output += colorGroup.generateCss(prefix, mode);
- }
- return output;
-}
-
-function generateThemeColorAliasCss(): string {
- let result = "";
- for (const name of themeColorNames) {
- const varColorGroup = new VarColorGroup("theme", name);
- result += `.${prefix}-${name} {\n${indentText(
- varColorGroup.generateCss(prefix),
- 1,
- )}\n}\n`;
- }
- return result;
-}
-
-function main() {
- print("/* Generated by palette.ts */\n");
-
- print(":root {");
- print(generateThemeColorCss("light"), 1);
- print("}\n");
-
- print("@media (prefers-color-scheme: dark) {");
- print(":root {", 1);
- print(generateThemeColorCss("dark"), 2);
- print("}", 1);
- print("}\n");
-
- print(generateThemeColorAliasCss());
-}
-
-main();
-process.stdout.write(output);
diff --git a/FrontEnd/tools/theme-generator.ts b/FrontEnd/tools/theme-generator.ts
new file mode 100644
index 00000000..27dd5d1d
--- /dev/null
+++ b/FrontEnd/tools/theme-generator.ts
@@ -0,0 +1,403 @@
+#!/usr/bin/env ts-node
+
+/**
+ * Color variable name scheme:
+ * has variant: --[prefix]-[name]-[variant]-color: [color];
+ * no variant: --[prefix]-[name]-color: [color];
+ * Variant scheme:
+ * [variant-prefix][level]
+ * eg. --cru-primary-color: [color]; --cru-primary-l1-color: [color];
+ */
+
+import { stdout } from "process";
+
+interface CssSegment {
+ toCssString(): string;
+}
+
+interface Color extends CssSegment {
+ readonly type: "hsl" | "css-var";
+ toString(): string;
+}
+
+class HslColor implements Color {
+ readonly type = "hsl";
+
+ constructor(
+ public h: number,
+ public s: number,
+ public l: number,
+ ) {}
+
+ lighter(level: number): HslColor {
+ return new HslColor(this.h, this.s, this.l + level * 10);
+ }
+
+ darker(level: number): HslColor {
+ return new HslColor(this.h, this.s, this.l - level * 10);
+ }
+
+ toCssString(): string {
+ return this.toString();
+ }
+
+ toString(): string {
+ return `hsl(${this.h} ${this.s}% ${this.l}%)`;
+ }
+
+ static readonly white = new HslColor(0, 0, 100);
+ static readonly black = new HslColor(0, 0, 0);
+}
+
+class ColorVariable implements CssSegment {
+ constructor(
+ public prefix: string,
+ public name: string,
+ public variant?: string | null,
+ ) {}
+
+ toString(): string {
+ const variantPart = this.variant == null ? "" : `-${this.variant}`;
+ return `--${this.prefix}-${this.name}${variantPart}-color`;
+ }
+
+ toCssString(): string {
+ return this.toString();
+ }
+}
+
+class CssVarColor implements Color {
+ readonly type = "css-var";
+
+ constructor(public colorVariable: ColorVariable) {}
+
+ toCssString(): string {
+ return this.toString();
+ }
+
+ toString(): string {
+ return `var(${this.colorVariable.toString()})`;
+ }
+}
+
+class ColorVariableDefinition implements CssSegment {
+ constructor(
+ public name: ColorVariable,
+ public color: Color,
+ ) {}
+
+ toCssString(): string {
+ return `${this.name.toCssString()}: ${this.color.toCssString()};`;
+ }
+}
+
+type LightnessVariantType = "lighter" | "darker";
+
+interface LightnessVariantInfo {
+ prefix: string;
+ type: LightnessVariantType;
+ levels: number;
+}
+
+abstract class ColorGroup implements CssSegment {
+ abstract getColorVariables(): ColorVariableDefinition[];
+ toCssString(): string {
+ return this.getColorVariables()
+ .map((c) => c.toCssString())
+ .join("\n");
+ }
+}
+
+class LightnessVariantColorGroup extends ColorGroup {
+ constructor(
+ public prefix: string,
+ public name: string,
+ public baseColor: HslColor,
+ public lightnessVariants: LightnessVariantInfo[],
+ ) {
+ super();
+ }
+
+ getColorVariables(): ColorVariableDefinition[] {
+ const result: ColorVariableDefinition[] = [
+ new ColorVariableDefinition(
+ new ColorVariable(this.prefix, this.name),
+ this.baseColor,
+ ),
+ ];
+
+ for (const lightnessVariant of this.lightnessVariants) {
+ for (let i = 1; i <= lightnessVariant.levels; i++) {
+ const color =
+ lightnessVariant.type === "lighter"
+ ? this.baseColor.lighter(i)
+ : this.baseColor.darker(i);
+ const colorVariant = `${lightnessVariant.prefix}${i}`;
+ result.push(
+ new ColorVariableDefinition(
+ new ColorVariable(this.prefix, this.name, colorVariant),
+ color,
+ ),
+ );
+ }
+ }
+
+ return result;
+ }
+}
+
+class VarAliasColorGroup extends ColorGroup {
+ constructor(
+ public prefix: string,
+ public newName: string,
+ public oldName: string,
+ public variants: string[],
+ ) {
+ super();
+ }
+
+ getColorVariables(): ColorVariableDefinition[] {
+ const result = [
+ new ColorVariableDefinition(
+ new ColorVariable(this.prefix, this.newName),
+ new CssVarColor(new ColorVariable(this.prefix, this.oldName)),
+ ),
+ ];
+ for (const variant of this.variants) {
+ result.push(
+ new ColorVariableDefinition(
+ new ColorVariable(this.prefix, this.newName, variant),
+ new CssVarColor(
+ new ColorVariable(this.prefix, this.oldName, variant),
+ ),
+ ),
+ );
+ }
+ return result;
+ }
+}
+
+class GrayscaleColorGroup extends ColorGroup {
+ _delegate: LightnessVariantColorGroup;
+
+ constructor(
+ public prefix: string,
+ public name: string,
+ public baseColor: HslColor,
+ public type: LightnessVariantType,
+ public levels = 3,
+ ) {
+ super();
+
+ this._delegate = new LightnessVariantColorGroup(
+ prefix,
+ name,
+ this.baseColor,
+ [{ prefix: "", type: this.type, levels }],
+ );
+ }
+
+ getColorVariables(): ColorVariableDefinition[] {
+ return this._delegate.getColorVariables();
+ }
+
+ static white(prefix: string, name: string, levels = 3): GrayscaleColorGroup {
+ return new GrayscaleColorGroup(
+ prefix,
+ name,
+ HslColor.white,
+ "darker",
+ levels,
+ );
+ }
+
+ static black(prefix: string, name: string, levels = 3): GrayscaleColorGroup {
+ return new GrayscaleColorGroup(
+ prefix,
+ name,
+ HslColor.black,
+ "lighter",
+ levels,
+ );
+ }
+}
+
+class CompositeColorGroup extends ColorGroup {
+ constructor(public groups: ColorGroup[]) {
+ super();
+ }
+
+ getColorVariables(): ColorVariableDefinition[] {
+ return this.groups
+ .map((g) => g.getColorVariables())
+ .reduce((prev, curr) => prev.concat(curr), []);
+ }
+}
+
+type ThemeColors = { name: string; color: HslColor }[];
+
+type ColorMode = "light" | "dark";
+
+class Theme {
+ static getDefaultThemeColorLightnessVariants(
+ mode: ColorMode,
+ levels = 3,
+ ): LightnessVariantInfo[] {
+ return [
+ {
+ prefix: "l",
+ type: "lighter",
+ levels,
+ },
+ {
+ prefix: "d",
+ type: "darker",
+ levels,
+ },
+ {
+ prefix: "f",
+ type: mode === "light" ? "lighter" : "darker",
+ levels,
+ },
+ {
+ prefix: "b",
+ type: mode === "light" ? "darker" : "lighter",
+ levels,
+ },
+ ];
+ }
+
+ static getThemeColorAllVariants(): string[] {
+ const lightnessVariantInfos =
+ Theme.getDefaultThemeColorLightnessVariants("light");
+ const result: string[] = [];
+ for (const { prefix, levels } of lightnessVariantInfos) {
+ for (let i = 1; i <= levels; i++) {
+ result.push(`${prefix}${i}`);
+ }
+ }
+ return result;
+ }
+
+ constructor(
+ public prefix: string,
+ public themeColors: ThemeColors,
+ public levels = 3,
+ ) {}
+
+ getThemeColorDefinitions(mode: ColorMode): ColorGroup {
+ const groups: ColorGroup[] = [];
+ for (const { name, color } of this.themeColors) {
+ const colorGroup = new LightnessVariantColorGroup(
+ this.prefix,
+ name,
+ color,
+ Theme.getDefaultThemeColorLightnessVariants(mode, this.levels),
+ );
+ groups.push(colorGroup);
+ }
+ return new CompositeColorGroup(groups);
+ }
+
+ getAliasColorDefinitions(name: string): ColorGroup {
+ return new VarAliasColorGroup(
+ this.prefix,
+ "theme",
+ name,
+ Theme.getThemeColorAllVariants(),
+ );
+ }
+
+ getGrayscaleDefinitions(mode: ColorMode): ColorGroup {
+ const textGroup =
+ mode === "light"
+ ? GrayscaleColorGroup.black(this.prefix, "text", this.levels)
+ : GrayscaleColorGroup.white(this.prefix, "text", this.levels);
+ const bgGroup =
+ mode === "light"
+ ? GrayscaleColorGroup.white(this.prefix, "bg", this.levels)
+ : GrayscaleColorGroup.black(this.prefix, "bg", this.levels);
+ const disabledGroup =
+ mode == "light"
+ ? new GrayscaleColorGroup(
+ this.prefix,
+ "disabled",
+ new HslColor(0, 0, 75),
+ "darker",
+ this.levels,
+ )
+ : new GrayscaleColorGroup(
+ this.prefix,
+ "disabled",
+ new HslColor(0, 0, 25),
+ "lighter",
+ this.levels,
+ );
+ return new CompositeColorGroup([textGroup, bgGroup, disabledGroup]);
+ }
+
+ generateCss(print: (text: string, indent: number) => void): void {
+ print(":root {", 0);
+ print(this.getThemeColorDefinitions("light").toCssString(), 1);
+ print(this.getGrayscaleDefinitions("light").toCssString(), 1);
+ print("}", 0);
+
+ print("", 0);
+
+ print("@media (prefers-color-scheme: dark) {", 0);
+ print(":root {", 1);
+ print(this.getThemeColorDefinitions("dark").toCssString(), 2);
+ print(this.getGrayscaleDefinitions("dark").toCssString(), 2);
+ print("}", 1);
+ print("}", 0);
+
+ print("", 0);
+
+ for (const { name } of this.themeColors) {
+ print(`.${this.prefix}-${name} {`, 0);
+ print(this.getAliasColorDefinitions(name).toCssString(), 1);
+ print("}", 0);
+
+ print("", 0);
+ }
+ }
+}
+
+(function main() {
+ const prefix = "cru";
+ const themeColors: ThemeColors = [
+ { name: "primary", color: new HslColor(210, 100, 50) },
+ { name: "secondary", color: new HslColor(40, 100, 50) },
+ { name: "tertiary", color: new HslColor(160, 100, 50) },
+ { name: "danger", color: new HslColor(0, 100, 50) },
+ { name: "success", color: new HslColor(120, 100, 50) },
+ ];
+
+ const theme = new Theme(prefix, themeColors);
+
+ let output = "";
+
+ function indentText(
+ text: string,
+ level: number,
+ indentWidth = 2,
+ appendNewlines = 1,
+ ): string {
+ const lines = text.split("\n");
+ const indent = " ".repeat(level * indentWidth);
+ return (
+ lines
+ .map((line) => (line.length === 0 ? "" : `${indent}${line}`))
+ .join("\n") + "\n".repeat(appendNewlines)
+ );
+ }
+
+ function print(text: string, indent = 0, appendNewlines = 1) {
+ output += indentText(text, indent, 2, appendNewlines);
+ }
+
+ print("/* Generated by theme-generator.ts */\n");
+ theme.generateCss(print);
+
+ stdout.write(output);
+})();
diff --git a/FrontEnd/tools/tsconfig.json b/FrontEnd/tools/tsconfig.json
index 8c9b3c47..08f53190 100644
--- a/FrontEnd/tools/tsconfig.json
+++ b/FrontEnd/tools/tsconfig.json
@@ -1,6 +1,6 @@
{
- // This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases
- "extends": "ts-node/node16/tsconfig.json",
+ // This is an alias to @tsconfig/node20: https://github.com/tsconfig/bases
+ "extends": "@tsconfig/node20/tsconfig.json",
// Most ts-node options can be specified here using their programmatic names.
"ts-node": {
// It is faster to skip typechecking.