mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-22 07:39:29 +00:00
Compare commits
2 Commits
dev
...
overview-d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5d9044e07a | ||
![]() |
e0a24ca641 |
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
"*.?(c|m){js,ts}": [
|
||||
"eslint --flag v10_config_lookup_from_file --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --fix",
|
||||
"eslint --flag unstable_config_lookup_from_file --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --fix",
|
||||
"prettier --cache --write",
|
||||
"lit-analyzer --quiet",
|
||||
],
|
||||
|
10
package.json
10
package.json
@@ -8,8 +8,8 @@
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "script/build_frontend",
|
||||
"lint:eslint": "eslint --flag v10_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --max-warnings=0",
|
||||
"format:eslint": "eslint --flag v10_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
|
||||
"lint:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --max-warnings=0",
|
||||
"format:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
|
||||
"lint:prettier": "prettier . --cache --check",
|
||||
"format:prettier": "prettier . --cache --write",
|
||||
"lint:types": "tsc",
|
||||
@@ -123,7 +123,7 @@
|
||||
"lit": "3.3.1",
|
||||
"lit-html": "3.3.1",
|
||||
"luxon": "3.7.1",
|
||||
"marked": "16.2.0",
|
||||
"marked": "16.1.2",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.3",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -191,7 +191,7 @@
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-lit": "2.1.1",
|
||||
"eslint-plugin-lit-a11y": "5.1.1",
|
||||
"eslint-plugin-unused-imports": "4.2.0",
|
||||
"eslint-plugin-unused-imports": "4.1.4",
|
||||
"eslint-plugin-wc": "3.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.1",
|
||||
@@ -218,7 +218,7 @@
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.2",
|
||||
"typescript-eslint": "8.40.0",
|
||||
"typescript-eslint": "8.39.1",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.2.4",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
@@ -31,7 +31,7 @@ export const computeEntityEntryName = (
|
||||
entry.name ||
|
||||
("original_name" in entry && entry.original_name != null
|
||||
? String(entry.original_name)
|
||||
: undefined);
|
||||
: "");
|
||||
|
||||
const device = entry.device_id ? hass.devices[entry.device_id] : undefined;
|
||||
|
||||
|
@@ -101,7 +101,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
fill: api.value(4) as string,
|
||||
},
|
||||
};
|
||||
const text = (api.value(3) as string).replaceAll("\n", " ");
|
||||
const text = api.value(3) as string;
|
||||
const textWidth = measureTextWidth(text, 12);
|
||||
const LABEL_PADDING = 4;
|
||||
if (textWidth < rectShape.width - LABEL_PADDING * 2) {
|
||||
|
@@ -8,6 +8,7 @@ import type {
|
||||
LocalizeKeys,
|
||||
} from "../../common/translations/localize";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-alert";
|
||||
import "../ha-form/ha-form";
|
||||
|
||||
const SELECTOR_DEFAULTS = {
|
||||
@@ -155,8 +156,6 @@ export class HaSelectorSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public required = true;
|
||||
|
||||
private _yamlMode = false;
|
||||
@@ -173,10 +172,10 @@ export class HaSelectorSelector extends LitElement {
|
||||
[
|
||||
{
|
||||
name: "type",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
required: true,
|
||||
options: Object.keys(SELECTOR_SCHEMAS)
|
||||
.concat("manual")
|
||||
.map((key) => ({
|
||||
@@ -229,17 +228,17 @@ export class HaSelectorSelector extends LitElement {
|
||||
|
||||
const schema = this._schema(type, this.hass.localize);
|
||||
|
||||
return html`<div>
|
||||
<p>${this.label ? this.label : ""}</p>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
.narrow=${this.narrow}
|
||||
></ha-form>
|
||||
</div>`;
|
||||
return html`<ha-card>
|
||||
<div class="card-content">
|
||||
<p>${this.label ? this.label : ""}</p>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
@@ -286,6 +285,23 @@ export class HaSelectorSelector extends LitElement {
|
||||
) || schema.name;
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-card {
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
ha-card.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
.card-content {
|
||||
padding: 0px 16px 16px 16px;
|
||||
}
|
||||
.title {
|
||||
font-size: var(--ha-font-size-l);
|
||||
padding-top: 16px;
|
||||
|
@@ -7,10 +7,10 @@ import { navigate } from "../common/navigate";
|
||||
import { createSearchParam } from "../common/url/search-params";
|
||||
import type { Context, HomeAssistant } from "../types";
|
||||
import type { BlueprintInput } from "./blueprint";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import type { Action, Field, MODES } from "./script";
|
||||
import type { Action, MODES } from "./script";
|
||||
import { migrateAutomationAction } from "./script";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
||||
|
||||
export const AUTOMATION_DEFAULT_MODE: (typeof MODES)[number] = "single";
|
||||
export const AUTOMATION_DEFAULT_MAX = 10;
|
||||
@@ -513,14 +513,6 @@ export const isCondition = (config: unknown): boolean => {
|
||||
return "condition" in condition && typeof condition.condition === "string";
|
||||
};
|
||||
|
||||
export const isScriptField = (config: unknown): boolean => {
|
||||
if (!config || typeof config !== "object") {
|
||||
return false;
|
||||
}
|
||||
const field = config as Record<string, unknown>;
|
||||
return "field" in field && typeof field.field === "object";
|
||||
};
|
||||
|
||||
export const subscribeTrigger = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (result: {
|
||||
@@ -554,67 +546,3 @@ export interface AutomationClipboard {
|
||||
condition?: Condition;
|
||||
action?: Action;
|
||||
}
|
||||
|
||||
export interface BaseSidebarConfig {
|
||||
toggleYamlMode: () => boolean;
|
||||
delete: () => void;
|
||||
}
|
||||
|
||||
export interface TriggerSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Trigger) => void;
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
disable: () => void;
|
||||
config: Trigger;
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
}
|
||||
|
||||
export interface ConditionSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Condition) => void;
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
disable: () => void;
|
||||
config: Condition;
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
}
|
||||
|
||||
export interface ActionSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Action) => void;
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
disable: () => void;
|
||||
config: Action;
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
}
|
||||
|
||||
export interface OptionSidebarConfig extends BaseSidebarConfig {
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
}
|
||||
|
||||
export interface ScriptFieldSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Field) => void;
|
||||
close: () => void;
|
||||
config: {
|
||||
field: Field;
|
||||
selector: boolean;
|
||||
key: string;
|
||||
excludeKeys: string[];
|
||||
};
|
||||
yamlMode: boolean;
|
||||
}
|
||||
|
||||
export type SidebarConfig =
|
||||
| TriggerSidebarConfig
|
||||
| ConditionSidebarConfig
|
||||
| ActionSidebarConfig
|
||||
| OptionSidebarConfig
|
||||
| ScriptFieldSidebarConfig;
|
||||
|
||||
export interface ShowAutomationEditorParams {
|
||||
data?: Partial<AutomationConfig>;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
@@ -1 +0,0 @@
|
||||
export const SELECTOR_SELECTOR_BUILDING_BLOCKS = ["condition", "action"];
|
@@ -12,7 +12,6 @@ export interface Zone {
|
||||
}
|
||||
|
||||
export interface HomeZoneMutableParams {
|
||||
name?: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
radius: number;
|
||||
|
@@ -856,9 +856,7 @@ export class QuickBar extends LitElement {
|
||||
|
||||
private _generateNavigationPanelCommands(): BaseNavigationCommand[] {
|
||||
return Object.keys(this.hass.panels)
|
||||
.filter(
|
||||
(panelKey) => panelKey !== "_my_redirect" && panelKey !== "hassio"
|
||||
)
|
||||
.filter((panelKey) => panelKey !== "_my_redirect")
|
||||
.map((panelKey) => {
|
||||
const panel = this.hass.panels[panelKey];
|
||||
const translationKey = getPanelNameTranslationKey(panel);
|
||||
|
@@ -112,9 +112,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
this.assistConfiguration.available_wake_words.length > 1
|
||||
? html`<div class="row">
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.voice_assistants.assistants.pipeline.detail.form.wake_word_id"
|
||||
)}
|
||||
.label=${"Wake word"}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
@@ -146,9 +144,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
${pipelineEntity
|
||||
? html`<div class="row">
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.voice_assistants.assistants.pipeline.devices.pipeline"
|
||||
)}
|
||||
.label=${"Assistant"}
|
||||
@closed=${stopPropagation}
|
||||
.value=${pipelineEntity?.state}
|
||||
fixedMenuPosition
|
||||
|
@@ -41,7 +41,6 @@ import {
|
||||
YAML_ONLY_ACTION_TYPES,
|
||||
} from "../../../../data/action";
|
||||
import type {
|
||||
ActionSidebarConfig,
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
} from "../../../../data/automation";
|
||||
@@ -666,9 +665,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
config: sidebarAction,
|
||||
type: "action",
|
||||
uiSupported: actionType ? this._uiSupported(actionType) : false,
|
||||
yamlMode: this._yamlMode,
|
||||
} satisfies ActionSidebarConfig);
|
||||
});
|
||||
this._selected = true;
|
||||
}
|
||||
|
||||
|
@@ -167,9 +167,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
} else if (!this.optionsInSidebar) {
|
||||
row.expand();
|
||||
}
|
||||
if (this.narrow) {
|
||||
row.scrollIntoView();
|
||||
}
|
||||
row.scrollIntoView();
|
||||
row.focus();
|
||||
});
|
||||
}
|
||||
|
@@ -1,31 +1,24 @@
|
||||
import { mdiClose, mdiPlus } from "@mdi/js";
|
||||
import { dump } from "js-yaml";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mdiClose, mdiPlus } from "@mdi/js";
|
||||
import { dump } from "js-yaml";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/chips/ha-assist-chip";
|
||||
import "../../../../components/chips/ha-chip-set";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-area-picker";
|
||||
import "../../../../components/ha-domain-icon";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-labels-picker";
|
||||
import "../../../../components/ha-suggest-with-ai-button";
|
||||
import type { SuggestWithAIGenerateTask } from "../../../../components/ha-suggest-with-ai-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-textarea";
|
||||
import "../../../../components/ha-textfield";
|
||||
import "../../../../components/ha-labels-picker";
|
||||
import "../../../../components/ha-suggest-with-ai-button";
|
||||
import type { SuggestWithAIGenerateTask } from "../../../../components/ha-suggest-with-ai-button";
|
||||
import "../../category/ha-category-picker";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/chips/ha-chip-set";
|
||||
import "../../../../components/chips/ha-assist-chip";
|
||||
import "../../../../components/ha-area-picker";
|
||||
|
||||
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
|
||||
import { supportsMarkdownHelper } from "../../../../common/translations/markdown_support";
|
||||
import { subscribeOne } from "../../../../common/util/subscribe-one";
|
||||
import type { GenDataTaskResult } from "../../../../data/ai_task";
|
||||
import { fetchCategoryRegistry } from "../../../../data/category_registry";
|
||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
||||
import { subscribeLabelRegistry } from "../../../../data/label_registry";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -33,6 +26,13 @@ import type {
|
||||
EntityRegistryUpdate,
|
||||
SaveDialogParams,
|
||||
} from "./show-dialog-automation-save";
|
||||
import { supportsMarkdownHelper } from "../../../../common/translations/markdown_support";
|
||||
import type { GenDataTaskResult } from "../../../../data/ai_task";
|
||||
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
|
||||
import { subscribeOne } from "../../../../common/util/subscribe-one";
|
||||
import { subscribeLabelRegistry } from "../../../../data/label_registry";
|
||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
||||
import { fetchCategoryRegistry } from "../../../../data/category_registry";
|
||||
|
||||
@customElement("ha-dialog-automation-save")
|
||||
class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
@@ -242,7 +242,7 @@ class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
const title = this.hass.localize(
|
||||
this._params.config.alias
|
||||
? "ui.panel.config.automation.editor.rename"
|
||||
: "ui.common.save"
|
||||
: "ui.panel.config.automation.editor.save"
|
||||
);
|
||||
|
||||
return html`
|
||||
@@ -289,7 +289,7 @@ class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
${this.hass.localize(
|
||||
this._params.config.alias && !this._params.onDiscard
|
||||
? "ui.panel.config.automation.editor.rename"
|
||||
: "ui.common.save"
|
||||
: "ui.panel.config.automation.editor.save"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
|
@@ -6,7 +6,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-fab";
|
||||
import type { BlueprintAutomationConfig } from "../../../data/automation";
|
||||
import { fetchBlueprints } from "../../../data/blueprint";
|
||||
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
||||
@@ -59,7 +58,7 @@ export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.label=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveAutomation}
|
||||
|
@@ -35,7 +35,6 @@ import "../../../../components/ha-md-menu-item";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
ConditionSidebarConfig,
|
||||
} from "../../../../data/automation";
|
||||
import { testCondition } from "../../../../data/automation";
|
||||
import { describeCondition } from "../../../../data/automation_i18n";
|
||||
@@ -622,9 +621,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
config: sidebarCondition,
|
||||
type: "condition",
|
||||
uiSupported: this._uiSupported(sidebarCondition.condition),
|
||||
yamlMode: this._yamlMode,
|
||||
} satisfies ConditionSidebarConfig);
|
||||
});
|
||||
this._selected = true;
|
||||
}
|
||||
|
||||
|
@@ -16,7 +16,6 @@ import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
} from "../../../../data/automation";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import "./ha-automation-condition-row";
|
||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
||||
|
||||
@customElement("ha-automation-condition")
|
||||
export default class HaAutomationCondition extends LitElement {
|
||||
@@ -111,9 +111,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
} else if (!this.optionsInSidebar) {
|
||||
row.expand();
|
||||
}
|
||||
if (this.narrow) {
|
||||
row.scrollIntoView();
|
||||
}
|
||||
row.scrollIntoView();
|
||||
row.focus();
|
||||
});
|
||||
}
|
||||
|
@@ -535,10 +535,12 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this._dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.save"
|
||||
)}
|
||||
.disabled=${this._saving}
|
||||
extended
|
||||
@click=${this._handleSaveAutomation}
|
||||
@click=${this._saveAutomation}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
|
@@ -1,32 +1,59 @@
|
||||
import {
|
||||
mdiClose,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiIdentifier,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import {
|
||||
isCondition,
|
||||
isScriptField,
|
||||
isTrigger,
|
||||
type ActionSidebarConfig,
|
||||
type ConditionSidebarConfig,
|
||||
type ScriptFieldSidebarConfig,
|
||||
type SidebarConfig,
|
||||
type TriggerSidebarConfig,
|
||||
} from "../../../data/automation";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { handleStructError } from "../../../common/structs/handle-errors";
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dialog-header";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-button-menu";
|
||||
import "../../../components/ha-md-divider";
|
||||
import "../../../components/ha-md-menu-item";
|
||||
import type { Condition, Trigger } from "../../../data/automation";
|
||||
import type { Action, RepeatAction } from "../../../data/script";
|
||||
import { isTriggerList } from "../../../data/trigger";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./action/ha-automation-action-editor";
|
||||
import { getAutomationActionType } from "./action/ha-automation-action-row";
|
||||
import { getRepeatType } from "./action/types/ha-automation-action-repeat";
|
||||
import "./condition/ha-automation-condition-editor";
|
||||
import type HaAutomationConditionEditor from "./condition/ha-automation-condition-editor";
|
||||
import "./sidebar/ha-automation-sidebar-action";
|
||||
import "./sidebar/ha-automation-sidebar-condition";
|
||||
import "./sidebar/ha-automation-sidebar-option";
|
||||
import "./sidebar/ha-automation-sidebar-script-field";
|
||||
import "./sidebar/ha-automation-sidebar-script-field-selector";
|
||||
import "./sidebar/ha-automation-sidebar-trigger";
|
||||
import "./ha-automation-editor-warning";
|
||||
import "./trigger/ha-automation-trigger-editor";
|
||||
import type HaAutomationTriggerEditor from "./trigger/ha-automation-trigger-editor";
|
||||
import { ACTION_BUILDING_BLOCKS } from "../../../data/action";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "../../../data/condition";
|
||||
|
||||
export interface OpenSidebarConfig {
|
||||
save: (config: Trigger | Condition | Action) => void;
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
toggleYamlMode: () => boolean;
|
||||
disable: () => void;
|
||||
delete: () => void;
|
||||
config: Trigger | Condition | Action;
|
||||
type: "trigger" | "condition" | "action" | "option";
|
||||
uiSupported: boolean;
|
||||
yamlMode: boolean;
|
||||
}
|
||||
|
||||
@customElement("ha-automation-sidebar")
|
||||
export default class HaAutomationSidebar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config?: SidebarConfig;
|
||||
@property({ attribute: false }) public config?: OpenSidebarConfig;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@@ -34,134 +61,270 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@state() private _requestShowId = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationTriggerEditor | HaAutomationConditionEditor;
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("config")) {
|
||||
this._requestShowId = false;
|
||||
this._warnings = undefined;
|
||||
if (this.config) {
|
||||
this._yamlMode = this.config.yamlMode;
|
||||
if (this._yamlMode) {
|
||||
this.editor?.yamlEditor?.setValue(this.config.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
// get config type
|
||||
const type = this._getType();
|
||||
const disabled =
|
||||
this.disabled ||
|
||||
("enabled" in this.config.config && this.config.config.enabled === false);
|
||||
let type = isTriggerList(this.config.config as Trigger)
|
||||
? "list"
|
||||
: this.config.type === "action"
|
||||
? getAutomationActionType(this.config.config as Action)
|
||||
: this.config.config[this.config.type];
|
||||
|
||||
if (type === "trigger") {
|
||||
return html`
|
||||
<ha-automation-sidebar-trigger
|
||||
.hass=${this.hass}
|
||||
.config=${this.config}
|
||||
.isWide=${this.isWide}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._closeSidebar}
|
||||
></ha-automation-sidebar-trigger>
|
||||
`;
|
||||
}
|
||||
if (type === "condition") {
|
||||
return html`
|
||||
<ha-automation-sidebar-condition
|
||||
.hass=${this.hass}
|
||||
.config=${this.config}
|
||||
.isWide=${this.isWide}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._closeSidebar}
|
||||
></ha-automation-sidebar-condition>
|
||||
`;
|
||||
}
|
||||
if (type === "action") {
|
||||
return html`
|
||||
<ha-automation-sidebar-action
|
||||
.hass=${this.hass}
|
||||
.config=${this.config}
|
||||
.isWide=${this.isWide}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._closeSidebar}
|
||||
></ha-automation-sidebar-action>
|
||||
`;
|
||||
}
|
||||
if (type === "option") {
|
||||
return html`
|
||||
<ha-automation-sidebar-option
|
||||
.hass=${this.hass}
|
||||
.config=${this.config}
|
||||
.isWide=${this.isWide}
|
||||
.disabled=${this.disabled}
|
||||
@close-sidebar=${this._closeSidebar}
|
||||
></ha-automation-sidebar-option>
|
||||
`;
|
||||
}
|
||||
if (type === "script-field-selector") {
|
||||
return html`
|
||||
<ha-automation-sidebar-script-field-selector
|
||||
.hass=${this.hass}
|
||||
.config=${this.config}
|
||||
.isWide=${this.isWide}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._closeSidebar}
|
||||
></ha-automation-sidebar-script-field-selector>
|
||||
`;
|
||||
}
|
||||
if (type === "script-field") {
|
||||
return html`
|
||||
<ha-automation-sidebar-script-field
|
||||
.hass=${this.hass}
|
||||
.config=${this.config}
|
||||
.isWide=${this.isWide}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._closeSidebar}
|
||||
></ha-automation-sidebar-script-field>
|
||||
`;
|
||||
if (this.config.type === "action" && type === "repeat") {
|
||||
type = `repeat_${getRepeatType((this.config.config as RepeatAction).repeat)}`;
|
||||
}
|
||||
|
||||
return nothing;
|
||||
const isBuildingBlock = [
|
||||
...CONDITION_BUILDING_BLOCKS,
|
||||
...ACTION_BUILDING_BLOCKS,
|
||||
].includes(type);
|
||||
|
||||
const subtitle = this.hass.localize(
|
||||
(this.config.type === "option"
|
||||
? "ui.panel.config.automation.editor.actions.type.choose.label"
|
||||
: `ui.panel.config.automation.editor.${this.config.type}s.${this.config.type}`) as LocalizeKeys
|
||||
);
|
||||
const title =
|
||||
this.hass.localize(
|
||||
(this.config.type === "option"
|
||||
? "ui.panel.config.automation.editor.actions.type.choose.option_label"
|
||||
: `ui.panel.config.automation.editor.${this.config.type}s.type.${type}.label`) as LocalizeKeys
|
||||
) || type;
|
||||
|
||||
const description =
|
||||
isBuildingBlock || this.config.type === "option"
|
||||
? this.hass.localize(
|
||||
(this.config.type === "option"
|
||||
? "ui.panel.config.automation.editor.actions.type.choose.option_description"
|
||||
: `ui.panel.config.automation.editor.${this.config.type}s.type.${type}.description.picker`) as LocalizeKeys
|
||||
)
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class=${classMap({
|
||||
mobile: !this.isWide,
|
||||
yaml: this._yamlMode,
|
||||
})}
|
||||
>
|
||||
<ha-dialog-header>
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@click=${this._closeSidebar}
|
||||
></ha-icon-button>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-button-menu
|
||||
slot="actionItems"
|
||||
@click=${this._openOverflowMenu}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
${this.config.type === "trigger" &&
|
||||
!this._yamlMode &&
|
||||
!("id" in this.config.config) &&
|
||||
!this._requestShowId
|
||||
? html`<ha-md-menu-item
|
||||
.clickAction=${this._showTriggerId}
|
||||
.disabled=${disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiIdentifier}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>`
|
||||
: nothing}
|
||||
${this.config.type !== "option"
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlaylistEdit}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
${this.config.type !== "option"
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this.config.disable}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
${disabled
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.enable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.disable"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${disabled
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this.config.delete}
|
||||
class="warning"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${this.config.type !== "option" ? "delete" : "type.choose.remove_option"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dialog-header>
|
||||
${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
.localize=${this.hass.localize}
|
||||
.warnings=${this._warnings}
|
||||
>
|
||||
</ha-automation-editor-warning>`
|
||||
: nothing}
|
||||
<div class="card-content">
|
||||
${this.config.type === "trigger"
|
||||
? html`<ha-automation-trigger-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.trigger=${this.config.config as Trigger}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
.showId=${this._requestShowId}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-trigger-editor>`
|
||||
: this.config.type === "condition" &&
|
||||
(this._yamlMode || !CONDITION_BUILDING_BLOCKS.includes(type))
|
||||
? html`
|
||||
<ha-automation-condition-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.config.config as Condition}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-condition-editor>
|
||||
`
|
||||
: this.config.type === "action" &&
|
||||
(this._yamlMode || !ACTION_BUILDING_BLOCKS.includes(type))
|
||||
? html`
|
||||
<ha-automation-action-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.action=${this.config.config as Action}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
sidebar
|
||||
narrow
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-action-editor>
|
||||
`
|
||||
: description || nothing}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _getType() {
|
||||
if (
|
||||
(this.config as TriggerSidebarConfig)?.config &&
|
||||
(isTrigger((this.config as TriggerSidebarConfig)?.config) ||
|
||||
isTriggerList((this.config as TriggerSidebarConfig)?.config))
|
||||
) {
|
||||
return "trigger";
|
||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
||||
if (!this._yamlMode) {
|
||||
this._yamlMode = true;
|
||||
}
|
||||
if (isCondition((this.config as ConditionSidebarConfig)?.config)) {
|
||||
return "condition";
|
||||
}
|
||||
if (
|
||||
(this.config as ScriptFieldSidebarConfig)?.config &&
|
||||
isScriptField((this.config as ScriptFieldSidebarConfig)?.config)
|
||||
) {
|
||||
return (this.config as ScriptFieldSidebarConfig)?.config.selector
|
||||
? "script-field-selector"
|
||||
: "script-field";
|
||||
}
|
||||
|
||||
// option is always a building block and doesn't have a config
|
||||
if (this.config && !(this.config as any)?.config) {
|
||||
return "option";
|
||||
}
|
||||
|
||||
if ((this.config as ActionSidebarConfig)?.config) {
|
||||
return "action";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _closeSidebar(ev: CustomEvent) {
|
||||
private _valueChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save(ev.detail.value);
|
||||
|
||||
if (this.config) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
config: ev.detail.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _closeSidebar() {
|
||||
this.config?.close();
|
||||
}
|
||||
|
||||
private _openOverflowMenu(ev: MouseEvent) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
this._yamlMode = this.config!.toggleYamlMode();
|
||||
fireEvent(this, "value-changed", {
|
||||
@@ -172,6 +335,10 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
});
|
||||
};
|
||||
|
||||
private _showTriggerId = () => {
|
||||
this._requestShowId = true;
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
height: 100%;
|
||||
@@ -181,6 +348,60 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius);
|
||||
}
|
||||
|
||||
ha-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-color: var(--primary-color);
|
||||
border-width: 2px;
|
||||
display: block;
|
||||
}
|
||||
ha-card.mobile {
|
||||
border-bottom-right-radius: var(--ha-border-radius-square);
|
||||
border-bottom-left-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
ha-card.mobile {
|
||||
max-height: 70vh;
|
||||
max-height: 70dvh;
|
||||
border-width: 2px 2px 0;
|
||||
}
|
||||
ha-card.mobile.yaml {
|
||||
height: 70vh;
|
||||
height: 70dvh;
|
||||
}
|
||||
}
|
||||
|
||||
ha-dialog-header {
|
||||
border-radius: var(--ha-card-border-radius);
|
||||
}
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
max-height: calc(100% - 80px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 450px) and (min-height: 500px) {
|
||||
.card-content {
|
||||
max-height: calc(100% - 104px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
ha-card.mobile .card-content {
|
||||
max-height: calc(
|
||||
70vh - 88px - max(var(--safe-area-inset-bottom), 16px)
|
||||
);
|
||||
max-height: calc(
|
||||
70dvh - 88px - max(var(--safe-area-inset-bottom), 16px)
|
||||
);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -188,8 +409,4 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar": HaAutomationSidebar;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"toggle-yaml-mode": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { mdiContentSave, mdiHelpCircle } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { load } from "js-yaml";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -18,6 +18,11 @@ import {
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { canOverrideAlphanumericInput } from "../../../common/dom/can-override-input";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { constructUrlCurrentPath } from "../../../common/url/construct-url";
|
||||
import {
|
||||
extractSearchParam,
|
||||
removeSearchParam,
|
||||
} from "../../../common/url/search-params";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
@@ -26,7 +31,6 @@ import type {
|
||||
AutomationConfig,
|
||||
Condition,
|
||||
ManualAutomationConfig,
|
||||
SidebarConfig,
|
||||
Trigger,
|
||||
} from "../../../data/automation";
|
||||
import {
|
||||
@@ -39,11 +43,15 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "./action/ha-automation-action";
|
||||
import type HaAutomationAction from "./action/ha-automation-action";
|
||||
import "./condition/ha-automation-condition";
|
||||
import type HaAutomationCondition from "./condition/ha-automation-condition";
|
||||
import "./ha-automation-sidebar";
|
||||
import type { OpenSidebarConfig } from "./ha-automation-sidebar";
|
||||
import { showPasteReplaceDialog } from "./paste-replace-dialog/show-dialog-paste-replace";
|
||||
import { saveFabStyles } from "./styles";
|
||||
import "./trigger/ha-automation-trigger";
|
||||
import type HaAutomationTrigger from "./trigger/ha-automation-trigger";
|
||||
|
||||
const baseConfigStruct = object({
|
||||
alias: optional(string()),
|
||||
@@ -82,7 +90,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
@state() private _pastedConfig?: ManualAutomationConfig;
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
@state() private _sidebarConfig?: OpenSidebarConfig;
|
||||
|
||||
private _previousConfig?: ManualAutomationConfig;
|
||||
|
||||
@@ -96,6 +104,31 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
const expanded = extractSearchParam("expanded");
|
||||
if (expanded === "1") {
|
||||
this._clearParam("expanded");
|
||||
const items = this.shadowRoot!.querySelectorAll<
|
||||
HaAutomationTrigger | HaAutomationCondition | HaAutomationAction
|
||||
>("ha-automation-trigger, ha-automation-condition, ha-automation-action");
|
||||
|
||||
items.forEach((el) => {
|
||||
el.updateComplete.then(() => {
|
||||
el.expandAll();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _clearParam(param: string) {
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
constructUrlCurrentPath(removeSearchParam(param))
|
||||
);
|
||||
}
|
||||
|
||||
private _renderContent() {
|
||||
return html`
|
||||
${this.stateObj?.state === "off"
|
||||
@@ -261,7 +294,9 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.save"
|
||||
)}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveAutomation}
|
||||
@@ -285,13 +320,13 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _openSidebar(ev: CustomEvent<SidebarConfig>) {
|
||||
private _openSidebar(ev: CustomEvent<OpenSidebarConfig>) {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
}
|
||||
|
||||
private _sidebarConfigChanged(ev: CustomEvent<{ value: SidebarConfig }>) {
|
||||
private _sidebarConfigChanged(ev: CustomEvent<{ value: OpenSidebarConfig }>) {
|
||||
ev.stopPropagation();
|
||||
if (!this._sidebarConfig) {
|
||||
return;
|
||||
@@ -683,7 +718,7 @@ declare global {
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"open-sidebar": SidebarConfig;
|
||||
"open-sidebar": OpenSidebarConfig;
|
||||
"close-sidebar": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -23,10 +23,7 @@ import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type {
|
||||
Condition,
|
||||
OptionSidebarConfig,
|
||||
} from "../../../../data/automation";
|
||||
import type { Condition } from "../../../../data/automation";
|
||||
import { describeCondition } from "../../../../data/automation_i18n";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||
@@ -348,6 +345,9 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
}
|
||||
|
||||
fireEvent(this, "open-sidebar", {
|
||||
save: () => {
|
||||
// nothing to save for an option in the sidebar
|
||||
},
|
||||
close: () => {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
@@ -356,8 +356,15 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
this._renameOption();
|
||||
},
|
||||
toggleYamlMode: () => false, // no yaml mode for options
|
||||
disable: () => {
|
||||
// option cannot be disabled
|
||||
},
|
||||
delete: this._removeOption,
|
||||
} satisfies OptionSidebarConfig);
|
||||
config: {},
|
||||
type: "option",
|
||||
uiSupported: true,
|
||||
yamlMode: false,
|
||||
});
|
||||
this._selected = true;
|
||||
}
|
||||
|
||||
|
@@ -133,10 +133,7 @@ export default class HaAutomationOption extends LitElement {
|
||||
if (!this.optionsInSidebar) {
|
||||
row.expand();
|
||||
}
|
||||
|
||||
if (this.narrow) {
|
||||
row.scrollIntoView();
|
||||
}
|
||||
row.scrollIntoView();
|
||||
row.focus();
|
||||
});
|
||||
}
|
||||
|
@@ -1,189 +0,0 @@
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
|
||||
import type { ActionSidebarConfig } from "../../../../data/automation";
|
||||
import type { RepeatAction } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import { getAutomationActionType } from "../action/ha-automation-action-row";
|
||||
import { getRepeatType } from "../action/types/ha-automation-action-repeat";
|
||||
import "../trigger/ha-automation-trigger-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-action")
|
||||
export default class HaAutomationSidebarAction extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: ActionSidebarConfig;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationConditionEditor;
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("config")) {
|
||||
this._warnings = undefined;
|
||||
if (this.config) {
|
||||
this.yamlMode = this.config.yamlMode;
|
||||
if (this.yamlMode) {
|
||||
this.editor?.yamlEditor?.setValue(this.config.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const disabled =
|
||||
this.disabled ||
|
||||
("enabled" in this.config.config && this.config.config.enabled === false);
|
||||
|
||||
const actionType = getAutomationActionType(this.config.config);
|
||||
|
||||
const type =
|
||||
actionType !== "repeat"
|
||||
? actionType
|
||||
: `repeat_${getRepeatType((this.config.config as RepeatAction).repeat)}`;
|
||||
|
||||
const isBuildingBlock = ACTION_BUILDING_BLOCKS.includes(type || "");
|
||||
|
||||
const subtitle = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.action"
|
||||
);
|
||||
|
||||
const title =
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.type.${type}.label` as LocalizeKeys
|
||||
) || type;
|
||||
|
||||
const description = isBuildingBlock
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.type.${type}.description.picker` as LocalizeKeys
|
||||
)
|
||||
: "";
|
||||
|
||||
return html`<ha-automation-sidebar-card
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
${description ||
|
||||
html`<ha-automation-action-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.action=${this.config.config}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
sidebar
|
||||
narrow
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-action-editor>`}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
||||
if (!this.yamlMode) {
|
||||
this.yamlMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
|
||||
if (this.config) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
config: ev.detail.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar-action": HaAutomationSidebarAction;
|
||||
}
|
||||
}
|
@@ -1,151 +0,0 @@
|
||||
import { mdiClose, mdiDotsVertical } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../ha-automation-editor-warning";
|
||||
|
||||
export interface SidebarOverflowMenuEntry {
|
||||
clickAction: () => void;
|
||||
disabled?: boolean;
|
||||
label: string;
|
||||
icon?: string;
|
||||
danger?: boolean;
|
||||
}
|
||||
|
||||
export type SidebarOverflowMenu = (SidebarOverflowMenuEntry | "separator")[];
|
||||
|
||||
@customElement("ha-automation-sidebar-card")
|
||||
export default class HaAutomationSidebarCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@property({ attribute: false }) public warnings?: string[];
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class=${classMap({
|
||||
mobile: !this.isWide,
|
||||
yaml: this.yamlMode,
|
||||
})}
|
||||
>
|
||||
<ha-dialog-header>
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@click=${this._closeSidebar}
|
||||
></ha-icon-button>
|
||||
<slot slot="title" name="title"></slot>
|
||||
<slot slot="subtitle" name="subtitle"></slot>
|
||||
<slot name="overflow-menu" slot="actionItems">
|
||||
<ha-md-button-menu
|
||||
@click=${this._openOverflowMenu}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<slot name="menu-items"></slot>
|
||||
</ha-md-button-menu>
|
||||
</slot>
|
||||
</ha-dialog-header>
|
||||
${this.warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
.localize=${this.hass.localize}
|
||||
.warnings=${this.warnings}
|
||||
>
|
||||
</ha-automation-editor-warning>`
|
||||
: nothing}
|
||||
<div class="card-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeSidebar() {
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
|
||||
private _openOverflowMenu(ev: MouseEvent) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-color: var(--primary-color);
|
||||
border-width: 2px;
|
||||
display: block;
|
||||
}
|
||||
ha-card.mobile {
|
||||
border-bottom-right-radius: var(--ha-border-radius-square);
|
||||
border-bottom-left-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
ha-card.mobile {
|
||||
max-height: 70vh;
|
||||
max-height: 70dvh;
|
||||
border-width: 2px 2px 0;
|
||||
}
|
||||
ha-card.mobile.yaml {
|
||||
height: 70vh;
|
||||
height: 70dvh;
|
||||
}
|
||||
}
|
||||
|
||||
ha-dialog-header {
|
||||
border-radius: var(--ha-card-border-radius);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
max-height: calc(100% - 80px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 450px) and (min-height: 500px) {
|
||||
.card-content {
|
||||
max-height: calc(100% - 104px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
ha-card.mobile .card-content {
|
||||
max-height: calc(
|
||||
70vh - 88px - max(var(--safe-area-inset-bottom), 16px)
|
||||
);
|
||||
max-height: calc(
|
||||
70dvh - 88px - max(var(--safe-area-inset-bottom), 16px)
|
||||
);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar-card": HaAutomationSidebarCard;
|
||||
}
|
||||
}
|
@@ -1,176 +0,0 @@
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { ConditionSidebarConfig } from "../../../../data/automation";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../condition/ha-automation-condition-editor";
|
||||
import type HaAutomationConditionEditor from "../condition/ha-automation-condition-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-condition")
|
||||
export default class HaAutomationSidebarCondition extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: ConditionSidebarConfig;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationConditionEditor;
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("config")) {
|
||||
this._warnings = undefined;
|
||||
if (this.config) {
|
||||
this.yamlMode = this.config.yamlMode;
|
||||
if (this.yamlMode) {
|
||||
this.editor?.yamlEditor?.setValue(this.config.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const disabled =
|
||||
this.disabled ||
|
||||
("enabled" in this.config.config && this.config.config.enabled === false);
|
||||
|
||||
const type = this.config.config.condition;
|
||||
|
||||
const isBuildingBlock = CONDITION_BUILDING_BLOCKS.includes(type);
|
||||
|
||||
const subtitle = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.condition"
|
||||
);
|
||||
|
||||
const title =
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.${type}.label`
|
||||
) || type;
|
||||
|
||||
const description = isBuildingBlock
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.${type}.description.picker`
|
||||
)
|
||||
: "";
|
||||
|
||||
return html`<ha-automation-sidebar-card
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
${description ||
|
||||
html`<ha-automation-condition-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.config.config}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-condition-editor> `}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
||||
if (!this.yamlMode) {
|
||||
this.yamlMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
|
||||
if (this.config) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
config: ev.detail.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar-condition": HaAutomationSidebarCondition;
|
||||
}
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
import { mdiDelete, mdiRenameBox } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import type { OptionSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-option")
|
||||
export default class HaAutomationSidebarOption extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: OptionSidebarConfig;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationConditionEditor;
|
||||
|
||||
protected render() {
|
||||
const disabled = this.disabled;
|
||||
|
||||
const subtitle = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.label"
|
||||
);
|
||||
|
||||
const title = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.option_label"
|
||||
);
|
||||
|
||||
const description = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.option_description"
|
||||
);
|
||||
|
||||
return html`<ha-automation-sidebar-card
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
${description}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar-option": HaAutomationSidebarOption;
|
||||
}
|
||||
}
|
@@ -1,130 +0,0 @@
|
||||
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../../script/ha-script-field-selector-editor";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-script-field-selector")
|
||||
export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: ScriptFieldSidebarConfig;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationConditionEditor;
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("config")) {
|
||||
this._warnings = undefined;
|
||||
if (this.config) {
|
||||
this.yamlMode = this.config.yamlMode;
|
||||
if (this.yamlMode) {
|
||||
this.editor?.yamlEditor?.setValue(this.config.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const subtitle = this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.field_selector"
|
||||
);
|
||||
|
||||
const title =
|
||||
this.hass.localize(
|
||||
`ui.components.selectors.selector.types.${Object.keys(this.config.config.field.selector)[0]}` as LocalizeKeys
|
||||
) || Object.keys(this.config.config.field.selector)[0];
|
||||
|
||||
return html`<ha-automation-sidebar-card
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-script-field-selector-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.yamlMode=${this.yamlMode}
|
||||
></ha-script-field-selector-editor>
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _valueChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.({
|
||||
...this.config.config.field,
|
||||
key: this.config.config.key,
|
||||
...ev.detail.value,
|
||||
});
|
||||
|
||||
if (this.config) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
config: {
|
||||
field: ev.detail.value,
|
||||
key: this.config.config.key,
|
||||
excludeKeys: this.config.config.excludeKeys,
|
||||
selector: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar-script-field-selector": HaAutomationSidebarScriptFieldSelector;
|
||||
}
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../../script/ha-script-field-editor";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-script-field")
|
||||
export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: ScriptFieldSidebarConfig;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationConditionEditor;
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("config")) {
|
||||
this._warnings = undefined;
|
||||
if (this.config) {
|
||||
this.yamlMode = this.config.yamlMode;
|
||||
if (this.yamlMode) {
|
||||
this.editor?.yamlEditor?.setValue(this.config.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const title = this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.label"
|
||||
);
|
||||
|
||||
return html`<ha-automation-sidebar-card
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-script-field-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.key=${this.config.config.key}
|
||||
.excludeKeys=${this.config.config.excludeKeys}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this.yamlMode}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
></ha-script-field-editor>
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _valueChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.({
|
||||
...this.config.config.field,
|
||||
key: ev.detail.value.key ?? this.config.config.key,
|
||||
...ev.detail.value,
|
||||
});
|
||||
|
||||
if (this.config) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
config: {
|
||||
field: ev.detail.value,
|
||||
key: ev.detail.value.key ?? this.config.config.key,
|
||||
excludeKeys: this.config.config.excludeKeys,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar-script-field": HaAutomationSidebarScriptField;
|
||||
}
|
||||
}
|
@@ -1,196 +0,0 @@
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiIdentifier,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { TriggerSidebarConfig } from "../../../../data/automation";
|
||||
import { isTriggerList } from "../../../../data/trigger";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../trigger/ha-automation-trigger-editor";
|
||||
import type HaAutomationTriggerEditor from "../trigger/ha-automation-trigger-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-trigger")
|
||||
export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: TriggerSidebarConfig;
|
||||
|
||||
@property({ type: Boolean, attribute: "wide" }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@state() private _requestShowId = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationTriggerEditor;
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("config")) {
|
||||
this._requestShowId = false;
|
||||
this._warnings = undefined;
|
||||
if (this.config) {
|
||||
this.yamlMode = this.config.yamlMode;
|
||||
if (this.yamlMode) {
|
||||
this.editor?.yamlEditor?.setValue(this.config.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const disabled =
|
||||
this.disabled ||
|
||||
("enabled" in this.config.config && this.config.config.enabled === false);
|
||||
const type = isTriggerList(this.config.config)
|
||||
? "list"
|
||||
: this.config.config.trigger;
|
||||
|
||||
const subtitle = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.trigger"
|
||||
);
|
||||
|
||||
const title = this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.${type}.label`
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-automation-sidebar-card
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
${!this.yamlMode &&
|
||||
!("id" in this.config.config) &&
|
||||
!this._requestShowId
|
||||
? html`<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._showTriggerId}
|
||||
.disabled=${disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
</ha-md-menu-item>`
|
||||
: nothing}
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
.disabled=${type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-automation-trigger-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.trigger=${this.config.config}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
.showId=${this._requestShowId}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-trigger-editor>
|
||||
</ha-automation-sidebar-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
||||
if (!this.yamlMode) {
|
||||
this.yamlMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
|
||||
if (this.config) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
config: ev.detail.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _showTriggerId = () => {
|
||||
this._requestShowId = true;
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-sidebar-trigger": HaAutomationSidebarTrigger;
|
||||
}
|
||||
}
|
@@ -34,11 +34,7 @@ import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Trigger,
|
||||
TriggerSidebarConfig,
|
||||
} from "../../../../data/automation";
|
||||
import type { AutomationClipboard, Trigger } from "../../../../data/automation";
|
||||
import { subscribeTrigger } from "../../../../data/automation";
|
||||
import { describeTrigger } from "../../../../data/automation_i18n";
|
||||
import { validateConfig } from "../../../../data/config";
|
||||
@@ -490,9 +486,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
config: trigger || this.trigger,
|
||||
type: "trigger",
|
||||
uiSupported: this._uiSupported(this._getType(trigger || this.trigger)),
|
||||
yamlMode: this._yamlMode,
|
||||
} satisfies TriggerSidebarConfig);
|
||||
});
|
||||
this._selected = true;
|
||||
}
|
||||
|
||||
|
@@ -180,9 +180,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
} else {
|
||||
row.expand();
|
||||
}
|
||||
if (this.narrow) {
|
||||
row.scrollIntoView();
|
||||
}
|
||||
row.scrollIntoView();
|
||||
row.focus();
|
||||
});
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
this._deviceTrackers = this._params.entry.device_trackers || [];
|
||||
this._picture = this._params.entry.picture || null;
|
||||
this._user = this._userId
|
||||
? this._params.users?.find((user) => user.id === this._userId)
|
||||
? this._params.users.find((user) => user.id === this._userId)
|
||||
: undefined;
|
||||
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
|
||||
this._localOnly = this._user?.local_only;
|
||||
@@ -372,10 +372,10 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
userAddedCallback: async (user?: User) => {
|
||||
if (user) {
|
||||
target.checked = true;
|
||||
if (this._params!.entry && this._params!.updateEntry) {
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry({ user_id: user.id });
|
||||
}
|
||||
this._params?.refreshUsers?.();
|
||||
this._params?.refreshUsers();
|
||||
this._user = user;
|
||||
this._userId = user.id;
|
||||
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
|
||||
@@ -403,7 +403,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
return;
|
||||
}
|
||||
await deleteUser(this.hass, this._userId);
|
||||
this._params?.refreshUsers?.();
|
||||
this._params?.refreshUsers();
|
||||
this._userId = undefined;
|
||||
this._user = undefined;
|
||||
this._isAdmin = undefined;
|
||||
@@ -466,7 +466,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
if (newUsername) {
|
||||
try {
|
||||
await adminChangeUsername(this.hass, this._user.id, newUsername);
|
||||
this._params?.refreshUsers?.();
|
||||
this._params?.refreshUsers();
|
||||
this._user = { ...this._user, username: newUsername };
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
@@ -500,7 +500,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
],
|
||||
local_only: this._localOnly,
|
||||
});
|
||||
this._params?.refreshUsers?.();
|
||||
this._params?.refreshUsers();
|
||||
}
|
||||
const values: PersonMutableParams = {
|
||||
name: this._name.trim(),
|
||||
@@ -509,9 +509,9 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
picture: this._picture,
|
||||
};
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry?.(values);
|
||||
await this._params!.updateEntry(values);
|
||||
} else {
|
||||
await this._params!.createEntry?.(values);
|
||||
await this._params!.createEntry(values);
|
||||
this._personExists = true;
|
||||
}
|
||||
this.closeDialog();
|
||||
@@ -525,7 +525,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
private async _deleteEntry() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
if (await this._params!.removeEntry?.()) {
|
||||
if (await this._params!.removeEntry()) {
|
||||
if (this._params!.entry!.user_id) {
|
||||
deleteUser(this.hass, this._params!.entry!.user_id);
|
||||
}
|
||||
|
@@ -4,22 +4,22 @@ import type { User } from "../../../data/user";
|
||||
|
||||
export interface PersonDetailDialogParams {
|
||||
entry?: Person;
|
||||
users?: User[];
|
||||
refreshUsers?: () => void;
|
||||
createEntry?: (values: PersonMutableParams) => Promise<unknown>;
|
||||
updateEntry?: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
|
||||
removeEntry?: () => Promise<boolean>;
|
||||
users: User[];
|
||||
refreshUsers: () => void;
|
||||
createEntry: (values: PersonMutableParams) => Promise<unknown>;
|
||||
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
|
||||
removeEntry: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const loadPersonDetailDialog = () => import("./dialog-person-detail");
|
||||
|
||||
export const showPersonDetailDialog = (
|
||||
element: HTMLElement,
|
||||
params: PersonDetailDialogParams
|
||||
systemLogDetailParams: PersonDetailDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-person-detail",
|
||||
dialogImport: loadPersonDetailDialog,
|
||||
dialogParams: params,
|
||||
dialogParams: systemLogDetailParams,
|
||||
});
|
||||
};
|
||||
|
@@ -1,22 +1,14 @@
|
||||
import { mdiContentSave } from "@mdi/js";
|
||||
import { css, html, nothing, type CSSResultGroup } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-markdown";
|
||||
import { fetchBlueprints } from "../../../data/blueprint";
|
||||
import type { BlueprintScriptConfig } from "../../../data/script";
|
||||
import { saveFabStyles } from "../automation/styles";
|
||||
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
||||
|
||||
@customElement("blueprint-script-editor")
|
||||
export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {
|
||||
@property({ attribute: false }) public config!: BlueprintScriptConfig;
|
||||
|
||||
@property({ type: Boolean }) public saving = false;
|
||||
|
||||
@property({ type: Boolean }) public dirty = false;
|
||||
|
||||
protected get _config(): BlueprintScriptConfig {
|
||||
return this.config;
|
||||
}
|
||||
@@ -31,45 +23,12 @@ export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {
|
||||
></ha-markdown>`
|
||||
: nothing}
|
||||
${this.renderCard()}
|
||||
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveScript}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
`;
|
||||
}
|
||||
|
||||
private _saveScript() {
|
||||
fireEvent(this, "save-script");
|
||||
}
|
||||
|
||||
protected async _getBlueprints() {
|
||||
this._blueprints = await fetchBlueprints(this.hass, "script");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
HaBlueprintGenericEditor.styles,
|
||||
saveFabStyles,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: calc(100vh - 85px);
|
||||
min-height: calc(100dvh - 85px);
|
||||
}
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@@ -358,61 +358,59 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
</ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div class=${this._mode === "yaml" ? "yaml-mode" : ""}>
|
||||
<div class="error-wrapper">
|
||||
${this._errors || stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${stateObj?.state === UNAVAILABLE
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.script.editor.unavailable"
|
||||
)
|
||||
: undefined}
|
||||
>
|
||||
${this._errors || this._validationErrors}
|
||||
${stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiRobotConfused}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
<div
|
||||
class="content ${classMap({
|
||||
"yaml-mode": this._mode === "yaml",
|
||||
})}"
|
||||
>
|
||||
${this._errors || stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${stateObj?.state === UNAVAILABLE
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.script.editor.unavailable"
|
||||
)
|
||||
: undefined}
|
||||
>
|
||||
${this._errors || this._validationErrors}
|
||||
${stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiRobotConfused}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${this._blueprintConfig
|
||||
? html`<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.confirm_take_control"
|
||||
)}
|
||||
<div slot="action" style="display: flex;">
|
||||
<ha-button appearance="plain" @click=${this._takeControlSave}
|
||||
>${this.hass.localize("ui.common.yes")}</ha-button
|
||||
>
|
||||
<ha-button appearance="plain" @click=${this._revertBlueprint}
|
||||
>${this.hass.localize("ui.common.no")}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-alert>`
|
||||
: this._readOnly
|
||||
? html`<ha-alert alert-type="warning" dismissable
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.script.editor.read_only"
|
||||
)}
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="action"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.migrate"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
${this._blueprintConfig
|
||||
? html`<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.confirm_take_control"
|
||||
)}
|
||||
<div slot="action" style="display: flex;">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._takeControlSave}
|
||||
>${this.hass.localize("ui.common.yes")}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._revertBlueprint}
|
||||
>${this.hass.localize("ui.common.no")}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-alert>`
|
||||
: this._readOnly
|
||||
? html`<ha-alert alert-type="warning" dismissable
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.script.editor.read_only"
|
||||
)}
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="action"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.migrate"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._mode === "gui"
|
||||
? html`
|
||||
<div
|
||||
@@ -428,10 +426,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
.isWide=${this.isWide}
|
||||
.config=${this._config}
|
||||
.disabled=${this._readOnly}
|
||||
.saving=${this._saving}
|
||||
.dirty=${this._dirty}
|
||||
@value-changed=${this._valueChanged}
|
||||
@save-script=${this._handleSaveScript}
|
||||
></blueprint-script-editor>
|
||||
`
|
||||
: html`
|
||||
@@ -442,40 +437,39 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
.config=${this._config}
|
||||
.disabled=${this._readOnly}
|
||||
.dirty=${this._dirty}
|
||||
.saving=${this._saving}
|
||||
@value-changed=${this._valueChanged}
|
||||
@editor-save=${this._handleSaveScript}
|
||||
@save-script=${this._handleSaveScript}
|
||||
@editor-save=${this._handleSave}
|
||||
></manual-script-editor>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
: this._mode === "yaml"
|
||||
? html`<ha-yaml-editor
|
||||
copy-clipboard
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this._readOnly}
|
||||
disable-fullscreen
|
||||
@value-changed=${this._yamlChanged}
|
||||
@editor-save=${this._handleSaveScript}
|
||||
.showErrors=${false}
|
||||
></ha-yaml-editor>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${!this._readOnly && this._dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.disabled=${this._saving}
|
||||
extended
|
||||
@click=${this._handleSaveScript}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiContentSave}
|
||||
></ha-svg-icon>
|
||||
</ha-fab>`
|
||||
copy-clipboard
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this._readOnly}
|
||||
disable-fullscreen
|
||||
@value-changed=${this._yamlChanged}
|
||||
@editor-save=${this._handleSave}
|
||||
.showErrors=${false}
|
||||
></ha-yaml-editor>`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${classMap({
|
||||
dirty: !this._readOnly && this._dirty,
|
||||
})}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.save_script"
|
||||
)}
|
||||
.disabled=${this._saving}
|
||||
extended
|
||||
@click=${this._handleSave}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -911,7 +905,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
});
|
||||
}
|
||||
|
||||
private async _handleSaveScript() {
|
||||
private async _handleSave() {
|
||||
if (this._yamlErrors) {
|
||||
showToast(this, {
|
||||
message: this._yamlErrors,
|
||||
@@ -1018,7 +1012,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
|
||||
protected supportedShortcuts(): SupportedShortcuts {
|
||||
return {
|
||||
s: () => this._handleSaveScript(),
|
||||
s: () => this._handleSave(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1034,40 +1028,33 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.errors {
|
||||
padding: 20px;
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--error-color);
|
||||
}
|
||||
.yaml-mode {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.config-container,
|
||||
manual-script-editor,
|
||||
blueprint-script-editor {
|
||||
blueprint-script-editor,
|
||||
:not(.yaml-mode) > ha-alert {
|
||||
margin: 0 auto;
|
||||
max-width: 1040px;
|
||||
padding: 28px 20px 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:not(.yaml-mode) > .error-wrapper {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
z-index: 3;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.config-container ha-alert {
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
:not(.yaml-mode) > .error-wrapper ha-alert {
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
}
|
||||
|
||||
manual-script-editor {
|
||||
max-width: 1540px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
ha-yaml-editor {
|
||||
flex-grow: 1;
|
||||
--actions-border-radius: 0;
|
||||
@@ -1076,20 +1063,16 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
position: relative;
|
||||
bottom: calc(-80px - var(--safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
ha-fab.dirty {
|
||||
bottom: 16px;
|
||||
bottom: 0;
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
@@ -1122,8 +1105,4 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-script-editor": HaScriptEditor;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"save-script": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -1,187 +0,0 @@
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../components/ha-form/types";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { Field } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("ha-script-field-editor")
|
||||
export default class HaScriptFieldEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public key!: string;
|
||||
|
||||
@property({ attribute: false, type: Array }) public excludeKeys: string[] =
|
||||
[];
|
||||
|
||||
@property({ attribute: false }) public field!: Field;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@state() private _uiError?: Record<string, string>;
|
||||
|
||||
@state() private _yamlError?: undefined | "yaml_error" | "key_not_unique";
|
||||
|
||||
private _errorKey?: string;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
name: "name",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "required",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const schema = this._schema();
|
||||
const data = { ...this.field, key: this._errorKey ?? this.key };
|
||||
|
||||
const yamlValue = { [this.key]: this.field };
|
||||
|
||||
return html`
|
||||
${this.yamlMode
|
||||
? html`${this._yamlError
|
||||
? html`<ha-alert alert-type="error">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.script.editor.field.${this._yamlError}`
|
||||
)}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${yamlValue}
|
||||
@value-changed=${this._onYamlChange}
|
||||
></ha-yaml-editor>`
|
||||
: html`<ha-form
|
||||
.schema=${schema}
|
||||
.data=${data}
|
||||
.error=${this._uiError}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.computeError=${this._computeError}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _maybeSetKey(value): void {
|
||||
const nameChanged = value.name !== this.field.name;
|
||||
const keyChanged = value.key !== this.key;
|
||||
if (!nameChanged || keyChanged) {
|
||||
return;
|
||||
}
|
||||
const slugifyName = this.field.name
|
||||
? slugify(this.field.name)
|
||||
: this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field";
|
||||
const regex = new RegExp(`^${slugifyName}(_\\d)?$`);
|
||||
if (regex.test(this.key)) {
|
||||
let key = !value.name
|
||||
? this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field"
|
||||
: slugify(value.name);
|
||||
if (this.excludeKeys.includes(key)) {
|
||||
let uniqueKey = key;
|
||||
let i = 2;
|
||||
do {
|
||||
uniqueKey = `${key}_${i}`;
|
||||
i++;
|
||||
} while (this.excludeKeys.includes(uniqueKey));
|
||||
key = uniqueKey;
|
||||
}
|
||||
value.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
this._maybeSetKey(value);
|
||||
|
||||
// Don't allow to set an empty key, or duplicate an existing key.
|
||||
if (!value.key || this.excludeKeys.includes(value.key)) {
|
||||
this._uiError = value.key
|
||||
? {
|
||||
key: "key_not_unique",
|
||||
}
|
||||
: {
|
||||
key: "key_not_null",
|
||||
};
|
||||
this._errorKey = value.key ?? "";
|
||||
return;
|
||||
}
|
||||
this._errorKey = undefined;
|
||||
this._uiError = undefined;
|
||||
|
||||
// If we render the default with an incompatible selector, it risks throwing an exception and not rendering.
|
||||
// Clear the default when changing the selector type.
|
||||
if (
|
||||
Object.keys(this.field.selector)[0] !== Object.keys(value.selector)[0]
|
||||
) {
|
||||
delete value.default;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
private _onYamlChange(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
if (typeof value !== "object" || Object.keys(value).length !== 1) {
|
||||
this._yamlError = "yaml_error";
|
||||
return;
|
||||
}
|
||||
const key = Object.keys(value)[0];
|
||||
if (this.excludeKeys.includes(key)) {
|
||||
this._yamlError = "key_not_unique";
|
||||
return;
|
||||
}
|
||||
this._yamlError = undefined;
|
||||
|
||||
const newValue = { ...value[key], key };
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string =>
|
||||
this.hass.localize(`ui.panel.config.script.editor.field.${schema.name}`);
|
||||
|
||||
private _computeError = (error: string) =>
|
||||
this.hass.localize(`ui.panel.config.script.editor.field.${error}` as any) ||
|
||||
error;
|
||||
|
||||
static styles = haStyle;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-script-field-editor": HaScriptFieldEditor;
|
||||
}
|
||||
}
|
@@ -1,24 +1,27 @@
|
||||
import { mdiDelete, mdiDotsVertical } from "@mdi/js";
|
||||
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import { mdiDelete, mdiDotsVertical, mdiPlaylistEdit } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { preventDefaultStopPropagation } from "../../../common/dom/prevent_default_stop_propagation";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-automation-row";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-form/ha-form";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import "../../../components/ha-list-item";
|
||||
import type { SchemaUnion } from "../../../components/ha-form/types";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-button-menu";
|
||||
import "../../../components/ha-md-menu-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../data/automation";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { Field } from "../../../data/script";
|
||||
import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./ha-script-field-selector-editor";
|
||||
|
||||
const preventDefault = (ev) => ev.preventDefault();
|
||||
|
||||
@customElement("ha-script-field-row")
|
||||
export default class HaScriptFieldRow extends LitElement {
|
||||
@@ -33,38 +36,61 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
@state() private _uiError?: Record<string, string>;
|
||||
|
||||
@state() private _yamlError?: undefined | "yaml_error" | "key_not_unique";
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@state() private _selected = false;
|
||||
private _errorKey?: string;
|
||||
|
||||
@state() private _collapsed = false;
|
||||
|
||||
@state() private _selectorRowSelected = false;
|
||||
|
||||
@state() private _selectorRowCollapsed = false;
|
||||
private _schema = memoizeOne(
|
||||
(selector: any) =>
|
||||
[
|
||||
{
|
||||
name: "name",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "selector",
|
||||
selector: { selector: {} },
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
selector: selector && typeof selector === "object" ? selector : {},
|
||||
},
|
||||
{
|
||||
name: "required",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const schema = this._schema(this.field.selector);
|
||||
const data = { ...this.field, key: this._errorKey ?? this.key };
|
||||
|
||||
const yamlValue = { [this.key]: this.field };
|
||||
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
<ha-automation-row
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._toggleSidebar}
|
||||
.selected=${this._selected}
|
||||
left-chevron
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
.collapsed=${this._collapsed}
|
||||
>
|
||||
<ha-expansion-panel left-chevron>
|
||||
<h3 slot="header">${this.key}</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
<ha-md-button-menu
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
fixed
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -72,9 +98,19 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPlaylistEdit}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
<ha-list-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
graphic="icon"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
@@ -85,144 +121,54 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
slot="graphic"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-automation-row>
|
||||
</ha-card>
|
||||
<div
|
||||
class=${classMap({
|
||||
"selector-row": true,
|
||||
"parent-selected": this._selected,
|
||||
hidden: this._collapsed,
|
||||
})}
|
||||
>
|
||||
<ha-card>
|
||||
<ha-automation-row
|
||||
.selected=${this._selectorRowSelected}
|
||||
@click=${this._toggleSelectorSidebar}
|
||||
.collapsed=${this._selectorRowCollapsed}
|
||||
@toggle-collapsed=${this._toggleSelectorRowCollapse}
|
||||
.leftChevron=${SELECTOR_SELECTOR_BUILDING_BLOCKS.includes(
|
||||
Object.keys(this.field.selector)[0]
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
})}
|
||||
>
|
||||
<h3 slot="header">
|
||||
${this.hass.localize(
|
||||
`ui.components.selectors.selector.types.${Object.keys(this.field.selector)[0]}` as LocalizeKeys
|
||||
)}
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.selector"
|
||||
)}
|
||||
</h3>
|
||||
</ha-automation-row>
|
||||
</ha-card>
|
||||
${typeof this.field.selector === "object" &&
|
||||
SELECTOR_SELECTOR_BUILDING_BLOCKS.includes(
|
||||
Object.keys(this.field.selector)[0]
|
||||
)
|
||||
? html`
|
||||
<ha-script-field-selector-editor
|
||||
class=${this._selectorRowCollapsed ? "hidden" : ""}
|
||||
.selected=${this._selectorRowSelected}
|
||||
.hass=${this.hass}
|
||||
.field=${this.field}
|
||||
.disabled=${this.disabled}
|
||||
indent
|
||||
@value-changed=${this._selectorValueChanged}
|
||||
.narrow=${this.narrow}
|
||||
></ha-script-field-selector-editor>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._yamlMode
|
||||
? html` ${this._yamlError
|
||||
? html`<ha-alert alert-type="error">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.script.editor.field.${this._yamlError}`
|
||||
)}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${yamlValue}
|
||||
@value-changed=${this._onYamlChange}
|
||||
></ha-yaml-editor>`
|
||||
: html`<ha-form
|
||||
.schema=${schema}
|
||||
.data=${data}
|
||||
.error=${this._uiError}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.computeError=${this._computeError}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`}
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleCollapse() {
|
||||
this._collapsed = !this._collapsed;
|
||||
}
|
||||
|
||||
private _toggleSelectorRowCollapse() {
|
||||
this._selectorRowCollapsed = !this._selectorRowCollapsed;
|
||||
}
|
||||
|
||||
private _toggleSidebar(ev: Event) {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selected) {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
return;
|
||||
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._yamlMode = !this._yamlMode;
|
||||
break;
|
||||
case 1:
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
|
||||
this._selected = true;
|
||||
this.openSidebar();
|
||||
}
|
||||
|
||||
private _toggleSelectorSidebar(ev: Event) {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selectorRowSelected) {
|
||||
this._selectorRowSelected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectorRowSelected = true;
|
||||
this.openSidebar(true);
|
||||
}
|
||||
|
||||
private _selectorValueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.field,
|
||||
key: this.key,
|
||||
...ev.detail.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public openSidebar(selectorEditor = false): void {
|
||||
if (!selectorEditor) {
|
||||
this._selected = true;
|
||||
}
|
||||
if (this.narrow) {
|
||||
this.scrollIntoView();
|
||||
}
|
||||
|
||||
fireEvent(this, "open-sidebar", {
|
||||
save: (value) => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
},
|
||||
close: () => {
|
||||
if (selectorEditor) {
|
||||
this._selectorRowSelected = false;
|
||||
} else {
|
||||
this._selected = false;
|
||||
}
|
||||
fireEvent(this, "close-sidebar");
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
},
|
||||
delete: this._onDelete,
|
||||
config: {
|
||||
field: this.field,
|
||||
selector: selectorEditor,
|
||||
key: this.key,
|
||||
excludeKeys: this.excludeKeys,
|
||||
},
|
||||
yamlMode: this._yamlMode,
|
||||
} satisfies ScriptFieldSidebarConfig);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
this._yamlMode = !this._yamlMode;
|
||||
};
|
||||
|
||||
private _onDelete = () => {
|
||||
private _onDelete() {
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.script.editor.field_delete_confirm_title"
|
||||
@@ -235,13 +181,112 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
destructive: true,
|
||||
confirm: () => {
|
||||
fireEvent(this, "value-changed", { value: null });
|
||||
if (this._selected || this._selectorRowSelected) {
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _onYamlChange(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
if (typeof value !== "object" || Object.keys(value).length !== 1) {
|
||||
this._yamlError = "yaml_error";
|
||||
return;
|
||||
}
|
||||
const key = Object.keys(value)[0];
|
||||
if (this.excludeKeys.includes(key)) {
|
||||
this._yamlError = "key_not_unique";
|
||||
return;
|
||||
}
|
||||
this._yamlError = undefined;
|
||||
|
||||
const newValue = { ...value[key], key };
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _maybeSetKey(value): void {
|
||||
const nameChanged = value.name !== this.field.name;
|
||||
const keyChanged = value.key !== this.key;
|
||||
if (!nameChanged || keyChanged) {
|
||||
return;
|
||||
}
|
||||
const slugifyName = this.field.name
|
||||
? slugify(this.field.name)
|
||||
: this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field";
|
||||
const regex = new RegExp(`^${slugifyName}(_\\d)?$`);
|
||||
if (regex.test(this.key)) {
|
||||
let key = !value.name
|
||||
? this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field"
|
||||
: slugify(value.name);
|
||||
if (this.excludeKeys.includes(key)) {
|
||||
let uniqueKey = key;
|
||||
let i = 2;
|
||||
do {
|
||||
uniqueKey = `${key}_${i}`;
|
||||
i++;
|
||||
} while (this.excludeKeys.includes(uniqueKey));
|
||||
key = uniqueKey;
|
||||
}
|
||||
value.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
this._maybeSetKey(value);
|
||||
|
||||
// Don't allow to set an empty key, or duplicate an existing key.
|
||||
if (!value.key || this.excludeKeys.includes(value.key)) {
|
||||
this._uiError = value.key
|
||||
? {
|
||||
key: "key_not_unique",
|
||||
}
|
||||
: {
|
||||
key: "key_not_null",
|
||||
};
|
||||
this._errorKey = value.key ?? "";
|
||||
return;
|
||||
}
|
||||
this._errorKey = undefined;
|
||||
this._uiError = undefined;
|
||||
|
||||
// If we render the default with an incompatible selector, it risks throwing an exception and not rendering.
|
||||
// Clear the default when changing the selector type.
|
||||
if (
|
||||
Object.keys(this.field.selector)[0] !== Object.keys(value.selector)[0]
|
||||
) {
|
||||
delete value.default;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
public expand() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true;
|
||||
});
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string => {
|
||||
switch (schema.name) {
|
||||
default:
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.script.editor.field.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private _computeError = (error: string) =>
|
||||
this.hass.localize(`ui.panel.config.script.editor.field.${error}` as any) ||
|
||||
error;
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -254,8 +299,9 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0 0 0 8px;
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
@@ -295,7 +341,7 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
ha-md-menu-item[disabled] {
|
||||
ha-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
.warning ul {
|
||||
@@ -313,18 +359,6 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
border-color: var(--state-inactive-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
.selector-row {
|
||||
margin-left: 12px;
|
||||
padding: 12px 4px 16px 16px;
|
||||
margin-right: -4px;
|
||||
border-left: 2px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
.selector-row.parent-selected {
|
||||
border-color: var(--primary-color);
|
||||
background-color: var(--ha-color-fill-primary-quiet-resting);
|
||||
border-top-right-radius: var(--ha-border-radius-xl);
|
||||
border-bottom-right-radius: var(--ha-border-radius-xl);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,167 +0,0 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../components/ha-form/types";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { Field } from "../../../data/script";
|
||||
import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("ha-script-field-selector-editor")
|
||||
export default class HaScriptFieldSelectorEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public field!: Field;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public selected = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public indent = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "yaml-mode" }) public yamlMode = false;
|
||||
|
||||
@state() private _uiError?: Record<string, string>;
|
||||
|
||||
@state() private _yamlError?: undefined | "yaml_error" | "key_not_unique";
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(selector: any) =>
|
||||
[
|
||||
...(!this.indent
|
||||
? [
|
||||
{
|
||||
name: "selector",
|
||||
selector: { selector: {} },
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(selector &&
|
||||
typeof selector === "object" &&
|
||||
(this.indent ||
|
||||
!SELECTOR_SELECTOR_BUILDING_BLOCKS.includes(Object.keys(selector)[0]))
|
||||
? [
|
||||
{
|
||||
name: "default",
|
||||
selector: !this.indent
|
||||
? selector
|
||||
: {
|
||||
[Object.keys(selector)[0]]: {
|
||||
...selector[Object.keys(selector)[0]],
|
||||
optionsInSidebar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const schema = this._schema(this.field.selector);
|
||||
const data = { selector: this.field.selector, default: this.field.default };
|
||||
|
||||
return html`
|
||||
${this.yamlMode
|
||||
? html`${this._yamlError
|
||||
? html`<ha-alert alert-type="error">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.script.editor.field.${this._yamlError}`
|
||||
)}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${data}
|
||||
@value-changed=${this._onYamlChange}
|
||||
></ha-yaml-editor>`
|
||||
: html`<ha-form
|
||||
.schema=${schema}
|
||||
.data=${data}
|
||||
.error=${this._uiError}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.computeError=${this._computeError}
|
||||
@value-changed=${this._valueChanged}
|
||||
.narrow=${this.narrow}
|
||||
></ha-form>`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
this._uiError = undefined;
|
||||
|
||||
// If we render the default with an incompatible selector, it risks throwing an exception and not rendering.
|
||||
// Clear the default when changing the selector type.
|
||||
if (
|
||||
!this.indent &&
|
||||
Object.keys(this.field.selector)[0] !== Object.keys(value.selector)[0]
|
||||
) {
|
||||
value.default = undefined;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
private _onYamlChange(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
if (typeof value !== "object" || Object.keys(value).length !== 2) {
|
||||
this._yamlError = "yaml_error";
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.script.editor.field.${schema.name}` as LocalizeKeys
|
||||
) ?? schema.name;
|
||||
|
||||
private _computeError = (error: string) =>
|
||||
this.hass.localize(`ui.panel.config.script.editor.field.${error}` as any) ||
|
||||
error;
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host([indent]) ha-form {
|
||||
display: block;
|
||||
margin-left: 12px;
|
||||
padding: 12px 20px 16px 16px;
|
||||
margin-right: -4px;
|
||||
border-left: 2px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
:host([selected]) ha-form {
|
||||
border-color: var(--primary-color);
|
||||
background-color: var(--ha-color-fill-primary-quiet-resting);
|
||||
border-top-right-radius: var(--ha-border-radius-xl);
|
||||
border-bottom-right-radius: var(--ha-border-radius-xl);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-script-field-selector-editor": HaScriptFieldSelectorEditor;
|
||||
}
|
||||
}
|
@@ -21,8 +21,6 @@ export default class HaScriptFields extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public highlightedFields?: Fields;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
private _focusLastActionOnChange = false;
|
||||
|
||||
protected render() {
|
||||
@@ -41,7 +39,6 @@ export default class HaScriptFields extends LitElement {
|
||||
@value-changed=${this._fieldChanged}
|
||||
.hass=${this.hass}
|
||||
?highlight=${this.highlightedFields?.[key] !== undefined}
|
||||
.narrow=${this.narrow}
|
||||
>
|
||||
</ha-script-field-row>
|
||||
`
|
||||
@@ -74,11 +71,8 @@ export default class HaScriptFields extends LitElement {
|
||||
"ha-script-field-row:last-of-type"
|
||||
)!;
|
||||
row.updateComplete.then(() => {
|
||||
row.openSidebar();
|
||||
|
||||
if (this.narrow) {
|
||||
row.scrollIntoView();
|
||||
}
|
||||
row.expand();
|
||||
row.scrollIntoView();
|
||||
row.focus();
|
||||
});
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { mdiContentSave, mdiHelpCircle } from "@mdi/js";
|
||||
import { load } from "js-yaml";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { load } from "js-yaml";
|
||||
import {
|
||||
any,
|
||||
array,
|
||||
@@ -14,27 +13,32 @@ import {
|
||||
optional,
|
||||
string,
|
||||
} from "superstruct";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { canOverrideAlphanumericInput } from "../../../common/dom/can-override-input";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { constructUrlCurrentPath } from "../../../common/url/construct-url";
|
||||
import {
|
||||
extractSearchParam,
|
||||
removeSearchParam,
|
||||
} from "../../../common/url/search-params";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-markdown";
|
||||
import type { SidebarConfig } from "../../../data/automation";
|
||||
import type { Action, Fields, ScriptConfig } from "../../../data/script";
|
||||
import {
|
||||
getActionType,
|
||||
MODES,
|
||||
normalizeScriptConfig,
|
||||
} from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "../automation/action/ha-automation-action";
|
||||
import "../automation/ha-automation-sidebar";
|
||||
import { showPasteReplaceDialog } from "../automation/paste-replace-dialog/show-dialog-paste-replace";
|
||||
import { saveFabStyles } from "../automation/styles";
|
||||
import type HaAutomationAction from "../automation/action/ha-automation-action";
|
||||
import "./ha-script-fields";
|
||||
import type HaScriptFields from "./ha-script-fields";
|
||||
import { canOverrideAlphanumericInput } from "../../../common/dom/can-override-input";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showPasteReplaceDialog } from "../automation/paste-replace-dialog/show-dialog-paste-replace";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
|
||||
const scriptConfigStruct = object({
|
||||
alias: optional(string()),
|
||||
@@ -56,8 +60,6 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public saving = false;
|
||||
|
||||
@property({ attribute: false }) public config!: ScriptConfig;
|
||||
|
||||
@property({ attribute: false }) public dirty = false;
|
||||
@@ -69,8 +71,6 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
@state() private _pastedConfig?: ScriptConfig;
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
|
||||
private _previousConfig?: ScriptConfig;
|
||||
|
||||
public addFields() {
|
||||
@@ -99,19 +99,41 @@ export class HaManualScriptEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _renderContent() {
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
const expanded = extractSearchParam("expanded");
|
||||
if (expanded === "1") {
|
||||
this._clearParam("expanded");
|
||||
const items = this.shadowRoot!.querySelectorAll<HaAutomationAction>(
|
||||
"ha-automation-action"
|
||||
);
|
||||
|
||||
items.forEach((el) => {
|
||||
el.updateComplete.then(() => {
|
||||
el.expandAll();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _clearParam(param: string) {
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
constructUrlCurrentPath(removeSearchParam(param))
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${
|
||||
this.config.description
|
||||
? html`<ha-markdown
|
||||
class="description"
|
||||
breaks
|
||||
.content=${this.config.description}
|
||||
></ha-markdown>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this.config.fields
|
||||
${this.config.description
|
||||
? html`<ha-markdown
|
||||
class="description"
|
||||
breaks
|
||||
.content=${this.config.description}
|
||||
></ha-markdown>`
|
||||
: nothing}
|
||||
${this.config.fields
|
||||
? html`<div class="header">
|
||||
<h2 id="fields-heading" class="name">
|
||||
${this.hass.localize(
|
||||
@@ -143,77 +165,39 @@ export class HaManualScriptEditor extends LitElement {
|
||||
@value-changed=${this._fieldsChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.narrow=${this.narrow}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
></ha-script-fields>`
|
||||
: nothing
|
||||
}
|
||||
: nothing}
|
||||
|
||||
<div class="header">
|
||||
<h2 id="sequence-heading" class="name">
|
||||
${this.hass.localize("ui.panel.config.script.editor.sequence")}
|
||||
</h2>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/docs/scripts/")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.link_available_actions"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ha-automation-action
|
||||
role="region"
|
||||
aria-labelledby="sequence-heading"
|
||||
.actions=${this.config.sequence || []}
|
||||
.highlightedActions=${this._pastedConfig?.sequence || []}
|
||||
@value-changed=${this._sequenceChanged}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled || this.saving}
|
||||
root
|
||||
sidebar
|
||||
></ha-automation-action>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="split-view">
|
||||
<div class="content-wrapper">
|
||||
<div class="content">${this._renderContent()}</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveScript}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</div>
|
||||
<ha-automation-sidebar
|
||||
class=${classMap({
|
||||
sidebar: true,
|
||||
hidden: !this._sidebarConfig,
|
||||
overlay: !this.isWide,
|
||||
})}
|
||||
.isWide=${this.isWide}
|
||||
.hass=${this.hass}
|
||||
.config=${this._sidebarConfig}
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-automation-sidebar>
|
||||
<div class="header">
|
||||
<h2 id="sequence-heading" class="name">
|
||||
${this.hass.localize("ui.panel.config.script.editor.sequence")}
|
||||
</h2>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/docs/scripts/")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.link_available_actions"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ha-automation-action
|
||||
role="region"
|
||||
aria-labelledby="sequence-heading"
|
||||
.actions=${this.config.sequence || []}
|
||||
.highlightedActions=${this._pastedConfig?.sequence || []}
|
||||
.path=${["sequence"]}
|
||||
@value-changed=${this._sequenceChanged}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
root
|
||||
></ha-automation-action>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -422,116 +406,22 @@ export class HaManualScriptEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _openSidebar(ev: CustomEvent<SidebarConfig>) {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
}
|
||||
|
||||
private _sidebarConfigChanged(ev: CustomEvent<{ value: SidebarConfig }>) {
|
||||
ev.stopPropagation();
|
||||
if (!this._sidebarConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sidebarConfig = {
|
||||
...this._sidebarConfig,
|
||||
...ev.detail.value,
|
||||
};
|
||||
}
|
||||
|
||||
private _closeSidebar() {
|
||||
if (this._sidebarConfig) {
|
||||
const closeRow = this._sidebarConfig?.close;
|
||||
this._sidebarConfig = undefined;
|
||||
closeRow?.();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleCloseSidebar() {
|
||||
this._sidebarConfig = undefined;
|
||||
}
|
||||
|
||||
private _saveScript() {
|
||||
this._closeSidebar();
|
||||
fireEvent(this, "save-script");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
saveFabStyles,
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.split-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
flex: 6;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px 16px 64px 0;
|
||||
height: calc(100vh - 153px);
|
||||
height: calc(100dvh - 153px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 12px 0;
|
||||
flex: 4;
|
||||
height: calc(100vh - 81px);
|
||||
height: calc(100dvh - 81px);
|
||||
width: 40%;
|
||||
}
|
||||
.sidebar.hidden {
|
||||
border-color: transparent;
|
||||
border-width: 0;
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
flex: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.sidebar.overlay {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: calc(100% - 64px);
|
||||
padding: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
.sidebar.overlay {
|
||||
max-height: 70vh;
|
||||
max-height: 70dvh;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
.sidebar.overlay.hidden {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar.overlay.hidden {
|
||||
width: 0;
|
||||
}
|
||||
.description {
|
||||
margin: 0;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@@ -34,7 +34,6 @@ class DialogHomeZoneDetail extends LitElement {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._data = {
|
||||
name: this.hass.config.location_name,
|
||||
latitude: this.hass.config.latitude,
|
||||
longitude: this.hass.config.longitude,
|
||||
radius: this.hass.config.radius,
|
||||
@@ -64,7 +63,7 @@ class DialogHomeZoneDetail extends LitElement {
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass!.localize("ui.common.edit_item", { name: this._data.name })
|
||||
this.hass!.localize("ui.panel.config.zone.edit_home")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
|
@@ -85,9 +85,7 @@ class DialogZoneDetail extends LitElement {
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.entry
|
||||
? this.hass!.localize("ui.common.edit_item", {
|
||||
name: this._params.entry.name,
|
||||
})
|
||||
? this._params.entry.name
|
||||
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")
|
||||
)}
|
||||
>
|
||||
|
@@ -165,9 +165,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
.entry=${entry}
|
||||
@click=${this._openEditEntry}
|
||||
.path=${mdiPencil}
|
||||
.label=${hass.localize("ui.common.edit_item", {
|
||||
name: entry.name,
|
||||
})}
|
||||
.label=${hass.localize(
|
||||
"ui.panel.config.zone.edit_zone"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
@@ -218,9 +218,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
this._canEditCore
|
||||
? mdiPencil
|
||||
: mdiPencilOff}
|
||||
.label=${hass.localize("ui.common.edit_item", {
|
||||
name: hass.config.location_name,
|
||||
})}
|
||||
.label=${stateObject.entity_id === "zone.home"
|
||||
? hass.localize("ui.panel.config.zone.edit_home")
|
||||
: hass.localize("ui.panel.config.zone.edit_zone")}
|
||||
@click=${this._editHomeZone}
|
||||
></ha-icon-button>
|
||||
</ha-tooltip>`}
|
||||
|
@@ -1,323 +0,0 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import {
|
||||
mdiPause,
|
||||
mdiPlay,
|
||||
mdiPlayPause,
|
||||
mdiPower,
|
||||
mdiSkipNext,
|
||||
mdiSkipPrevious,
|
||||
mdiStop,
|
||||
} from "@mdi/js";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
MediaPlayerPlaybackCardFeatureConfig,
|
||||
} from "./types";
|
||||
import type {
|
||||
ControlButton,
|
||||
MediaPlayerEntity,
|
||||
} from "../../../data/media-player";
|
||||
import { MediaPlayerEntityFeature } from "../../../data/media-player";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { hasConfigChanged } from "../common/has-changed";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon";
|
||||
|
||||
export const supportsMediaPlayerPlaybackCardFeature = (
|
||||
hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
) => {
|
||||
const stateObj = context.entity_id
|
||||
? hass.states[context.entity_id]
|
||||
: undefined;
|
||||
if (!stateObj) return false;
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
return domain === "media_player";
|
||||
};
|
||||
|
||||
@customElement("hui-media-player-playback-card-feature")
|
||||
class HuiMediaPlayerPlaybackCardFeature
|
||||
extends LitElement
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@property({ attribute: false }) public color?: string;
|
||||
|
||||
@state() private _config?: MediaPlayerPlaybackCardFeatureConfig;
|
||||
|
||||
@state() private _narrow?: boolean = false;
|
||||
|
||||
private get _stateObj() {
|
||||
if (!this.hass || !this.context || !this.context.entity_id) {
|
||||
return undefined;
|
||||
}
|
||||
return this.hass.states[this.context.entity_id] as
|
||||
| MediaPlayerEntity
|
||||
| undefined;
|
||||
}
|
||||
|
||||
static getStubConfig(): MediaPlayerPlaybackCardFeatureConfig {
|
||||
return {
|
||||
type: "media-player-playback",
|
||||
};
|
||||
}
|
||||
|
||||
public setConfig(config: MediaPlayerPlaybackCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public willUpdate(): void {
|
||||
if (!this.hasUpdated) {
|
||||
this._measureCard();
|
||||
}
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
const entityId = this.context?.entity_id;
|
||||
return (
|
||||
hasConfigChanged(this, changedProps) ||
|
||||
(changedProps.has("hass") &&
|
||||
(!oldHass ||
|
||||
!entityId ||
|
||||
oldHass.states[entityId] !== this.hass!.states[entityId]))
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.context ||
|
||||
!supportsMediaPlayerPlaybackCardFeature(this.hass, this.context) ||
|
||||
!this._stateObj
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const buttons = this._computeButtons(this._stateObj);
|
||||
|
||||
return html`
|
||||
<ha-control-button-group>
|
||||
${supportsFeature(this._stateObj, MediaPlayerEntityFeature.TURN_OFF) &&
|
||||
stateActive(this._stateObj)
|
||||
? html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.media_player.turn_off")}
|
||||
@click=${this._togglePower}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: ""}
|
||||
${supportsFeature(this._stateObj, MediaPlayerEntityFeature.TURN_ON) &&
|
||||
!stateActive(this._stateObj) &&
|
||||
!isUnavailableState(this._stateObj.state)
|
||||
? html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.media_player.turn_on")}
|
||||
@click=${this._togglePower}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: buttons.map(
|
||||
(button) => html`
|
||||
<ha-control-button
|
||||
key=${button.action}
|
||||
.label=${this.hass?.localize(
|
||||
`ui.card.media_player.${button.action}`
|
||||
)}
|
||||
@click=${this._action}
|
||||
>
|
||||
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
)}
|
||||
</ha-control-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
private _measureCard() {
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
const host = (this.getRootNode() as ShadowRoot).host as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const width = host?.clientWidth ?? this.clientWidth ?? 0;
|
||||
this._narrow = width < 300;
|
||||
}
|
||||
|
||||
private _computeControlButton(stateObj: MediaPlayerEntity): ControlButton {
|
||||
return stateObj.state === "on"
|
||||
? { icon: mdiPlayPause, action: "media_play_pause" }
|
||||
: stateObj.state !== "playing"
|
||||
? { icon: mdiPlay, action: "media_play" }
|
||||
: supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE)
|
||||
? { icon: mdiPause, action: "media_pause" }
|
||||
: { icon: mdiStop, action: "media_stop" };
|
||||
}
|
||||
|
||||
private _computeButtons(stateObj: MediaPlayerEntity): ControlButton[] {
|
||||
const controlButton = this._computeControlButton(stateObj);
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
|
||||
const controls: ControlButton[] = [];
|
||||
|
||||
if (
|
||||
!this._narrow &&
|
||||
(stateObj.state === "playing" || assumedState) &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.PREVIOUS_TRACK)
|
||||
) {
|
||||
controls.push({ icon: mdiSkipPrevious, action: "media_previous_track" });
|
||||
}
|
||||
|
||||
if (
|
||||
!assumedState &&
|
||||
((stateObj.state === "playing" &&
|
||||
(supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE) ||
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.STOP))) ||
|
||||
((stateObj.state === "paused" || stateObj.state === "idle") &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY)) ||
|
||||
(stateObj.state === "on" &&
|
||||
(supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY) ||
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE))))
|
||||
) {
|
||||
controls.push({ icon: controlButton.icon, action: controlButton.action });
|
||||
}
|
||||
|
||||
if (assumedState) {
|
||||
if (supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY)) {
|
||||
controls.push({ icon: mdiPlay, action: "media_play" });
|
||||
}
|
||||
|
||||
if (supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE)) {
|
||||
controls.push({ icon: mdiPause, action: "media_pause" });
|
||||
}
|
||||
|
||||
if (supportsFeature(stateObj, MediaPlayerEntityFeature.STOP)) {
|
||||
controls.push({ icon: mdiStop, action: "media_stop" });
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(stateObj.state === "playing" || assumedState) &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.NEXT_TRACK)
|
||||
) {
|
||||
controls.push({ icon: mdiSkipNext, action: "media_next_track" });
|
||||
}
|
||||
|
||||
return controls;
|
||||
}
|
||||
|
||||
private _togglePower(): void {
|
||||
if (!this._stateObj) return;
|
||||
this.hass!.callService(
|
||||
"media_player",
|
||||
stateActive(this._stateObj) ? "turn_off" : "turn_on",
|
||||
{
|
||||
entity_id: this._stateObj.entity_id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _action(e: Event): void {
|
||||
const action = (e.currentTarget as HTMLElement).getAttribute("key");
|
||||
if (!action) return;
|
||||
|
||||
switch (action) {
|
||||
case "media_play_pause":
|
||||
this._playPauseStop();
|
||||
break;
|
||||
case "media_play":
|
||||
this._play();
|
||||
break;
|
||||
case "media_pause":
|
||||
this._pause();
|
||||
break;
|
||||
case "media_stop":
|
||||
this._stop();
|
||||
break;
|
||||
case "media_previous_track":
|
||||
this._previousTrack();
|
||||
break;
|
||||
case "media_next_track":
|
||||
this._nextTrack();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _playPauseStop(): void {
|
||||
if (!this._stateObj) return;
|
||||
|
||||
const service =
|
||||
this._stateObj.state !== "playing"
|
||||
? "media_play"
|
||||
: supportsFeature(this._stateObj, MediaPlayerEntityFeature.PAUSE)
|
||||
? "media_pause"
|
||||
: "media_stop";
|
||||
|
||||
this.hass!.callService("media_player", service, {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _play(): void {
|
||||
if (!this._stateObj) return;
|
||||
this.hass!.callService("media_player", "media_play", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _pause(): void {
|
||||
if (!this._stateObj) return;
|
||||
this.hass!.callService("media_player", "media_pause", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _stop(): void {
|
||||
if (!this._stateObj) return;
|
||||
this.hass!.callService("media_player", "media_stop", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _previousTrack(): void {
|
||||
if (!this._stateObj) return;
|
||||
this.hass!.callService("media_player", "media_previous_track", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _nextTrack(): void {
|
||||
if (!this._stateObj) return;
|
||||
this.hass!.callService("media_player", "media_next_track", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = cardFeatureStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-media-player-playback-card-feature": HuiMediaPlayerPlaybackCardFeature;
|
||||
}
|
||||
}
|
@@ -39,10 +39,6 @@ export interface LockOpenDoorCardFeatureConfig {
|
||||
type: "lock-open-door";
|
||||
}
|
||||
|
||||
export interface MediaPlayerPlaybackCardFeatureConfig {
|
||||
type: "media-player-playback";
|
||||
}
|
||||
|
||||
export interface MediaPlayerVolumeSliderCardFeatureConfig {
|
||||
type: "media-player-volume-slider";
|
||||
}
|
||||
@@ -246,7 +242,6 @@ export type LovelaceCardFeatureConfig =
|
||||
| LightColorTempCardFeatureConfig
|
||||
| LockCommandsCardFeatureConfig
|
||||
| LockOpenDoorCardFeatureConfig
|
||||
| MediaPlayerPlaybackCardFeatureConfig
|
||||
| MediaPlayerVolumeSliderCardFeatureConfig
|
||||
| NumericInputCardFeatureConfig
|
||||
| SelectOptionsCardFeatureConfig
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -84,8 +85,10 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const displayType =
|
||||
config.display_type || (config.show_camera ? "camera" : "picture");
|
||||
const vertical = displayType === "compact" ? config.vertical : false;
|
||||
this._config = {
|
||||
...config,
|
||||
vertical,
|
||||
display_type: displayType,
|
||||
};
|
||||
|
||||
@@ -109,7 +112,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
const featuresCount = this._config?.features?.length || 0;
|
||||
return (
|
||||
1 +
|
||||
(displayType === "compact" ? 0 : 2) +
|
||||
(displayType === "compact" ? (this._config?.vertical ? 1 : 0) : 2) +
|
||||
(featuresPosition === "inline" ? 0 : featuresCount)
|
||||
);
|
||||
}
|
||||
@@ -133,6 +136,11 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const displayType = this._config?.display_type || "picture";
|
||||
|
||||
if (this._config?.vertical) {
|
||||
rows++;
|
||||
min_columns = 3;
|
||||
}
|
||||
|
||||
if (displayType !== "compact") {
|
||||
if (featurePosition === "inline" && featuresCount > 0) {
|
||||
rows += 3;
|
||||
@@ -397,9 +405,12 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
return sensorStates;
|
||||
}
|
||||
|
||||
private _featurePosition = memoizeOne(
|
||||
(config: AreaCardConfig) => config.features_position || "bottom"
|
||||
);
|
||||
private _featurePosition = memoizeOne((config: AreaCardConfig) => {
|
||||
if (config.vertical) {
|
||||
return "bottom";
|
||||
}
|
||||
return config.features_position || "bottom";
|
||||
});
|
||||
|
||||
private _displayedFeatures = memoizeOne((config: AreaCardConfig) => {
|
||||
const features = config.features || [];
|
||||
@@ -439,6 +450,8 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
const icon = area.icon;
|
||||
|
||||
const name = this._config.name || computeAreaName(area);
|
||||
@@ -518,7 +531,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
</div>
|
||||
`}
|
||||
<div class="container ${containerOrientationClass}">
|
||||
<div class="content">
|
||||
<div class="content ${classMap(contentClasses)}">
|
||||
<ha-tile-icon>
|
||||
${displayType === "compact"
|
||||
? this._renderAlertSensorBadge()
|
||||
@@ -656,6 +669,16 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.vertical ha-tile-info {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
position: relative;
|
||||
|
@@ -251,8 +251,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
const entityId = this._config.entity;
|
||||
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<hui-warning .hass=${this.hass}>
|
||||
@@ -261,6 +259,8 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
const name = this._config.name || computeStateName(stateObj);
|
||||
const active = stateActive(stateObj);
|
||||
const color = this._computeStateColor(stateObj, this._config.color);
|
||||
|
@@ -104,12 +104,13 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
|
||||
state_color?: boolean;
|
||||
}
|
||||
|
||||
export type AreaCardDisplayType = "compact" | "icon" | "picture" | "camera";
|
||||
export interface AreaCardConfig extends LovelaceCardConfig {
|
||||
area?: string;
|
||||
name?: string;
|
||||
color?: string;
|
||||
navigation_path?: string;
|
||||
display_type?: "compact" | "icon" | "picture" | "camera";
|
||||
display_type?: AreaCardDisplayType;
|
||||
/** @deprecated Use `display_type` instead */
|
||||
show_camera?: boolean;
|
||||
camera_view?: HuiImage["cameraView"];
|
||||
|
@@ -22,7 +22,6 @@ import "../card-features/hui-light-brightness-card-feature";
|
||||
import "../card-features/hui-light-color-temp-card-feature";
|
||||
import "../card-features/hui-lock-commands-card-feature";
|
||||
import "../card-features/hui-lock-open-door-card-feature";
|
||||
import "../card-features/hui-media-player-playback-card-feature";
|
||||
import "../card-features/hui-media-player-volume-slider-card-feature";
|
||||
import "../card-features/hui-numeric-input-card-feature";
|
||||
import "../card-features/hui-select-options-card-feature";
|
||||
@@ -71,7 +70,6 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
|
||||
"light-color-temp",
|
||||
"lock-commands",
|
||||
"lock-open-door",
|
||||
"media-player-playback",
|
||||
"media-player-volume-slider",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
|
@@ -36,7 +36,7 @@ import {
|
||||
DEVICE_CLASSES,
|
||||
type AreaCardFeatureContext,
|
||||
} from "../../cards/hui-area-card";
|
||||
import type { AreaCardConfig } from "../../cards/types";
|
||||
import type { AreaCardConfig, AreaCardDisplayType } from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import type { EditDetailElementEvent, EditSubElementEvent } from "../types";
|
||||
@@ -52,6 +52,7 @@ const cardConfigStruct = assign(
|
||||
navigation_path: optional(string()),
|
||||
show_camera: optional(boolean()),
|
||||
display_type: optional(enums(["compact", "icon", "picture", "camera"])),
|
||||
vertical: optional(boolean()),
|
||||
camera_view: optional(string()),
|
||||
alert_classes: optional(array(string())),
|
||||
sensor_classes: optional(array(string())),
|
||||
@@ -78,7 +79,7 @@ export class HuiAreaCardEditor
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
showCamera: boolean,
|
||||
displayType: AreaCardDisplayType,
|
||||
binaryClasses: SelectOption[],
|
||||
sensorClasses: SelectOption[]
|
||||
) =>
|
||||
@@ -113,7 +114,28 @@ export class HuiAreaCardEditor
|
||||
},
|
||||
},
|
||||
},
|
||||
...(showCamera
|
||||
...(displayType === "compact"
|
||||
? ([
|
||||
{
|
||||
name: "content_layout",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
options: ["horizontal", "vertical"].map(
|
||||
(value) => ({
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.card.area.content_layout_options.${value}`
|
||||
),
|
||||
value,
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
...(displayType === "camera"
|
||||
? ([
|
||||
{
|
||||
name: "camera_view",
|
||||
@@ -282,7 +304,7 @@ export class HuiAreaCardEditor
|
||||
}
|
||||
|
||||
private _featuresSchema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
(localize: LocalizeFunc, vertical: boolean) =>
|
||||
[
|
||||
{
|
||||
name: "features_position",
|
||||
@@ -303,6 +325,7 @@ export class HuiAreaCardEditor
|
||||
src_dark: `/static/images/form/tile_features_position_${value}_dark.svg`,
|
||||
flip_rtl: true,
|
||||
},
|
||||
disabled: vertical && value === "inline",
|
||||
})),
|
||||
},
|
||||
},
|
||||
@@ -338,31 +361,35 @@ export class HuiAreaCardEditor
|
||||
this._config.sensor_classes || DEVICE_CLASSES.sensor
|
||||
);
|
||||
|
||||
const showCamera = this._config.display_type === "camera";
|
||||
|
||||
const displayType =
|
||||
this._config.display_type || this._config.show_camera
|
||||
? "camera"
|
||||
: "picture";
|
||||
this._config.display_type ||
|
||||
(this._config.show_camera ? "camera" : "picture");
|
||||
|
||||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
showCamera,
|
||||
displayType,
|
||||
binarySelectOptions,
|
||||
sensorSelectOptions
|
||||
);
|
||||
|
||||
const featuresSchema = this._featuresSchema(this.hass.localize);
|
||||
const vertical = this._config.vertical && displayType === "compact";
|
||||
|
||||
const featuresSchema = this._featuresSchema(this.hass.localize, vertical);
|
||||
|
||||
const data = {
|
||||
camera_view: "auto",
|
||||
alert_classes: DEVICE_CLASSES.binary_sensor,
|
||||
sensor_classes: DEVICE_CLASSES.sensor,
|
||||
features_position: "bottom",
|
||||
display_type: displayType,
|
||||
content_layout: vertical ? "vertical" : "horizontal",
|
||||
...this._config,
|
||||
};
|
||||
|
||||
// Default features position to bottom and force it to bottom in vertical mode
|
||||
if (!data.features_position || vertical) {
|
||||
data.features_position = "bottom";
|
||||
}
|
||||
|
||||
const hasCompatibleFeatures = this._hasCompatibleFeatures(
|
||||
this._featureContext
|
||||
);
|
||||
@@ -420,6 +447,12 @@ export class HuiAreaCardEditor
|
||||
delete config.camera_view;
|
||||
}
|
||||
|
||||
// Convert content_layout to vertical
|
||||
if (config.content_layout) {
|
||||
config.vertical = config.content_layout === "vertical";
|
||||
delete config.content_layout;
|
||||
}
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
|
@@ -42,7 +42,6 @@ import { supportsLightBrightnessCardFeature } from "../../card-features/hui-ligh
|
||||
import { supportsLightColorTempCardFeature } from "../../card-features/hui-light-color-temp-card-feature";
|
||||
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
|
||||
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
|
||||
import { supportsMediaPlayerPlaybackCardFeature } from "../../card-features/hui-media-player-playback-card-feature";
|
||||
import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature";
|
||||
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
|
||||
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
|
||||
@@ -96,7 +95,6 @@ const UI_FEATURE_TYPES = [
|
||||
"light-color-temp",
|
||||
"lock-commands",
|
||||
"lock-open-door",
|
||||
"media-player-playback",
|
||||
"media-player-volume-slider",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
@@ -164,7 +162,6 @@ const SUPPORTS_FEATURE_TYPES: Record<
|
||||
"light-color-temp": supportsLightColorTempCardFeature,
|
||||
"lock-commands": supportsLockCommandsCardFeature,
|
||||
"lock-open-door": supportsLockOpenDoorCardFeature,
|
||||
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,
|
||||
"media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature,
|
||||
"numeric-input": supportsNumericInputCardFeature,
|
||||
"select-options": supportsSelectOptionsCardFeature,
|
||||
|
@@ -261,18 +261,17 @@ export class HuiTileCardEditor
|
||||
this._config.hide_state ?? false
|
||||
);
|
||||
|
||||
const featuresSchema = this._featuresSchema(
|
||||
this.hass.localize,
|
||||
this._config.vertical ?? false
|
||||
);
|
||||
const vertical = this._config.vertical ?? false;
|
||||
|
||||
const featuresSchema = this._featuresSchema(this.hass.localize, vertical);
|
||||
|
||||
const data = {
|
||||
...this._config,
|
||||
content_layout: this._config.vertical ? "vertical" : "horizontal",
|
||||
content_layout: vertical ? "vertical" : "horizontal",
|
||||
};
|
||||
|
||||
// Default features position to bottom and force it to bottom in vertical mode
|
||||
if (!data.features_position || data.vertical) {
|
||||
if (!data.features_position || vertical) {
|
||||
data.features_position = "bottom";
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiCodeBraces,
|
||||
mdiCommentProcessingOutline,
|
||||
mdiDevices,
|
||||
mdiDotsVertical,
|
||||
mdiFileMultiple,
|
||||
mdiFormatListBulletedTriangle,
|
||||
@@ -12,9 +10,7 @@ import {
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
mdiRefresh,
|
||||
mdiRobot,
|
||||
mdiShape,
|
||||
mdiSofa,
|
||||
mdiViewDashboard,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
@@ -56,7 +52,6 @@ import {
|
||||
updateDashboard,
|
||||
} from "../../data/lovelace/dashboard";
|
||||
import { getPanelTitle } from "../../data/panel";
|
||||
import { createPerson } from "../../data/person";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -71,10 +66,7 @@ import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { showNewAutomationDialog } from "../config/automation/show-dialog-new-automation";
|
||||
import { showAddIntegrationDialog } from "../config/integrations/show-add-integration-dialog";
|
||||
import { showDashboardDetailDialog } from "../config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail";
|
||||
import { showPersonDetailDialog } from "../config/person/show-dialog-person-detail";
|
||||
import { swapView } from "./editor/config-util";
|
||||
import { showDashboardStrategyEditorDialog } from "./editor/dashboard-strategy-editor/dialogs/show-dialog-dashboard-strategy-editor";
|
||||
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
||||
@@ -86,28 +78,6 @@ import "./views/hui-view";
|
||||
import type { HUIView } from "./views/hui-view";
|
||||
import "./views/hui-view-background";
|
||||
import "./views/hui-view-container";
|
||||
import { showAreaRegistryDetailDialog } from "../config/areas/show-dialog-area-registry-detail";
|
||||
import { createAreaRegistryEntry } from "../../data/area_registry";
|
||||
import { showToast } from "../../util/toast";
|
||||
|
||||
interface ActionItem {
|
||||
icon: string;
|
||||
key: LocalizeKeys;
|
||||
overflowAction?: any;
|
||||
buttonAction?: any;
|
||||
visible: boolean | undefined;
|
||||
overflow: boolean;
|
||||
overflow_can_promote?: boolean;
|
||||
suffix?: string;
|
||||
subItems?: SubActionItem[];
|
||||
}
|
||||
|
||||
interface SubActionItem {
|
||||
icon: string;
|
||||
key: LocalizeKeys;
|
||||
action?: any;
|
||||
visible: boolean | undefined;
|
||||
}
|
||||
|
||||
@customElement("hui-root")
|
||||
class HUIRoot extends LitElement {
|
||||
@@ -175,7 +145,16 @@ class HUIRoot extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
const items: ActionItem[] = [
|
||||
const items: {
|
||||
icon: string;
|
||||
key: LocalizeKeys;
|
||||
overflowAction?: any;
|
||||
buttonAction?: any;
|
||||
visible: boolean | undefined;
|
||||
overflow: boolean;
|
||||
overflow_can_promote?: boolean;
|
||||
suffix?: string;
|
||||
}[] = [
|
||||
{
|
||||
icon: mdiFormatListBulletedTriangle,
|
||||
key: "ui.panel.lovelace.unused_entities.title",
|
||||
@@ -204,45 +183,13 @@ class HUIRoot extends LitElement {
|
||||
visible: this._editMode && this.hass.userData?.showAdvanced,
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
icon: mdiPlus,
|
||||
key: "ui.panel.lovelace.menu.add",
|
||||
visible: !this._editMode && this.hass.user?.is_admin,
|
||||
overflow: false,
|
||||
subItems: [
|
||||
{
|
||||
icon: mdiDevices,
|
||||
key: "ui.panel.lovelace.menu.add_device",
|
||||
visible: true,
|
||||
action: this._handleAddDevice,
|
||||
},
|
||||
{
|
||||
icon: mdiRobot,
|
||||
key: "ui.panel.lovelace.menu.create_automation",
|
||||
visible: true,
|
||||
action: this._handleACreateAutomation,
|
||||
},
|
||||
{
|
||||
icon: mdiSofa,
|
||||
key: "ui.panel.lovelace.menu.add_area",
|
||||
visible: true,
|
||||
action: this._handleAddArea,
|
||||
},
|
||||
{
|
||||
icon: mdiAccount,
|
||||
key: "ui.panel.lovelace.menu.invite_person",
|
||||
visible: true,
|
||||
action: this._handleInvitePerson,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: mdiMagnify,
|
||||
key: "ui.panel.lovelace.menu.search_entities",
|
||||
buttonAction: this._showQuickBar,
|
||||
overflowAction: this._handleShowQuickBar,
|
||||
visible: !this._editMode,
|
||||
overflow: false,
|
||||
overflow: this.narrow,
|
||||
suffix: this.hass.enableShortcuts ? "(E)" : undefined,
|
||||
},
|
||||
{
|
||||
@@ -252,7 +199,7 @@ class HUIRoot extends LitElement {
|
||||
overflowAction: this._handleShowVoiceCommandDialog,
|
||||
visible:
|
||||
!this._editMode && this._conversation(this.hass.config.components),
|
||||
overflow: false,
|
||||
overflow: this.narrow,
|
||||
suffix: this.hass.enableShortcuts ? "(A)" : undefined,
|
||||
},
|
||||
{
|
||||
@@ -300,50 +247,20 @@ class HUIRoot extends LitElement {
|
||||
(i) => i.visible && (!i.overflow || overflowCanPromote)
|
||||
);
|
||||
|
||||
buttonItems.forEach((item) => {
|
||||
const label = [this.hass!.localize(item.key), item.suffix].join(" ");
|
||||
const button = item.subItems
|
||||
? html`
|
||||
<ha-button-menu
|
||||
slot="actionItems"
|
||||
corner="BOTTOM_END"
|
||||
menu-corner="END"
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${label}
|
||||
.path=${item.icon}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
${item.subItems
|
||||
.filter((subItem) => subItem.visible)
|
||||
.map(
|
||||
(subItem) => html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.key=${subItem.key}
|
||||
@request-selected=${subItem.action}
|
||||
>
|
||||
${this.hass!.localize(subItem.key)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${subItem.icon}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
`
|
||||
: html`
|
||||
<ha-tooltip slot="actionItems" placement="bottom" .content=${label}>
|
||||
<ha-icon-button
|
||||
.path=${item.icon}
|
||||
@click=${item.buttonAction}
|
||||
></ha-icon-button>
|
||||
</ha-tooltip>
|
||||
`;
|
||||
result.push(button);
|
||||
buttonItems.forEach((i) => {
|
||||
result.push(
|
||||
html`<ha-tooltip
|
||||
slot="actionItems"
|
||||
placement="bottom"
|
||||
.content=${[this.hass!.localize(i.key), i.suffix].join(" ")}
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${i.icon}
|
||||
@click=${i.buttonAction}
|
||||
></ha-icon-button>
|
||||
</ha-tooltip>`
|
||||
);
|
||||
});
|
||||
|
||||
if (overflowItems.length && !overflowCanPromote) {
|
||||
const listItems: TemplateResult[] = [];
|
||||
overflowItems.forEach((i) => {
|
||||
@@ -456,15 +373,10 @@ class HUIRoot extends LitElement {
|
||||
})}
|
||||
</sl-tab-group>`;
|
||||
|
||||
const isSubview = curViewConfig?.subview;
|
||||
const hasTabViews = views.filter((view) => !view.subview).length > 1;
|
||||
const showTabBar = this._editMode || (!isSubview && hasTabViews);
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${classMap({
|
||||
"edit-mode": this._editMode,
|
||||
narrow: this.narrow,
|
||||
})}
|
||||
>
|
||||
<div class="header">
|
||||
@@ -487,7 +399,7 @@ class HUIRoot extends LitElement {
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`
|
||||
: html`
|
||||
${isSubview
|
||||
${curViewConfig?.subview
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
slot="navigationIcon"
|
||||
@@ -501,37 +413,34 @@ class HUIRoot extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`}
|
||||
${isSubview
|
||||
${curViewConfig?.subview
|
||||
? html`<div class="main-title">${curViewConfig.title}</div>`
|
||||
: hasTabViews && !showTabBar
|
||||
: views.filter((view) => !view.subview).length > 1
|
||||
? tabs
|
||||
: html`
|
||||
<div class="main-title">
|
||||
${curViewConfig?.title ?? dashboardTitle}
|
||||
${views[0]?.title ?? dashboardTitle}
|
||||
</div>
|
||||
`}
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`}
|
||||
</div>
|
||||
${showTabBar
|
||||
${this._editMode
|
||||
? html`<div class="edit-tab-bar">
|
||||
${tabs}
|
||||
${this._editMode
|
||||
? html`<ha-icon-button
|
||||
slot="nav"
|
||||
id="add-view"
|
||||
@click=${this._addView}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.add"
|
||||
)}
|
||||
.path=${mdiPlus}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
<ha-icon-button
|
||||
slot="nav"
|
||||
id="add-view"
|
||||
@click=${this._addView}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.add"
|
||||
)}
|
||||
.path=${mdiPlus}
|
||||
></ha-icon-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<hui-view-container
|
||||
class=${showTabBar ? "has-tab-bar" : ""}
|
||||
.hass=${this.hass}
|
||||
.theme=${curViewConfig?.theme}
|
||||
id="view"
|
||||
@@ -787,79 +696,6 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleAddDevice(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
await this.hass.loadFragmentTranslation("config");
|
||||
showAddIntegrationDialog(this);
|
||||
}
|
||||
|
||||
private async _handleACreateAutomation(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
await this.hass.loadFragmentTranslation("config");
|
||||
showNewAutomationDialog(this, { mode: "automation" });
|
||||
}
|
||||
|
||||
private async _handleAddArea(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
await this.hass.loadFragmentTranslation("config");
|
||||
showAreaRegistryDetailDialog(this, {
|
||||
createEntry: async (values) => {
|
||||
const area = await createAreaRegistryEntry(this.hass, values);
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.lovelace.menu.add_area_success"
|
||||
),
|
||||
action: {
|
||||
action: () => {
|
||||
navigate(`/config/areas/area/${area.area_id}`);
|
||||
},
|
||||
text: this.hass.localize("ui.panel.lovelace.menu.add_area_action"),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _handleInvitePerson(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
await this.hass.loadFragmentTranslation("config");
|
||||
showPersonDetailDialog(this, {
|
||||
users: [],
|
||||
createEntry: async (values) => {
|
||||
await createPerson(this.hass!, values);
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.lovelace.menu.add_person_success"
|
||||
),
|
||||
action: {
|
||||
action: () => {
|
||||
navigate(`/config/person`);
|
||||
},
|
||||
text: this.hass.localize(
|
||||
"ui.panel.lovelace.menu.add_person_action"
|
||||
),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _handleRawEditor(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
@@ -1214,14 +1050,6 @@ class HUIRoot extends LitElement {
|
||||
margin: var(--margin-title);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
flex-grow: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
.narrow .main-title {
|
||||
margin: 0;
|
||||
margin-inline-start: 8px;
|
||||
}
|
||||
.action-items {
|
||||
white-space: nowrap;
|
||||
@@ -1286,6 +1114,9 @@ class HUIRoot extends LitElement {
|
||||
--ha-tab-active-text-color: var(--app-header-edit-text-color, #fff);
|
||||
--ha-tab-indicator-color: var(--app-header-edit-text-color, #fff);
|
||||
}
|
||||
.edit-mode sl-tab {
|
||||
height: 54px;
|
||||
}
|
||||
sl-tab {
|
||||
height: calc(var(--header-height, 56px) - 2px);
|
||||
}
|
||||
@@ -1357,10 +1188,9 @@ class HUIRoot extends LitElement {
|
||||
/**
|
||||
* In edit mode we have the tab bar on a new line *
|
||||
*/
|
||||
hui-view-container.has-tab-bar {
|
||||
.edit-mode hui-view-container {
|
||||
padding-top: calc(
|
||||
var(--header-height) + calc(var(--header-height, 56px) - 2px) +
|
||||
var(--safe-area-inset-top)
|
||||
var(--header-height) + 48px + var(--safe-area-inset-top)
|
||||
);
|
||||
}
|
||||
.hide-tab {
|
||||
|
@@ -3,6 +3,7 @@ import { customElement } from "lit/decorators";
|
||||
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
|
||||
import { clamp } from "../../../../common/number/clamp";
|
||||
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -11,11 +12,11 @@ import {
|
||||
getAreas,
|
||||
getFloors,
|
||||
} from "../areas/helpers/areas-strategy-helper";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
import {
|
||||
findEntities,
|
||||
OVERVIEW_SUMMARIES_FILTERS,
|
||||
} from "./helpers/overview-summaries";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
|
||||
export interface OverviewClimateViewStrategyConfig {
|
||||
type: "overview-climate";
|
||||
@@ -24,10 +25,9 @@ export interface OverviewClimateViewStrategyConfig {
|
||||
const processAreasForClimate = (
|
||||
areaIds: string[],
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
computeTileCard: (entityId: string) => any
|
||||
): any[] => {
|
||||
const cards: any[] = [];
|
||||
entities: string[]
|
||||
): LovelaceCardConfig[] => {
|
||||
const cards: LovelaceCardConfig[] = [];
|
||||
|
||||
for (const areaId of areaIds) {
|
||||
const area = hass.areas[areaId];
|
||||
@@ -38,6 +38,8 @@ const processAreasForClimate = (
|
||||
});
|
||||
const areaEntities = entities.filter(areaFilter);
|
||||
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
|
||||
|
||||
if (areaEntities.length > 0) {
|
||||
cards.push({
|
||||
heading_style: "subtitle",
|
||||
@@ -81,8 +83,6 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
|
||||
|
||||
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
|
||||
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
|
||||
|
||||
// Process floors
|
||||
for (const floorStructure of home.floors) {
|
||||
const floorId = floorStructure.id;
|
||||
@@ -100,12 +100,7 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForClimate(
|
||||
areaIds,
|
||||
hass,
|
||||
entities,
|
||||
computeTileCard
|
||||
);
|
||||
const areaCards = processAreasForClimate(areaIds, hass, entities);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
@@ -126,12 +121,7 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForClimate(
|
||||
home.areas,
|
||||
hass,
|
||||
entities,
|
||||
computeTileCard
|
||||
);
|
||||
const areaCards = processAreasForClimate(home.areas, hass, entities);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
|
@@ -1,22 +1,16 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { clamp } from "../../../../common/number/clamp";
|
||||
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
|
||||
import type { AreaRegistryEntry } from "../../../../data/area_registry";
|
||||
import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature";
|
||||
import { AREA_CONTROLS, type AreaControl } from "../../card-features/types";
|
||||
import type {
|
||||
AreaCardConfig,
|
||||
ButtonCardConfig,
|
||||
HeadingCardConfig,
|
||||
MarkdownCardConfig,
|
||||
TileCardConfig,
|
||||
} from "../../cards/types";
|
||||
import { getAreas, getFloors } from "../areas/helpers/areas-strategy-helper";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
import { getAreas } from "../areas/helpers/areas-strategy-helper";
|
||||
import { OVERVIEW_SUMMARIES_ICONS } from "./helpers/overview-summaries";
|
||||
|
||||
export interface OverviewHomeViewStrategyConfig {
|
||||
@@ -31,42 +25,22 @@ const computeAreaCard = (
|
||||
const area = hass.areas[areaId] as AreaRegistryEntry | undefined;
|
||||
const path = `areas-${areaId}`;
|
||||
|
||||
const controls: AreaControl[] = AREA_CONTROLS.filter(
|
||||
(a) => a !== "switch" // Exclude switches control for areas as we don't know what the switches control
|
||||
);
|
||||
const controlEntities = getAreaControlEntities(controls, areaId, [], hass);
|
||||
|
||||
const filteredControls = controls.filter(
|
||||
(control) => controlEntities[control].length > 0
|
||||
);
|
||||
|
||||
const sensorClasses: string[] = [];
|
||||
if (area?.temperature_entity_id) {
|
||||
sensorClasses.push("temperature");
|
||||
}
|
||||
if (area?.humidity_entity_id) {
|
||||
sensorClasses.push("humidity");
|
||||
}
|
||||
|
||||
return {
|
||||
type: "area",
|
||||
area: areaId,
|
||||
display_type: "compact",
|
||||
sensor_classes: sensorClasses,
|
||||
features: filteredControls.length
|
||||
? [
|
||||
{
|
||||
type: "area-controls",
|
||||
controls: filteredControls,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
grid_options: {
|
||||
rows: 1,
|
||||
columns: 12,
|
||||
},
|
||||
features_position: "inline",
|
||||
navigation_path: path,
|
||||
vertical: true,
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -76,58 +50,25 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
config: OverviewHomeViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const floors = getFloors(hass.floors);
|
||||
const areas = getAreas(hass.areas);
|
||||
|
||||
const home = getHomeStructure(floors, areas);
|
||||
|
||||
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
|
||||
|
||||
const floorsSections: LovelaceSectionConfig[] = home.floors.map(
|
||||
(floorStructure) => {
|
||||
const floorId = floorStructure.id;
|
||||
const areaIds = floorStructure.areas;
|
||||
const floor = hass.floors[floorId];
|
||||
|
||||
const headingCard: HeadingCardConfig = {
|
||||
const areasSection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
column_span: 2,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading_style: "title",
|
||||
heading: floorCount > 1 ? floor.name : "Areas",
|
||||
icon: floor.icon || floorDefaultIcon(floor),
|
||||
};
|
||||
|
||||
const areasCards = areaIds.map<AreaCardConfig>((areaId) =>
|
||||
computeAreaCard(areaId, hass)
|
||||
);
|
||||
|
||||
return {
|
||||
max_columns: 3,
|
||||
type: "grid",
|
||||
cards: [headingCard, ...areasCards],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
if (home.areas.length > 0) {
|
||||
floorsSections.push({
|
||||
type: "grid",
|
||||
max_columns: 3,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading_style: "title",
|
||||
icon: "mdi:home",
|
||||
heading: floorCount > 1 ? "Other areas" : "Areas",
|
||||
},
|
||||
...home.areas.map<AreaCardConfig>((areaId) =>
|
||||
computeAreaCard(areaId, hass)
|
||||
),
|
||||
],
|
||||
} as LovelaceSectionConfig);
|
||||
}
|
||||
heading: "Areas",
|
||||
},
|
||||
...areas.map<AreaCardConfig>((area) =>
|
||||
computeAreaCard(area.area_id, hass)
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
|
||||
const maxColumns = clamp(floorsSections.length, 2, 3);
|
||||
const maxColumns = 2;
|
||||
|
||||
const favoriteSection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
@@ -143,7 +84,8 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
favoriteSection.cards!.push(
|
||||
{
|
||||
type: "heading",
|
||||
heading: "Quick actions",
|
||||
headiing: "",
|
||||
heading_style: "subtitle",
|
||||
},
|
||||
...favoriteEntities.map(
|
||||
(entityId) =>
|
||||
@@ -167,6 +109,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
type: "button",
|
||||
icon: OVERVIEW_SUMMARIES_ICONS.lights,
|
||||
name: "Lights",
|
||||
icon_height: "24px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
@@ -180,6 +123,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
type: "button",
|
||||
icon: OVERVIEW_SUMMARIES_ICONS.climate,
|
||||
name: "Climate",
|
||||
icon_height: "30px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
@@ -193,6 +137,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
type: "button",
|
||||
icon: OVERVIEW_SUMMARIES_ICONS.security,
|
||||
name: "Security",
|
||||
icon_height: "30px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
@@ -206,6 +151,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
type: "button",
|
||||
icon: OVERVIEW_SUMMARIES_ICONS.media_players,
|
||||
name: "Media Players",
|
||||
icon_height: "30px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
@@ -219,6 +165,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
type: "button",
|
||||
icon: "mdi:lightning-bolt",
|
||||
name: "Energy",
|
||||
icon_height: "30px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
@@ -234,7 +181,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
|
||||
const sections = [
|
||||
...(favoriteSection.cards ? [favoriteSection] : []),
|
||||
summarySection,
|
||||
...floorsSections,
|
||||
areasSection,
|
||||
];
|
||||
return {
|
||||
type: "sections",
|
||||
|
@@ -3,6 +3,7 @@ import { customElement } from "lit/decorators";
|
||||
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
|
||||
import { clamp } from "../../../../common/number/clamp";
|
||||
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -11,11 +12,11 @@ import {
|
||||
getAreas,
|
||||
getFloors,
|
||||
} from "../areas/helpers/areas-strategy-helper";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
import {
|
||||
findEntities,
|
||||
OVERVIEW_SUMMARIES_FILTERS,
|
||||
} from "./helpers/overview-summaries";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
|
||||
export interface OverviewLightsViewStrategyConfig {
|
||||
type: "overview-lights";
|
||||
@@ -24,10 +25,9 @@ export interface OverviewLightsViewStrategyConfig {
|
||||
const processAreasForLights = (
|
||||
areaIds: string[],
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
computeTileCard: (entityId: string) => any
|
||||
): any[] => {
|
||||
const cards: any[] = [];
|
||||
entities: string[]
|
||||
): LovelaceCardConfig[] => {
|
||||
const cards: LovelaceCardConfig[] = [];
|
||||
|
||||
for (const areaId of areaIds) {
|
||||
const area = hass.areas[areaId];
|
||||
@@ -38,6 +38,8 @@ const processAreasForLights = (
|
||||
});
|
||||
const areaLights = entities.filter(areaFilter);
|
||||
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", false);
|
||||
|
||||
if (areaLights.length > 0) {
|
||||
cards.push({
|
||||
heading_style: "subtitle",
|
||||
@@ -81,8 +83,6 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
|
||||
|
||||
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
|
||||
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
|
||||
|
||||
// Process floors
|
||||
for (const floorStructure of home.floors) {
|
||||
const floorId = floorStructure.id;
|
||||
@@ -100,12 +100,7 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForLights(
|
||||
areaIds,
|
||||
hass,
|
||||
entities,
|
||||
computeTileCard
|
||||
);
|
||||
const areaCards = processAreasForLights(areaIds, hass, entities);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
@@ -126,12 +121,7 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForLights(
|
||||
home.areas,
|
||||
hass,
|
||||
entities,
|
||||
computeTileCard
|
||||
);
|
||||
const areaCards = processAreasForLights(home.areas, hass, entities);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
|
@@ -3,16 +3,17 @@ import { customElement } from "lit/decorators";
|
||||
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
|
||||
import { clamp } from "../../../../common/number/clamp";
|
||||
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { MediaControlCardConfig } from "../../cards/types";
|
||||
import { getAreas, getFloors } from "../areas/helpers/areas-strategy-helper";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
import {
|
||||
findEntities,
|
||||
OVERVIEW_SUMMARIES_FILTERS,
|
||||
} from "./helpers/overview-summaries";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
|
||||
export interface OvervieMediaPlayersViewStrategyConfig {
|
||||
type: "overview-media-players";
|
||||
@@ -22,8 +23,8 @@ const processAreasForMediaPlayers = (
|
||||
areaIds: string[],
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
): any[] => {
|
||||
const cards: any[] = [];
|
||||
): LovelaceCardConfig[] => {
|
||||
const cards: LovelaceCardConfig[] = [];
|
||||
|
||||
for (const areaId of areaIds) {
|
||||
const area = hass.areas[areaId];
|
||||
|
@@ -16,6 +16,7 @@ import {
|
||||
OVERVIEW_SUMMARIES_FILTERS,
|
||||
} from "./helpers/overview-summaries";
|
||||
import { getHomeStructure } from "./helpers/overview-home-structure";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
|
||||
export interface OverviewSecurityViewStrategyConfig {
|
||||
type: "overview-security";
|
||||
@@ -24,10 +25,9 @@ export interface OverviewSecurityViewStrategyConfig {
|
||||
const processAreasForSecurity = (
|
||||
areaIds: string[],
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
computeTileCard: (entityId: string) => any
|
||||
): any[] => {
|
||||
const cards: any[] = [];
|
||||
entities: string[]
|
||||
): LovelaceCardConfig[] => {
|
||||
const cards: LovelaceCardConfig[] = [];
|
||||
|
||||
for (const areaId of areaIds) {
|
||||
const area = hass.areas[areaId];
|
||||
@@ -38,6 +38,8 @@ const processAreasForSecurity = (
|
||||
});
|
||||
const areaEntities = entities.filter(areaFilter);
|
||||
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
|
||||
|
||||
if (areaEntities.length > 0) {
|
||||
cards.push({
|
||||
heading_style: "subtitle",
|
||||
@@ -81,8 +83,6 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
|
||||
|
||||
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
|
||||
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
|
||||
|
||||
// Process floors
|
||||
for (const floorStructure of home.floors) {
|
||||
const floorId = floorStructure.id;
|
||||
@@ -100,12 +100,7 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForSecurity(
|
||||
areaIds,
|
||||
hass,
|
||||
entities,
|
||||
computeTileCard
|
||||
);
|
||||
const areaCards = processAreasForSecurity(areaIds, hass, entities);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
@@ -126,12 +121,7 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForSecurity(
|
||||
home.areas,
|
||||
hass,
|
||||
entities,
|
||||
computeTileCard
|
||||
);
|
||||
const areaCards = processAreasForSecurity(home.areas, hass, entities);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
|
@@ -1,36 +0,0 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import {
|
||||
showAutomationEditor,
|
||||
type ShowAutomationEditorParams,
|
||||
} from "../data/automation";
|
||||
import type { Constructor } from "../types";
|
||||
import type { HassBaseEl } from "./hass-base-mixin";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
/**
|
||||
* Dispatched to open the automation editor.
|
||||
* Used by custom cards/panels to trigger the editor view.
|
||||
*/
|
||||
"hass-automation-editor": ShowAutomationEditorParams;
|
||||
}
|
||||
}
|
||||
|
||||
export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
class extends superClass {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("hass-automation-editor", (ev) =>
|
||||
this._handleShowAutomationEditor(
|
||||
ev as HASSDomEvent<ShowAutomationEditorParams>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _handleShowAutomationEditor(
|
||||
ev: HASSDomEvent<ShowAutomationEditorParams>
|
||||
) {
|
||||
showAutomationEditor(ev.detail.data, ev.detail.expanded);
|
||||
}
|
||||
};
|
@@ -8,7 +8,6 @@ import { HassBaseEl } from "./hass-base-mixin";
|
||||
import { loggingMixin } from "./logging-mixin";
|
||||
import { contextMixin } from "./context-mixin";
|
||||
import MoreInfoMixin from "./more-info-mixin";
|
||||
import AutomationEditorMixin from "./automation-editor-mixin";
|
||||
import ActionMixin from "./action-mixin";
|
||||
import NotificationMixin from "./notification-mixin";
|
||||
import { panelTitleMixin } from "./panel-title-mixin";
|
||||
@@ -27,7 +26,6 @@ export class HassElement extends ext(HassBaseEl, [
|
||||
TranslationsMixin,
|
||||
StateDisplayMixin,
|
||||
MoreInfoMixin,
|
||||
AutomationEditorMixin,
|
||||
ActionMixin,
|
||||
SidebarMixin,
|
||||
DisconnectToastMixin,
|
||||
|
@@ -413,7 +413,6 @@
|
||||
"add": "Add",
|
||||
"create": "Create",
|
||||
"edit": "Edit",
|
||||
"edit_item": "Edit {name}",
|
||||
"submit": "Submit",
|
||||
"rename": "Rename",
|
||||
"search": "[%key:ui::components::data-table::search%]",
|
||||
@@ -474,8 +473,6 @@
|
||||
},
|
||||
"selector": {
|
||||
"options": "Selector options",
|
||||
"type": "Type",
|
||||
"multiple": "Multiple",
|
||||
"types": {
|
||||
"action": "Action",
|
||||
"area": "Area",
|
||||
@@ -3783,6 +3780,7 @@
|
||||
"load_error_not_editable": "Only automations in automations.yaml are editable.",
|
||||
"load_error_not_deletable": "Only automations in automations.yaml can be deleted.",
|
||||
"load_error_unknown": "Error loading automation ({err_no}).",
|
||||
"save": "Save",
|
||||
"unsaved_confirm_title": "Leave editor?",
|
||||
"unsaved_confirm_text": "Unsaved changes will be lost.",
|
||||
"alias": "Name",
|
||||
@@ -4743,9 +4741,7 @@
|
||||
"link_help_fields": "Learn more about fields.",
|
||||
"add_fields": "Add fields",
|
||||
"add_field": "Add field",
|
||||
"field": "field",
|
||||
"label": "Field",
|
||||
"field_selector": "Field selector"
|
||||
"field": "field"
|
||||
},
|
||||
"field_delete_confirm_title": "Delete field?",
|
||||
"field_delete_confirm_text": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm_text%]",
|
||||
@@ -4768,6 +4764,7 @@
|
||||
"load_error_unknown": "Error loading script ({err_no}).",
|
||||
"delete_confirm_title": "Delete script?",
|
||||
"delete_confirm_text": "{name} will be permanently deleted.",
|
||||
"save_script": "Save script",
|
||||
"sequence": "Sequence",
|
||||
"sequence_sentence": "The sequence of actions of this script.",
|
||||
"link_available_actions": "Learn more about available actions.",
|
||||
@@ -5338,6 +5335,8 @@
|
||||
"introduction": "Zones allow you to specify certain regions on Earth. When a person is within a zone, the state will take the name from the zone. Zones can also be used as a trigger or condition inside automation setups.",
|
||||
"no_zones_created_yet": "Looks like you have not created any zones yet.",
|
||||
"create_zone": "Create zone",
|
||||
"edit_zone": "Edit zone",
|
||||
"edit_home": "Edit home",
|
||||
"confirm_delete": "Are you sure you want to delete this zone?",
|
||||
"can_not_edit": "Unable to edit zone",
|
||||
"configured_in_yaml": "Zones configured via configuration.yaml cannot be edited via the UI.",
|
||||
@@ -6967,16 +6966,7 @@
|
||||
"assist_tooltip": "Assist",
|
||||
"reload_resources": "Reload resources",
|
||||
"exit_edit_mode": "Done",
|
||||
"close": "Close",
|
||||
"add": "Add to Home Assistant",
|
||||
"add_device": "Add device",
|
||||
"create_automation": "Create automation",
|
||||
"add_area": "Add area",
|
||||
"add_area_success": "Area added",
|
||||
"add_area_action": "View area",
|
||||
"add_person_success": "Person added",
|
||||
"add_person_action": "View persons",
|
||||
"invite_person": "Invite person"
|
||||
"close": "Close"
|
||||
},
|
||||
"reload_resources": {
|
||||
"refresh_header": "Do you want to refresh?",
|
||||
@@ -7371,6 +7361,11 @@
|
||||
"sensor_classes": "Sensor classes",
|
||||
"description": "The Area card automatically displays entities of a specific area.",
|
||||
"display_type": "Display type",
|
||||
"content_layout": "[%key:ui::panel::lovelace::editor::card::tile::content_layout%]",
|
||||
"content_layout_options": {
|
||||
"horizontal": "[%key:ui::panel::lovelace::editor::card::tile::content_layout_options::horizontal%]",
|
||||
"vertical": "[%key:ui::panel::lovelace::editor::card::tile::content_layout_options::vertical%]"
|
||||
},
|
||||
"display_type_options": {
|
||||
"compact": "Compact",
|
||||
"icon": "Area icon",
|
||||
@@ -7913,9 +7908,6 @@
|
||||
"lock-open-door": {
|
||||
"label": "Lock open door"
|
||||
},
|
||||
"media-player-playback": {
|
||||
"label": "Media player playback controls"
|
||||
},
|
||||
"media-player-volume-slider": {
|
||||
"label": "Media player volume slider"
|
||||
},
|
||||
|
@@ -159,25 +159,4 @@ describe("computeEntityEntryName", () => {
|
||||
expect(computeEntityEntryName(entry as any, hass as any)).toBe("2");
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns undefined when entity has device but no name or original_name", () => {
|
||||
vi.spyOn(computeDeviceNameModule, "computeDeviceName").mockReturnValue(
|
||||
"Kitchen Device"
|
||||
);
|
||||
|
||||
const entry = {
|
||||
entity_id: "sensor.kitchen_sensor",
|
||||
// No name property
|
||||
// No original_name property
|
||||
device_id: "dev1",
|
||||
};
|
||||
const hass = {
|
||||
devices: { dev1: {} },
|
||||
states: {},
|
||||
};
|
||||
|
||||
// Should return undefined to maintain function contract
|
||||
expect(computeEntityEntryName(entry as any, hass as any)).toBeUndefined();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
170
yarn.lock
170
yarn.lock
@@ -4994,67 +4994,76 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.40.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.39.1"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.10.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.40.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.40.0"
|
||||
"@typescript-eslint/utils": "npm:8.40.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.40.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.39.1"
|
||||
"@typescript-eslint/type-utils": "npm:8.39.1"
|
||||
"@typescript-eslint/utils": "npm:8.39.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.39.1"
|
||||
graphemer: "npm:^1.4.0"
|
||||
ignore: "npm:^7.0.0"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.40.0
|
||||
"@typescript-eslint/parser": ^8.39.1
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/9df4d4ac58734a34964b791622dcb94ffc6c49c1d0f4fd0480b3fc0e026527df7167ff78a4f8bbd29089d605756c28c1a90b2f0653df34b40ac8b969bc6c92e9
|
||||
checksum: 10/446050aa43d54c0107c7c927ae1f68a4384c2bba514d5c22edabbe355426cb37bd5bb5a3faf240a6be8ef06f68de6099c2a53d9cbb1849ed35a152fb156171e2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.40.0"
|
||||
"@typescript-eslint/parser@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/parser@npm:8.39.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.40.0"
|
||||
"@typescript-eslint/types": "npm:8.40.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.40.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.40.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.39.1"
|
||||
"@typescript-eslint/types": "npm:8.39.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.39.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.39.1"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/1e60f70e9d02f930553db7f4684c27c376fadf345db155414a22d1a32cd21def7d36496bd63c1acbf3afbec9fb8794947e880f88c1143b83e1d3c45146cec41a
|
||||
checksum: 10/ff45ce76353ed564e0f9db47b02b4b20895c96182b3693c610ef3dbceda373c476037a99f90d9f28633c192f301e5d554c89e1ba72da216763f960648ddf1f34
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.40.0"
|
||||
"@typescript-eslint/project-service@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/project-service@npm:8.39.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.40.0"
|
||||
"@typescript-eslint/types": "npm:^8.40.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.39.1"
|
||||
"@typescript-eslint/types": "npm:^8.39.1"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/86491aa65c1dd78c9784dddd8467601aef8be652c5fb3a901e8b1995cf07c1dbe11d0ab4610d770e3f4063c0c254a6c6aa5fb7cf724bf12fa4ee56f47f3a2955
|
||||
checksum: 10/1970633d1a338190f0125e186beaa39b3ef912f287e4815934faf64b72f140e87fdf7d861962683635a450d270dd76faf0c865d72bfd57b471a36739f943676b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.40.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.39.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.40.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.40.0"
|
||||
checksum: 10/0c5aa10208bfbb506bf3925a420c3de667298064bde400f03ee52c19cd0785dd05c2c820e05724d005737e2920d925aff0318ec3308156f9b81c84736a1fe46b
|
||||
"@typescript-eslint/types": "npm:8.39.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.39.1"
|
||||
checksum: 10/8874f7479043b3fc878f2c04b2c565051deceb7e425a8e4e79a7f40f1ee696bb979bd91fff619e016fe6793f537b30609c0ee8a5c40911c4829fa264863f7a70
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.40.0, @typescript-eslint/tsconfig-utils@npm:^8.40.0":
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.39.1"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/38c1e1982504e606e525ad0ce47fdb4c7acc686a28a94c2b30fe988c439977e991ce69cb88a1724a41a8096fc2d18d7ced7fe8725e42879d841515ff36a37ecf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:^8.39.1":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.40.0"
|
||||
peerDependencies:
|
||||
@@ -5063,37 +5072,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.40.0"
|
||||
"@typescript-eslint/type-utils@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.39.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.40.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.40.0"
|
||||
"@typescript-eslint/utils": "npm:8.40.0"
|
||||
"@typescript-eslint/types": "npm:8.39.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.39.1"
|
||||
"@typescript-eslint/utils": "npm:8.39.1"
|
||||
debug: "npm:^4.3.4"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/296c718330b2ac4408840258c30c01072de01ffe1c009be00c5049be1b19a71cbb2e258363ae349150760bcd2d34799df305b4cfc4d7f3b2fa9760ac8ffb3f75
|
||||
checksum: 10/1195d65970f79f820558810f7e1edf0ea360bbeee55841fdbb71b5b40c09f1a65741b67a70b85c2834ae1f9a027b82da4234d01f42ab4e85dceef3eea84bfdaa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.40.0, @typescript-eslint/types@npm:^8.40.0":
|
||||
"@typescript-eslint/types@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/types@npm:8.39.1"
|
||||
checksum: 10/8013f4f48a98da0de270d5fef1ff28b35407de82fce5acf3efa212fce60bc92a81bbb15b4b358d9facf4f161e49feec856fbf1a6d96f5027d013b542f2fe1bcc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:^8.39.1":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/types@npm:8.40.0"
|
||||
checksum: 10/f3931d0920a42b3bc69e9cdeb67a0c710597271cdd9d7c736302bdc52d21df1c962082df7cd712eeabd2c47658415d0a4b7d72f819cb38f82f4e234b48dbaa57
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.40.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.39.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.40.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.40.0"
|
||||
"@typescript-eslint/types": "npm:8.40.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.40.0"
|
||||
"@typescript-eslint/project-service": "npm:8.39.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.39.1"
|
||||
"@typescript-eslint/types": "npm:8.39.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.39.1"
|
||||
debug: "npm:^4.3.4"
|
||||
fast-glob: "npm:^3.3.2"
|
||||
is-glob: "npm:^4.0.3"
|
||||
@@ -5102,32 +5118,32 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/2e61ecfecb933f644799a7c11e4c7a730df57290c8d0482082cff7739b2401b0cf3b1ebef7b08a54a90285978957a49850d1a53061e8770164da651172ebee32
|
||||
checksum: 10/07ed9d7ab4d146ee3ce6cf82ffebf947e045a9289b01522e11b3985b64f590c00cac0ca10366df828ca213bf08216a67c7b2b76e7c8be650df2511a7e6385425
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.40.0"
|
||||
"@typescript-eslint/utils@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.39.1"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.40.0"
|
||||
"@typescript-eslint/types": "npm:8.40.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.40.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.39.1"
|
||||
"@typescript-eslint/types": "npm:8.39.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.39.1"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/b4cd1e6a4f55cc6475189de12e6bd418080a227e794745a2af304ab21655b031c28dae6387d4e9b54dd2f420696cec4f77cca9c66db405ed2281e0e09c95ba1c
|
||||
checksum: 10/39bb105f26aa1ba234ad7d284c277cbd66df9d51e245094892db140aac80d3656d0480f133b2db54e87af3ef9c371a12973120c9cfbff71e8e85865f9e1d0077
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.40.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.39.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.40.0"
|
||||
"@typescript-eslint/types": "npm:8.39.1"
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10/191f47998001a5e9cdde7491b0215d9c6c45c637aedd7d32cafd35ce2a4a0f4b8582edab015e09238f48e025a788b99efd8e70e4e3200e32143f91c95112abcd
|
||||
checksum: 10/6d4e4d0b19ebb3f21b692bbb0dcf9961876ca28cdf502296888a78eb4cd802a2ec8d3d5721d19970411edfd1c06f3e272e4057014c859ee1f0546804d07945e3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8141,16 +8157,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-unused-imports@npm:4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "eslint-plugin-unused-imports@npm:4.2.0"
|
||||
"eslint-plugin-unused-imports@npm:4.1.4":
|
||||
version: 4.1.4
|
||||
resolution: "eslint-plugin-unused-imports@npm:4.1.4"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
|
||||
eslint: ^9.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
"@typescript-eslint/eslint-plugin":
|
||||
optional: true
|
||||
checksum: 10/99285bd61a46ad5eaacd54525c04564feed3529dc2ae7800c736b7f4f011daa370133adb481677ad840f020681c0f409f3edfe3bc8f9f234c42bf4674bc4b576
|
||||
checksum: 10/8e987028ad925ce1e04c01dcae70adbf44c2878a8b15c4327b33a2861e471d7fe00f6fe213fbd2b936f3fcefc8ccabb0d778aa1d6e0e0387a3dc7fe150cd4ed4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9501,7 +9517,7 @@ __metadata:
|
||||
eslint-plugin-import: "npm:2.32.0"
|
||||
eslint-plugin-lit: "npm:2.1.1"
|
||||
eslint-plugin-lit-a11y: "npm:5.1.1"
|
||||
eslint-plugin-unused-imports: "npm:4.2.0"
|
||||
eslint-plugin-unused-imports: "npm:4.1.4"
|
||||
eslint-plugin-wc: "npm:3.0.1"
|
||||
fancy-log: "npm:2.0.0"
|
||||
fs-extra: "npm:11.3.1"
|
||||
@@ -9533,7 +9549,7 @@ __metadata:
|
||||
lodash.template: "npm:4.5.0"
|
||||
luxon: "npm:3.7.1"
|
||||
map-stream: "npm:0.0.7"
|
||||
marked: "npm:16.2.0"
|
||||
marked: "npm:16.1.2"
|
||||
memoize-one: "npm:6.0.0"
|
||||
node-vibrant: "npm:4.0.3"
|
||||
object-hash: "npm:3.0.0"
|
||||
@@ -9555,7 +9571,7 @@ __metadata:
|
||||
tinykeys: "npm:3.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:5.9.2"
|
||||
typescript-eslint: "npm:8.40.0"
|
||||
typescript-eslint: "npm:8.39.1"
|
||||
ua-parser-js: "npm:2.0.4"
|
||||
vite-tsconfig-paths: "npm:5.1.4"
|
||||
vitest: "npm:3.2.4"
|
||||
@@ -11204,12 +11220,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"marked@npm:16.2.0":
|
||||
version: 16.2.0
|
||||
resolution: "marked@npm:16.2.0"
|
||||
"marked@npm:16.1.2":
|
||||
version: 16.1.2
|
||||
resolution: "marked@npm:16.1.2"
|
||||
bin:
|
||||
marked: bin/marked.js
|
||||
checksum: 10/0a73dcfbe500514d2f1106da99708beed8a31de586e2826e1aa47ca0e0a4850b1e9598569b09d5366d4f4dee2d279a13f32616ed1ee75c832068eb7dd660f66f
|
||||
checksum: 10/190d9b206f05d87a7acac3b50ab19505878297971a0c5652a9d4fa2b022407f22d2b79e1aa1e9f23a32c0158b1f5852ad33da2e83cc12100116a8fc0afc2b17e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14603,18 +14619,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.40.0":
|
||||
version: 8.40.0
|
||||
resolution: "typescript-eslint@npm:8.40.0"
|
||||
"typescript-eslint@npm:8.39.1":
|
||||
version: 8.39.1
|
||||
resolution: "typescript-eslint@npm:8.39.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.40.0"
|
||||
"@typescript-eslint/parser": "npm:8.40.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.40.0"
|
||||
"@typescript-eslint/utils": "npm:8.40.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.39.1"
|
||||
"@typescript-eslint/parser": "npm:8.39.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.39.1"
|
||||
"@typescript-eslint/utils": "npm:8.39.1"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/b96dc4e70bd551e5399b928e946957cce622cba89f0ff87521f9e93f223acbe406930a1ebee845b158f586959cb7d85f15ea2250b97341aa87f50a3c987d068a
|
||||
checksum: 10/1c29c18f2e93b8b74d019590196b158006d7c65be87a56a4c953e52a9c4c40280a42f8ff1464fea870b3a1a4d54925b5cb7d54b4dc86b23cdf65a9b7787585b5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Reference in New Issue
Block a user