Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce dark mode and primary color picker #6430

Merged
merged 29 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
42a62a7
WIP
bramkragten Jul 18, 2020
84caefe
Add primary color variants calculation
bramkragten Jul 18, 2020
fade7ed
Add meta fix tsc
bramkragten Jul 18, 2020
f79bf17
Bump marked to match types
bramkragten Jul 18, 2020
c616071
default_dark_theme
bramkragten Jul 18, 2020
0102d10
Update types.ts
bramkragten Jul 18, 2020
8a5de14
Fix
bramkragten Jul 18, 2020
9646694
Update provide_hass.ts
bramkragten Jul 18, 2020
2e29447
Create `ha-radio`
bramkragten Jul 22, 2020
71c80a3
Update radio styles
bramkragten Jul 22, 2020
8c35a9e
Add dark mode to map
bramkragten Jul 22, 2020
a05c30e
tweak state-icon-color
bramkragten Jul 22, 2020
1c4933a
Fix markdown code background color
bramkragten Jul 22, 2020
a9c370e
Update themes-mixin.ts
bramkragten Jul 22, 2020
991c183
Add reset button
bramkragten Jul 22, 2020
deeed44
Replace color functions
bramkragten Jul 22, 2020
becb699
Less copy
bramkragten Jul 22, 2020
9c50ecf
faster
bramkragten Jul 22, 2020
3694555
Tweaks
bramkragten Jul 23, 2020
2cabea8
Fix text color on light-primary
bramkragten Jul 23, 2020
2004d2a
Use lab instead of hsl
bramkragten Jul 23, 2020
9bb1c52
Final theme tweaks
bramkragten Jul 23, 2020
869d910
Add copyright notice
bramkragten Jul 23, 2020
fdb9012
More theme tweaks
bramkragten Jul 23, 2020
c704c72
Update ha-dialog.ts
bramkragten Jul 23, 2020
ccdb9ac
Update ha-location-editor.ts
bramkragten Jul 24, 2020
f951b0b
Update hui-card-options.ts
bramkragten Jul 24, 2020
bf5500a
Refactor maps darkmode
bramkragten Aug 2, 2020
1779d6c
Remove debug console.log
balloob Aug 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion hassio/src/hassio-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
applyThemesOnElement(
this.parentElement,
this.hass.themes,
this.hass.selectedTheme || this.hass.themes.default_theme
this.hass.selectedTheme?.theme || this.hass.themes.default_theme,
this.hass.selectedTheme
);

