diff options
-rw-r--r-- | FrontEnd/package-lock.json | 124 | ||||
-rw-r--r-- | FrontEnd/package.json | 2 | ||||
-rw-r--r-- | FrontEnd/src/app/index.sass | 5 | ||||
-rw-r--r-- | FrontEnd/src/app/index.tsx | 2 | ||||
-rw-r--r-- | FrontEnd/src/app/palette.ts | 111 | ||||
-rw-r--r-- | FrontEnd/src/app/views/common/common.sass | 2 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx | 7 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/timeline-common.sass | 20 |
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 |