diff options
Diffstat (limited to 'FrontEnd')
-rw-r--r-- | FrontEnd/.eslintrc.js (renamed from FrontEnd/.eslintrc.cjs) | 0 | ||||
-rw-r--r-- | FrontEnd/package.json | 5 | ||||
-rw-r--r-- | FrontEnd/pnpm-lock.yaml | 9 | ||||
-rw-r--r-- | FrontEnd/src/common.ts | 10 | ||||
-rw-r--r-- | FrontEnd/src/index.css | 3 | ||||
-rw-r--r-- | FrontEnd/src/index.tsx | 7 | ||||
-rw-r--r-- | FrontEnd/src/palette.ts | 167 | ||||
-rw-r--r-- | FrontEnd/src/views/common/alert/alert.css | 2 | ||||
-rw-r--r-- | FrontEnd/src/views/common/button/Button.css | 10 | ||||
-rw-r--r-- | FrontEnd/src/views/common/button/Button.tsx | 5 | ||||
-rw-r--r-- | FrontEnd/src/views/common/button/FlatButton.tsx | 5 | ||||
-rw-r--r-- | FrontEnd/src/views/common/button/IconButton.tsx | 4 | ||||
-rw-r--r-- | FrontEnd/src/views/common/button/LoadingButton.tsx | 5 | ||||
-rw-r--r-- | FrontEnd/src/views/common/index.css | 216 | ||||
-rw-r--r-- | FrontEnd/src/views/common/theme.css | 244 | ||||
-rw-r--r-- | FrontEnd/tools/palette.ts | 271 | ||||
-rw-r--r-- | FrontEnd/tools/theme-generator.ts | 403 | ||||
-rw-r--r-- | FrontEnd/tools/tsconfig.json | 4 |
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. |