mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 16:56:35 +00:00
Introduce dark mode and primary color picker (#6430)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
0d515e2303
commit
4ca13c409b
@ -94,7 +94,8 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
|||||||
applyThemesOnElement(
|
applyThemesOnElement(
|
||||||
this.parentElement,
|
this.parentElement,
|
||||||
this.hass.themes,
|
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(
|
this.style.setProperty(
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"@material/mwc-icon-button": "^0.17.2",
|
"@material/mwc-icon-button": "^0.17.2",
|
||||||
"@material/mwc-list": "^0.17.2",
|
"@material/mwc-list": "^0.17.2",
|
||||||
"@material/mwc-menu": "^0.17.2",
|
"@material/mwc-menu": "^0.17.2",
|
||||||
|
"@material/mwc-radio": "^0.17.2",
|
||||||
"@material/mwc-ripple": "^0.17.2",
|
"@material/mwc-ripple": "^0.17.2",
|
||||||
"@material/mwc-switch": "^0.17.2",
|
"@material/mwc-switch": "^0.17.2",
|
||||||
"@material/mwc-tab": "^0.17.2",
|
"@material/mwc-tab": "^0.17.2",
|
||||||
@ -100,7 +101,7 @@
|
|||||||
"lit-element": "^2.3.1",
|
"lit-element": "^2.3.1",
|
||||||
"lit-html": "^1.2.1",
|
"lit-html": "^1.2.1",
|
||||||
"lit-virtualizer": "^0.4.2",
|
"lit-virtualizer": "^0.4.2",
|
||||||
"marked": "^0.6.1",
|
"marked": "^1.1.1",
|
||||||
"mdn-polyfills": "^5.16.0",
|
"mdn-polyfills": "^5.16.0",
|
||||||
"memoize-one": "^5.0.2",
|
"memoize-one": "^5.0.2",
|
||||||
"node-vibrant": "^3.1.5",
|
"node-vibrant": "^3.1.5",
|
||||||
@ -136,11 +137,12 @@
|
|||||||
"@rollup/plugin-replace": "^2.3.2",
|
"@rollup/plugin-replace": "^2.3.2",
|
||||||
"@types/chai": "^4.1.7",
|
"@types/chai": "^4.1.7",
|
||||||
"@types/chromecast-caf-receiver": "^3.0.12",
|
"@types/chromecast-caf-receiver": "^3.0.12",
|
||||||
"@types/codemirror": "^0.0.78",
|
"@types/codemirror": "^0.0.97",
|
||||||
"@types/hls.js": "^0.12.3",
|
"@types/hls.js": "^0.12.3",
|
||||||
"@types/js-yaml": "^3.12.1",
|
"@types/js-yaml": "^3.12.1",
|
||||||
"@types/leaflet": "^1.4.3",
|
"@types/leaflet": "^1.4.3",
|
||||||
"@types/leaflet-draw": "^1.0.1",
|
"@types/leaflet-draw": "^1.0.1",
|
||||||
|
"@types/marked": "^1.1.0",
|
||||||
"@types/memoize-one": "4.1.0",
|
"@types/memoize-one": "4.1.0",
|
||||||
"@types/mocha": "^5.2.6",
|
"@types/mocha": "^5.2.6",
|
||||||
"@types/resize-observer-browser": "^0.1.3",
|
"@types/resize-observer-browser": "^0.1.3",
|
||||||
|
113
src/common/color/convert-color.ts
Normal file
113
src/common/color/convert-color.ts
Normal file
@ -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
src/common/color/lab.ts
Normal file
16
src/common/color/lab.ts
Normal file
@ -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
src/common/color/rgb.ts
Normal file
24
src/common/color/rgb.ts
Normal file
@ -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);
|
||||||
|
};
|
@ -1,26 +1,20 @@
|
|||||||
import { derivedStyles } from "../../resources/styles";
|
import { derivedStyles, darkStyles } from "../../resources/styles";
|
||||||
import { HomeAssistant, Theme } from "../../types";
|
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 {
|
interface ProcessedTheme {
|
||||||
keys: { [key: string]: "" };
|
keys: { [key: string]: "" };
|
||||||
styles: { [key: string]: 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 } = {};
|
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,17 +27,56 @@ let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
|
|||||||
export const applyThemesOnElement = (
|
export const applyThemesOnElement = (
|
||||||
element,
|
element,
|
||||||
themes: HomeAssistant["themes"],
|
themes: HomeAssistant["themes"],
|
||||||
selectedTheme?: string
|
selectedTheme?: string,
|
||||||
|
themeOptions?: Partial<HomeAssistant["selectedTheme"]>
|
||||||
) => {
|
) => {
|
||||||
const newTheme = selectedTheme
|
let cacheKey = selectedTheme;
|
||||||
? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
|
let themeRules: Partial<Theme> = {};
|
||||||
: undefined;
|
|
||||||
|
|
||||||
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
|
// No styles to reset, and no styles to set
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newTheme =
|
||||||
|
themeRules && cacheKey
|
||||||
|
? PROCESSED_THEMES[cacheKey] || processTheme(cacheKey, themeRules)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Add previous set keys to reset them, and new theme
|
// Add previous set keys to reset them, and new theme
|
||||||
const styles = { ...element._themes, ...newTheme?.styles };
|
const styles = { ...element._themes, ...newTheme?.styles };
|
||||||
element._themes = newTheme?.keys;
|
element._themes = newTheme?.keys;
|
||||||
@ -58,42 +91,45 @@ export const applyThemesOnElement = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const processTheme = (
|
const processTheme = (
|
||||||
themeName: string,
|
cacheKey: string,
|
||||||
themes: HomeAssistant["themes"]
|
theme: Partial<Theme>
|
||||||
): ProcessedTheme | undefined => {
|
): ProcessedTheme | undefined => {
|
||||||
if (!themes.themes[themeName]) {
|
if (!theme || !Object.keys(theme).length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const theme: Theme = {
|
const combinedTheme: Partial<Theme> = {
|
||||||
...derivedStyles,
|
...derivedStyles,
|
||||||
...themes.themes[themeName],
|
...theme,
|
||||||
};
|
};
|
||||||
const styles = {};
|
const styles = {};
|
||||||
const keys = {};
|
const keys = {};
|
||||||
for (const key of Object.keys(theme)) {
|
for (const key of Object.keys(combinedTheme)) {
|
||||||
const prefixedKey = `--${key}`;
|
const prefixedKey = `--${key}`;
|
||||||
const value = theme[key];
|
const value = combinedTheme[key]!;
|
||||||
styles[prefixedKey] = value;
|
styles[prefixedKey] = value;
|
||||||
keys[prefixedKey] = "";
|
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("#")) {
|
if (!value.startsWith("#")) {
|
||||||
// Not a hex color
|
// Can't convert non hex value
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rgbKey = `rgb-${key}`;
|
const rgbKey = `rgb-${key}`;
|
||||||
if (theme[rgbKey] !== undefined) {
|
if (combinedTheme[rgbKey] !== undefined) {
|
||||||
// Theme has it's own rgb value
|
// Theme has it's own rgb value
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const rgbValue = hexToRgb(value);
|
try {
|
||||||
if (rgbValue !== null) {
|
const rgbValue = hex2rgb(value).join(",");
|
||||||
const prefixedRgbKey = `--${rgbKey}`;
|
const prefixedRgbKey = `--${rgbKey}`;
|
||||||
styles[prefixedRgbKey] = rgbValue;
|
styles[prefixedRgbKey] = rgbValue;
|
||||||
keys[prefixedRgbKey] = "";
|
keys[prefixedRgbKey] = "";
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PROCESSED_THEMES[themeName] = { styles, keys };
|
PROCESSED_THEMES[cacheKey] = { styles, keys };
|
||||||
return { styles, keys };
|
return { styles, keys };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Map } from "leaflet";
|
import type { Map, TileLayer } from "leaflet";
|
||||||
|
|
||||||
// Sets up a Leaflet map on the provided DOM element
|
// Sets up a Leaflet map on the provided DOM element
|
||||||
export type LeafletModuleType = typeof import("leaflet");
|
export type LeafletModuleType = typeof import("leaflet");
|
||||||
@ -6,9 +6,9 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
|
|||||||
|
|
||||||
export const setupLeafletMap = async (
|
export const setupLeafletMap = async (
|
||||||
mapElement: HTMLElement,
|
mapElement: HTMLElement,
|
||||||
darkMode = false,
|
darkMode?: boolean,
|
||||||
draw = false
|
draw = false
|
||||||
): Promise<[Map, LeafletModuleType]> => {
|
): Promise<[Map, LeafletModuleType, TileLayer]> => {
|
||||||
if (!mapElement.parentNode) {
|
if (!mapElement.parentNode) {
|
||||||
throw new Error("Cannot setup Leaflet map on disconnected element");
|
throw new Error("Cannot setup Leaflet map on disconnected element");
|
||||||
}
|
}
|
||||||
@ -28,15 +28,28 @@ export const setupLeafletMap = async (
|
|||||||
style.setAttribute("rel", "stylesheet");
|
style.setAttribute("rel", "stylesheet");
|
||||||
mapElement.parentNode.appendChild(style);
|
mapElement.parentNode.appendChild(style);
|
||||||
map.setView([52.3731339, 4.8903147], 13);
|
map.setView([52.3731339, 4.8903147], 13);
|
||||||
createTileLayer(Leaflet, darkMode).addTo(map);
|
|
||||||
|
|
||||||
return [map, Leaflet];
|
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map);
|
||||||
|
|
||||||
|
return [map, Leaflet, tileLayer];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTileLayer = (
|
export const replaceTileLayer = (
|
||||||
|
leaflet: LeafletModuleType,
|
||||||
|
map: Map,
|
||||||
|
tileLayer: TileLayer,
|
||||||
|
darkMode: boolean
|
||||||
|
): TileLayer => {
|
||||||
|
map.removeLayer(tileLayer);
|
||||||
|
tileLayer = createTileLayer(leaflet, darkMode);
|
||||||
|
tileLayer.addTo(map);
|
||||||
|
return tileLayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createTileLayer = (
|
||||||
leaflet: LeafletModuleType,
|
leaflet: LeafletModuleType,
|
||||||
darkMode: boolean
|
darkMode: boolean
|
||||||
) => {
|
): TileLayer => {
|
||||||
return leaflet.tileLayer(
|
return leaflet.tileLayer(
|
||||||
`https://{s}.basemaps.cartocdn.com/${
|
`https://{s}.basemaps.cartocdn.com/${
|
||||||
darkMode ? "dark_all" : "light_all"
|
darkMode ? "dark_all" : "light_all"
|
||||||
|
@ -541,7 +541,7 @@ export class HaDataTable extends LitElement {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: rgba(var(--rgb-primary-text-color), 0.12);
|
border-color: var(--divider-color);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -559,7 +559,7 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__row ~ .mdc-data-table__row {
|
.mdc-data-table__row ~ .mdc-data-table__row {
|
||||||
border-top: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-top: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
|
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
|
||||||
@ -578,7 +578,7 @@ export class HaDataTable extends LitElement {
|
|||||||
height: 56px;
|
height: 56px;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,7 +831,7 @@ export class HaDataTable extends LitElement {
|
|||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
.table-header {
|
.table-header {
|
||||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
|
@ -135,7 +135,7 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
}
|
}
|
||||||
.daterangepicker td.in-range {
|
.daterangepicker td.in-range {
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
color: var(--primary-text-color);
|
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||||
}
|
}
|
||||||
.daterangepicker td.active,
|
.daterangepicker td.active,
|
||||||
.daterangepicker td.active:hover {
|
.daterangepicker td.active:hover {
|
||||||
|
@ -66,7 +66,7 @@ export class HaCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host ::slotted(.card-actions) {
|
:host ::slotted(.card-actions) {
|
||||||
border-top: 1px solid #e8e8e8;
|
border-top: 1px solid var(--divider-color, #e8e8e8);
|
||||||
padding: 5px 16px;
|
padding: 5px 16px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -34,6 +34,7 @@ export class HaDialog extends MwcDialog {
|
|||||||
style,
|
style,
|
||||||
css`
|
css`
|
||||||
.mdc-dialog {
|
.mdc-dialog {
|
||||||
|
--mdc-dialog-scroll-divider-color: var(--divider-color);
|
||||||
z-index: var(--dialog-z-index, 7);
|
z-index: var(--dialog-z-index, 7);
|
||||||
}
|
}
|
||||||
.mdc-dialog__actions {
|
.mdc-dialog__actions {
|
||||||
|
@ -23,7 +23,6 @@ class HaMarkdownElement extends UpdatingElement {
|
|||||||
{
|
{
|
||||||
breaks: this.breaks,
|
breaks: this.breaks,
|
||||||
gfm: true,
|
gfm: true,
|
||||||
tables: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
allowSvg: this.allowSvg,
|
allowSvg: this.allowSvg,
|
||||||
|
20
src/components/ha-radio.ts
Normal file
20
src/components/ha-radio.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import "@material/mwc-radio";
|
||||||
|
import type { Radio } from "@material/mwc-radio";
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
import type { Constructor } from "../types";
|
||||||
|
|
||||||
|
const MwcRadio = customElements.get("mwc-radio") as Constructor<Radio>;
|
||||||
|
|
||||||
|
@customElement("ha-radio")
|
||||||
|
export class HaRadio extends MwcRadio {
|
||||||
|
public firstUpdated() {
|
||||||
|
super.firstUpdated();
|
||||||
|
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-radio": HaRadio;
|
||||||
|
}
|
||||||
|
}
|
@ -717,7 +717,7 @@ class HaSidebar extends LitElement {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0px 6px;
|
padding: 0px 6px;
|
||||||
color: var(--text-primary-color);
|
color: var(--text-accent-color, var(--text-primary-color));
|
||||||
}
|
}
|
||||||
ha-svg-icon + .notification-badge {
|
ha-svg-icon + .notification-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
LeafletMouseEvent,
|
LeafletMouseEvent,
|
||||||
Map,
|
Map,
|
||||||
Marker,
|
Marker,
|
||||||
|
TileLayer,
|
||||||
} from "leaflet";
|
} from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -21,15 +22,19 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import {
|
import {
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
} from "../../common/dom/setup-leaflet-map";
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { nextRender } from "../../common/util/render-status";
|
import { nextRender } from "../../common/util/render-status";
|
||||||
import { defaultRadiusColor } from "../../data/zone";
|
import { defaultRadiusColor } from "../../data/zone";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-location-editor")
|
@customElement("ha-location-editor")
|
||||||
class LocationEditor extends LitElement {
|
class LocationEditor extends LitElement {
|
||||||
@property() public location?: [number, number];
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public radius?: number;
|
@property({ type: Array }) public location?: [number, number];
|
||||||
|
|
||||||
|
@property({ type: Number }) public radius?: number;
|
||||||
|
|
||||||
@property() public radiusColor?: string;
|
@property() public radiusColor?: string;
|
||||||
|
|
||||||
@ -46,6 +51,8 @@ class LocationEditor extends LitElement {
|
|||||||
|
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
|
|
||||||
|
private _tileLayer?: TileLayer;
|
||||||
|
|
||||||
private _locationMarker?: Marker | Circle;
|
private _locationMarker?: Marker | Circle;
|
||||||
|
|
||||||
public fitMap(): void {
|
public fitMap(): void {
|
||||||
@ -97,6 +104,22 @@ class LocationEditor extends LitElement {
|
|||||||
if (changedProps.has("icon")) {
|
if (changedProps.has("icon")) {
|
||||||
this._updateIcon();
|
this._updateIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProps.has("hass")) {
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._leafletMap || !this._tileLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
this.Leaflet,
|
||||||
|
this._leafletMap,
|
||||||
|
this._tileLayer,
|
||||||
|
this.hass.themes.darkMode
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _mapEl(): HTMLDivElement {
|
private get _mapEl(): HTMLDivElement {
|
||||||
@ -104,9 +127,9 @@ class LocationEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _initMap(): Promise<void> {
|
private async _initMap(): Promise<void> {
|
||||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
this._mapEl,
|
this._mapEl,
|
||||||
false,
|
this.hass.themes.darkMode,
|
||||||
Boolean(this.radius)
|
Boolean(this.radius)
|
||||||
);
|
);
|
||||||
this._leafletMap.addEventListener(
|
this._leafletMap.addEventListener(
|
||||||
@ -255,9 +278,6 @@ class LocationEditor extends LitElement {
|
|||||||
#map {
|
#map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.light {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
.leaflet-edit-move {
|
.leaflet-edit-move {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: move !important;
|
cursor: move !important;
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
Map,
|
Map,
|
||||||
Marker,
|
Marker,
|
||||||
MarkerOptions,
|
MarkerOptions,
|
||||||
|
TileLayer,
|
||||||
} from "leaflet";
|
} from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -21,8 +22,10 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import {
|
import {
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
} from "../../common/dom/setup-leaflet-map";
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { defaultRadiusColor } from "../../data/zone";
|
import { defaultRadiusColor } from "../../data/zone";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -47,6 +50,8 @@ export interface MarkerLocation {
|
|||||||
|
|
||||||
@customElement("ha-locations-editor")
|
@customElement("ha-locations-editor")
|
||||||
export class HaLocationsEditor extends LitElement {
|
export class HaLocationsEditor extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public locations?: MarkerLocation[];
|
@property() public locations?: MarkerLocation[];
|
||||||
|
|
||||||
public fitZoom = 16;
|
public fitZoom = 16;
|
||||||
@ -57,6 +62,8 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
|
|
||||||
|
private _tileLayer?: TileLayer;
|
||||||
|
|
||||||
private _locationMarkers?: { [key: string]: Marker | Circle };
|
private _locationMarkers?: { [key: string]: Marker | Circle };
|
||||||
|
|
||||||
private _circles: { [key: string]: Circle } = {};
|
private _circles: { [key: string]: Circle } = {};
|
||||||
@ -116,6 +123,22 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
if (changedProps.has("locations")) {
|
if (changedProps.has("locations")) {
|
||||||
this._updateMarkers();
|
this._updateMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProps.has("hass")) {
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._leafletMap || !this._tileLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
this.Leaflet,
|
||||||
|
this._leafletMap,
|
||||||
|
this._tileLayer,
|
||||||
|
this.hass.themes.darkMode
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _mapEl(): HTMLDivElement {
|
private get _mapEl(): HTMLDivElement {
|
||||||
@ -123,9 +146,9 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _initMap(): Promise<void> {
|
private async _initMap(): Promise<void> {
|
||||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
this._mapEl,
|
this._mapEl,
|
||||||
false,
|
this.hass.themes.darkMode,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
this._updateMarkers();
|
this._updateMarkers();
|
||||||
@ -290,9 +313,6 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
#map {
|
#map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.light {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
.leaflet-marker-draggable {
|
.leaflet-marker-draggable {
|
||||||
cursor: move !important;
|
cursor: move !important;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import { Circle, Layer, Map, Marker } from "leaflet";
|
import { Circle, Layer, Map, Marker, TileLayer } from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
@ -13,6 +13,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
} from "../../common/dom/setup-leaflet-map";
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
@ -22,11 +23,11 @@ import { HomeAssistant } from "../../types";
|
|||||||
|
|
||||||
@customElement("ha-map")
|
@customElement("ha-map")
|
||||||
class HaMap extends LitElement {
|
class HaMap extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public entities?: string[];
|
@property() public entities?: string[];
|
||||||
|
|
||||||
@property() public darkMode = false;
|
@property() public darkMode?: boolean;
|
||||||
|
|
||||||
@property() public zoom?: number;
|
@property() public zoom?: number;
|
||||||
|
|
||||||
@ -35,6 +36,8 @@ class HaMap extends LitElement {
|
|||||||
|
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
|
|
||||||
|
private _tileLayer?: TileLayer;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
private _resizeObserver?: ResizeObserver;
|
private _resizeObserver?: ResizeObserver;
|
||||||
|
|
||||||
@ -122,6 +125,20 @@ class HaMap extends LitElement {
|
|||||||
if (changedProps.has("hass")) {
|
if (changedProps.has("hass")) {
|
||||||
this._drawEntities();
|
this._drawEntities();
|
||||||
this._fitMap();
|
this._fitMap();
|
||||||
|
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.Leaflet || !this._leafletMap || !this._tileLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
this.Leaflet,
|
||||||
|
this._leafletMap,
|
||||||
|
this._tileLayer,
|
||||||
|
this.hass.themes.darkMode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,9 +147,9 @@ class HaMap extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadMap(): Promise<void> {
|
private async loadMap(): Promise<void> {
|
||||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
this._mapEl,
|
this._mapEl,
|
||||||
this.darkMode
|
this.darkMode ?? this.hass.themes.darkMode
|
||||||
);
|
);
|
||||||
this._drawEntities();
|
this._drawEntities();
|
||||||
this._leafletMap.invalidateSize();
|
this._leafletMap.invalidateSize();
|
||||||
@ -229,7 +246,8 @@ class HaMap extends LitElement {
|
|||||||
icon: Leaflet.divIcon({
|
icon: Leaflet.divIcon({
|
||||||
html: iconHTML,
|
html: iconHTML,
|
||||||
iconSize: [24, 24],
|
iconSize: [24, 24],
|
||||||
className: this.darkMode ? "dark" : "light",
|
className:
|
||||||
|
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light",
|
||||||
}),
|
}),
|
||||||
interactive: false,
|
interactive: false,
|
||||||
title,
|
title,
|
||||||
|
@ -57,7 +57,7 @@ class StateBadge extends LitElement {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
|
|
||||||
ha-header-bar {
|
ha-header-bar {
|
||||||
--mdc-theme-on-primary: var(--primary-text-color);
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
--mdc-theme-primary: var(--card-background-color);
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-bottom: 1px solid
|
border-bottom: 1px solid
|
||||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||||
|
@ -426,7 +426,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
border-bottom-right-radius: 0px;
|
border-bottom-right-radius: 0px;
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
color: var(--primary-text-color);
|
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.hass {
|
.message.hass {
|
||||||
|
@ -180,7 +180,9 @@ export const provideHass = (
|
|||||||
config: demoConfig,
|
config: demoConfig,
|
||||||
themes: {
|
themes: {
|
||||||
default_theme: "default",
|
default_theme: "default",
|
||||||
|
default_dark_theme: null,
|
||||||
themes: {},
|
themes: {},
|
||||||
|
darkMode: false,
|
||||||
},
|
},
|
||||||
panels: demoPanels,
|
panels: demoPanels,
|
||||||
services: demoServices,
|
services: demoServices,
|
||||||
@ -253,7 +255,7 @@ export const provideHass = (
|
|||||||
mockTheme(theme) {
|
mockTheme(theme) {
|
||||||
invalidateThemeCache();
|
invalidateThemeCache();
|
||||||
hass().updateHass({
|
hass().updateHass({
|
||||||
selectedTheme: theme ? "mock" : "default",
|
selectedTheme: { theme: theme ? "mock" : "default" },
|
||||||
themes: {
|
themes: {
|
||||||
...hass().themes,
|
...hass().themes,
|
||||||
themes: {
|
themes: {
|
||||||
@ -265,7 +267,7 @@ export const provideHass = (
|
|||||||
applyThemesOnElement(
|
applyThemesOnElement(
|
||||||
document.documentElement,
|
document.documentElement,
|
||||||
themes,
|
themes,
|
||||||
selectedTheme as string
|
selectedTheme!.theme
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="referrer" content="same-origin" />
|
<meta name="referrer" content="same-origin" />
|
||||||
<meta name="theme-color" content="#THEMEC" />
|
<meta name="theme-color" content="#THEMEC" />
|
||||||
|
<meta name="color-scheme" content="dark light" />
|
||||||
<style>
|
<style>
|
||||||
#ha-init-skeleton::before {
|
#ha-init-skeleton::before {
|
||||||
display: block;
|
display: block;
|
||||||
@ -43,7 +44,7 @@
|
|||||||
background-color: #THEMEC;
|
background-color: #THEMEC;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
background-color: var(--primary-background-color, #fafafa);
|
background-color: var(--primary-background-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
@ -90,6 +90,7 @@ class OnboardingCoreConfig extends LitElement {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-location-editor
|
<ha-location-editor
|
||||||
class="flex"
|
class="flex"
|
||||||
|
.hass=${this.hass}
|
||||||
.location=${this._locationValue}
|
.location=${this._locationValue}
|
||||||
.fitZoom=${14}
|
.fitZoom=${14}
|
||||||
@change=${this._locationChanged}
|
@change=${this._locationChanged}
|
||||||
|
@ -211,6 +211,7 @@ class HAFullCalendar extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
--fc-theme-standard-border-color: var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@ -234,6 +235,10 @@ class HAFullCalendar extends LitElement {
|
|||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -259,6 +264,10 @@ class HAFullCalendar extends LitElement {
|
|||||||
background-color: var(--card-background-color);
|
background-color: var(--card-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc-theme-standard .fc-scrollgrid {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
.fc-scrollgrid-section-header td {
|
.fc-scrollgrid-section-header td {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@ -293,14 +302,15 @@ class HAFullCalendar extends LitElement {
|
|||||||
|
|
||||||
td.fc-day-today .fc-daygrid-day-number {
|
td.fc-day-today .fc-daygrid-day-number {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
color: #fff;
|
color: var(--text-primary-color);
|
||||||
background-color: #1a73e8;
|
background-color: var(--primary-color);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
|
line-height: 140%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-daygrid-day-events {
|
.fc-daygrid-day-events {
|
||||||
|
@ -61,6 +61,7 @@ class ConfigCoreForm extends LitElement {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-location-editor
|
<ha-location-editor
|
||||||
class="flex"
|
class="flex"
|
||||||
|
.hass=${this.hass}
|
||||||
.location=${this._locationValue}
|
.location=${this._locationValue}
|
||||||
@change=${this._locationChanged}
|
@change=${this._locationChanged}
|
||||||
></ha-location-editor>
|
></ha-location-editor>
|
||||||
|
@ -235,7 +235,7 @@ export class DialogEntityEditor extends LitElement {
|
|||||||
css`
|
css`
|
||||||
ha-header-bar {
|
ha-header-bar {
|
||||||
--mdc-theme-on-primary: var(--primary-text-color);
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
--mdc-theme-primary: var(--card-background-color);
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ class DialogZoneDetail extends LitElement {
|
|||||||
></paper-input>
|
></paper-input>
|
||||||
<ha-location-editor
|
<ha-location-editor
|
||||||
class="flex"
|
class="flex"
|
||||||
|
.hass=${this.hass}
|
||||||
.location=${this._locationValue}
|
.location=${this._locationValue}
|
||||||
.radius=${this._radius}
|
.radius=${this._radius}
|
||||||
.radiusColor=${this._passive
|
.radiusColor=${this._passive
|
||||||
|
@ -240,6 +240,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
|||||||
? html`
|
? html`
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ha-locations-editor
|
<ha-locations-editor
|
||||||
|
.hass=${this.hass}
|
||||||
.locations=${this._getZones(
|
.locations=${this._getZones(
|
||||||
this._storageItems,
|
this._storageItems,
|
||||||
this._stateItems
|
this._stateItems
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
Map,
|
Map,
|
||||||
Marker,
|
Marker,
|
||||||
Polyline,
|
Polyline,
|
||||||
|
TileLayer,
|
||||||
} from "leaflet";
|
} from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -21,9 +22,9 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import {
|
import {
|
||||||
createTileLayer,
|
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
} from "../../../common/dom/setup-leaflet-map";
|
} from "../../../common/dom/setup-leaflet-map";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
@ -68,7 +69,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
return { type: "map", entities: foundEntities };
|
return { type: "map", entities: foundEntities };
|
||||||
}
|
}
|
||||||
|
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public isPanel = false;
|
public isPanel = false;
|
||||||
@ -91,6 +92,8 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
|
|
||||||
|
private _tileLayer?: TileLayer;
|
||||||
|
|
||||||
private _resizeObserver?: ResizeObserver;
|
private _resizeObserver?: ResizeObserver;
|
||||||
|
|
||||||
private _debouncedResizeListener = debounce(
|
private _debouncedResizeListener = debounce(
|
||||||
@ -225,6 +228,10 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldHass.themes.darkMode !== this.hass.themes.darkMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if any state has changed
|
// Check if any state has changed
|
||||||
for (const entity of this._configEntities) {
|
for (const entity of this._configEntities) {
|
||||||
if (oldHass.states[entity.entity] !== this.hass!.states[entity.entity]) {
|
if (oldHass.states[entity.entity] !== this.hass!.states[entity.entity]) {
|
||||||
@ -266,6 +273,12 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
this._drawEntities();
|
this._drawEntities();
|
||||||
this._fitMap();
|
this._fitMap();
|
||||||
}
|
}
|
||||||
|
if (changedProps.has("hass")) {
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (oldHass && oldHass.themes.darkMode !== this.hass.themes.darkMode) {
|
||||||
|
this._replaceTileLayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
changedProps.has("_config") &&
|
changedProps.has("_config") &&
|
||||||
changedProps.get("_config") !== undefined
|
changedProps.get("_config") !== undefined
|
||||||
@ -288,24 +301,39 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadMap(): Promise<void> {
|
private async loadMap(): Promise<void> {
|
||||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
this._mapEl,
|
this._mapEl,
|
||||||
this._config !== undefined ? this._config.dark_mode === true : false
|
this._config!.dark_mode ?? this.hass.themes.darkMode
|
||||||
);
|
);
|
||||||
this._drawEntities();
|
this._drawEntities();
|
||||||
this._leafletMap.invalidateSize();
|
this._leafletMap.invalidateSize();
|
||||||
this._fitMap();
|
this._fitMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _replaceTileLayer() {
|
||||||
|
const map = this._leafletMap;
|
||||||
|
const config = this._config;
|
||||||
|
const Leaflet = this.Leaflet;
|
||||||
|
if (!map || !config || !Leaflet || !this._tileLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
Leaflet,
|
||||||
|
map,
|
||||||
|
this._tileLayer,
|
||||||
|
this._config!.dark_mode ?? this.hass.themes.darkMode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private updateMap(oldConfig: MapCardConfig): void {
|
private updateMap(oldConfig: MapCardConfig): void {
|
||||||
const map = this._leafletMap;
|
const map = this._leafletMap;
|
||||||
const config = this._config;
|
const config = this._config;
|
||||||
const Leaflet = this.Leaflet;
|
const Leaflet = this.Leaflet;
|
||||||
if (!map || !config || !Leaflet) {
|
if (!map || !config || !Leaflet || !this._tileLayer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (config.dark_mode !== oldConfig.dark_mode) {
|
if (this._config!.dark_mode !== oldConfig.dark_mode) {
|
||||||
createTileLayer(Leaflet, config.dark_mode === true).addTo(map);
|
this._replaceTileLayer();
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
config.entities !== oldConfig.entities ||
|
config.entities !== oldConfig.entities ||
|
||||||
@ -493,7 +521,11 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
icon: Leaflet.divIcon({
|
icon: Leaflet.divIcon({
|
||||||
html: iconHTML,
|
html: iconHTML,
|
||||||
iconSize: [24, 24],
|
iconSize: [24, 24],
|
||||||
className: this._config!.dark_mode === true ? "dark" : "light",
|
className: this._config!.dark_mode
|
||||||
|
? "dark"
|
||||||
|
: this._config!.dark_mode === false
|
||||||
|
? "light"
|
||||||
|
: "",
|
||||||
}),
|
}),
|
||||||
interactive: false,
|
interactive: false,
|
||||||
title,
|
title,
|
||||||
@ -649,11 +681,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #fafaf8;
|
background: inherit;
|
||||||
}
|
|
||||||
|
|
||||||
#map.dark {
|
|
||||||
background: #090909;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
|
@ -40,15 +40,13 @@ export class HuiCardOptions extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="options">
|
<div class="card-actions">
|
||||||
<div class="primary-actions">
|
|
||||||
<mwc-button @click=${this._editCard}
|
<mwc-button @click=${this._editCard}
|
||||||
>${this.hass!.localize(
|
>${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.edit"
|
"ui.panel.lovelace.editor.edit_card.edit"
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
>
|
>
|
||||||
</div>
|
<div>
|
||||||
<div class="secondary-actions">
|
|
||||||
<mwc-icon-button
|
<mwc-icon-button
|
||||||
title="Move card down"
|
title="Move card down"
|
||||||
class="move-arrow"
|
class="move-arrow"
|
||||||
@ -112,21 +110,10 @@ export class HuiCardOptions extends LitElement {
|
|||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.options {
|
.card-actions {
|
||||||
border-top: 1px solid #e8e8e8;
|
|
||||||
padding: 5px 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: -1px;
|
justify-content: space-between;
|
||||||
}
|
align-items: center;
|
||||||
|
|
||||||
div.options .primary-actions {
|
|
||||||
flex: 1;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.options .secondary-actions {
|
|
||||||
flex: 4;
|
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-icon-button {
|
mwc-icon-button {
|
||||||
|
@ -22,7 +22,7 @@ class HuiDividerRow extends LitElement implements LovelaceRow {
|
|||||||
this._config = {
|
this._config = {
|
||||||
style: {
|
style: {
|
||||||
height: "1px",
|
height: "1px",
|
||||||
"background-color": "var(--secondary-text-color)",
|
"background-color": "var(--divider-color)",
|
||||||
},
|
},
|
||||||
...config,
|
...config,
|
||||||
};
|
};
|
||||||
|
@ -48,8 +48,7 @@ class HuiSectionRow extends LitElement implements LovelaceRow {
|
|||||||
}
|
}
|
||||||
.divider {
|
.divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: var(--secondary-text-color);
|
background-color: var(--divider-color);
|
||||||
opacity: 0.25;
|
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
margin-right: -16px;
|
margin-right: -16px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
@ -2,7 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
/* eslint-plugin-disable lit */
|
/* eslint-plugin-disable lit */
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
import { setupLeafletMap } from "../../common/dom/setup-leaflet-map";
|
import {
|
||||||
|
setupLeafletMap,
|
||||||
|
replaceTileLayer,
|
||||||
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
@ -25,10 +28,11 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
|||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
background: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light {
|
.icon {
|
||||||
color: #000000;
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -69,7 +73,11 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadMap() {
|
async loadMap() {
|
||||||
[this._map, this.Leaflet] = await setupLeafletMap(this.$.map);
|
this._darkMode = this.hass.themes.darkMode;
|
||||||
|
[this._map, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||||
|
this.$.map,
|
||||||
|
this._darkMode
|
||||||
|
);
|
||||||
this.drawEntities(this.hass);
|
this.drawEntities(this.hass);
|
||||||
this._map.invalidateSize();
|
this._map.invalidateSize();
|
||||||
this.fitMap();
|
this.fitMap();
|
||||||
@ -113,6 +121,16 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
|||||||
var map = this._map;
|
var map = this._map;
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
||||||
|
if (this._darkMode !== this.hass.themes.darkMode) {
|
||||||
|
this._darkMode = this.hass.themes.darkMode;
|
||||||
|
this._tileLayer = replaceTileLayer(
|
||||||
|
this.Leaflet,
|
||||||
|
map,
|
||||||
|
this._tileLayer,
|
||||||
|
this.hass.themes.darkMode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._mapItems) {
|
if (this._mapItems) {
|
||||||
this._mapItems.forEach(function (marker) {
|
this._mapItems.forEach(function (marker) {
|
||||||
marker.remove();
|
marker.remove();
|
||||||
@ -160,7 +178,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
|||||||
icon = this.Leaflet.divIcon({
|
icon = this.Leaflet.divIcon({
|
||||||
html: iconHTML,
|
html: iconHTML,
|
||||||
iconSize: [24, 24],
|
iconSize: [24, 24],
|
||||||
className: "light",
|
className: "icon",
|
||||||
});
|
});
|
||||||
|
|
||||||
// create marker with the icon
|
// create marker with the icon
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "../../components/ha-paper-dropdown-menu";
|
|
||||||
import { EventsMixin } from "../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class HaPickThemeRow extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-settings-row narrow="[[narrow]]">
|
|
||||||
<span slot="heading"
|
|
||||||
>[[localize('ui.panel.profile.themes.header')]]</span
|
|
||||||
>
|
|
||||||
<span slot="description">
|
|
||||||
<template is="dom-if" if="[[!_hasThemes]]">
|
|
||||||
[[localize('ui.panel.profile.themes.error_no_theme')]]
|
|
||||||
</template>
|
|
||||||
<a
|
|
||||||
href="https://www.home-assistant.io/integrations/frontend/#defining-themes"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>[[localize('ui.panel.profile.themes.link_promo')]]</a
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<ha-paper-dropdown-menu
|
|
||||||
label="[[localize('ui.panel.profile.themes.dropdown_label')]]"
|
|
||||||
dynamic-align
|
|
||||||
disabled="[[!_hasThemes]]"
|
|
||||||
>
|
|
||||||
<paper-listbox slot="dropdown-content" selected="{{selectedTheme}}">
|
|
||||||
<template is="dom-repeat" items="[[themes]]" as="theme">
|
|
||||||
<paper-item>[[theme]]</paper-item>
|
|
||||||
</template>
|
|
||||||
</paper-listbox>
|
|
||||||
</ha-paper-dropdown-menu>
|
|
||||||
</ha-settings-row>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
narrow: Boolean,
|
|
||||||
_hasThemes: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "_compHasThemes(hass)",
|
|
||||||
},
|
|
||||||
themes: {
|
|
||||||
type: Array,
|
|
||||||
computed: "_computeThemes(hass)",
|
|
||||||
},
|
|
||||||
selectedTheme: {
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observers() {
|
|
||||||
return ["selectionChanged(hass, selectedTheme)"];
|
|
||||||
}
|
|
||||||
|
|
||||||
_compHasThemes(hass) {
|
|
||||||
return (
|
|
||||||
hass.themes &&
|
|
||||||
hass.themes.themes &&
|
|
||||||
Object.keys(hass.themes.themes).length
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
if (
|
|
||||||
this.hass.selectedTheme &&
|
|
||||||
this.themes.indexOf(this.hass.selectedTheme) > 0
|
|
||||||
) {
|
|
||||||
this.selectedTheme = this.themes.indexOf(this.hass.selectedTheme);
|
|
||||||
} else if (!this.hass.selectedTheme) {
|
|
||||||
this.selectedTheme = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeThemes(hass) {
|
|
||||||
if (!hass) return [];
|
|
||||||
return ["Backend-selected", "default"].concat(
|
|
||||||
Object.keys(hass.themes.themes).sort()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectionChanged(hass, selection) {
|
|
||||||
if (selection > 0 && selection < this.themes.length) {
|
|
||||||
if (hass.selectedTheme !== this.themes[selection]) {
|
|
||||||
this.fire("settheme", this.themes[selection]);
|
|
||||||
}
|
|
||||||
} else if (selection === 0 && hass.selectedTheme !== "") {
|
|
||||||
this.fire("settheme", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-pick-theme-row", HaPickThemeRow);
|
|
240
src/panels/profile/ha-pick-theme-row.ts
Normal file
240
src/panels/profile/ha-pick-theme-row.ts
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import "../../components/ha-paper-dropdown-menu";
|
||||||
|
import { TemplateResult, html } from "lit-html";
|
||||||
|
import {
|
||||||
|
property,
|
||||||
|
internalProperty,
|
||||||
|
LitElement,
|
||||||
|
customElement,
|
||||||
|
PropertyValues,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "./ha-settings-row";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../../components/ha-formfield";
|
||||||
|
import "../../components/ha-radio";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import type { HaRadio } from "../../components/ha-radio";
|
||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
|
||||||
|
@customElement("ha-pick-theme-row")
|
||||||
|
export class HaPickThemeRow extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@internalProperty() _themes: string[] = [];
|
||||||
|
|
||||||
|
@internalProperty() _selectedTheme = 0;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const hasThemes =
|
||||||
|
this.hass.themes?.themes && Object.keys(this.hass.themes.themes).length;
|
||||||
|
const curTheme =
|
||||||
|
this.hass!.selectedTheme?.theme || this.hass!.themes.default_theme;
|
||||||
|
return html`
|
||||||
|
<ha-settings-row .narrow=${this.narrow}>
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize("ui.panel.profile.themes.header")}</span
|
||||||
|
>
|
||||||
|
<span slot="description">
|
||||||
|
${!hasThemes
|
||||||
|
? this.hass.localize("ui.panel.profile.themes.error_no_theme")
|
||||||
|
: ""}
|
||||||
|
<a
|
||||||
|
href="https://www.home-assistant.io/integrations/frontend/#defining-themes"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.panel.profile.themes.link_promo")}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ha-paper-dropdown-menu
|
||||||
|
.label=${this.hass.localize("ui.panel.profile.themes.dropdown_label")}
|
||||||
|
dynamic-align
|
||||||
|
.disabled=${!hasThemes}
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected=${this._selectedTheme}
|
||||||
|
@iron-select=${this._handleThemeSelection}
|
||||||
|
>
|
||||||
|
${this._themes.map(
|
||||||
|
(theme) => html`<paper-item .theme=${theme}>${theme}</paper-item>`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</ha-paper-dropdown-menu>
|
||||||
|
</ha-settings-row>
|
||||||
|
${curTheme === "default"
|
||||||
|
? html` <div class="inputs">
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.profile.themes.dark_mode.auto"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-radio
|
||||||
|
@change=${this._handleDarkMode}
|
||||||
|
name="dark_mode"
|
||||||
|
value="auto"
|
||||||
|
?checked=${this.hass.selectedTheme?.dark === undefined}
|
||||||
|
></ha-radio>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.profile.themes.dark_mode.light"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-radio
|
||||||
|
@change=${this._handleDarkMode}
|
||||||
|
name="dark_mode"
|
||||||
|
value="light"
|
||||||
|
?checked=${this.hass.selectedTheme?.dark === false}
|
||||||
|
>
|
||||||
|
</ha-radio>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.profile.themes.dark_mode.dark"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-radio
|
||||||
|
@change=${this._handleDarkMode}
|
||||||
|
name="dark_mode"
|
||||||
|
value="dark"
|
||||||
|
?checked=${this.hass.selectedTheme?.dark === true}
|
||||||
|
>
|
||||||
|
</ha-radio>
|
||||||
|
</ha-formfield>
|
||||||
|
<div class="color-pickers">
|
||||||
|
<paper-input
|
||||||
|
.value=${this.hass!.selectedTheme?.primaryColor || "#03a9f4"}
|
||||||
|
type="color"
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.profile.themes.primary_color"
|
||||||
|
)}
|
||||||
|
.name=${"primaryColor"}
|
||||||
|
@change=${this._handleColorChange}
|
||||||
|
></paper-input>
|
||||||
|
<paper-input
|
||||||
|
.value=${this.hass!.selectedTheme?.accentColor || "#ff9800"}
|
||||||
|
type="color"
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.profile.themes.accent_color"
|
||||||
|
)}
|
||||||
|
.name=${"accentColor"}
|
||||||
|
@change=${this._handleColorChange}
|
||||||
|
></paper-input>
|
||||||
|
${this.hass!.selectedTheme?.primaryColor ||
|
||||||
|
this.hass!.selectedTheme?.accentColor
|
||||||
|
? html` <mwc-button @click=${this._resetColors}>
|
||||||
|
${this.hass!.localize("ui.panel.profile.themes.reset")}
|
||||||
|
</mwc-button>`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues) {
|
||||||
|
const oldHass = changedProperties.get("hass") as undefined | HomeAssistant;
|
||||||
|
const themesChanged =
|
||||||
|
changedProperties.has("hass") &&
|
||||||
|
(!oldHass || oldHass.themes?.themes !== this.hass.themes?.themes);
|
||||||
|
const selectedThemeChanged =
|
||||||
|
changedProperties.has("hass") &&
|
||||||
|
(!oldHass || oldHass.selectedTheme !== this.hass.selectedTheme);
|
||||||
|
|
||||||
|
if (themesChanged) {
|
||||||
|
this._themes = ["Backend-selected", "default"].concat(
|
||||||
|
Object.keys(this.hass.themes.themes).sort()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedThemeChanged) {
|
||||||
|
if (
|
||||||
|
this.hass.selectedTheme &&
|
||||||
|
this._themes.indexOf(this.hass.selectedTheme.theme) > 0
|
||||||
|
) {
|
||||||
|
this._selectedTheme = this._themes.indexOf(
|
||||||
|
this.hass.selectedTheme.theme
|
||||||
|
);
|
||||||
|
} else if (!this.hass.selectedTheme) {
|
||||||
|
this._selectedTheme = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleColorChange(ev: CustomEvent) {
|
||||||
|
const target = ev.target as any;
|
||||||
|
fireEvent(this, "settheme", { [target.name]: target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resetColors() {
|
||||||
|
fireEvent(this, "settheme", {
|
||||||
|
primaryColor: undefined,
|
||||||
|
accentColor: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDarkMode(ev: CustomEvent) {
|
||||||
|
let dark: boolean | undefined;
|
||||||
|
switch ((ev.target as HaRadio).value) {
|
||||||
|
case "light":
|
||||||
|
dark = false;
|
||||||
|
break;
|
||||||
|
case "dark":
|
||||||
|
dark = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fireEvent(this, "settheme", { dark });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleThemeSelection(ev: CustomEvent) {
|
||||||
|
const theme = ev.detail.item.theme;
|
||||||
|
if (theme === "Backend-selected") {
|
||||||
|
if (this.hass.selectedTheme?.theme) {
|
||||||
|
fireEvent(this, "settheme", { theme: "" });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "settheme", { theme });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.inputs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
ha-formfield {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
.color-pickers {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
paper-input {
|
||||||
|
min-width: 75px;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-pick-theme-row": HaPickThemeRow;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import "codemirror/mode/jinja2/jinja2";
|
|||||||
import "codemirror/mode/yaml/yaml";
|
import "codemirror/mode/yaml/yaml";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
_CodeMirror.commands.save = (cm: Editor) => {
|
_CodeMirror.commands.save = (cm: Editor) => {
|
||||||
fireEvent(cm.getWrapperElement(), "editor-save");
|
fireEvent(cm.getWrapperElement(), "editor-save");
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,7 @@ documentContainer.innerHTML = `<custom-style>
|
|||||||
--primary-text-color: #212121;
|
--primary-text-color: #212121;
|
||||||
--secondary-text-color: #727272;
|
--secondary-text-color: #727272;
|
||||||
--text-primary-color: #ffffff;
|
--text-primary-color: #ffffff;
|
||||||
|
--text-light-primary-color: #212121;
|
||||||
--disabled-text-color: #bdbdbd;
|
--disabled-text-color: #bdbdbd;
|
||||||
|
|
||||||
/* main interface colors */
|
/* main interface colors */
|
||||||
@ -83,9 +84,6 @@ documentContainer.innerHTML = `<custom-style>
|
|||||||
/* set our slider style */
|
/* set our slider style */
|
||||||
--ha-paper-slider-pin-font-size: 15px;
|
--ha-paper-slider-pin-font-size: 15px;
|
||||||
|
|
||||||
/* markdown styles */
|
|
||||||
--markdown-code-background-color: #f6f8fa;
|
|
||||||
|
|
||||||
/* rgb */
|
/* rgb */
|
||||||
--rgb-primary-color: 3, 169, 244;
|
--rgb-primary-color: 3, 169, 244;
|
||||||
--rgb-accent-color: 255, 152, 0;
|
--rgb-accent-color: 255, 152, 0;
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
import "proxy-polyfill";
|
import "proxy-polyfill";
|
||||||
import { expose } from "comlink";
|
import { expose } from "comlink";
|
||||||
import marked from "marked";
|
import marked from "marked";
|
||||||
// @ts-ignore
|
import { filterXSS, getDefaultWhiteList } from "xss";
|
||||||
import filterXSS from "xss";
|
|
||||||
|
|
||||||
interface WhiteList {
|
interface WhiteList {
|
||||||
[tag: string]: string[];
|
[tag: string]: string[];
|
||||||
@ -14,7 +13,7 @@ let whiteListSvg: WhiteList | undefined;
|
|||||||
|
|
||||||
const renderMarkdown = (
|
const renderMarkdown = (
|
||||||
content: string,
|
content: string,
|
||||||
markedOptions: object,
|
markedOptions: marked.MarkedOptions,
|
||||||
hassOptions: {
|
hassOptions: {
|
||||||
// Do not allow SVG on untrusted content, it allows XSS.
|
// Do not allow SVG on untrusted content, it allows XSS.
|
||||||
allowSvg?: boolean;
|
allowSvg?: boolean;
|
||||||
@ -22,7 +21,7 @@ const renderMarkdown = (
|
|||||||
): string => {
|
): string => {
|
||||||
if (!whiteListNormal) {
|
if (!whiteListNormal) {
|
||||||
whiteListNormal = {
|
whiteListNormal = {
|
||||||
...filterXSS.whiteList,
|
...(getDefaultWhiteList() as WhiteList),
|
||||||
"ha-icon": ["icon"],
|
"ha-icon": ["icon"],
|
||||||
"ha-svg-icon": ["path"],
|
"ha-svg-icon": ["path"],
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
import { css } from "lit-element";
|
import { css } from "lit-element";
|
||||||
|
|
||||||
|
export const darkStyles = {
|
||||||
|
"primary-background-color": "#111111",
|
||||||
|
"card-background-color": "#1c1c1c",
|
||||||
|
"secondary-background-color": "#1e1e1e",
|
||||||
|
"primary-text-color": "#e1e1e1",
|
||||||
|
"secondary-text-color": "#9b9b9b",
|
||||||
|
"app-header-text-color": "#e1e1e1",
|
||||||
|
"app-header-background-color": "#1c1c1c",
|
||||||
|
"switch-unchecked-button-color": "#999999",
|
||||||
|
"switch-unchecked-track-color": "#9b9b9b",
|
||||||
|
"divider-color": "rgba(225, 225, 225, .12)",
|
||||||
|
};
|
||||||
|
|
||||||
export const derivedStyles = {
|
export const derivedStyles = {
|
||||||
"error-state-color": "var(--error-color)",
|
"error-state-color": "var(--error-color)",
|
||||||
"state-icon-unavailable-color": "var(--disabled-text-color)",
|
"state-icon-unavailable-color": "var(--disabled-text-color)",
|
||||||
@ -33,6 +46,7 @@ export const derivedStyles = {
|
|||||||
"paper-slider-secondary-color": "var(--slider-secondary-color)",
|
"paper-slider-secondary-color": "var(--slider-secondary-color)",
|
||||||
"paper-slider-container-color": "var(--slider-bar-color)",
|
"paper-slider-container-color": "var(--slider-bar-color)",
|
||||||
"data-table-background-color": "var(--card-background-color)",
|
"data-table-background-color": "var(--card-background-color)",
|
||||||
|
"markdown-code-background-color": "var(--primary-background-color)",
|
||||||
"mdc-theme-primary": "var(--primary-color)",
|
"mdc-theme-primary": "var(--primary-color)",
|
||||||
"mdc-theme-secondary": "var(--accent-color)",
|
"mdc-theme-secondary": "var(--accent-color)",
|
||||||
"mdc-theme-background": "var(--primary-background-color)",
|
"mdc-theme-background": "var(--primary-background-color)",
|
||||||
@ -48,6 +62,8 @@ export const derivedStyles = {
|
|||||||
"material-secondary-background-color": "var(--secondary-background-color)",
|
"material-secondary-background-color": "var(--secondary-background-color)",
|
||||||
"mdc-checkbox-unchecked-color": "rgba(var(--rgb-primary-text-color), 0.54)",
|
"mdc-checkbox-unchecked-color": "rgba(var(--rgb-primary-text-color), 0.54)",
|
||||||
"mdc-checkbox-disabled-color": "var(--disabled-text-color)",
|
"mdc-checkbox-disabled-color": "var(--disabled-text-color)",
|
||||||
|
"mdc-radio-unchecked-color": "rgba(var(--rgb-primary-text-color), 0.54)",
|
||||||
|
"mdc-radio-disabled-color": "var(--disabled-text-color)",
|
||||||
"mdc-tab-text-label-color-default": "var(--primary-text-color)",
|
"mdc-tab-text-label-color-default": "var(--primary-text-color)",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,26 +4,34 @@ import {
|
|||||||
} from "../common/dom/apply_themes_on_element";
|
} from "../common/dom/apply_themes_on_element";
|
||||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||||
import { subscribeThemes } from "../data/ws-themes";
|
import { subscribeThemes } from "../data/ws-themes";
|
||||||
import { Constructor } from "../types";
|
import { Constructor, HomeAssistant } from "../types";
|
||||||
import { storeState } from "../util/ha-pref-storage";
|
import { storeState } from "../util/ha-pref-storage";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for add event listener
|
// for add event listener
|
||||||
interface HTMLElementEventMap {
|
interface HTMLElementEventMap {
|
||||||
settheme: HASSDomEvent<string>;
|
settheme: HASSDomEvent<Partial<HomeAssistant["selectedTheme"]>>;
|
||||||
|
}
|
||||||
|
interface HASSDomEvents {
|
||||||
|
settheme: Partial<HomeAssistant["selectedTheme"]>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mql = matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||||
class extends superClass {
|
class extends superClass {
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this.addEventListener("settheme", (ev) => {
|
this.addEventListener("settheme", (ev) => {
|
||||||
this._updateHass({ selectedTheme: ev.detail });
|
this._updateHass({
|
||||||
this._applyTheme();
|
selectedTheme: { ...this.hass!.selectedTheme!, ...ev.detail },
|
||||||
|
});
|
||||||
|
this._applyTheme(mql.matches);
|
||||||
storeState(this.hass!);
|
storeState(this.hass!);
|
||||||
});
|
});
|
||||||
|
mql.addListener((ev) => this._applyTheme(ev.matches));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hassConnected() {
|
protected hassConnected() {
|
||||||
@ -32,29 +40,68 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
subscribeThemes(this.hass!.connection, (themes) => {
|
subscribeThemes(this.hass!.connection, (themes) => {
|
||||||
this._updateHass({ themes });
|
this._updateHass({ themes });
|
||||||
invalidateThemeCache();
|
invalidateThemeCache();
|
||||||
this._applyTheme();
|
this._applyTheme(mql.matches);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _applyTheme() {
|
private _applyTheme(dark: boolean) {
|
||||||
|
const themeName =
|
||||||
|
this.hass!.selectedTheme?.theme ||
|
||||||
|
(dark && this.hass!.themes.default_dark_theme
|
||||||
|
? this.hass!.themes.default_dark_theme!
|
||||||
|
: this.hass!.themes.default_theme);
|
||||||
|
|
||||||
|
let options: Partial<HomeAssistant["selectedTheme"]> = this.hass!
|
||||||
|
.selectedTheme;
|
||||||
|
|
||||||
|
if (themeName === "default" && options?.dark === undefined) {
|
||||||
|
options = {
|
||||||
|
...this.hass!.selectedTheme!,
|
||||||
|
dark,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
applyThemesOnElement(
|
applyThemesOnElement(
|
||||||
document.documentElement,
|
document.documentElement,
|
||||||
this.hass!.themes,
|
this.hass!.themes,
|
||||||
this.hass!.selectedTheme || this.hass!.themes.default_theme
|
themeName,
|
||||||
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
const meta = document.querySelector("meta[name=theme-color]");
|
const darkMode =
|
||||||
|
themeName === "default"
|
||||||
|
? !!options?.dark
|
||||||
|
: !!(dark && this.hass!.themes.default_dark_theme);
|
||||||
|
|
||||||
|
if (darkMode !== this.hass!.themes.darkMode) {
|
||||||
|
this._updateHass({
|
||||||
|
themes: { ...this.hass!.themes, darkMode },
|
||||||
|
});
|
||||||
|
|
||||||
|
const schemeMeta = document.querySelector("meta[name=color-scheme]");
|
||||||
|
if (schemeMeta) {
|
||||||
|
schemeMeta.setAttribute(
|
||||||
|
"content",
|
||||||
|
darkMode ? "dark" : themeName === "default" ? "light" : "dark light"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeMeta = document.querySelector("meta[name=theme-color]");
|
||||||
const headerColor = getComputedStyle(
|
const headerColor = getComputedStyle(
|
||||||
document.documentElement
|
document.documentElement
|
||||||
).getPropertyValue("--app-header-background-color");
|
).getPropertyValue("--app-header-background-color");
|
||||||
if (meta) {
|
if (themeMeta) {
|
||||||
if (!meta.hasAttribute("default-content")) {
|
if (!themeMeta.hasAttribute("default-content")) {
|
||||||
meta.setAttribute("default-content", meta.getAttribute("content")!);
|
themeMeta.setAttribute(
|
||||||
|
"default-content",
|
||||||
|
themeMeta.getAttribute("content")!
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const themeColor =
|
const themeColor =
|
||||||
headerColor.trim() ||
|
headerColor.trim() ||
|
||||||
(meta.getAttribute("default-content") as string);
|
(themeMeta.getAttribute("default-content") as string);
|
||||||
meta.setAttribute("content", themeColor);
|
themeMeta.setAttribute("content", themeColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2199,7 +2199,15 @@
|
|||||||
"header": "Theme",
|
"header": "Theme",
|
||||||
"error_no_theme": "No themes available.",
|
"error_no_theme": "No themes available.",
|
||||||
"link_promo": "Learn about themes",
|
"link_promo": "Learn about themes",
|
||||||
"dropdown_label": "Theme"
|
"dropdown_label": "Theme",
|
||||||
|
"dark_mode": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"light": "Light",
|
||||||
|
"dark": "Dark"
|
||||||
|
},
|
||||||
|
"primary_color": "Primary color",
|
||||||
|
"accent_color": "Accent color",
|
||||||
|
"reset": "Reset"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"header": "Dashboard",
|
"header": "Dashboard",
|
||||||
|
12
src/types.ts
12
src/types.ts
@ -87,11 +87,21 @@ export interface Theme {
|
|||||||
"primary-color": string;
|
"primary-color": string;
|
||||||
"text-primary-color": string;
|
"text-primary-color": string;
|
||||||
"accent-color": string;
|
"accent-color": string;
|
||||||
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Themes {
|
export interface Themes {
|
||||||
default_theme: string;
|
default_theme: string;
|
||||||
|
default_dark_theme: string | null;
|
||||||
themes: { [key: string]: Theme };
|
themes: { [key: string]: Theme };
|
||||||
|
darkMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThemeSettings {
|
||||||
|
theme: string;
|
||||||
|
dark?: boolean;
|
||||||
|
primaryColor?: string;
|
||||||
|
accentColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PanelInfo<T = {} | null> {
|
export interface PanelInfo<T = {} | null> {
|
||||||
@ -193,7 +203,7 @@ export interface HomeAssistant {
|
|||||||
services: HassServices;
|
services: HassServices;
|
||||||
config: HassConfig;
|
config: HassConfig;
|
||||||
themes: Themes;
|
themes: Themes;
|
||||||
selectedTheme?: string | null;
|
selectedTheme?: ThemeSettings | null;
|
||||||
panels: Panels;
|
panels: Panels;
|
||||||
panelUrl: string;
|
panelUrl: string;
|
||||||
|
|
||||||
|
@ -27,6 +27,10 @@ export function getState() {
|
|||||||
STORED_STATE.forEach((key) => {
|
STORED_STATE.forEach((key) => {
|
||||||
if (key in STORAGE) {
|
if (key in STORAGE) {
|
||||||
let value = JSON.parse(STORAGE[key]);
|
let value = JSON.parse(STORAGE[key]);
|
||||||
|
// selectedTheme went from string to object on 20200718
|
||||||
|
if (key === "selectedTheme" && typeof value === "string") {
|
||||||
|
value = { theme: value };
|
||||||
|
}
|
||||||
// dockedSidebar went from boolean to enum on 20190720
|
// dockedSidebar went from boolean to enum on 20190720
|
||||||
if (key === "dockedSidebar" && typeof value === "boolean") {
|
if (key === "dockedSidebar" && typeof value === "boolean") {
|
||||||
value = value ? "docked" : "auto";
|
value = value ? "docked" : "auto";
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "ts-lit-plugin",
|
"name": "ts-lit-plugin",
|
||||||
|
21
yarn.lock
21
yarn.lock
@ -2532,10 +2532,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/chrome" "*"
|
"@types/chrome" "*"
|
||||||
|
|
||||||
"@types/codemirror@^0.0.78":
|
"@types/codemirror@^0.0.97":
|
||||||
version "0.0.78"
|
version "0.0.97"
|
||||||
resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.78.tgz#75a8eabda268c8e734855fb24e8c86192e2e18ad"
|
resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.97.tgz#6f2d8266b7f1b34aacfe8c77221fafe324c3d081"
|
||||||
integrity sha512-QpMQUpEL+ZNcpEhjvYM/H6jqDx9nNcJqymA2kbkNthFS2I7ekL7ofEZ7+MoQAFTBuJers91K0FGCMpL7MwC9TQ==
|
integrity sha512-n5d7o9nWhC49DjfhsxANP7naWSeTzrjXASkUDQh7626sM4zK9XP2EVcHp1IcCf/IPV6c7ORzDUDF3Bkt231VKg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/tern" "*"
|
"@types/tern" "*"
|
||||||
|
|
||||||
@ -2641,6 +2641,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
|
||||||
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
|
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
|
||||||
|
|
||||||
|
"@types/marked@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-1.1.0.tgz#53509b5f127e0c05c19176fcf1d743a41e00ff19"
|
||||||
|
integrity sha512-j8XXj6/l9kFvCwMyVqozznqpd/nk80krrW+QiIJN60Uu9gX5Pvn4/qPJ2YngQrR3QREPwmrE1f9/EWKVTFzoEw==
|
||||||
|
|
||||||
"@types/memoize-one@4.1.0":
|
"@types/memoize-one@4.1.0":
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/memoize-one/-/memoize-one-4.1.0.tgz#62119f26055b3193ae43ca1882c5b29b88b71ece"
|
resolved "https://registry.yarnpkg.com/@types/memoize-one/-/memoize-one-4.1.0.tgz#62119f26055b3193ae43ca1882c5b29b88b71ece"
|
||||||
@ -8258,10 +8263,10 @@ map-visit@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
marked@^0.6.1:
|
marked@^1.1.1:
|
||||||
version "0.6.2"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.2.tgz#c574be8b545a8b48641456ca1dbe0e37b6dccc1a"
|
resolved "https://registry.yarnpkg.com/marked/-/marked-1.1.1.tgz#e5d61b69842210d5df57b05856e0c91572703e6a"
|
||||||
integrity sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==
|
integrity sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==
|
||||||
|
|
||||||
matchdep@^2.0.0:
|
matchdep@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user