this.style.setProperty(
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@material/mwc-icon-button": "^0.17.2",
"@material/mwc-list": "^0.17.2",
"@material/mwc-menu": "^0.17.2",
"@material/mwc-radio": "^0.17.2",
"@material/mwc-ripple": "^0.17.2",
"@material/mwc-switch": "^0.17.2",
"@material/mwc-tab": "^0.17.2",
Expand Down Expand Up @@ -100,7 +101,7 @@
"lit-element": "^2.3.1",
"lit-html": "^1.2.1",
"lit-virtualizer": "^0.4.2",
"marked": "^0.6.1",
"marked": "^1.1.1",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
"node-vibrant": "^3.1.5",
Expand Down Expand Up @@ -136,11 +137,12 @@
"@rollup/plugin-replace": "^2.3.2",
"@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12",
"@types/codemirror": "^0.0.78",
"@types/codemirror": "^0.0.97",
"@types/hls.js": "^0.12.3",
"@types/js-yaml": "^3.12.1",
"@types/leaflet": "^1.4.3",
"@types/leaflet-draw": "^1.0.1",
"@types/marked": "^1.1.0",
"@types/memoize-one": "4.1.0",
"@types/mocha": "^5.2.6",
"@types/resize-observer-browser": "^0.1.3",
Expand Down
113 changes: 113 additions & 0 deletions src/common/color/convert-color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const expand_hex = (hex: string): string => {
let result = "";
for (const val of hex) {
result += val + val;
}
return result;
};

const rgb_hex = (component: number): string => {
const hex = Math.round(Math.min(Math.max(component, 0), 255)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};

// Conversion between HEX and RGB

export const hex2rgb = (hex: string): [number, number, number] => {
hex = hex.replace("#", "");
if (hex.length === 3 || hex.length === 4) {
hex = expand_hex(hex);
}

return [
parseInt(hex.substring(0, 2), 16),
parseInt(hex.substring(2, 4), 16),
parseInt(hex.substring(4, 6), 16),
];
};

export const rgb2hex = (rgb: [number, number, number]): string => {
return `#${rgb_hex(rgb[0])}${rgb_hex(rgb[1])}${rgb_hex(rgb[2])}`;
};

// Conversion between LAB, XYZ and RGB from https://github.com/gka/chroma.js
// Copyright (c) 2011-2019, Gregor Aisch

// Constants for XYZ and LAB conversion
const Xn = 0.95047;
const Yn = 1;
const Zn = 1.08883;

const t0 = 0.137931034; // 4 / 29
const t1 = 0.206896552; // 6 / 29
const t2 = 0.12841855; // 3 * t1 * t1
const t3 = 0.008856452; // t1 * t1 * t1

const rgb_xyz = (r: number) => {
r /= 255;
if (r <= 0.04045) {
return r / 12.92;
}
return ((r + 0.055) / 1.055) ** 2.4;
};

const xyz_lab = (t: number) => {
if (t > t3) {
return t ** (1 / 3);
}
return t / t2 + t0;
};

const xyz_rgb = (r: number) => {
return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * r ** (1 / 2.4) - 0.055);
};

const lab_xyz = (t: number) => {
return t > t1 ? t * t * t : t2 * (t - t0);
};

// Conversions between RGB and LAB

const rgb2xyz = (rgb: [number, number, number]): [number, number, number] => {
let [r, g, b] = rgb;
r = rgb_xyz(r);
g = rgb_xyz(g);
b = rgb_xyz(b);
const x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn);
const y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.072175 * b) / Yn);
const z = xyz_lab((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / Zn);
return [x, y, z];
};

export const rgb2lab = (
rgb: [number, number, number]
): [number, number, number] => {
const [x, y, z] = rgb2xyz(rgb);
const l = 116 * y - 16;
return [l < 0 ? 0 : l, 500 * (x - y), 200 * (y - z)];
};

export const lab2rgb = (
lab: [number, number, number]
): [number, number, number] => {
const [l, a, b] = lab;

let y = (l + 16) / 116;
let x = isNaN(a) ? y : y + a / 500;
let z = isNaN(b) ? y : y - b / 200;

y = Yn * lab_xyz(y);
x = Xn * lab_xyz(x);
z = Zn * lab_xyz(z);

const r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); // D65 -> sRGB
const g = xyz_rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z);
const b_ = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);

return [r, g, b_];
};

export const lab2hex = (lab: [number, number, number]): string => {
const rgb = lab2rgb(lab);
return rgb2hex(rgb);
};
16 changes: 16 additions & 0 deletions src/common/color/lab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// From https://github.com/gka/chroma.js
// Copyright (c) 2011-2019, Gregor Aisch

export const labDarken = (
lab: [number, number, number],
amount = 1
): [number, number, number] => {
return [lab[0] - 18 * amount, lab[1], lab[2]];
};

export const labBrighten = (
lab: [number, number, number],
amount = 1
): [number, number, number] => {
return labDarken(lab, -amount);
};
24 changes: 24 additions & 0 deletions src/common/color/rgb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const luminosity = (rgb: [number, number, number]): number => {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
const lum: [number, number, number] = [0, 0, 0];
for (let i = 0; i < rgb.length; i++) {
const chan = rgb[i] / 255;
lum[i] = chan <= 0.03928 ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4;
}

return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
};

export const rgbContrast = (
color1: [number, number, number],
color2: [number, number, number]
) => {
const lum1 = luminosity(color1);
const lum2 = luminosity(color2);

if (lum1 > lum2) {
return (lum1 + 0.05) / (lum2 + 0.05);
}

return (lum2 + 0.05) / (lum1 + 0.05);
};
104 changes: 70 additions & 34 deletions src/common/dom/apply_themes_on_element.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import { derivedStyles } from "../../resources/styles";
import { derivedStyles, darkStyles } from "../../resources/styles";
import { HomeAssistant, Theme } from "../../types";
import {
hex2rgb,
rgb2hex,
rgb2lab,
lab2rgb,
lab2hex,
} from "../color/convert-color";
import { rgbContrast } from "../color/rgb";
import { labDarken, labBrighten } from "../color/lab";

