aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/palette.ts
blob: c4f4f4f91c558f9b2d84029ffd40390e90479c6c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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.lightness() > 60
      ? darkenBy(c, 0.1)
      : 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.lightness() > 60 ? "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) => {
  const styleTagId = "timeline-palette-css";
  let styleTag = document.getElementById(styleTagId);
  if (styleTag == null) {
    styleTag = document.createElement("style");
    styleTag.id = styleTagId;
    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);
  };
}