aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/package-lock.json124
-rw-r--r--FrontEnd/package.json2
-rw-r--r--FrontEnd/src/app/index.sass5
-rw-r--r--FrontEnd/src/app/index.tsx2
-rw-r--r--FrontEnd/src/app/palette.ts111
-rw-r--r--FrontEnd/src/app/views/common/common.sass2
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx7
-rw-r--r--FrontEnd/src/app/views/timeline-common/timeline-common.sass20
8 files changed, 247 insertions, 26 deletions
diff --git a/FrontEnd/package-lock.json b/FrontEnd/package-lock.json
index 169ce3a3..a2b8539e 100644
--- a/FrontEnd/package-lock.json
+++ b/FrontEnd/package-lock.json
@@ -12,6 +12,7 @@
"bootstrap": "^5.0.0",
"bootstrap-icons": "^1.4.1",
"classnames": "^2.3.1",
+ "color": "^3.1.3",
"core-js": "^3.11.2",
"i18next": "^20.2.2",
"i18next-browser-languagedetector": "^6.1.0",
@@ -48,7 +49,8 @@
"@babel/preset-env": "^7.14.1",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
- "@pmmmwh/react-refresh-webpack-plugin": "0.5.0-beta.7",
+ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.7",
+ "@types/color": "^3.0.1",
"@types/lodash": "^4.14.168",
"@types/node": "^15.0.2",
"@types/react": "^17.0.5",
@@ -1923,6 +1925,30 @@
"classnames": "*"
}
},
+ "node_modules/@types/color": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz",
+ "integrity": "sha512-oeUWVaAwI+xINDUx+3F2vJkl/vVB03VChFF/Gl3iQCdbcakjuoJyMOba+3BXRtnBhxZ7uBYqQBi9EpLnvSoztA==",
+ "dev": true,
+ "dependencies": {
+ "@types/color-convert": "*"
+ }
+ },
+ "node_modules/@types/color-convert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.0.tgz",
+ "integrity": "sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/color-name": "*"
+ }
+ },
+ "node_modules/@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
+ },
"node_modules/@types/emscripten": {
"version": "1.39.4",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.4.tgz",
@@ -4085,11 +4111,19 @@
"mimic-response": "^1.0.0"
}
},
+ "node_modules/color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz",
+ "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==",
+ "dependencies": {
+ "color-convert": "^1.9.1",
+ "color-string": "^1.5.4"
+ }
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@@ -4097,8 +4131,16 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "node_modules/color-string": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz",
+ "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
},
"node_modules/colorette": {
"version": "1.2.2",
@@ -11887,6 +11929,19 @@
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/simple-swizzle/node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -15241,6 +15296,30 @@
"classnames": "*"
}
},
+ "@types/color": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz",
+ "integrity": "sha512-oeUWVaAwI+xINDUx+3F2vJkl/vVB03VChFF/Gl3iQCdbcakjuoJyMOba+3BXRtnBhxZ7uBYqQBi9EpLnvSoztA==",
+ "dev": true,
+ "requires": {
+ "@types/color-convert": "*"
+ }
+ },
+ "@types/color-convert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.0.tgz",
+ "integrity": "sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "*"
+ }
+ },
+ "@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
+ },
"@types/emscripten": {
"version": "1.39.4",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.4.tgz",
@@ -16978,11 +17057,19 @@
"mimic-response": "^1.0.0"
}
},
+ "color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz",
+ "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==",
+ "requires": {
+ "color-convert": "^1.9.1",
+ "color-string": "^1.5.4"
+ }
+ },
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"requires": {
"color-name": "1.1.3"
}
@@ -16990,8 +17077,16 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "color-string": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz",
+ "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==",
+ "requires": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
},
"colorette": {
"version": "1.2.2",
@@ -22917,6 +23012,21 @@
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
+ "simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
+ "requires": {
+ "is-arrayish": "^0.3.1"
+ },
+ "dependencies": {
+ "is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ }
+ }
+ },
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
diff --git a/FrontEnd/package.json b/FrontEnd/package.json
index c8f32e9c..5abb8d65 100644
--- a/FrontEnd/package.json
+++ b/FrontEnd/package.json
@@ -10,6 +10,7 @@
"bootstrap": "^5.0.0",
"bootstrap-icons": "^1.4.1",
"classnames": "^2.3.1",
+ "color": "^3.1.3",
"core-js": "^3.11.2",
"i18next": "^20.2.2",
"i18next-browser-languagedetector": "^6.1.0",
@@ -64,6 +65,7 @@
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.7",
+ "@types/color": "^3.0.1",
"@types/lodash": "^4.14.168",
"@types/node": "^15.0.2",
"@types/react": "^17.0.5",
diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass
index 19c57953..348219eb 100644
--- a/FrontEnd/src/app/index.sass
+++ b/FrontEnd/src/app/index.sass
@@ -15,11 +15,6 @@
@import './views/admin/admin'
-:root
- --tl-primary-color: #007bff
- --tl-text-on-primary-color: #ffffff
- --tl-text-inactive-on-primary-color: rgb(255 255 255 / 75%)
-
small
line-height: 1.2
diff --git a/FrontEnd/src/app/index.tsx b/FrontEnd/src/app/index.tsx
index 00a75a4a..64a2cd1f 100644
--- a/FrontEnd/src/app/index.tsx
+++ b/FrontEnd/src/app/index.tsx
@@ -12,4 +12,6 @@ import "./i18n";
import App from "./App";
+import "./palette";
+
ReactDOM.render(<App />, document.getElementById("app"));
diff --git a/FrontEnd/src/app/palette.ts b/FrontEnd/src/app/palette.ts
new file mode 100644
index 00000000..63a70c4e
--- /dev/null
+++ b/FrontEnd/src/app/palette.ts
@@ -0,0 +1,111 @@
+import Color from "color";
+import { BehaviorSubject, Observable } from "rxjs";
+
+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;
+ inactive: string;
+ lighter: string;
+ darker: string;
+ [key: string]: string;
+}
+
+export interface Palette {
+ primary: PaletteColor;
+ primaryEnhance: PaletteColor;
+ secondary: PaletteColor;
+ textPrimary: PaletteColor;
+ textOnPrimary: PaletteColor;
+ danger: PaletteColor;
+ success: PaletteColor;
+ [key: string]: PaletteColor;
+}
+
+export function generatePaletteColor(color: string): PaletteColor {
+ const c = Color(color);
+ return {
+ color: c.toString(),
+ inactive: (c.isLight() ? darkenBy(c, 0.2) : lightenBy(c, 0.2)).toString(),
+ lighter: lightenBy(c, 0.1).fade(0.1).toString(),
+ darker: darkenBy(c, 0.1).toString(),
+ };
+}
+
+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 ? p.rotate(90) : Color(secondary);
+
+ return {
+ primary: generatePaletteColor(p.toString()),
+ primaryEnhance: generatePaletteColor(pe.toString()),
+ secondary: generatePaletteColor(s.toString()),
+ textPrimary: generatePaletteColor("#111111"),
+ textOnPrimary: generatePaletteColor(p.isLight() ? "black" : "white"),
+ danger: generatePaletteColor("red"),
+ success: generatePaletteColor("green"),
+ };
+}
+
+export function generatePaletteCSS(palette: Palette): string {
+ function toSnakeCase(s: string): string {
+ return s.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
+ }
+
+ const colors: [string, string][] = [];
+ for (const paletteColorName in palette) {
+ const paletteColor = palette[paletteColorName];
+ for (const variant in paletteColor) {
+ let key = `--tl-${toSnakeCase(paletteColorName)}`;
+ if (variant !== "color") key += `-${toSnakeCase(variant)}`;
+ key += "-color";
+ colors.push([key, paletteColor[variant]]);
+ }
+ }
+
+ return `:root {${colors
+ .map(([key, color]) => `${key} : ${color};`)
+ .join("")}}`;
+}
+
+const paletteSubject: BehaviorSubject<Palette> = new BehaviorSubject<Palette>(
+ generatePalette({ primary: "#007bff" })
+);
+
+export const palette$: Observable<Palette> = paletteSubject.asObservable();
+
+palette$.subscribe((palette) => {
+ let styleTag = document.getElementById("timeline-palette-css");
+ if (styleTag == null) {
+ styleTag = document.createElement("style");
+ document.head.append(styleTag);
+ }
+ styleTag.innerHTML = generatePaletteCSS(palette);
+});
+
+export function setPalette(palette: Palette): () => void {
+ const old = paletteSubject.value;
+
+ paletteSubject.next(palette);
+
+ return () => {
+ paletteSubject.next(old);
+ };
+}
diff --git a/FrontEnd/src/app/views/common/common.sass b/FrontEnd/src/app/views/common/common.sass
index fd0beb75..3561c3da 100644
--- a/FrontEnd/src/app/views/common/common.sass
+++ b/FrontEnd/src/app/views/common/common.sass
@@ -46,7 +46,7 @@
background-color: var(--tl-primary-color)
a
- color: var(--tl-text-inactive-on-primary-color)
+ color: var(--tl-text-on-primary-inactive-color)
text-decoration: none
margin: 0 1em
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index 5b6dfa9c..baaad139 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -11,6 +11,7 @@ import Timeline from "./Timeline";
import TimelinePostEdit from "./TimelinePostEdit";
import useReverseScrollPositionRemember from "@/utilities/useReverseScrollPositionRemember";
+import { generatePalette, setPalette } from "@/palette";
export interface TimelinePageCardProps {
timeline: HttpTimelineInfo;
@@ -75,6 +76,12 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => {
};
}, [timelineName, reloadKey]);
+ React.useEffect(() => {
+ if (timeline != null && timeline.color != null) {
+ return setPalette(generatePalette({ primary: timeline.color }));
+ }
+ }, [timeline]);
+
const [bottomSpaceHeight, setBottomSpaceHeight] = React.useState<number>(0);
const [timelineReloadKey, setTimelineReloadKey] = React.useState<number>(0);
diff --git a/FrontEnd/src/app/views/timeline-common/timeline-common.sass b/FrontEnd/src/app/views/timeline-common/timeline-common.sass
index 3734ba25..0b0bd24d 100644
--- a/FrontEnd/src/app/views/timeline-common/timeline-common.sass
+++ b/FrontEnd/src/app/views/timeline-common/timeline-common.sass
@@ -9,28 +9,20 @@
$timeline-line-width: 7px
$timeline-line-node-radius: 18px
-$timeline-line-color: $primary
-$timeline-line-color-current: #36c2e6
+$timeline-line-color: var(--tl-primary-color)
+$timeline-line-color-current: var(--tl-primary-enhance-color)
@keyframes timeline-line-node-noncurrent
- from
- background: $timeline-line-color
-
to
- background: color.adjust($timeline-line-color, $lightness: +10%)
- box-shadow: 0 0 20px 3px color.adjust($timeline-line-color, $lightness: +10%, $alpha: -0.1)
+ box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color)
@keyframes timeline-line-node-current
- from
- background: $timeline-line-color-current
-
to
- background: color.adjust($timeline-line-color-current, $lightness: +10%)
- box-shadow: 0 0 20px 3px color.adjust($timeline-line-color-current, $lightness: +10%, $alpha: -0.1)
+ box-shadow: 0 0 20px 3px var(--tl-primary-enhance-lighter-color)
@keyframes timeline-line-node-loading
to
- box-shadow: 0 0 20px 3px color.adjust($timeline-line-color, $lightness: +20%)
+ box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color)
@keyframes timeline-line-node-loading-edge
from
@@ -100,6 +92,7 @@ $timeline-line-color-current: #36c2e6
width: $timeline-line-node-radius + 2
height: $timeline-line-node-radius + 2
position: absolute
+ background: $timeline-line-color
left: -1px
top: -1px
border-radius: 50%
@@ -126,6 +119,7 @@ $timeline-line-color-current: #36c2e6
&.end
background: $timeline-line-color-current
.node
+ background: $timeline-line-color-current
animation-name: timeline-line-node-current
&.loading