interface ProcessedTheme {
keys: { [key: string]: "" };
styles: { [key: string]: string };
}

const hexToRgb = (hex: string): string | null => {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const checkHex = hex.replace(shorthandRegex, (_m, r, g, b) => {
return r + r + g + g + b + b;
});

const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(checkHex);
return result
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
result[3],
16
)}`
: null;
};

let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};

/**
Expand All @@ -33,17 +27,56 @@ let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
export const applyThemesOnElement = (
element,
themes: HomeAssistant["themes"],
selectedTheme?: string
selectedTheme?: string,
themeOptions?: Partial<HomeAssistant["selectedTheme"]>
) => {
const newTheme = selectedTheme
? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
: undefined;
let cacheKey = selectedTheme;
let themeRules: Partial<Theme> = {};

if (!element._themes && !newTheme) {
if (selectedTheme === "default" && themeOptions) {
if (themeOptions.dark) {
cacheKey = `${cacheKey}__dark`;
themeRules = darkStyles;
}
if (themeOptions.primaryColor) {
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
const rgbPrimaryColor = hex2rgb(themeOptions.primaryColor);
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
themeRules["primary-color"] = themeOptions.primaryColor;
const rgbLigthPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
themeRules["light-primary-color"] = rgb2hex(rgbLigthPrimaryColor);
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
themeRules["text-primary-color"] =
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
themeRules["text-light-primary-color"] =
rgbContrast(rgbLigthPrimaryColor, [33, 33, 33]) < 6
? "#fff"
: "#212121";
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
}
if (themeOptions.accentColor) {
cacheKey = `${cacheKey}__accent_${themeOptions.accentColor}`;
themeRules["accent-color"] = themeOptions.accentColor;
const rgbAccentColor = hex2rgb(themeOptions.accentColor);
themeRules["text-accent-color"] =
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
}
}

if (selectedTheme && themes.themes[selectedTheme]) {
themeRules = themes.themes[selectedTheme];
}

if (!element._themes && !Object.keys(themeRules).length) {
// No styles to reset, and no styles to set
return;
}

const newTheme =
themeRules && cacheKey
? PROCESSED_THEMES[cacheKey] || processTheme(cacheKey, themeRules)
: undefined;

// Add previous set keys to reset them, and new theme
const styles = { ...element._themes, ...newTheme?.styles };
element._themes = newTheme?.keys;
Expand All @@ -58,42 +91,45 @@ export const applyThemesOnElement = (
};

const processTheme = (
themeName: string,
themes: HomeAssistant["themes"]
cacheKey: string,
theme: Partial<Theme>
): ProcessedTheme | undefined => {
if (!themes.themes[themeName]) {
if (!theme || !Object.keys(theme).length) {
return undefined;
}
const theme: Theme = {
const combinedTheme: Partial<Theme> = {
...derivedStyles,
...themes.themes[themeName],
...theme,
};
const styles = {};
const keys = {};
for (const key of Object.keys(theme)) {
for (const key of Object.keys(combinedTheme)) {
const prefixedKey = `--${key}`;
const value = theme[key];
const value = combinedTheme[key]!;
styles[prefixedKey] = value;
keys[prefixedKey] = "";

// Try to create a rgb value for this key if it is a hex color
// Try to create a rgb value for this key if it is not a var
if (!value.startsWith("#")) {
// Not a hex color
// Can't convert non hex value
continue;
}

const rgbKey = `rgb-${key}`;
if (theme[rgbKey] !== undefined) {
if (combinedTheme[rgbKey] !== undefined) {
// Theme has it's own rgb value
continue;
}
const rgbValue = hexToRgb(value);
if (rgbValue !== null) {
try {
const rgbValue = hex2rgb(value).join(",");
const prefixedRgbKey = `--${rgbKey}`;
styles[prefixedRgbKey] = rgbValue;
keys[prefixedRgbKey] = "";
} catch (e) {
continue;
}
}
PROCESSED_THEMES[themeName] = { styles, keys };
PROCESSED_THEMES[cacheKey] = { styles, keys };
return { styles, keys };
};

Expand Down