Compare commits

...

34 Commits

Author SHA1 Message Date
Paul Bottein ea9ef9a165 Use new name 2023-10-04 14:22:33 +02:00
Paul Bottein 5270ca4db6 Use responsive condition in energy dashboard 2023-10-04 14:21:13 +02:00
Paul Bottein 12cb94cdc0 Remove unused code 2023-10-04 14:20:44 +02:00
Paul Bottein d473a30263 Improve editor 2023-10-04 14:20:07 +02:00
Paul Bottein e767b807b0 Rename responsive to screen 2023-10-04 14:20:06 +02:00
Paul Bottein f593fb6043 Add icon 2023-10-04 14:20:06 +02:00
Paul Bottein bbf8ee7c64 Add current state 2023-10-04 14:20:06 +02:00
Paul Bottein 3d9993563f Add "Add condition" button in the editor 2023-10-04 14:20:06 +02:00
Paul Bottein fc3e353e94 Improve performance and editor 2023-10-04 14:20:06 +02:00
Paul Bottein e9738bd5ae Add reponsive editor with breakpoints 2023-10-04 14:20:06 +02:00
Paul Bottein 87d9298b20 Use media query, remove ui editor for responsive 2023-10-04 14:20:06 +02:00
Paul Bottein a29b92a92a Add UI for responsive condition 2023-10-04 14:20:06 +02:00
Paul Bottein 5b4504688a Migrate state condition ui to ha-form 2023-10-04 14:20:06 +02:00
Paul Bottein 0a4401b417 Add responsive condition for conditional card 2023-10-04 14:20:06 +02:00
dependabot[bot] 8e1e42cd50 Bump postcss from 8.4.30 to 8.4.31 (#18110)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.30 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.30...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 03:29:36 +00:00
renovate[bot] 014f9b8b73 Update dependency magic-string to v0.30.4 (#18089)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-03 23:15:36 -04:00
renovate[bot] 774c7e275c Update dependency @material/web to v1.0.0 (#18070)
* Update dependency @material/web to v1.0.0

* Fix icon button size

* Remove unused ios override

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2023-10-03 09:48:27 +00:00
Bram Kragten e288b003d8 Bumped version to 20231002.0 2023-10-02 21:41:37 +02:00
yousaf465 4aa8518ed6 Change Urdu language to RTL (#18063)
Co-authored-by: karwosts <32912880+karwosts@users.noreply.github.com>
2023-10-02 21:26:07 +02:00
Bram Kragten 6acbf6395c Ignore prettier styling for tooltip of disk life time (#18097) 2023-10-02 21:23:13 +02:00
Bram Kragten 2030feabf7 Only set wakeword when not in new wakewords (#18095) 2023-10-02 21:23:02 +02:00
Bram Kragten 46d1dbcb47 Dont continue debug assist run when disconnected (#18098) 2023-10-02 21:22:50 +02:00
Bram Kragten 2f6297ec17 Always deduplicate found integrations in onboarding (#18091)
always deduplicate found integrations in onboarding
2023-10-02 14:30:28 +02:00
Paul Bottein a3400a2f9c Prevent ha-form data lost if quick data updates (#18094) 2023-10-02 11:44:43 +02:00
Kendell R c345f41416 Factor out style data to reduce bundle size (#18077) 2023-10-02 11:18:44 +02:00
Kendell R 292cdc7621 Fix clicking on radio buttons (and improve code) (#18078) 2023-10-02 10:58:10 +02:00
dependabot[bot] c5ba74e0b4 Bump home-assistant/wheels from 2023.09.1 to 2023.10.1 (#18088)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 08:54:53 +02:00
renovate[bot] 41b24de559 Update dependency chai to v4.3.10 (#18082) 2023-10-01 20:24:14 -04:00
renovate[bot] 4fe7b18161 Update dependency chai to v4.3.9 (#18074)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-30 16:17:27 -04:00
renovate[bot] 399a979c33 Update dependency glob to v10.3.10 (#18072)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-30 16:16:20 -04:00
renovate[bot] 03c5482860 Update dependency @types/mocha to v10.0.2 (#18075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-30 16:15:30 -04:00
Franck Nijhof a116a50604 Hide shopping list from discovered onboarding integrations (#18069) 2023-09-29 22:39:57 +02:00
renovate[bot] 0dfa292c40 Update dependency glob to v10.3.9 (#18060)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-29 13:30:08 -04:00
Bram Kragten 5914a6c1a4 Clear wake word when new engine is picked, select first wake word. by… (#18058)
* Clear wake word when new engine is picked, select first wake word. by default

* Update assist-pipeline-detail-wakeword.ts
2023-09-29 12:38:11 +02:00
38 changed files with 1175 additions and 490 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ jobs:
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels
uses: home-assistant/wheels@2023.09.1
uses: home-assistant/wheels@2023.10.1
with:
abi: cp311
tag: musllinux_1_2
+1 -2
View File
@@ -324,8 +324,7 @@ export class HcMain extends HassElement {
{
type: DEFAULT_STRATEGY,
},
this.hass!,
{ narrow: false }
this.hass!
)
);
}
+5 -5
View File
@@ -80,7 +80,7 @@
"@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "=1.0.0-pre.17",
"@material/web": "=1.0.0",
"@mdi/js": "7.2.96",
"@mdi/svg": "7.2.96",
"@polymer/iron-flex-layout": "3.0.1",
@@ -180,7 +180,7 @@
"@types/leaflet": "1.9.6",
"@types/leaflet-draw": "1.0.8",
"@types/luxon": "3.3.2",
"@types/mocha": "10.0.1",
"@types/mocha": "10.0.2",
"@types/qrcode": "1.5.2",
"@types/serve-handler": "6.1.2",
"@types/sortablejs": "1.15.3",
@@ -193,7 +193,7 @@
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.8",
"chai": "4.3.10",
"del": "7.1.0",
"eslint": "8.50.0",
"eslint-config-airbnb-base": "15.0.0",
@@ -209,7 +209,7 @@
"esprima": "4.0.1",
"fancy-log": "2.0.0",
"fs-extra": "11.1.1",
"glob": "10.3.7",
"glob": "10.3.10",
"gulp": "4.0.2",
"gulp-flatmap": "1.0.2",
"gulp-json-transform": "0.4.8",
@@ -223,7 +223,7 @@
"lint-staged": "14.0.1",
"lit-analyzer": "2.0.0-pre.3",
"lodash.template": "4.5.0",
"magic-string": "0.30.3",
"magic-string": "0.30.4",
"map-stream": "0.0.7",
"mocha": "10.2.0",
"object-hash": "3.0.0",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20230928.0"
version = "20231002.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
+9
View File
@@ -0,0 +1,9 @@
export function getAllCombinations<T>(arr: T[]) {
return arr.reduce<T[][]>(
(combinations, element) =>
combinations.concat(
combinations.map((combination) => [...combination, element])
),
[[]]
);
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { ThemeVars } from "../../data/ws-themes";
import { darkStyles, derivedStyles } from "../../resources/styles";
import { darkStyles, derivedStyles } from "../../resources/styles-data";
import type { HomeAssistant } from "../../types";
import {
hex2rgb,
+1
View File
@@ -0,0 +1 @@
export const NARROW_MEDIA_QUERY = `(max-width: 870px)`;
+6 -1
View File
@@ -189,8 +189,13 @@ export class HaForm extends LitElement implements HaFormElement {
? ev.detail.value
: { [schema.name]: ev.detail.value };
this.data = {
...this.data,
...newValue,
};
fireEvent(this, "value-changed", {
value: { ...this.data, ...newValue },
value: this.data,
});
});
}
+19 -16
View File
@@ -7,22 +7,25 @@ import { fireEvent } from "../common/dom/fire_event";
@customElement("ha-formfield")
export class HaFormfield extends FormfieldBase {
protected _labelClick() {
const input = this.input;
if (input) {
input.focus();
switch (input.tagName) {
case "HA-CHECKBOX":
case "HA-RADIO":
if ((input as any).disabled) {
break;
}
(input as any).checked = !(input as any).checked;
fireEvent(input, "change");
break;
default:
input.click();
break;
}
const input = this.input as HTMLInputElement | undefined;
if (!input) return;
input.focus();
if (input.disabled) {
return;
}
switch (input.tagName) {
case "HA-CHECKBOX":
input.checked = !input.checked;
fireEvent(input, "change");
break;
case "HA-RADIO":
input.checked = true;
fireEvent(input, "change");
break;
default:
input.click();
break;
}
}
@@ -29,11 +29,6 @@ export class HaOutlinedIconButton extends IconButton {
--md-ripple-hover-opacity: 0;
--md-ripple-pressed-opacity: 0;
}
.outlined {
/* Fix md-outlined-icon-button padding and margin for iOS */
padding: 0;
margin: 0;
}
`,
];
}
@@ -60,7 +60,8 @@ export const moreInfoControlCircularSliderStyle = css`
justify-content: space-between;
}
.buttons ha-outlined-icon-button {
--md-outlined-icon-button-container-size: 48px;
--md-outlined-icon-button-container-width: 48px;
--md-outlined-icon-button-container-height: 48px;
--md-outlined-icon-button-icon-size: 24px;
}
/* Accessibility */
+2 -1
View File
@@ -16,6 +16,7 @@ import "../components/ha-drawer";
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
import type { HomeAssistant, Route } from "../types";
import "./partial-panel-resolver";
import { NARROW_MEDIA_QUERY } from "../common/util/media_query";
declare global {
// for fire event
@@ -50,7 +51,7 @@ export class HomeAssistantMain extends LitElement {
constructor() {
super();
listenMediaQuery("(max-width: 870px)", (matches) => {
listenMediaQuery(NARROW_MEDIA_QUERY, (matches) => {
this.narrow = matches;
});
}
+26 -39
View File
@@ -15,7 +15,6 @@ import { stringCompare } from "../common/string/compare";
import { LocalizeFunc } from "../common/translations/localize";
import { ConfigEntry, subscribeConfigEntries } from "../data/config_entries";
import { subscribeConfigFlowInProgress } from "../data/config_flow";
import { DataEntryFlowProgress } from "../data/data_entry_flow";
import { domainToName } from "../data/integration";
import { scanUSBDevices } from "../data/usb";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
@@ -24,12 +23,13 @@ import "./integration-badge";
import { onBoardingStyles } from "./styles";
const HIDDEN_DOMAINS = new Set([
"google_translate",
"hassio",
"met",
"radio_browser",
"rpi_power",
"shopping_list",
"sun",
"google_translate",
]);
@customElement("onboarding-integrations")
@@ -40,19 +40,20 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) {
@state() private _entries: ConfigEntry[] = [];
@state() private _discovered?: DataEntryFlowProgress[];
@state() private _discoveredDomains?: Set<string>;
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
return [
subscribeConfigFlowInProgress(this.hass, (flows) => {
this._discovered = flows.filter(
(flow) => !HIDDEN_DOMAINS.has(flow.handler)
this._discoveredDomains = new Set(
flows
.filter((flow) => !HIDDEN_DOMAINS.has(flow.handler))
.map((flow) => flow.handler)
);
this.hass.loadBackendTranslation(
"title",
Array.from(this._discoveredDomains)
);
const integrations: Set<string> = new Set();
for (const flow of this._discovered) {
integrations.add(flow.handler);
}
this.hass.loadBackendTranslation("title", Array.from(integrations));
}),
subscribeConfigEntries(
this.hass,
@@ -97,41 +98,27 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) {
}
protected render() {
if (!this._discovered) {
if (!this._discoveredDomains) {
return nothing;
}
// Render discovered and existing entries together sorted by localized title.
const entries: Array<[string, string]> = this._entries.map((entry) => [
entry.domain,
domainToName(this.hass.localize, entry.domain),
]);
const discovered: Array<[string, string]> = this._discovered.map((flow) => [
flow.handler,
domainToName(this.hass.localize, flow.handler),
]);
let domains = [...entries, ...discovered].sort((a, b) =>
let uniqueDomains: Set<string> = new Set();
this._entries.forEach((entry) => {
uniqueDomains.add(entry.domain);
});
uniqueDomains = new Set([...uniqueDomains, ...this._discoveredDomains]);
let domains: Array<[string, string]> = [];
for (const domain of uniqueDomains.values()) {
domains.push([domain, domainToName(this.hass.localize, domain)]);
}
domains = domains.sort((a, b) =>
stringCompare(a[0], b[0], this.hass.locale.language)
);
const foundDevices = domains.length;
const foundIntegrations = domains.length;
if (domains.length > 12) {
const uniqueDomains: Set<string> = new Set();
domains.forEach(([domain]) => {
uniqueDomains.add(domain);
});
if (uniqueDomains.size < domains.length) {
domains = domains.filter(([domain]) => {
if (uniqueDomains.has(domain)) {
uniqueDomains.delete(domain);
return true;
}
return false;
});
}
if (domains.length > 12) {
domains = domains.slice(0, 11);
}
domains = domains.slice(0, 11);
}
return html`
@@ -152,11 +139,11 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) {
.darkOptimizedIcon=${this.hass.themes?.darkMode}
></integration-badge>`
)}
${foundDevices > domains.length
${foundIntegrations > domains.length
? html`<div class="more">
${this.onboardingLocalize(
"ui.panel.page-onboarding.integration.more_integrations",
{ count: foundDevices - domains.length }
{ count: foundIntegrations - domains.length }
)}
</div>`
: nothing}
@@ -110,16 +110,14 @@ class HaConfigSectionStorage extends LitElement {
></ha-metric>
${this._hostInfo.disk_life_time !== "" &&
this._hostInfo.disk_life_time >= 10
? html`
? // prettier-ignore
html`
<ha-metric
.heading=${this.hass.localize(
"ui.panel.config.storage.emmc_lifetime_used"
)}
.value=${this._hostInfo.disk_life_time}
.tooltip=${`${
this._hostInfo.disk_life_time - 10
} % -
${this._hostInfo.disk_life_time} %`}
.tooltip=${`${this._hostInfo.disk_life_time - 10}% - ${this._hostInfo.disk_life_time}%`}
class="emmc"
></ha-metric>
`
@@ -14,6 +14,7 @@ import { AssistPipeline } from "../../../../data/assist_pipeline";
import { HomeAssistant } from "../../../../types";
import { fetchWakeWordInfo, WakeWord } from "../../../../data/wake_word";
import { documentationUrl } from "../../../../util/documentation-url";
import { fireEvent } from "../../../../common/dom/fire_event";
@customElement("assist-pipeline-detail-wakeword")
export class AssistPipelineDetailWakeWord extends LitElement {
@@ -71,6 +72,11 @@ export class AssistPipelineDetailWakeWord extends LitElement {
changedProps.has("data") &&
changedProps.get("data")?.wake_word_entity !== this.data?.wake_word_entity
) {
if (this.data?.wake_word_id) {
fireEvent(this, "value-changed", {
value: { ...this.data, wake_word_id: undefined },
});
}
this._fetchWakeWords();
}
}
@@ -122,13 +128,25 @@ export class AssistPipelineDetailWakeWord extends LitElement {
}
private async _fetchWakeWords() {
this._wakeWords = undefined;
if (!this.data?.wake_word_entity) {
this._wakeWords = undefined;
return;
}
this._wakeWords = (
await fetchWakeWordInfo(this.hass, this.data.wake_word_entity)
).wake_words;
const wakeWordEntity = this.data.wake_word_entity;
const wakewordInfo = await fetchWakeWordInfo(this.hass, wakeWordEntity);
if (this.data.wake_word_entity !== wakeWordEntity) {
// wake word entity changed while we were fetching
return;
}
this._wakeWords = wakewordInfo.wake_words;
if (
!this.data?.wake_word_id ||
!this._wakeWords.some((ww) => ww.id === this.data!.wake_word_id)
) {
fireEvent(this, "value-changed", {
value: { ...this.data, wake_word_id: this._wakeWords[0]?.id },
});
}
}
static get styles(): CSSResultGroup {
@@ -246,7 +246,10 @@ export class AssistPipelineRunDebug extends LitElement {
const url = updatedRun.tts!.tts_output!.url;
const audio = new Audio(url);
audio.addEventListener("ended", () => {
if (this._continueConversationCheckbox.checked) {
if (
this.isConnected &&
this._continueConversationCheckbox.checked
) {
this._runAudioWakeWordPipeline();
} else {
this._finished = true;
@@ -320,7 +323,10 @@ export class AssistPipelineRunDebug extends LitElement {
const url = updatedRun.tts!.tts_output!.url;
const audio = new Audio(url);
audio.addEventListener("ended", () => {
if (this._continueConversationCheckbox.checked) {
if (
this.isConnected &&
this._continueConversationCheckbox.checked
) {
this._runAudioPipeline();
} else {
this._finished = true;
@@ -1,5 +1,6 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { NARROW_MEDIA_QUERY } from "../../../common/util/media_query";
import {
EnergyPreferences,
getEnergyPreferences,
@@ -10,7 +11,6 @@ import {
LovelaceViewConfig,
} from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { LovelaceStrategyParams } from "../../lovelace/strategies/types";
const setupWizard = async (): Promise<LovelaceViewConfig> => {
await import("../cards/energy-setup-wizard-card");
@@ -32,8 +32,7 @@ export interface EnergeryViewStrategyConfig extends LovelaceStrategyConfig {
export class EnergyViewStrategy extends ReactiveElement {
static async generate(
config: EnergeryViewStrategyConfig,
hass: HomeAssistant,
params: LovelaceStrategyParams
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const view: LovelaceViewConfig = { cards: [] };
@@ -67,11 +66,24 @@ export class EnergyViewStrategy extends ReactiveElement {
(source) => source.type === "water"
);
if (params.narrow || config.show_date_selection) {
const dateSelectionCard = {
type: "energy-date-selection",
collection_key: "energy_dashboard",
view_layout: { position: "sidebar" },
};
if (config.show_date_selection) {
view.cards!.push(dateSelectionCard);
} else {
view.cards!.push({
type: "energy-date-selection",
collection_key: "energy_dashboard",
view_layout: { position: "sidebar" },
type: "conditional",
conditions: [
{
condition: "screen",
media_query: NARROW_MEDIA_QUERY,
},
],
card: dateSelectionCard,
});
}
@@ -0,0 +1,7 @@
import { mdiResponsive, mdiStateMachine } from "@mdi/js";
import { Condition } from "./validate-condition";
export const ICON_CONDITION: Record<Condition["condition"], string> = {
state: mdiStateMachine,
screen: mdiResponsive,
};
@@ -1,10 +1,44 @@
import { UNAVAILABLE } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
export interface Condition {
entity: string;
export type Condition = StateCondition | ScreenCondition;
export type LegacyCondition = {
entity?: string;
state?: string;
state_not?: string;
};
export type StateCondition = {
condition: "state";
entity?: string;
state?: string;
state_not?: string;
};
export type ScreenCondition = {
condition: "screen";
media_query?: string;
};
function checkStateCondition(condition: StateCondition, hass: HomeAssistant) {
const state =
condition.entity && hass.states[condition.entity]
? hass.states[condition.entity].state
: UNAVAILABLE;
return condition.state != null
? state === condition.state
: state !== condition.state_not;
}
function checkScreenCondition(
condition: ScreenCondition,
_hass: HomeAssistant
) {
return condition.media_query
? matchMedia(condition.media_query).matches
: false;
}
export function checkConditionsMet(
@@ -12,18 +46,30 @@ export function checkConditionsMet(
hass: HomeAssistant
): boolean {
return conditions.every((c) => {
const state = hass.states[c.entity]
? hass!.states[c.entity].state
: UNAVAILABLE;
if (c.condition === "screen") {
return checkScreenCondition(c, hass);
}
return c.state != null ? state === c.state : state !== c.state_not;
return checkStateCondition(c, hass);
});
}
export function validateConditionalConfig(conditions: Condition[]): boolean {
return conditions.every(
(c) =>
(c.entity &&
(c.state != null || c.state_not != null)) as unknown as boolean
function valideStateCondition(condition: StateCondition) {
return (
condition.entity != null &&
(condition.state != null || condition.state_not != null)
);
}
function validateScreenCondition(condition: ScreenCondition) {
return condition.media_query != null;
}
export function validateConditionalConfig(conditions: Condition[]): boolean {
return conditions.every((c) => {
if (c.condition === "screen") {
return validateScreenCondition(c);
}
return valideStateCondition(c);
});
}
@@ -3,11 +3,14 @@ import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../../../types";
import { ConditionalCardConfig } from "../cards/types";
import {
ScreenCondition,
checkConditionsMet,
validateConditionalConfig,
} from "../common/validate-condition";
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
import { LovelaceCard } from "../types";
import { listenMediaQuery } from "../../../common/dom/media_query";
import { deepEqual } from "../../../common/util/deep-equal";
@customElement("hui-conditional-base")
export class HuiConditionalBase extends ReactiveElement {
@@ -21,6 +24,10 @@ export class HuiConditionalBase extends ReactiveElement {
protected _element?: LovelaceCard | LovelaceRow;
private _mediaQueriesListeners: Array<() => void> = [];
private _mediaQueries: string[] = [];
protected createRenderRoot() {
return this;
}
@@ -47,27 +54,98 @@ export class HuiConditionalBase extends ReactiveElement {
this._config = config;
}
public disconnectedCallback() {
super.disconnectedCallback();
this._clearMediaQueries();
}
public connectedCallback() {
super.connectedCallback();
this._listenMediaQueries();
this._updateVisibility();
}
private _clearMediaQueries() {
this._mediaQueries = [];
while (this._mediaQueriesListeners.length) {
this._mediaQueriesListeners.pop()!();
}
}
private _listenMediaQueries() {
if (!this._config) {
return;
}
const conditions = this._config.conditions.filter(
(c) => c.condition === "screen"
) as ScreenCondition[];
const mediaQueries = conditions
.filter((c) => c.media_query)
.map((c) => c.media_query as string);
if (deepEqual(mediaQueries, this._mediaQueries)) return;
this._mediaQueries = mediaQueries;
while (this._mediaQueriesListeners.length) {
this._mediaQueriesListeners.pop()!();
}
mediaQueries.forEach((query) => {
const listener = listenMediaQuery(query, (matches) => {
// For performance, if there is only one condition, set the visibility directly
if (this._config!.conditions.length === 1) {
this._setVisibility(matches);
return;
}
this._updateVisibility();
});
this._mediaQueriesListeners.push(listener);
});
}
protected update(changed: PropertyValues): void {
super.update(changed);
if (
changed.has("_element") ||
changed.has("_config") ||
changed.has("hass")
) {
this._listenMediaQueries();
this._updateVisibility();
}
}
private _updateVisibility() {
if (!this._element || !this.hass || !this._config) {
return;
}
this._element.editMode = this.editMode;
const visible =
this.editMode || checkConditionsMet(this._config.conditions, this.hass);
this.hidden = !visible;
const conditionMet = checkConditionsMet(
this._config!.conditions,
this.hass!
);
this._setVisibility(conditionMet);
}
private _setVisibility(conditionMet: boolean) {
if (!this._element || !this.hass) {
return;
}
const visible = this.editMode || conditionMet;
this.hidden = !visible;
this.style.setProperty("display", visible ? "" : "none");
if (visible) {
this._element.hass = this.hass;
if (!this._element.parentElement) {
this.appendChild(this._element);
if (!this._element!.parentElement) {
this.appendChild(this._element!);
}
} else if (this._element.parentElement) {
this.removeChild(this._element);
this.removeChild(this._element!);
}
}
}
@@ -0,0 +1,197 @@
import { preventDefault } from "@fullcalendar/core/internal";
import { ActionDetail } from "@material/mwc-list";
import { mdiCheck, mdiDelete, mdiDotsVertical } from "@mdi/js";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-list-item";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-yaml-editor";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { Condition, LegacyCondition } from "../../common/validate-condition";
import type { LovelaceConditionEditorConstructor } from "./types";
import { ICON_CONDITION } from "../../common/icon-condition";
@customElement("ha-card-condition-editor")
export default class HaCardConditionEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) condition!: Condition | LegacyCondition;
@state() public _yamlMode = false;
protected render() {
const condition: Condition = {
condition: "state",
...this.condition,
};
const element = customElements.get(
`ha-card-condition-${condition.condition}`
) as LovelaceConditionEditorConstructor | undefined;
const supported = element !== undefined;
const valid =
element &&
(!element.validateUIConfig || element.validateUIConfig(condition));
const yamlMode = this._yamlMode || !supported || !valid;
return html`
<div class="header">
<ha-svg-icon
class="icon"
.path=${ICON_CONDITION[condition.condition]}
></ha-svg-icon>
<span class="title">
${this.hass.localize(
`ui.panel.lovelace.editor.card.conditional.condition.${condition.condition}.label`
) || condition.condition}
</span>
<ha-button-menu
slot="icons"
@action=${this._handleAction}
@click=${preventDefault}
@closed=${stopPropagation}
fixed
.corner=${"BOTTOM_END"}
.menuCorner=${"END"}
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
>
</ha-icon-button>
<ha-list-item graphic="icon" .disabled=${!supported || !valid}>
${this.hass.localize("ui.panel.lovelace.editor.edit_card.edit_ui")}
${!yamlMode
? html`
<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>
`
: ``}
</ha-list-item>
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.edit_yaml"
)}
${yamlMode
? html`
<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>
`
: ``}
</ha-list-item>
<li divider role="separator"></li>
<ha-list-item class="warning" graphic="icon">
${this.hass!.localize("ui.common.delete")}
<ha-svg-icon
class="warning"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
</div>
${!valid
? html`
<ha-alert alert-type="warning">
${this.hass.localize("ui.errors.config.editor_not_supported")}
</ha-alert>
`
: nothing}
<div class="content">
${yamlMode
? html`
<ha-yaml-editor
.hass=${this.hass}
.defaultValue=${this.condition}
@value-changed=${this._onYamlChange}
></ha-yaml-editor>
`
: html`
${dynamicElement(`ha-card-condition-${condition.condition}`, {
hass: this.hass,
condition: condition,
})}
`}
</div>
`;
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._yamlMode = false;
break;
case 1:
this._yamlMode = true;
break;
case 2:
this._delete();
break;
}
}
private _delete() {
fireEvent(this, "value-changed", { value: null });
}
private _onYamlChange(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.isValid) {
return;
}
// @ts-ignore
fireEvent(this, "value-changed", { value: ev.detail.value });
}
static styles = [
haStyle,
css`
.header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.header span {
flex: 1;
font-size: 16px;
}
.content {
padding: 12px;
}
.header .icon {
padding: 12px;
}
.selected_menu_item {
color: var(--primary-color);
}
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-card-condition-editor": HaCardConditionEditor;
}
}
@@ -0,0 +1,6 @@
import { Condition } from "../../common/validate-condition";
export interface LovelaceConditionEditorConstructor {
defaultConfig?: Condition;
validateUIConfig?: (condition: Condition) => boolean;
}
@@ -0,0 +1,196 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { getAllCombinations } from "../../../../../common/array/combinations";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { LocalizeFunc } from "../../../../../common/translations/localize";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types";
import { HaFormSchema } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../../types";
import { ScreenCondition } from "../../../common/validate-condition";
const BREAKPOINT_VALUES = [0, 768, 1024, 1280, Infinity];
const BREAKPOINTS = ["mobile", "tablet", "desktop", "wide"] as const;
type BreakpointSize = [number, number];
type Breakpoint = (typeof BREAKPOINTS)[number];
function mergeConsecutiveRanges(arr: [number, number][]): [number, number][] {
if (arr.length === 0) {
return [];
}
[...arr].sort((a, b) => a[0] - b[0]);
const mergedRanges = [arr[0]];
for (let i = 1; i < arr.length; i++) {
const currentRange = arr[i];
const previousRange = mergedRanges[mergedRanges.length - 1];
if (currentRange[0] <= previousRange[1] + 1) {
previousRange[1] = currentRange[1];
} else {
mergedRanges.push(currentRange);
}
}
return mergedRanges;
}
function buildMediaQuery(size: BreakpointSize) {
const [min, max] = size;
const query: string[] = [];
if (min != null) {
query.push(`(min-width: ${min}px)`);
}
if (max != null && max !== Infinity) {
query.push(`(max-width: ${max - 1}px)`);
}
return query.join(" and ");
}
function computeBreakpointsSize(breakpoints: Breakpoint[]) {
const sizes = breakpoints.map<BreakpointSize>((breakpoint) => {
const index = BREAKPOINTS.indexOf(breakpoint);
return [BREAKPOINT_VALUES[index], BREAKPOINT_VALUES[index + 1] || Infinity];
});
const mergedSizes = mergeConsecutiveRanges(sizes);
const queries = mergedSizes
.map((size) => buildMediaQuery(size))
.filter((size) => size);
return queries.join(", ");
}
function computeBreakpointsKey(breakpoints) {
return [...breakpoints].sort().join("_");
}
// Compute all possible media queries from each breakpoints combination (2 ^ breakpoints = 16)
const queries = getAllCombinations(BREAKPOINTS as unknown as Breakpoint[])
.filter((arr) => arr.length !== 0)
.map(
(breakpoints) =>
[breakpoints, computeBreakpointsSize(breakpoints)] as [
Breakpoint[],
string,
]
);
// Store them in maps to avoid recomputing them
const mediaQueryMap = new Map(
queries.map(([b, m]) => [computeBreakpointsKey(b), m])
);
const mediaQueryReverseMap = new Map(queries.map(([b, m]) => [m, b]));
type ScreenConditionData = {
breakpoints: Breakpoint[];
};
@customElement("ha-card-condition-screen")
export class HaCardConditionScreen extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public condition!: ScreenCondition;
@property({ type: Boolean }) public disabled = false;
public static get defaultConfig(): ScreenCondition {
return { condition: "screen", media_query: "" };
}
protected static validateUIConfig(condition: ScreenCondition) {
return (
!condition.media_query || mediaQueryReverseMap.get(condition.media_query)
);
}
private _schema = memoizeOne(
(localize: LocalizeFunc) =>
[
{
name: "breakpoints",
selector: {
select: {
mode: "list",
options: BREAKPOINTS.map((b) => {
const value = BREAKPOINT_VALUES[BREAKPOINTS.indexOf(b)];
return {
value: b,
label: `${localize(
`ui.panel.lovelace.editor.card.conditional.condition.screen.breakpoints_list.${b}`
)}${
value
? ` (${localize(
`ui.panel.lovelace.editor.card.conditional.condition.screen.min`,
{ size: value }
)})`
: ""
}`,
};
}),
multiple: true,
},
},
},
] as const satisfies readonly HaFormSchema[]
);
protected render() {
const breakpoints = this.condition.media_query
? mediaQueryReverseMap.get(this.condition.media_query)
: undefined;
const data: ScreenConditionData = {
breakpoints: breakpoints ?? [],
};
return html`
<ha-form
.hass=${this.hass}
.data=${data}
.schema=${this._schema(this.hass.localize)}
.disabled=${this.disabled}
@value-changed=${this._valueChanged}
.computeLabel=${this._computeLabelCallback}
></ha-form>
`;
}
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const data = ev.detail.value as ScreenConditionData;
const { breakpoints } = data;
const condition: ScreenCondition = {
condition: "screen",
media_query: mediaQueryMap.get(computeBreakpointsKey(breakpoints)) ?? "",
};
fireEvent(this, "value-changed", { value: condition });
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string => {
switch (schema.name) {
case "breakpoints":
return this.hass.localize(
`ui.panel.lovelace.editor.card.conditional.condition.screen.${schema.name}`
);
default:
return "";
}
};
}
declare global {
interface HTMLElementTagNameMap {
"ha-card-condition-screen": HaCardConditionScreen;
}
}
@@ -0,0 +1,164 @@
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { assert, literal, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { LocalizeFunc } from "../../../../../common/translations/localize";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types";
import { HaFormSchema } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../../types";
import { StateCondition } from "../../../common/validate-condition";
const stateConditionStruct = object({
condition: literal("state"),
entity: string(),
state: optional(string()),
state_not: optional(string()),
});
type StateConditionData = {
condition: "state";
entity: string;
invert: "true" | "false";
state?: string;
};
@customElement("ha-card-condition-state")
export class HaCardConditionState extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public condition!: StateCondition;
@property({ type: Boolean }) public disabled = false;
public static get defaultConfig(): StateCondition {
return { condition: "state", entity: "", state: "" };
}
protected willUpdate(changedProperties: PropertyValues): void {
if (!changedProperties.has("condition")) {
return;
}
try {
assert(this.condition, stateConditionStruct);
} catch (err: any) {
fireEvent(this, "ui-mode-not-available", err);
}
}
private _schema = memoizeOne(
(localize: LocalizeFunc) =>
[
{ name: "entity", selector: { entity: {} } },
{
name: "",
type: "grid",
schema: [
{
name: "invert",
selector: {
select: {
mode: "dropdown",
options: [
{
label: localize(
"ui.panel.lovelace.editor.card.conditional.state_equal"
),
value: "false",
},
{
label: localize(
"ui.panel.lovelace.editor.card.conditional.state_not_equal"
),
value: "true",
},
],
},
},
},
{
name: "state",
selector: {
state: {},
},
context: {
filter_entity: "entity",
},
},
],
},
] as const satisfies readonly HaFormSchema[]
);
protected render() {
const { state, state_not, ...content } = this.condition;
const data: StateConditionData = {
...content,
entity: this.condition.entity ?? "",
invert: this.condition.state_not ? "true" : "false",
state: this.condition.state_not ?? this.condition.state ?? "",
};
return html`
<ha-form
.hass=${this.hass}
.data=${data}
.schema=${this._schema(this.hass.localize)}
.disabled=${this.disabled}
@value-changed=${this._valueChanged}
.computeLabel=${this._computeLabelCallback}
></ha-form>
`;
}
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const data = ev.detail.value as StateConditionData;
const { invert, state, entity, condition: _, ...content } = data;
const condition: StateCondition = {
condition: "state",
...content,
entity: entity ?? "",
state: invert === "false" ? state ?? "" : undefined,
state_not: invert === "true" ? state ?? "" : undefined,
};
fireEvent(this, "value-changed", { value: condition });
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string => {
const entity = this.condition.entity
? this.hass.states[this.condition.entity]
: undefined;
switch (schema.name) {
case "entity":
return this.hass.localize("ui.components.entity.entity-picker.entity");
case "state":
if (entity) {
return `${this.hass.localize(
"ui.components.entity.entity-state-picker.state"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.conditional.current_state"
)}: ${this.hass.formatEntityState(entity)})`;
}
return `${this.hass.localize(
"ui.components.entity.entity-state-picker.state"
)}`;
default:
return "";
}
};
}
declare global {
interface HTMLElementTagNameMap {
"ha-card-condition-state": HaCardConditionState;
}
}
@@ -1,52 +1,56 @@
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
import { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js";
import deepClone from "deep-clone-simple";
import type { MDCTabBarActivatedEvent } from "@material/tab-bar";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import {
any,
array,
assert,
assign,
object,
optional,
string,
} from "superstruct";
mdiCodeBraces,
mdiContentCopy,
mdiListBoxOutline,
mdiPlus,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { any, array, assert, assign, object, optional } from "superstruct";
import { storage } from "../../../../common/decorators/storage";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-select";
import "../../../../components/ha-textfield";
import "../../../../components/ha-button";
import "../../../../components/ha-list-item";
import "../../../../components/ha-menu-button";
import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-svg-icon";
import type {
LovelaceCardConfig,
LovelaceConfig,
} from "../../../../data/lovelace";
import type { HomeAssistant } from "../../../../types";
import type { ConditionalCardConfig } from "../../cards/types";
import { ICON_CONDITION } from "../../common/icon-condition";
import { Condition } from "../../common/validate-condition";
import type { LovelaceCardEditor } from "../../types";
import "../card-editor/hui-card-element-editor";
import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor";
import "../card-editor/hui-card-picker";
import "../conditions/ha-card-condition-editor";
import { LovelaceConditionEditorConstructor } from "../conditions/types";
import "../conditions/types/ha-card-condition-screen";
import "../conditions/types/ha-card-condition-state";
import "../hui-element-editor";
import type { ConfigChangedEvent } from "../hui-element-editor";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import type { GUIModeChangedEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
const conditionStruct = object({
entity: string(),
state: optional(string()),
state_not: optional(string()),
});
const UI_CONDITION = [
"state",
"screen",
] as const satisfies readonly Condition["condition"][];
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
card: any(),
conditions: optional(array(conditionStruct)),
conditions: optional(array(any())),
})
);
@@ -127,7 +131,6 @@ export class HuiConditionalCardEditor
)}
.path=${isGuiMode ? mdiCodeBraces : mdiListBoxOutline}
></ha-icon-button>
<ha-icon-button
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.copy"
@@ -166,61 +169,44 @@ export class HuiConditionalCardEditor
${this._config.conditions.map(
(cond, idx) => html`
<div class="condition">
<div class="entity">
<ha-entity-picker
.hass=${this.hass}
.value=${cond.entity}
.idx=${idx}
.configValue=${"entity"}
@change=${this._changeCondition}
allow-custom-entity
></ha-entity-picker>
</div>
<div class="state">
<ha-select
.value=${cond.state_not !== undefined
? "true"
: "false"}
.idx=${idx}
.configValue=${"invert"}
@selected=${this._changeCondition}
@closed=${stopPropagation}
naturalMenuWidth
fixedMenuPosition
>
<mwc-list-item value="false">
${this.hass!.localize(
"ui.panel.lovelace.editor.card.conditional.state_equal"
)}
</mwc-list-item>
<mwc-list-item value="true">
${this.hass!.localize(
"ui.panel.lovelace.editor.card.conditional.state_not_equal"
)}
</mwc-list-item>
</ha-select>
<ha-textfield
.label="${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.state"
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.conditional.current_state"
)}: ${this.hass?.states[cond.entity].state})"
.value=${cond.state_not !== undefined
? cond.state_not
: cond.state}
.idx=${idx}
.configValue=${"state"}
@input=${this._changeCondition}
></ha-textfield>
</div>
<ha-card-condition-editor
.index=${idx}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
.condition=${cond}
></ha-card-condition-editor>
</div>
`
)}
<div class="condition">
<ha-entity-picker
.hass=${this.hass}
@change=${this._addCondition}
></ha-entity-picker>
<div>
<ha-button-menu
@action=${this._addCondition}
fixed
@closed=${stopPropagation}
>
<ha-button
slot="trigger"
outlined
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.conditional.add_condition"
)}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
${UI_CONDITION.map(
(condition) => html`
<ha-list-item .value=${condition} graphic="icon">
${this.hass!.localize(
`ui.panel.lovelace.editor.card.conditional.condition.${condition}.label`
) || condition}
<ha-svg-icon
slot="graphic"
.path=${ICON_CONDITION[condition]}
></ha-svg-icon>
</ha-list-item>
`
)}
</ha-button-menu>
</div>
</div>
`}
@@ -289,53 +275,40 @@ export class HuiConditionalCardEditor
fireEvent(this, "config-changed", { config: this._config });
}
private _addCondition(ev: Event): void {
const target = ev.target! as any;
if (target.value === "" || !this._config) {
private _addCondition(ev: CustomEvent): void {
const condition = (ev.currentTarget as HaSelect).items[ev.detail.index]
.value as Condition["condition"];
if (!this._config) {
return;
}
const conditions = [...this._config.conditions];
conditions.push({
entity: target.value,
state: "",
});
const elClass = customElements.get(`ha-card-condition-${condition}`) as
| LovelaceConditionEditorConstructor
| undefined;
conditions.push(
elClass?.defaultConfig
? { ...elClass.defaultConfig }
: { condition: condition }
);
this._config = { ...this._config, conditions };
target.value = "";
fireEvent(this, "config-changed", { config: this._config });
}
private _changeCondition(ev: Event): void {
const target = ev.target as any;
if (!this._config || !target) {
return;
}
const conditions = [...this._config.conditions];
if (target.configValue === "entity" && target.value === "") {
conditions.splice(target.idx, 1);
private _conditionChanged(ev: CustomEvent) {
ev.stopPropagation();
const conditions = [...this._config!.conditions];
const newValue = ev.detail.value;
const index = (ev.target as any).index;
if (newValue === null) {
conditions.splice(index, 1);
} else {
const condition = { ...conditions[target.idx] };
if (target.configValue === "entity") {
condition.entity = target.value;
} else if (target.configValue === "state") {
if (condition.state_not !== undefined) {
condition.state_not = target.value;
} else {
condition.state = target.value;
}
} else if (target.configValue === "invert") {
if (target.value === "true") {
if (condition.state) {
condition.state_not = condition.state;
delete condition.state;
}
} else if (condition.state_not) {
condition.state = condition.state_not;
delete condition.state_not;
}
}
conditions[target.idx] = condition;
conditions[index] = newValue;
}
this._config = { ...this._config, conditions };
this._config = { ...this._config!, conditions };
fireEvent(this, "config-changed", { config: this._config });
}
@@ -352,22 +325,13 @@ export class HuiConditionalCardEditor
.condition {
margin-top: 8px;
border: 1px solid var(--divider-color);
}
.condition .content {
padding: 12px;
}
.condition .state {
display: flex;
align-items: flex-end;
ha-button-menu {
margin-top: 12px;
}
.condition .state ha-select {
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
direction: var(--direction);
}
.condition .state ha-textfield {
flex-grow: 1;
}
.card {
margin-top: 8px;
border: 1px solid var(--divider-color);
@@ -174,9 +174,7 @@ export class HuiSaveConfig extends LitElement implements HassDialog {
await lovelace.saveConfig(
this._emptyConfig
? EMPTY_CONFIG
: await expandLovelaceConfigStrategies(lovelace.config, this.hass, {
narrow: this._params.narrow,
})
: await expandLovelaceConfigStrategies(lovelace.config, this.hass)
);
lovelace.setEditMode(true);
this._saving = false;
+5 -10
View File
@@ -167,8 +167,7 @@ export class LovelacePanel extends LitElement {
{
type: DEFAULT_STRATEGY,
},
this.hass!,
{ narrow: this.narrow }
this.hass!
);
this._setLovelaceConfig(conf, undefined, "generated");
this._panelState = "loaded";
@@ -258,8 +257,7 @@ export class LovelacePanel extends LitElement {
if (rawConf.strategy) {
conf = await generateLovelaceDashboardStrategy(
rawConf.strategy,
this.hass!,
{ narrow: this.narrow }
this.hass!
);
} else {
conf = rawConf;
@@ -276,8 +274,7 @@ export class LovelacePanel extends LitElement {
{
type: DEFAULT_STRATEGY,
},
this.hass!,
{ narrow: this.narrow }
this.hass!
);
confMode = "generated";
} finally {
@@ -365,8 +362,7 @@ export class LovelacePanel extends LitElement {
if (newConfig.strategy) {
conf = await generateLovelaceDashboardStrategy(
newConfig.strategy,
this.hass!,
{ narrow: this.narrow }
this.hass!
);
} else {
conf = newConfig;
@@ -404,8 +400,7 @@ export class LovelacePanel extends LitElement {
{
type: DEFAULT_STRATEGY,
},
this.hass!,
{ narrow: this.narrow }
this.hass!
);
this._updateLovelace({
config: generatedConf,
+11 -20
View File
@@ -8,7 +8,6 @@ import { isLegacyStrategy } from "./legacy-strategy";
import {
LovelaceDashboardStrategy,
LovelaceStrategy,
LovelaceStrategyParams,
LovelaceViewStrategy,
} from "./types";
@@ -77,8 +76,7 @@ const generateStrategy = async <T extends LovelaceStrategyConfigType>(
configType: T,
renderError: (err: string | Error) => StrategyConfig<T>,
strategyConfig: LovelaceStrategyConfig,
hass: HomeAssistant,
params: LovelaceStrategyParams
hass: HomeAssistant
): Promise<StrategyConfig<T>> => {
const strategyType = strategyConfig.type;
if (!strategyType) {
@@ -95,7 +93,7 @@ const generateStrategy = async <T extends LovelaceStrategyConfigType>(
return (await strategy.generateDashboard({
config: { strategy: strategyConfig, views: [] },
hass,
narrow: params.narrow,
narrow: undefined,
})) as StrategyConfig<T>;
}
if (configType === "view" && "generateView" in strategy) {
@@ -103,7 +101,7 @@ const generateStrategy = async <T extends LovelaceStrategyConfigType>(
config: { views: [] },
view: { strategy: strategyConfig },
hass,
narrow: params.narrow,
narrow: undefined,
})) as StrategyConfig<T>;
}
}
@@ -115,7 +113,7 @@ const generateStrategy = async <T extends LovelaceStrategyConfigType>(
delete config.options;
return await strategy.generate(config, hass, params);
return await strategy.generate(config, hass);
} catch (err: any) {
if (err.message !== "timeout") {
// eslint-disable-next-line
@@ -128,8 +126,7 @@ const generateStrategy = async <T extends LovelaceStrategyConfigType>(
export const generateLovelaceDashboardStrategy = async (
strategyConfig: LovelaceStrategyConfig,
hass: HomeAssistant,
params: LovelaceStrategyParams
hass: HomeAssistant
): Promise<LovelaceConfig> =>
generateStrategy(
"dashboard",
@@ -147,14 +144,12 @@ export const generateLovelaceDashboardStrategy = async (
],
}),
strategyConfig,
hass,
params
hass
);
export const generateLovelaceViewStrategy = async (
strategyConfig: LovelaceStrategyConfig,
hass: HomeAssistant,
params: LovelaceStrategyParams
hass: HomeAssistant
): Promise<LovelaceViewConfig> =>
generateStrategy(
"view",
@@ -167,8 +162,7 @@ export const generateLovelaceViewStrategy = async (
],
}),
strategyConfig,
hass,
params
hass
);
/**
@@ -176,18 +170,15 @@ export const generateLovelaceViewStrategy = async (
*/
export const expandLovelaceConfigStrategies = async (
config: LovelaceConfig,
hass: HomeAssistant,
params: LovelaceStrategyParams
hass: HomeAssistant
): Promise<LovelaceConfig> => {
const newConfig = config.strategy
? await generateLovelaceDashboardStrategy(config.strategy, hass, params)
? await generateLovelaceDashboardStrategy(config.strategy, hass)
: { ...config };
newConfig.views = await Promise.all(
newConfig.views.map((view) =>
view.strategy
? generateLovelaceViewStrategy(view.strategy, hass, params)
: view
view.strategy ? generateLovelaceViewStrategy(view.strategy, hass) : view
)
);
@@ -2,14 +2,12 @@ import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { LovelaceConfig, LovelaceStrategyConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { LovelaceStrategyParams } from "./types";
@customElement("original-states-dashboard-strategy")
export class OriginalStatesDashboardStrategy extends ReactiveElement {
static async generate(
_config: LovelaceStrategyConfig,
hass: HomeAssistant,
_params?: LovelaceStrategyParams
hass: HomeAssistant
): Promise<LovelaceConfig> {
return {
title: hass.config.location_name,
@@ -9,14 +9,12 @@ import {
} from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { generateDefaultViewConfig } from "../common/generate-lovelace-config";
import { LovelaceStrategyParams } from "./types";
@customElement("original-states-view-strategy")
export class OriginalStatesViewStrategy extends ReactiveElement {
static async generate(
_config: LovelaceStrategyConfig,
hass: HomeAssistant,
_params?: LovelaceStrategyParams
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
if (hass.config.state === STATE_NOT_RUNNING) {
return {
+1 -9
View File
@@ -5,16 +5,8 @@ import {
} from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
export type LovelaceStrategyParams = {
narrow?: boolean;
};
export type LovelaceStrategy<T = any> = {
generate(
config: LovelaceStrategyConfig,
hass: HomeAssistant,
params?: LovelaceStrategyParams
): Promise<T>;
generate(config: LovelaceStrategyConfig, hass: HomeAssistant): Promise<T>;
};
export interface LovelaceDashboardStrategy
+1 -2
View File
@@ -192,8 +192,7 @@ export class HUIView extends ReactiveElement {
isStrategy = true;
viewConfig = await generateLovelaceViewStrategy(
viewConfig.strategy,
this.hass!,
{ narrow: this.narrow }
this.hass!
);
}
+1 -1
View File
@@ -1,5 +1,5 @@
import "@polymer/polymer/lib/elements/custom-style";
import { derivedStyles } from "./styles";
import { derivedStyles } from "./styles-data";
export const DEFAULT_PRIMARY_COLOR = "#03a9f4";
export const DEFAULT_ACCENT_COLOR = "#ff9800";
+155
View File
@@ -0,0 +1,155 @@
export const darkStyles = {
"primary-background-color": "#111111",
"card-background-color": "#1c1c1c",
"secondary-background-color": "#282828",
"clear-background-color": "#111111",
"primary-text-color": "#e1e1e1",
"secondary-text-color": "#9b9b9b",
"disabled-text-color": "#6f6f6f",
"app-header-text-color": "#e1e1e1",
"app-header-background-color": "#101e24",
"switch-unchecked-button-color": "#999999",
"switch-unchecked-track-color": "#9b9b9b",
"divider-color": "rgba(225, 225, 225, .12)",
"mdc-ripple-color": "#AAAAAA",
"mdc-linear-progress-buffer-color": "rgba(255, 255, 255, 0.1)",
"input-idle-line-color": "rgba(255, 255, 255, 0.42)",
"input-hover-line-color": "rgba(255, 255, 255, 0.87)",
"input-disabled-line-color": "rgba(255, 255, 255, 0.06)",
"input-outlined-idle-border-color": "rgba(255, 255, 255, 0.38)",
"input-outlined-hover-border-color": "rgba(255, 255, 255, 0.87)",
"input-outlined-disabled-border-color": "rgba(255, 255, 255, 0.06)",
"input-fill-color": "rgba(255, 255, 255, 0.05)",
"input-disabled-fill-color": "rgba(255, 255, 255, 0.02)",
"input-ink-color": "rgba(255, 255, 255, 0.87)",
"input-label-ink-color": "rgba(255, 255, 255, 0.6)",
"input-disabled-ink-color": "rgba(255, 255, 255, 0.37)",
"input-dropdown-icon-color": "rgba(255, 255, 255, 0.54)",
"codemirror-keyword": "#C792EA",
"codemirror-operator": "#89DDFF",
"codemirror-variable": "#f07178",
"codemirror-variable-2": "#EEFFFF",
"codemirror-variable-3": "#DECB6B",
"codemirror-builtin": "#FFCB6B",
"codemirror-atom": "#F78C6C",
"codemirror-number": "#FF5370",
"codemirror-def": "#82AAFF",
"codemirror-string": "#C3E88D",
"codemirror-string-2": "#f07178",
"codemirror-comment": "#545454",
"codemirror-tag": "#FF5370",
"codemirror-meta": "#FFCB6B",
"codemirror-attribute": "#C792EA",
"codemirror-property": "#C792EA",
"codemirror-qualifier": "#DECB6B",
"codemirror-type": "#DECB6B",
"energy-grid-return-color": "#a280db",
"map-filter":
"invert(.9) hue-rotate(170deg) brightness(1.5) contrast(1.2) saturate(.3)",
"disabled-color": "#464646",
};
export const derivedStyles = {
"state-icon-error-color": "var(--error-state-color, var(--error-color))",
"state-unavailable-color":
"var(--state-icon-unavailable-color, var(--disabled-text-color))",
"sidebar-text-color": "var(--primary-text-color)",
"sidebar-background-color": "var(--card-background-color)",
"sidebar-selected-text-color": "var(--primary-color)",
"sidebar-selected-icon-color": "var(--primary-color)",
"sidebar-icon-color": "rgba(var(--rgb-primary-text-color), 0.6)",
"switch-checked-color": "var(--primary-color)",
"switch-checked-button-color":
"var(--switch-checked-color, var(--primary-background-color))",
"switch-checked-track-color": "var(--switch-checked-color, #000000)",
"switch-unchecked-button-color":
"var(--switch-unchecked-color, var(--primary-background-color))",
"switch-unchecked-track-color": "var(--switch-unchecked-color, #000000)",
"slider-color": "var(--primary-color)",
"slider-secondary-color": "var(--light-primary-color)",
"slider-track-color": "var(--scrollbar-thumb-color)",
"label-badge-background-color": "var(--card-background-color)",
"label-badge-text-color": "rgba(var(--rgb-primary-text-color), 0.8)",
"paper-listbox-background-color": "var(--card-background-color)",
"paper-item-icon-color": "var(--state-icon-color)",
"paper-item-icon-active-color": "var(--state-icon-active-color)",
"table-header-background-color": "var(--input-fill-color)",
"table-row-background-color": "var(--primary-background-color)",
"table-row-alternative-background-color": "var(--secondary-background-color)",
"paper-slider-knob-color": "var(--slider-color)",
"paper-slider-knob-start-color": "var(--slider-color)",
"paper-slider-pin-color": "var(--slider-color)",
"paper-slider-pin-start-color": "var(--slider-color)",
"paper-slider-active-color": "var(--slider-color)",
"paper-slider-secondary-color": "var(--slider-secondary-color)",
"paper-slider-container-color": "var(--slider-track-color)",
"data-table-background-color": "var(--card-background-color)",
"markdown-code-background-color": "var(--primary-background-color)",
// https://github.com/material-components/material-web/blob/master/docs/theming.md
"mdc-theme-primary": "var(--primary-color)",
"mdc-theme-secondary": "var(--accent-color)",
"mdc-theme-background": "var(--primary-background-color)",
"mdc-theme-surface": "var(--card-background-color)",
"mdc-theme-on-primary": "var(--text-primary-color)",
"mdc-theme-on-secondary": "var(--text-primary-color)",
"mdc-theme-on-surface": "var(--primary-text-color)",
"mdc-theme-text-disabled-on-light": "var(--disabled-text-color)",
"mdc-theme-text-primary-on-background": "var(--primary-text-color)",
"mdc-theme-text-secondary-on-background": "var(--secondary-text-color)",
"mdc-theme-text-hint-on-background": "var(--secondary-text-color)",
"mdc-theme-text-icon-on-background": "var(--secondary-text-color)",
"mdc-theme-error": "var(--error-color)",
"app-header-text-color": "var(--text-primary-color)",
"app-header-background-color": "var(--primary-color)",
"mdc-checkbox-unchecked-color": "rgba(var(--rgb-primary-text-color), 0.54)",
"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-button-disabled-ink-color": "var(--disabled-text-color)",
"mdc-button-outline-color": "var(--divider-color)",
"mdc-dialog-scroll-divider-color": "var(--divider-color)",
"mdc-dialog-heading-ink-color": "var(--primary-text-color)",
"mdc-dialog-content-ink-color": "var(--primary-text-color)",
"mdc-text-field-idle-line-color": "var(--input-idle-line-color)",
"mdc-text-field-hover-line-color": "var(--input-hover-line-color)",
"mdc-text-field-disabled-line-color": "var(--input-disabled-line-color)",
"mdc-text-field-outlined-idle-border-color":
"var(--input-outlined-idle-border-color)",
"mdc-text-field-outlined-hover-border-color":
"var(--input-outlined-hover-border-color)",
"mdc-text-field-outlined-disabled-border-color":
"var(--input-outlined-disabled-border-color)",
"mdc-text-field-fill-color": "var(--input-fill-color)",
"mdc-text-field-disabled-fill-color": "var(--input-disabled-fill-color)",
"mdc-text-field-ink-color": "var(--input-ink-color)",
"mdc-text-field-label-ink-color": "var(--input-label-ink-color)",
"mdc-text-field-disabled-ink-color": "var(--input-disabled-ink-color)",
"mdc-select-idle-line-color": "var(--input-idle-line-color)",
"mdc-select-hover-line-color": "var(--input-hover-line-color)",
"mdc-select-outlined-idle-border-color":
"var(--input-outlined-idle-border-color)",
"mdc-select-outlined-hover-border-color":
"var(--input-outlined-hover-border-color)",
"mdc-select-outlined-disabled-border-color":
"var(--input-outlined-disabled-border-color)",
"mdc-select-fill-color": "var(--input-fill-color)",
"mdc-select-disabled-fill-color": "var(--input-disabled-fill-color)",
"mdc-select-ink-color": "var(--input-ink-color)",
"mdc-select-label-ink-color": "var(--input-label-ink-color)",
"mdc-select-disabled-ink-color": "var(--input-disabled-ink-color)",
"mdc-select-dropdown-icon-color": "var(--input-dropdown-icon-color)",
"mdc-select-disabled-dropdown-icon-color": "var(--input-disabled-ink-color)",
"chip-background-color": "rgba(var(--rgb-primary-text-color), 0.15)",
// Vaadin
"material-body-text-color": "var(--primary-text-color)",
"material-background-color": "var(--card-background-color)",
"material-secondary-background-color": "var(--secondary-background-color)",
"material-secondary-text-color": "var(--secondary-text-color)",
};
-156
View File
@@ -1,161 +1,5 @@
import { css } from "lit";
export const darkStyles = {
"primary-background-color": "#111111",
"card-background-color": "#1c1c1c",
"secondary-background-color": "#282828",
"clear-background-color": "#111111",
"primary-text-color": "#e1e1e1",
"secondary-text-color": "#9b9b9b",
"disabled-text-color": "#6f6f6f",
"app-header-text-color": "#e1e1e1",
"app-header-background-color": "#101e24",
"switch-unchecked-button-color": "#999999",
"switch-unchecked-track-color": "#9b9b9b",
"divider-color": "rgba(225, 225, 225, .12)",
"mdc-ripple-color": "#AAAAAA",
"mdc-linear-progress-buffer-color": "rgba(255, 255, 255, 0.1)",
"input-idle-line-color": "rgba(255, 255, 255, 0.42)",
"input-hover-line-color": "rgba(255, 255, 255, 0.87)",
"input-disabled-line-color": "rgba(255, 255, 255, 0.06)",
"input-outlined-idle-border-color": "rgba(255, 255, 255, 0.38)",
"input-outlined-hover-border-color": "rgba(255, 255, 255, 0.87)",
"input-outlined-disabled-border-color": "rgba(255, 255, 255, 0.06)",
"input-fill-color": "rgba(255, 255, 255, 0.05)",
"input-disabled-fill-color": "rgba(255, 255, 255, 0.02)",
"input-ink-color": "rgba(255, 255, 255, 0.87)",
"input-label-ink-color": "rgba(255, 255, 255, 0.6)",
"input-disabled-ink-color": "rgba(255, 255, 255, 0.37)",
"input-dropdown-icon-color": "rgba(255, 255, 255, 0.54)",
"codemirror-keyword": "#C792EA",
"codemirror-operator": "#89DDFF",
"codemirror-variable": "#f07178",
"codemirror-variable-2": "#EEFFFF",
"codemirror-variable-3": "#DECB6B",
"codemirror-builtin": "#FFCB6B",
"codemirror-atom": "#F78C6C",
"codemirror-number": "#FF5370",
"codemirror-def": "#82AAFF",
"codemirror-string": "#C3E88D",
"codemirror-string-2": "#f07178",
"codemirror-comment": "#545454",
"codemirror-tag": "#FF5370",
"codemirror-meta": "#FFCB6B",
"codemirror-attribute": "#C792EA",
"codemirror-property": "#C792EA",
"codemirror-qualifier": "#DECB6B",
"codemirror-type": "#DECB6B",
"energy-grid-return-color": "#a280db",
"map-filter":
"invert(.9) hue-rotate(170deg) brightness(1.5) contrast(1.2) saturate(.3)",
"disabled-color": "#464646",
};
export const derivedStyles = {
"state-icon-error-color": "var(--error-state-color, var(--error-color))",
"state-unavailable-color":
"var(--state-icon-unavailable-color, var(--disabled-text-color))",
"sidebar-text-color": "var(--primary-text-color)",
"sidebar-background-color": "var(--card-background-color)",
"sidebar-selected-text-color": "var(--primary-color)",
"sidebar-selected-icon-color": "var(--primary-color)",
"sidebar-icon-color": "rgba(var(--rgb-primary-text-color), 0.6)",
"switch-checked-color": "var(--primary-color)",
"switch-checked-button-color":
"var(--switch-checked-color, var(--primary-background-color))",
"switch-checked-track-color": "var(--switch-checked-color, #000000)",
"switch-unchecked-button-color":
"var(--switch-unchecked-color, var(--primary-background-color))",
"switch-unchecked-track-color": "var(--switch-unchecked-color, #000000)",
"slider-color": "var(--primary-color)",
"slider-secondary-color": "var(--light-primary-color)",
"slider-track-color": "var(--scrollbar-thumb-color)",
"label-badge-background-color": "var(--card-background-color)",
"label-badge-text-color": "rgba(var(--rgb-primary-text-color), 0.8)",
"paper-listbox-background-color": "var(--card-background-color)",
"paper-item-icon-color": "var(--state-icon-color)",
"paper-item-icon-active-color": "var(--state-icon-active-color)",
"table-header-background-color": "var(--input-fill-color)",
"table-row-background-color": "var(--primary-background-color)",
"table-row-alternative-background-color": "var(--secondary-background-color)",
"paper-slider-knob-color": "var(--slider-color)",
"paper-slider-knob-start-color": "var(--slider-color)",
"paper-slider-pin-color": "var(--slider-color)",
"paper-slider-pin-start-color": "var(--slider-color)",
"paper-slider-active-color": "var(--slider-color)",
"paper-slider-secondary-color": "var(--slider-secondary-color)",
"paper-slider-container-color": "var(--slider-track-color)",
"data-table-background-color": "var(--card-background-color)",
"markdown-code-background-color": "var(--primary-background-color)",
// https://github.com/material-components/material-web/blob/master/docs/theming.md
"mdc-theme-primary": "var(--primary-color)",
"mdc-theme-secondary": "var(--accent-color)",
"mdc-theme-background": "var(--primary-background-color)",
"mdc-theme-surface": "var(--card-background-color)",
"mdc-theme-on-primary": "var(--text-primary-color)",
"mdc-theme-on-secondary": "var(--text-primary-color)",
"mdc-theme-on-surface": "var(--primary-text-color)",
"mdc-theme-text-disabled-on-light": "var(--disabled-text-color)",
"mdc-theme-text-primary-on-background": "var(--primary-text-color)",
"mdc-theme-text-secondary-on-background": "var(--secondary-text-color)",
"mdc-theme-text-hint-on-background": "var(--secondary-text-color)",
"mdc-theme-text-icon-on-background": "var(--secondary-text-color)",
"mdc-theme-error": "var(--error-color)",
"app-header-text-color": "var(--text-primary-color)",
"app-header-background-color": "var(--primary-color)",
"mdc-checkbox-unchecked-color": "rgba(var(--rgb-primary-text-color), 0.54)",
"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-button-disabled-ink-color": "var(--disabled-text-color)",
"mdc-button-outline-color": "var(--divider-color)",
"mdc-dialog-scroll-divider-color": "var(--divider-color)",
"mdc-dialog-heading-ink-color": "var(--primary-text-color)",
"mdc-dialog-content-ink-color": "var(--primary-text-color)",
"mdc-text-field-idle-line-color": "var(--input-idle-line-color)",
"mdc-text-field-hover-line-color": "var(--input-hover-line-color)",
"mdc-text-field-disabled-line-color": "var(--input-disabled-line-color)",
"mdc-text-field-outlined-idle-border-color":
"var(--input-outlined-idle-border-color)",
"mdc-text-field-outlined-hover-border-color":
"var(--input-outlined-hover-border-color)",
"mdc-text-field-outlined-disabled-border-color":
"var(--input-outlined-disabled-border-color)",
"mdc-text-field-fill-color": "var(--input-fill-color)",
"mdc-text-field-disabled-fill-color": "var(--input-disabled-fill-color)",
"mdc-text-field-ink-color": "var(--input-ink-color)",
"mdc-text-field-label-ink-color": "var(--input-label-ink-color)",
"mdc-text-field-disabled-ink-color": "var(--input-disabled-ink-color)",
"mdc-select-idle-line-color": "var(--input-idle-line-color)",
"mdc-select-hover-line-color": "var(--input-hover-line-color)",
"mdc-select-outlined-idle-border-color":
"var(--input-outlined-idle-border-color)",
"mdc-select-outlined-hover-border-color":
"var(--input-outlined-hover-border-color)",
"mdc-select-outlined-disabled-border-color":
"var(--input-outlined-disabled-border-color)",
"mdc-select-fill-color": "var(--input-fill-color)",
"mdc-select-disabled-fill-color": "var(--input-disabled-fill-color)",
"mdc-select-ink-color": "var(--input-ink-color)",
"mdc-select-label-ink-color": "var(--input-label-ink-color)",
"mdc-select-disabled-ink-color": "var(--input-disabled-ink-color)",
"mdc-select-dropdown-icon-color": "var(--input-dropdown-icon-color)",
"mdc-select-disabled-dropdown-icon-color": "var(--input-disabled-ink-color)",
"chip-background-color": "rgba(var(--rgb-primary-text-color), 0.15)",
// Vaadin
"material-body-text-color": "var(--primary-text-color)",
"material-background-color": "var(--card-background-color)",
"material-secondary-background-color": "var(--secondary-background-color)",
"material-secondary-text-color": "var(--secondary-text-color)",
};
export const buttonLinkStyle = css`
button.link {
background: none;
+20 -1
View File
@@ -4670,6 +4670,8 @@
"confirm_cancel": "Are you sure you want to cancel?",
"show_visual_editor": "Show visual editor",
"show_code_editor": "Show code editor",
"edit_ui": "[%key:ui::panel::config::automation::editor::edit_ui%]",
"edit_yaml": "[%key:ui::panel::config::automation::editor::edit_yaml%]",
"add": "Add card",
"edit": "Edit",
"clear": "Clear",
@@ -4768,7 +4770,24 @@
"state_not_equal": "State is not equal to",
"current_state": "current",
"condition_explanation": "The card will be shown when ALL conditions below are fulfilled.",
"change_type": "Change type"
"change_type": "Change type",
"add_condition": "Add condition",
"condition": {
"screen": {
"label": "Screen",
"breakpoints": "Screen sizes",
"breakpoints_list": {
"mobile": "Mobile",
"tablet": "Tablet",
"desktop": "Desktop",
"wide": "Wide"
},
"min": "min: {size}px"
},
"state": {
"label": "Entity state"
}
}
},
"config": {
"required": "required",
+2 -1
View File
@@ -174,7 +174,8 @@
"nativeName": "Українська"
},
"ur": {
"nativeName": "اُردُو"
"nativeName": "اُردُو",
"isRTL": true
},
"vi": {
"nativeName": "Tiếng Việt"
+48 -46
View File
@@ -3164,13 +3164,13 @@ __metadata:
languageName: node
linkType: hard
"@material/web@npm:=1.0.0-pre.17":
version: 1.0.0-pre.17
resolution: "@material/web@npm:1.0.0-pre.17"
"@material/web@npm:=1.0.0":
version: 1.0.0
resolution: "@material/web@npm:1.0.0"
dependencies:
lit: ^2.7.4 || ^3.0.0
tslib: ^2.4.0
checksum: a6443931677d981a0152fe52927b1aa2a9490354c81fbf77f3f9f33accfdc567dc2a431ae0a3cb375dff60a322ff7babd49fb62b186bcf43285e09fecd48a594
checksum: 0c6772ab2b2c144bba62f323a6d657d44ea3b2c9a2b8e745e0e5d17081983724e8e72857a58a930df28073ec931ed04a4f2364bdcbdf72ce5c85a03abc0578f3
languageName: node
linkType: hard
@@ -4418,10 +4418,10 @@ __metadata:
languageName: node
linkType: hard
"@types/mocha@npm:10.0.1":
version: 10.0.1
resolution: "@types/mocha@npm:10.0.1"
checksum: 224ea9fce7b1734ccdb9aa99a622d902a538ce1847bca7fd22c5fb38adcf3ed536f50f48f587085db988a4bb3c2eb68f4b98e1cd6a38bc5547bd3bbbedc54495
"@types/mocha@npm:10.0.2":
version: 10.0.2
resolution: "@types/mocha@npm:10.0.2"
checksum: a78a02691f102beb02f9ec435458107d21b518fc477c3b2f37c90b8e70b67bff888351715ae173bd31ede25ee5e0d688aefb0faf4284034d08ba63027c8b0c01
languageName: node
linkType: hard
@@ -6518,18 +6518,18 @@ __metadata:
languageName: node
linkType: hard
"chai@npm:4.3.8":
version: 4.3.8
resolution: "chai@npm:4.3.8"
"chai@npm:4.3.10":
version: 4.3.10
resolution: "chai@npm:4.3.10"
dependencies:
assertion-error: ^1.1.0
check-error: ^1.0.2
deep-eql: ^4.1.2
get-func-name: ^2.0.0
loupe: ^2.3.1
check-error: ^1.0.3
deep-eql: ^4.1.3
get-func-name: ^2.0.2
loupe: ^2.3.6
pathval: ^1.1.1
type-detect: ^4.0.5
checksum: 29e0984ed13308319cadc35437c8ef0a3e271544d226c991bf7e3b6d771bf89707321669e11d05e362bc0ad0bd26585079b989d1032f3c106e3bb95d7f079cce
type-detect: ^4.0.8
checksum: 536668c60a0d985a0fbd94418028e388d243a925d7c5e858c7443e334753511614a3b6a124bac9ca077dfc4c37acc367d62f8c294960f440749536dc181dfc6d
languageName: node
linkType: hard
@@ -6579,10 +6579,12 @@ __metadata:
languageName: node
linkType: hard
"check-error@npm:^1.0.2":
version: 1.0.2
resolution: "check-error@npm:1.0.2"
checksum: d9d106504404b8addd1ee3f63f8c0eaa7cd962a1a28eb9c519b1c4a1dc7098be38007fc0060f045ee00f075fbb7a2a4f42abcf61d68323677e11ab98dc16042e
"check-error@npm:^1.0.3":
version: 1.0.3
resolution: "check-error@npm:1.0.3"
dependencies:
get-func-name: ^2.0.2
checksum: e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399
languageName: node
linkType: hard
@@ -7282,7 +7284,7 @@ __metadata:
languageName: node
linkType: hard
"deep-eql@npm:^4.1.2":
"deep-eql@npm:^4.1.3":
version: 4.1.3
resolution: "deep-eql@npm:4.1.3"
dependencies:
@@ -9071,7 +9073,7 @@ __metadata:
languageName: node
linkType: hard
"get-func-name@npm:^2.0.0":
"get-func-name@npm:^2.0.0, get-func-name@npm:^2.0.2":
version: 2.0.2
resolution: "get-func-name@npm:2.0.2"
checksum: 3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b
@@ -9199,18 +9201,18 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:10.3.7, glob@npm:^10.2.2":
version: 10.3.7
resolution: "glob@npm:10.3.7"
"glob@npm:10.3.10, glob@npm:^10.2.2":
version: 10.3.10
resolution: "glob@npm:10.3.10"
dependencies:
foreground-child: ^3.1.0
jackspeak: ^2.0.3
jackspeak: ^2.3.5
minimatch: ^9.0.1
minipass: ^5.0.0 || ^6.0.2 || ^7.0.0
path-scurry: ^1.10.1
bin:
glob: dist/esm/bin.mjs
checksum: 9a27f1fa8774c3a8ab8f05c26a77276edaf5418aac29aff70c5d847ef75dabf536554cb113e945193323fb769fbe32edde12559d2d52266f38662595cbc7a031
checksum: 4f2fe2511e157b5a3f525a54092169a5f92405f24d2aed3142f4411df328baca13059f4182f1db1bf933e2c69c0bd89e57ae87edd8950cba8c7ccbe84f721cf3
languageName: node
linkType: hard
@@ -9687,7 +9689,7 @@ __metadata:
"@material/mwc-top-app-bar": 0.27.0
"@material/mwc-top-app-bar-fixed": 0.27.0
"@material/top-app-bar": =14.0.0-canary.53b3cad2f.0
"@material/web": =1.0.0-pre.17
"@material/web": =1.0.0
"@mdi/js": 7.2.96
"@mdi/svg": 7.2.96
"@octokit/auth-oauth-device": 6.0.1
@@ -9720,7 +9722,7 @@ __metadata:
"@types/leaflet": 1.9.6
"@types/leaflet-draw": 1.0.8
"@types/luxon": 3.3.2
"@types/mocha": 10.0.1
"@types/mocha": 10.0.2
"@types/qrcode": 1.5.2
"@types/serve-handler": 6.1.2
"@types/sortablejs": 1.15.3
@@ -9742,7 +9744,7 @@ __metadata:
app-datepicker: 5.1.1
babel-loader: 9.1.3
babel-plugin-template-html-minifier: 4.1.0
chai: 4.3.8
chai: 4.3.10
chart.js: 4.4.0
comlink: 4.4.1
core-js: 3.32.2
@@ -9767,7 +9769,7 @@ __metadata:
fancy-log: 2.0.0
fs-extra: 11.1.1
fuse.js: 6.6.2
glob: 10.3.7
glob: 10.3.10
google-timezones-json: 1.2.0
gulp: 4.0.2
gulp-flatmap: 1.0.2
@@ -9791,7 +9793,7 @@ __metadata:
lit-analyzer: 2.0.0-pre.3
lodash.template: 4.5.0
luxon: 3.4.3
magic-string: 0.30.3
magic-string: 0.30.4
map-stream: 0.0.7
marked: 9.0.3
memoize-one: 6.0.0
@@ -10901,16 +10903,16 @@ __metadata:
languageName: node
linkType: hard
"jackspeak@npm:^2.0.3":
version: 2.3.3
resolution: "jackspeak@npm:2.3.3"
"jackspeak@npm:^2.3.5":
version: 2.3.6
resolution: "jackspeak@npm:2.3.6"
dependencies:
"@isaacs/cliui": ^8.0.2
"@pkgjs/parseargs": ^0.11.0
dependenciesMeta:
"@pkgjs/parseargs":
optional: true
checksum: 4313a7c0cc44c7753c4cb9869935f0b06f4cf96827515f63f58ff46b3d2f6e29aba6b3b5151778397c3f5ae67ef8bfc48871967bd10343c27e90cff198ec7808
checksum: 57d43ad11eadc98cdfe7496612f6bbb5255ea69fe51ea431162db302c2a11011642f50cfad57288bd0aea78384a0612b16e131944ad8ecd09d619041c8531b54
languageName: node
linkType: hard
@@ -11651,7 +11653,7 @@ __metadata:
languageName: node
linkType: hard
"loupe@npm:^2.3.1":
"loupe@npm:^2.3.6":
version: 2.3.6
resolution: "loupe@npm:2.3.6"
dependencies:
@@ -11708,12 +11710,12 @@ __metadata:
languageName: node
linkType: hard
"magic-string@npm:0.30.3":
version: 0.30.3
resolution: "magic-string@npm:0.30.3"
"magic-string@npm:0.30.4":
version: 0.30.4
resolution: "magic-string@npm:0.30.4"
dependencies:
"@jridgewell/sourcemap-codec": ^1.4.15
checksum: a5a9ddf9bd3bf49a2de1048bf358464f1bda7b3cc1311550f4a0ba8f81a4070e25445d53a5ee28850161336f1bff3cf28aa3320c6b4aeff45ce3e689f300b2f3
checksum: bef57c926d14e9926c142307c1494cc4bdea28a56601a7624f1a5bcd34a63800e2d8a363e826436ce86104460a63ee76c7c185a6ab1f8f7ee5af2de475b98947
languageName: node
linkType: hard
@@ -13265,13 +13267,13 @@ __metadata:
linkType: hard
"postcss@npm:^8.4.14":
version: 8.4.30
resolution: "postcss@npm:8.4.30"
version: 8.4.31
resolution: "postcss@npm:8.4.31"
dependencies:
nanoid: ^3.3.6
picocolors: ^1.0.0
source-map-js: ^1.0.2
checksum: 6c810c10c9bd3e03ca016e0b6b6756261e640aba1a9a7b1200b55502bc34b9165e38f590aef3493afc2f30ab55cdfcd43fd0f8408d69a77318ddbcf2a8ad164b
checksum: 1d8611341b073143ad90486fcdfeab49edd243377b1f51834dc4f6d028e82ce5190e4f11bb2633276864503654fb7cab28e67abdc0fbf9d1f88cad4a0ff0beea
languageName: node
linkType: hard
@@ -15521,7 +15523,7 @@ __metadata:
languageName: node
linkType: hard
"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5, type-detect@npm:^4.0.8":
"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.8":
version: 4.0.8
resolution: "type-detect@npm:4.0.8"
checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15