Compare commits

..

17 Commits

Author SHA1 Message Date
Zack Barett
51f971337d Bumped version to 20220428.0 (#12501) 2022-04-28 13:50:08 -07:00
Zack Barett
1f3c23de29 Change Restart to be a button, update dialogs (#12499) 2022-04-28 13:43:00 -07:00
Zack Barett
bdfb17d957 Add Board Names, Move All Hardware (#12484)
Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-04-28 20:42:18 +00:00
Franck Nijhof
8c97aee1fe Add parallel automation/script action (#12491) 2022-04-28 15:09:03 -05:00
Bram Kragten
38b4090daa Add support for enabling/disabling trigger/condition/action (#12493)
* Add support for enabling/disabling trigger/condition/action

* Add more visual indication of disabled

* review

* margin

* Dont make overflow transparent

* Change color of bar
2022-04-28 18:37:58 +02:00
Thomas Lovén
b8c55f2f65 Evaluate condition shorthands in editors (#12473)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-04-28 08:36:17 -07:00
Bram Kragten
7ca379e0a1 Hide and sort secondary device automations (#12496) 2022-04-28 08:53:56 -05:00
Bram Kragten
1617a9dfed Address minor comments about config menu (#12492) 2022-04-28 08:44:01 -05:00
Franck Nijhof
2c9411c6c3 Add template editor to Markdown card editor (#12490) 2022-04-28 12:40:39 +02:00
Zack Barett
67626d4a06 add my redirects for new config pages (#12481) 2022-04-28 12:39:35 +02:00
Yosi Levy
8135611688 Media panel fix (#12485) 2022-04-28 05:16:18 +00:00
Zack Barett
3ccbf6983e Move General Up in the system menu (#12483) 2022-04-27 22:08:21 -07:00
Zack Barett
e4f91195d8 Fix Restarting Home Assistant (#12480)
* Fix Restarting Home ASsistant

* Update src/panels/config/core/ha-config-system-navigation.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Update src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* reviews

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-04-27 15:55:04 -07:00
Philip Allgaier
2751f8f33b Add some bottom padding to YAML conf dev tools page (#12477)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-04-27 22:18:25 +02:00
Philip Allgaier
57f2df3b3e Visual tweaks to YAML validation results (#12479) 2022-04-27 19:57:41 +00:00
Zack Barett
6822f0d067 Small config fixes (#12472) 2022-04-27 12:22:57 -07:00
Zack Barett
cfba957313 Fix YAML Config Invalid button (#12476) 2022-04-27 13:57:57 -05:00
32 changed files with 779 additions and 347 deletions

View File

@@ -62,6 +62,17 @@ const ACTIONS = [
entity_id: "input_boolean.toggle_4",
},
},
{
parallel: [
{ scene: "scene.kitchen_morning" },
{
service: "media_player.play_media",
target: { entity_id: "media_player.living_room" },
data: { media_content_id: "", media_content_type: "" },
metadata: { title: "Happy Song" },
},
],
},
];
@customElement("demo-automation-describe-action")

View File

@@ -20,6 +20,7 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
import { Action } from "../../../../src/data/script";
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "Event", actions: [HaEventAction.defaultConfig] },
@@ -33,6 +34,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
];
@customElement("demo-automation-editor-action")

View File

@@ -1,6 +1,6 @@
[metadata]
name = home-assistant-frontend
version = 20220427.0
version = 20220428.0
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0

View File

@@ -5,6 +5,7 @@ import { fireEvent } from "../../common/dom/fire_event";
import {
DeviceAutomation,
deviceAutomationsEqual,
sortDeviceAutomations,
} from "../../data/device_automation";
import { HomeAssistant } from "../../types";
import "../ha-select";
@@ -127,7 +128,9 @@ export abstract class HaDeviceAutomationPicker<
private async _updateDeviceInfo() {
this._automations = this.deviceId
? await this._fetchDeviceAutomations(this.hass, this.deviceId)
? (await this._fetchDeviceAutomations(this.hass, this.deviceId)).sort(
sortDeviceAutomations
)
: // No device, clear the list of automations
[];
@@ -161,8 +164,9 @@ export abstract class HaDeviceAutomationPicker<
if (this.value && deviceAutomationsEqual(automation, this.value)) {
return;
}
fireEvent(this, "change");
fireEvent(this, "value-changed", { value: automation });
const value = { ...automation };
delete value.metadata;
fireEvent(this, "value-changed", { value });
}
static get styles(): CSSResultGroup {

View File

@@ -152,6 +152,7 @@ class DialogMediaPlayerBrowse extends LitElement {
ha-media-player-browse {
--media-browser-max-height: calc(100vh - 65px);
height: calc(100vh - 65px);
direction: ltr;
}
@media (min-width: 800px) {

View File

@@ -65,6 +65,7 @@ export interface BaseTrigger {
platform: string;
id?: string;
variables?: Record<string, unknown>;
enabled?: boolean;
}
export interface StateTrigger extends BaseTrigger {
@@ -178,6 +179,7 @@ export type Trigger =
interface BaseCondition {
condition: string;
alias?: string;
enabled?: boolean;
}
export interface LogicalCondition extends BaseCondition {
@@ -235,6 +237,10 @@ export interface TriggerCondition extends BaseCondition {
type ShorthandBaseCondition = Omit<BaseCondition, "condition">;
export interface ShorthandAndConditionList extends ShorthandBaseCondition {
condition: Condition[];
}
export interface ShorthandAndCondition extends ShorthandBaseCondition {
and: Condition[];
}
@@ -260,10 +266,33 @@ export type Condition =
export type ConditionWithShorthand =
| Condition
| ShorthandAndConditionList
| ShorthandAndCondition
| ShorthandOrCondition
| ShorthandNotCondition;
export const expandConditionWithShorthand = (
cond: ConditionWithShorthand
): Condition => {
if ("condition" in cond && Array.isArray(cond.condition)) {
return {
condition: "and",
conditions: cond.condition,
};
}
for (const condition of ["and", "or", "not"]) {
if (condition in cond) {
return {
condition,
conditions: cond[condition],
} as Condition;
}
}
return cond as Condition;
};
export const triggerAutomationActions = (
hass: HomeAssistant,
entityId: string

View File

@@ -11,6 +11,8 @@ export interface DeviceAutomation {
type?: string;
subtype?: string;
event?: string;
enabled?: boolean;
metadata?: { secondary: boolean };
}
export interface DeviceAction extends DeviceAutomation {
@@ -179,3 +181,16 @@ export const localizeDeviceAutomationTrigger = (
(trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!)
);
};
export const sortDeviceAutomations = (
automationA: DeviceAutomation,
automationB: DeviceAutomation
) => {
if (automationA.metadata?.secondary && !automationB.metadata?.secondary) {
return 1;
}
if (!automationA.metadata?.secondary && automationB.metadata?.secondary) {
return -1;
}
return 0;
};

22
src/data/hardware.ts Normal file
View File

@@ -0,0 +1,22 @@
// Keep in sync with https://github.com/home-assistant/analytics.home-assistant.io/blob/dev/site/src/analytics-os-boards.ts#L6-L24
export const BOARD_NAMES: Record<string, string> = {
"odroid-n2": "Home Assistant Blue / ODROID-N2",
"odroid-xu4": "ODROID-XU4",
"odroid-c2": "ODROID-C2",
"odroid-c4": "ODROID-C4",
rpi: "Raspberry Pi",
rpi0: "Raspberry Pi Zero",
"rpi0-w": "Raspberry Pi Zero W",
rpi2: "Raspberry Pi 2",
rpi3: "Raspberry Pi 3 (32-bit)",
"rpi3-64": "Raspberry Pi 3",
rpi4: "Raspberry Pi 4 (32-bit)",
"rpi4-64": "Raspberry Pi 4",
tinker: "ASUS Tinker Board",
"khadas-vim3": "Khadas VIM3",
"generic-aarch64": "Generic AArch64",
ova: "Virtual Machine",
"generic-x86-64": "Generic x86-64",
"intel-nuc": "Intel NUC",
yellow: "Home Assistant Yellow",
};

View File

@@ -13,6 +13,7 @@ import {
literal,
is,
Describe,
boolean,
} from "superstruct";
import { computeObjectId } from "../common/entity/compute_object_id";
import { navigate } from "../common/navigate";
@@ -25,6 +26,7 @@ export const MODES_MAX = ["queued", "parallel"];
export const baseActionStruct = object({
alias: optional(string()),
enabled: optional(boolean()),
});
const targetStruct = object({
@@ -88,15 +90,18 @@ export interface BlueprintScriptConfig extends ManualScriptConfig {
use_blueprint: { path: string; input?: BlueprintInput };
}
export interface EventAction {
interface BaseAction {
alias?: string;
enabled?: boolean;
}
export interface EventAction extends BaseAction {
event: string;
event_data?: Record<string, any>;
event_data_template?: Record<string, any>;
}
export interface ServiceAction {
alias?: string;
export interface ServiceAction extends BaseAction {
service?: string;
service_template?: string;
entity_id?: string;
@@ -104,55 +109,48 @@ export interface ServiceAction {
data?: Record<string, unknown>;
}
export interface DeviceAction {
alias?: string;
export interface DeviceAction extends BaseAction {
type: string;
device_id: string;
domain: string;
entity_id: string;
}
export interface DelayActionParts {
export interface DelayActionParts extends BaseAction {
milliseconds?: number;
seconds?: number;
minutes?: number;
hours?: number;
days?: number;
}
export interface DelayAction {
alias?: string;
export interface DelayAction extends BaseAction {
delay: number | Partial<DelayActionParts> | string;
}
export interface ServiceSceneAction {
alias?: string;
export interface ServiceSceneAction extends BaseAction {
service: "scene.turn_on";
target?: { entity_id?: string };
entity_id?: string;
metadata: Record<string, unknown>;
}
export interface LegacySceneAction {
alias?: string;
export interface LegacySceneAction extends BaseAction {
scene: string;
}
export type SceneAction = ServiceSceneAction | LegacySceneAction;
export interface WaitAction {
alias?: string;
export interface WaitAction extends BaseAction {
wait_template: string;
timeout?: number;
continue_on_timeout?: boolean;
}
export interface WaitForTriggerAction {
alias?: string;
export interface WaitForTriggerAction extends BaseAction {
wait_for_trigger: Trigger | Trigger[];
timeout?: number;
continue_on_timeout?: boolean;
}
export interface PlayMediaAction {
alias?: string;
export interface PlayMediaAction extends BaseAction {
service: "media_player.play_media";
target?: { entity_id?: string };
entity_id?: string;
@@ -160,13 +158,11 @@ export interface PlayMediaAction {
metadata: Record<string, unknown>;
}
export interface RepeatAction {
alias?: string;
export interface RepeatAction extends BaseAction {
repeat: CountRepeat | WhileRepeat | UntilRepeat;
}
interface BaseRepeat {
alias?: string;
interface BaseRepeat extends BaseAction {
sequence: Action | Action[];
}
@@ -182,38 +178,36 @@ export interface UntilRepeat extends BaseRepeat {
until: Condition[];
}
export interface ChooseActionChoice {
alias?: string;
export interface ChooseActionChoice extends BaseAction {
conditions: string | Condition[];
sequence: Action | Action[];
}
export interface ChooseAction {
alias?: string;
export interface ChooseAction extends BaseAction {
choose: ChooseActionChoice | ChooseActionChoice[] | null;
default?: Action | Action[];
}
export interface IfAction {
alias?: string;
export interface IfAction extends BaseAction {
if: string | Condition[];
then: Action | Action[];
else?: Action | Action[];
}
export interface VariablesAction {
alias?: string;
export interface VariablesAction extends BaseAction {
variables: Record<string, unknown>;
}
export interface StopAction {
alias?: string;
export interface StopAction extends BaseAction {
stop: string;
error?: boolean;
}
interface UnknownAction {
alias?: string;
export interface ParallelAction extends BaseAction {
parallel: Action | Action[];
}
interface UnknownAction extends BaseAction {
[key: string]: unknown;
}
@@ -232,6 +226,7 @@ export type Action =
| VariablesAction
| PlayMediaAction
| StopAction
| ParallelAction
| UnknownAction;
export interface ActionTypes {
@@ -249,6 +244,7 @@ export interface ActionTypes {
service: ServiceAction;
play_media: PlayMediaAction;
stop: StopAction;
parallel: ParallelAction;
unknown: UnknownAction;
}
@@ -328,6 +324,9 @@ export const getActionType = (action: Action): ActionType => {
if ("stop" in action) {
return "stop";
}
if ("parallel" in action) {
return "parallel";
}
if ("service" in action) {
if ("metadata" in action) {
if (is(action, activateSceneActionStruct)) {

View File

@@ -169,5 +169,9 @@ export const describeAction = <T extends ActionType>(
}`;
}
if (actionType === "parallel") {
return "Run in parallel";
}
return actionType;
};

View File

@@ -99,6 +99,7 @@ class HassSubpage extends LitElement {
ha-icon-button-arrow-prev,
::slotted([slot="toolbar-icon"]) {
pointer-events: auto;
color: var(--sidebar-icon-color);
}
.main-title {

View File

@@ -33,6 +33,7 @@ import "./types/ha-automation-action-delay";
import "./types/ha-automation-action-device_id";
import "./types/ha-automation-action-event";
import "./types/ha-automation-action-if";
import "./types/ha-automation-action-parallel";
import "./types/ha-automation-action-play_media";
import "./types/ha-automation-action-repeat";
import "./types/ha-automation-action-service";
@@ -54,6 +55,7 @@ const OPTIONS = [
"if",
"device_id",
"stop",
"parallel",
];
const getType = (action: Action | undefined) => {
@@ -160,62 +162,82 @@ export default class HaAutomationActionRow extends LitElement {
return html`
<ha-card>
<div class="card-content">
<div class="card-menu">
${this.index !== 0
? html`
<ha-icon-button
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.path=${mdiArrowUp}
@click=${this._moveUp}
></ha-icon-button>
`
: ""}
${this.index !== this.totalActions - 1
? html`
<ha-icon-button
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.path=${mdiArrowDown}
@click=${this._moveDown}
></ha-icon-button>
`
: ""}
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.run_action"
)}
</mwc-list-item>
<mwc-list-item .disabled=${!this._uiModeAvailable}>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item class="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
${this.action.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>`
: ""}
<div class="card-menu">
${this.index !== 0
? html`
<ha-icon-button
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.path=${mdiArrowUp}
@click=${this._moveUp}
></ha-icon-button>
`
: ""}
${this.index !== this.totalActions - 1
? html`
<ha-icon-button
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.path=${mdiArrowDown}
@click=${this._moveDown}
></ha-icon-button>
`
: ""}
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.run_action"
)}
</mwc-list-item>
<mwc-list-item .disabled=${!this._uiModeAvailable}>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item>
${this.action.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
</mwc-list-item>
<mwc-list-item class="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
<div
class="card-content ${this.action.enabled === false
? "disabled"
: ""}"
>
${this._warnings
? html`<ha-alert
alert-type="warning"
@@ -314,11 +336,23 @@ export default class HaAutomationActionRow extends LitElement {
fireEvent(this, "duplicate");
break;
case 3:
this._onDisable();
break;
case 4:
this._onDelete();
break;
}
}
private _onDisable() {
const enabled = !(this.action.enabled ?? true);
const value = { ...this.action, enabled };
fireEvent(this, "value-changed", { value });
if (this._yamlMode) {
this._yamlEditor?.setValue(value);
}
}
private async _runAction() {
const validated = await validateConfig(this.hass, {
action: this.action,
@@ -408,11 +442,27 @@ export default class HaAutomationActionRow extends LitElement {
return [
haStyle,
css`
.disabled {
opacity: 0.5;
pointer-events: none;
}
.card-content {
padding-top: 16px;
margin-top: 0;
}
.disabled-bar {
background: var(--divider-color, #e0e0e0);
text-align: center;
border-top-right-radius: var(--ha-card-border-radius);
border-top-left-radius: var(--ha-card-border-radius);
}
.card-menu {
position: absolute;
right: 16px;
float: right;
z-index: 3;
margin: 4px;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
display: flex;
align-items: center;
}
:host-context([style*="direction: rtl;"]) .card-menu {
right: initial;

View File

@@ -0,0 +1,56 @@
import { CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { Action, ParallelAction } from "../../../../../data/script";
import { HaDeviceAction } from "./ha-automation-action-device_id";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import "../ha-automation-action";
import "../../../../../components/ha-textfield";
import type { ActionElement } from "../ha-automation-action-row";
@customElement("ha-automation-action-parallel")
export class HaParallelAction extends LitElement implements ActionElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public action!: ParallelAction;
public static get defaultConfig() {
return {
parallel: [HaDeviceAction.defaultConfig],
};
}
protected render() {
const action = this.action;
return html`
<ha-automation-action
.actions=${action.parallel}
@value-changed=${this._actionsChanged}
.hass=${this.hass}
></ha-automation-action>
`;
}
private _actionsChanged(ev: CustomEvent) {
ev.stopPropagation();
const value = ev.detail.value as Action[];
fireEvent(this, "value-changed", {
value: {
...this.action,
parallel: value,
},
});
}
static get styles(): CSSResultGroup {
return haStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-action-parallel": HaParallelAction;
}
}

View File

@@ -10,6 +10,7 @@ import "../../../../components/ha-select";
import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-yaml-editor";
import type { Condition } from "../../../../data/automation";
import { expandConditionWithShorthand } from "../../../../data/automation";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "./types/ha-automation-condition-and";
@@ -42,10 +43,14 @@ const OPTIONS = [
export default class HaAutomationConditionEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public condition!: Condition;
@property() condition!: Condition;
@property() public yamlMode = false;
private _processedCondition = memoizeOne((condition) =>
expandConditionWithShorthand(condition)
);
private _processedTypes = memoizeOne(
(localize: LocalizeFunc): [string, string][] =>
OPTIONS.map(
@@ -60,7 +65,8 @@ export default class HaAutomationConditionEditor extends LitElement {
);
protected render() {
const selected = OPTIONS.indexOf(this.condition.condition);
const condition = this._processedCondition(this.condition);
const selected = OPTIONS.indexOf(condition.condition);
const yamlMode = this.yamlMode || selected === -1;
return html`
${yamlMode
@@ -70,7 +76,7 @@ export default class HaAutomationConditionEditor extends LitElement {
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.unsupported_condition",
"condition",
this.condition.condition
condition.condition
)}
`
: ""}
@@ -90,7 +96,7 @@ export default class HaAutomationConditionEditor extends LitElement {
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type_select"
)}
.value=${this.condition.condition}
.value=${condition.condition}
naturalMenuWidth
@selected=${this._typeChanged}
>
@@ -103,8 +109,8 @@ export default class HaAutomationConditionEditor extends LitElement {
<div>
${dynamicElement(
`ha-automation-condition-${this.condition.condition}`,
{ hass: this.hass, condition: this.condition }
`ha-automation-condition-${condition.condition}`,
{ hass: this.hass, condition: condition }
)}
</div>
`}
@@ -124,7 +130,7 @@ export default class HaAutomationConditionEditor extends LitElement {
defaultConfig: Omit<Condition, "condition">;
};
if (type !== this.condition.condition) {
if (type !== this._processedCondition(this.condition).condition) {
fireEvent(this, "value-changed", {
value: {
condition: type,

View File

@@ -2,7 +2,7 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-button-menu";
@@ -19,6 +19,7 @@ import { haStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import "./ha-automation-condition-editor";
import { validateConfig } from "../../../../data/config";
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
export interface ConditionElement extends LitElement {
condition: Condition;
@@ -59,47 +60,69 @@ export default class HaAutomationConditionRow extends LitElement {
@state() private _warnings?: string[];
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
protected render() {
if (!this.condition) {
return html``;
}
return html`
<ha-card>
<div class="card-content">
<div class="card-menu">
<ha-progress-button @click=${this._testCondition}>
${this.condition.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
"ui.panel.config.automation.editor.actions.disabled"
)}
</ha-progress-button>
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
>
</ha-icon-button>
<mwc-list-item>
${this._yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item class="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
</div>`
: ""}
<div class="card-menu">
<ha-progress-button @click=${this._testCondition}>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
)}
</ha-progress-button>
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
>
</ha-icon-button>
<mwc-list-item>
${this._yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item>
${this.condition.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
</mwc-list-item>
<mwc-list-item class="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
<div
class="card-content ${this.condition.enabled === false
? "disabled"
: ""}"
>
${this._warnings
? html`<ha-alert
alert-type="warning"
@@ -153,11 +176,23 @@ export default class HaAutomationConditionRow extends LitElement {
fireEvent(this, "duplicate");
break;
case 2:
this._onDisable();
break;
case 3:
this._onDelete();
break;
}
}
private _onDisable() {
const enabled = !(this.condition.enabled ?? true);
const value = { ...this.condition, enabled };
fireEvent(this, "value-changed", { value });
if (this._yamlMode) {
this._yamlEditor?.setValue(value);
}
}
private _onDelete() {
showConfirmationDialog(this, {
text: this.hass.localize(
@@ -238,9 +273,24 @@ export default class HaAutomationConditionRow extends LitElement {
return [
haStyle,
css`
.disabled {
opacity: 0.5;
pointer-events: none;
}
.card-content {
padding-top: 16px;
margin-top: 0;
}
.disabled-bar {
background: var(--divider-color, #e0e0e0);
text-align: center;
border-top-right-radius: var(--ha-card-border-radius);
border-top-left-radius: var(--ha-card-border-radius);
}
.card-menu {
float: right;
z-index: 3;
margin: 4px;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
display: flex;
align-items: center;

View File

@@ -1,8 +1,9 @@
import { object, optional, number, string } from "superstruct";
import { object, optional, number, string, boolean } from "superstruct";
export const baseTriggerStruct = object({
platform: string(),
id: optional(string()),
enabled: optional(boolean()),
});
export const forDictStruct = object({

View File

@@ -3,7 +3,7 @@ import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
@@ -16,7 +16,7 @@ import "../../../../components/ha-alert";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-yaml-editor";
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import "../../../../components/ha-select";
import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-textfield";
@@ -104,6 +104,8 @@ export default class HaAutomationTriggerRow extends LitElement {
@state() private _triggerColor = false;
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
private _triggerUnsub?: Promise<UnsubscribeFunc>;
private _processedTypes = memoizeOne(
@@ -126,40 +128,60 @@ export default class HaAutomationTriggerRow extends LitElement {
return html`
<ha-card>
<div class="card-content">
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
</mwc-list-item>
<mwc-list-item .disabled=${selected === -1}>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item class="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
${this.trigger.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>`
: ""}
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
</mwc-list-item>
<mwc-list-item .disabled=${selected === -1}>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item>
${this.trigger.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
</mwc-list-item>
<mwc-list-item class="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
<div
class="card-content ${this.trigger.enabled === false
? "disabled"
: ""}"
>
${this._warnings
? html`<ha-alert
alert-type="warning"
@@ -214,7 +236,6 @@ export default class HaAutomationTriggerRow extends LitElement {
`
)}
</ha-select>
${showId
? html`
<ha-textfield
@@ -250,7 +271,7 @@ export default class HaAutomationTriggerRow extends LitElement {
`;
}
protected override updated(changedProps: PropertyValues): void {
protected override updated(changedProps: PropertyValues<this>): void {
super.updated(changedProps);
if (changedProps.has("trigger")) {
this._subscribeTrigger();
@@ -347,6 +368,9 @@ export default class HaAutomationTriggerRow extends LitElement {
fireEvent(this, "duplicate");
break;
case 3:
this._onDisable();
break;
case 4:
this._onDelete();
break;
}
@@ -365,6 +389,15 @@ export default class HaAutomationTriggerRow extends LitElement {
});
}
private _onDisable() {
const enabled = !(this.trigger.enabled ?? true);
const value = { ...this.trigger, enabled };
fireEvent(this, "value-changed", { value });
if (this._yamlMode) {
this._yamlEditor?.setValue(value);
}
}
private _typeChanged(ev: CustomEvent) {
const type = (ev.target as HaSelect).value;
@@ -439,10 +472,27 @@ export default class HaAutomationTriggerRow extends LitElement {
return [
haStyle,
css`
.disabled {
opacity: 0.5;
pointer-events: none;
}
.card-content {
padding-top: 16px;
margin-top: 0;
}
.disabled-bar {
background: var(--divider-color, #e0e0e0);
text-align: center;
border-top-right-radius: var(--ha-card-border-radius);
border-top-left-radius: var(--ha-card-border-radius);
}
.card-menu {
float: right;
z-index: 3;
margin: 4px;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
display: flex;
align-items: center;
}
:host-context([style*="direction: rtl;"]) .card-menu {
float: left;

View File

@@ -110,7 +110,9 @@ class ConfigAnalytics extends LitElement {
ha-settings-row {
padding: 0;
}
p {
margin-top: 0;
}
.card-actions {
display: flex;
flex-direction: row-reverse;

View File

@@ -1,15 +1,23 @@
import "@material/mwc-list/mwc-list-item";
import timezones from "google-timezones-json";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { UNIT_C } from "../../../common/const";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { navigate } from "../../../common/navigate";
import { HaProgressButton } from "../../../components/buttons/ha-progress-button";
import "../../../components/buttons/ha-progress-button";
import type { HaProgressButton } from "../../../components/buttons/ha-progress-button";
import { currencies } from "../../../components/currency-datalist";
import "../../../components/ha-card";
import "../../../components/ha-formfield";
import "../../../components/ha-radio";
import type { HaRadio } from "../../../components/ha-radio";
import "../../../components/ha-select";
import "../../../components/ha-settings-row";
import "../../../components/ha-textfield";
import "../../../components/map/ha-locations-editor";
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
import { SYMBOL_TO_ISO } from "../../../data/currency";
import "../../../layouts/hass-subpage";
@@ -34,6 +42,8 @@ class HaConfigSectionGeneral extends LitElement {
@state() private _timeZone?: string;
@state() private _location?: [number, number];
protected render(): TemplateResult {
const canEdit = ["storage", "default"].includes(
this.hass.config.config_source
@@ -47,7 +57,7 @@ class HaConfigSectionGeneral extends LitElement {
.header=${this.hass.localize("ui.panel.config.core.caption")}
>
<div class="content">
<ha-card>
<ha-card outlined>
<div class="card-content">
${!canEdit
? html`
@@ -177,27 +187,42 @@ class HaConfigSectionGeneral extends LitElement {
href="https://en.wikipedia.org/wiki/ISO_4217#Active_codes"
target="_blank"
rel="noopener noreferrer"
class="find-value"
>${this.hass.localize(
"ui.panel.config.core.section.core.core_config.find_currency_value"
)}</a
>
</div>
</div>
<ha-settings-row>
<div slot="heading">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.edit_location"
)}
</div>
<div slot="description" class="secondary">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.edit_location_description"
)}
</div>
<mwc-button @click=${this._editLocation}
>${this.hass.localize("ui.common.edit")}</mwc-button
>
</ha-settings-row>
${this.narrow
? html`
<ha-locations-editor
.hass=${this.hass}
.locations=${this._markerLocation(
this.hass.config.latitude,
this.hass.config.longitude,
this._location
)}
@location-updated=${this._locationChanged}
></ha-locations-editor>
`
: html`
<ha-settings-row>
<div slot="heading">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.edit_location"
)}
</div>
<div slot="description" class="secondary">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.edit_location_description"
)}
</div>
<mwc-button @click=${this._editLocation}
>${this.hass.localize("ui.common.edit")}</mwc-button
>
</ha-settings-row>
`}
<div class="card-actions">
<ha-progress-button @click=${this._updateEntry}>
${this.hass!.localize("ui.panel.config.zone.detail.update")}
@@ -237,7 +262,11 @@ class HaConfigSectionGeneral extends LitElement {
this._unitSystem = (ev.target as HaRadio).value as "metric" | "imperial";
}
private async _updateEntry(ev) {
private _locationChanged(ev: CustomEvent) {
this._location = ev.detail.location;
}
private async _updateEntry(ev: CustomEvent) {
const button = ev.target as HaProgressButton;
if (button.progress) {
return;
@@ -261,6 +290,21 @@ class HaConfigSectionGeneral extends LitElement {
}
}
private _markerLocation = memoizeOne(
(
lat: number,
lng: number,
location?: [number, number]
): MarkerLocation[] => [
{
id: "location",
latitude: location ? location[0] : lat,
longitude: location ? location[1] : lng,
location_editable: true,
},
]
);
private _editLocation() {
navigate("/config/zone");
}
@@ -274,7 +318,7 @@ class HaConfigSectionGeneral extends LitElement {
margin: 0 auto;
}
ha-card {
max-width: 500px;
max-width: 600px;
margin: 0 auto;
height: 100%;
justify-content: space-between;
@@ -302,6 +346,15 @@ class HaConfigSectionGeneral extends LitElement {
ha-select {
display: block;
}
a.find-value {
margin-top: 8px;
display: inline-block;
}
ha-locations-editor {
display: block;
height: 400px;
padding: 16px;
}
`,
];
}

View File

@@ -1,12 +1,13 @@
import { ActionDetail } from "@material/mwc-list";
import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { canShowPage } from "../../../common/config/can_show_page";
import "../../../components/ha-card";
import "../../../components/ha-navigation-list";
import { CloudStatus } from "../../../data/cloud";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
@@ -41,33 +42,24 @@ class HaConfigSystemNavigation extends LitElement {
back-path="/config"
.header=${this.hass.localize("ui.panel.config.dashboard.system.main")}
>
<ha-button-menu
corner="BOTTOM_START"
<mwc-button
slot="toolbar-icon"
@action=${this._handleAction}
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.overflow_menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.system_dashboard.restart_homeassistant"
)}
</mwc-list-item>
</ha-button-menu>
class="warning"
.label=${this.narrow
? this.hass.localize(
"ui.panel.config.system_dashboard.restart_homeassistant_short"
)
: this.hass.localize(
"ui.panel.config.system_dashboard.restart_homeassistant"
)}
@click=${this._restart}
></mwc-button>
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
full-width
>
<ha-card outlined>
${this.narrow
? html`<div class="title">
${this.hass.localize("ui.panel.config.dashboard.system.main")}
</div>`
: ""}
<ha-navigation-list
.hass=${this.hass}
.narrow=${this.narrow}
@@ -79,19 +71,28 @@ class HaConfigSystemNavigation extends LitElement {
`;
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.system_dashboard.confirm_restart"
),
confirm: () => {
this.hass.callService("homeassistant", "restart");
},
private _restart() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.system_dashboard.confirm_restart_title"
),
text: this.hass.localize(
"ui.panel.config.system_dashboard.confirm_restart_text"
),
confirmText: this.hass.localize(
"ui.panel.config.system_dashboard.restart_homeassistant_short"
),
confirm: () => {
this.hass.callService("homeassistant", "restart").catch((reason) => {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.system_dashboard.restart_error"
),
text: reason.message,
});
});
break;
}
},
});
}
static get styles(): CSSResultGroup {

View File

@@ -39,9 +39,9 @@ import { configSections } from "../ha-panel-config";
import "./ha-config-navigation";
import "./ha-config-updates";
const randomTip = (hass: HomeAssistant) => {
const randomTip = (hass: HomeAssistant, narrow: boolean) => {
const weighted: string[] = [];
const tips = [
let tips = [
{
content: hass.localize(
"ui.panel.config.tips.join",
@@ -84,11 +84,16 @@ const randomTip = (hass: HomeAssistant) => {
</span>`
),
weight: 2,
narrow: true,
},
{ content: hass.localize("ui.tips.key_c_hint"), weight: 1 },
{ content: hass.localize("ui.tips.key_m_hint"), weight: 1 },
{ content: hass.localize("ui.tips.key_c_hint"), weight: 1, narrow: false },
{ content: hass.localize("ui.tips.key_m_hint"), weight: 1, narrow: false },
];
if (narrow) {
tips = tips.filter((tip) => tip.narrow);
}
tips.forEach((tip) => {
for (let i = 0; i < tip.weight; i++) {
weighted.push(tip.content);
@@ -215,7 +220,7 @@ class HaConfigDashboard extends LitElement {
super.updated(changedProps);
if (!this._tip && changedProps.has("hass")) {
this._tip = randomTip(this.hass);
this._tip = randomTip(this.hass, this.narrow);
}
}

View File

@@ -1,5 +1,5 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property } from "lit/decorators";
import { css, html, LitElement, TemplateResult } from "lit";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-card";
import "../../../../components/ha-chip";
@@ -10,6 +10,7 @@ import {
DeviceAutomation,
} from "../../../../data/device_automation";
import { showScriptEditor } from "../../../../data/script";
import { buttonLinkStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
declare global {
@@ -29,6 +30,8 @@ export abstract class HaDeviceAutomationCard<
@property() public automations: T[] = [];
@state() public _showSecondary = false;
protected headerKey = "";
protected type = "";
@@ -60,28 +63,47 @@ export abstract class HaDeviceAutomationCard<
if (this.automations.length === 0) {
return html``;
}
const automations = this._showSecondary
? this.automations
: this.automations.filter(
(automation) => automation.metadata?.secondary === false
);
return html`
<h3>${this.hass.localize(this.headerKey)}</h3>
<div class="content">
<ha-chip-set>
${this.automations.map(
${automations.map(
(automation, idx) =>
html`
<ha-chip .index=${idx} @click=${this._handleAutomationClicked}>
<ha-chip
.index=${idx}
@click=${this._handleAutomationClicked}
class=${automation.metadata?.secondary ? "secondary" : ""}
>
${this._localizeDeviceAutomation(this.hass, automation)}
</ha-chip>
`
)}
</ha-chip-set>
${!this._showSecondary && automations.length < this.automations.length
? html`<button class="link" @click=${this._toggleSecondary}>
Show ${this.automations.length - automations.length} more...
</button>`
: ""}
</div>
`;
}
private _toggleSecondary() {
this._showSecondary = !this._showSecondary;
}
private _handleAutomationClicked(ev: CustomEvent) {
const automation = this.automations[(ev.currentTarget as any).index];
const automation = { ...this.automations[(ev.currentTarget as any).index] };
if (!automation) {
return;
}
delete automation.metadata;
if (this.script) {
showScriptEditor({ sequence: [automation as DeviceAction] });
fireEvent(this, "entry-selected");
@@ -93,11 +115,18 @@ export abstract class HaDeviceAutomationCard<
fireEvent(this, "entry-selected");
}
static get styles(): CSSResultGroup {
return css`
static styles = [
buttonLinkStyle,
css`
h3 {
color: var(--primary-text-color);
}
`;
}
.secondary {
--ha-chip-background-color: rgba(var(--rgb-primary-text-color), 0.07);
}
button.link {
color: var(--primary-color);
}
`,
];
}

View File

@@ -10,6 +10,7 @@ import {
fetchDeviceActions,
fetchDeviceConditions,
fetchDeviceTriggers,
sortDeviceAutomations,
} from "../../../../data/device_automation";
import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
@@ -63,16 +64,16 @@ export class DialogDeviceAutomation extends LitElement {
const { device, script } = this._params;
fetchDeviceActions(this.hass, device.id).then((actions) => {
this._actions = actions;
this._actions = actions.sort(sortDeviceAutomations);
});
if (script) {
return;
}
fetchDeviceTriggers(this.hass, device.id).then((triggers) => {
this._triggers = triggers;
this._triggers = triggers.sort(sortDeviceAutomations);
});
fetchDeviceConditions(this.hass, device.id).then((conditions) => {
this._conditions = conditions;
this._conditions = conditions.sort(sortDeviceAutomations);
});
}

View File

@@ -224,14 +224,14 @@ export const configSections: { [name: string]: PageNavigation[] } = {
path: "/config/person",
translationKey: "ui.panel.config.person.caption",
iconPath: mdiAccount,
iconColor: "#E48629",
iconColor: "#5A87FA",
},
{
component: "users",
path: "/config/users",
translationKey: "ui.panel.config.users.caption",
iconPath: mdiBadgeAccountHorizontal,
iconColor: "#E48629",
iconColor: "#5A87FA",
core: true,
advancedOnly: true,
},
@@ -254,6 +254,13 @@ export const configSections: { [name: string]: PageNavigation[] } = {
},
],
general: [
{
path: "/config/general",
translationKey: "ui.panel.config.core.caption",
iconPath: mdiCog,
iconColor: "#653249",
core: true,
},
{
path: "/config/updates",
translationKey: "ui.panel.config.updates.caption",
@@ -315,13 +322,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconColor: "#507FfE",
components: ["system_health", "hassio"],
},
{
path: "/config/general",
translationKey: "ui.panel.config.core.caption",
iconPath: mdiCog,
iconColor: "#653249",
core: true,
},
],
about: [
{

View File

@@ -8,6 +8,7 @@ import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-settings-row";
import { BOARD_NAMES } from "../../../data/hardware";
import {
extractApiErrorMessage,
ignoreSupervisorError,
@@ -56,6 +57,18 @@ class HaConfigHardware extends LitElement {
.narrow=${this.narrow}
.header=${this.hass.localize("ui.panel.config.hardware.caption")}
>
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-icon-button
.label=${this.hass.localize("common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item @click=${this._openHardware}
>${this.hass.localize(
"ui.panel.config.hardware.available_hardware.title"
)}</mwc-list-item
>
</ha-button-menu>
${this._error
? html`
<ha-alert alert-type="error"
@@ -68,59 +81,47 @@ class HaConfigHardware extends LitElement {
<div class="content">
<ha-card outlined>
<div class="card-content">
<ha-settings-row>
<span slot="heading"
>${this.hass.localize(
"ui.panel.config.hardware.board"
)}</span
>
<div slot="description">
<span class="value">${this._OSData.board}</span>
</div>
</ha-settings-row>
${this._OSData?.board
? html`
<ha-settings-row>
<span slot="heading"
>${BOARD_NAMES[this._OSData.board] ||
this.hass.localize(
"ui.panel.config.hardware.board"
)}</span
>
<div slot="description">
<span class="value">${this._OSData.board}</span>
</div>
</ha-settings-row>
`
: ""}
</div>
<div class="card-actions">
<div class="buttons">
${this._hostData.features.includes("reboot")
? html`
<ha-progress-button
class="warning"
@click=${this._hostReboot}
>
${this.hass.localize(
"ui.panel.config.hardware.reboot_host"
)}
</ha-progress-button>
`
: ""}
${this._hostData.features.includes("shutdown")
? html`
<ha-progress-button
class="warning"
@click=${this._hostShutdown}
>
${this.hass.localize(
"ui.panel.config.hardware.shutdown_host"
)}
</ha-progress-button>
`
: ""}
</div>
<ha-button-menu corner="BOTTOM_START">
<ha-icon-button
.label=${this.hass.localize("common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item
.action=${"hardware"}
@click=${this._openHardware}
>
${this.hass.localize(
"ui.panel.config.hardware.available_hardware.title"
)}
</mwc-list-item>
</ha-button-menu>
${this._hostData.features.includes("reboot")
? html`
<ha-progress-button
class="warning"
@click=${this._hostReboot}
>
${this.hass.localize(
"ui.panel.config.hardware.reboot_host"
)}
</ha-progress-button>
`
: ""}
${this._hostData.features.includes("shutdown")
? html`
<ha-progress-button
class="warning"
@click=${this._hostShutdown}
>
${this.hass.localize(
"ui.panel.config.hardware.shutdown_host"
)}
</ha-progress-button>
`
: ""}
</div>
</ha-card>
</div>
@@ -241,10 +242,6 @@ class HaConfigHardware extends LitElement {
justify-content: space-between;
align-items: center;
}
.buttons {
display: flex;
align-items: center;
}
`,
];
}

View File

@@ -111,6 +111,9 @@ export class HassioHostname extends LitElement {
justify-content: space-between;
align-items: center;
}
ha-settings-row {
border-top: none;
}
`;
}

View File

@@ -27,9 +27,9 @@ export class DeveloperYamlConfig extends LitElement {
@state() private _reloadableDomains: string[] = [];
private _validateLog = "";
@state() private _isValid: boolean | null = null;
private _isValid: boolean | null = null;
private _validateLog = "";
protected updated(changedProperties) {
const oldHass = changedProperties.get("hass");
@@ -82,11 +82,6 @@ export class DeveloperYamlConfig extends LitElement {
"ui.panel.developer-tools.tabs.yaml.section.validation.invalid"
)}
</span>
<mwc-button raised @click=${this._validateConfig}>
${this.hass.localize(
"ui.panel.developer-tools.tabs.yaml.section.validation.check_config"
)}
</mwc-button>
</div>
<div id="configLog" class="validate-log">
${this._validateLog}
@@ -94,10 +89,7 @@ export class DeveloperYamlConfig extends LitElement {
`}
</div>
<div class="card-actions">
<mwc-button
@click=${this._validateConfig}
.disabled=${this._validateLog}
>
<mwc-button @click=${this._validateConfig}>
${this.hass.localize(
"ui.panel.developer-tools.tabs.yaml.section.validation.check_config"
)}
@@ -175,13 +167,20 @@ export class DeveloperYamlConfig extends LitElement {
private _restart() {
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart"
title: this.hass.localize(
"ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart_title"
),
text: this.hass.localize(
"ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart_text"
),
confirmText: this.hass.localize(
"ui.panel.developer-tools.tabs.yaml.section.server_management.restart"
),
confirmText: this.hass!.localize("ui.common.leave"),
dismissText: this.hass!.localize("ui.common.stay"),
confirm: () => {
this.hass.callService("homeassistant", "restart");
this.hass.callService("homeassistant", "restart").catch((reason) => {
this._isValid = false;
this._validateLog = reason.message;
});
},
});
}
@@ -191,17 +190,17 @@ export class DeveloperYamlConfig extends LitElement {
haStyle,
css`
.validate-container {
height: 140px;
height: 60px;
}
.validate-result {
color: var(--success-color);
font-weight: 500;
margin-bottom: 1em;
}
.config-invalid {
margin: 1em 0;
text-align: center;
}
.config-invalid .text {
@@ -215,7 +214,7 @@ export class DeveloperYamlConfig extends LitElement {
}
.content {
padding: 28px 20px 0;
padding: 28px 20px 16px;
max-width: 1040px;
margin: 0 auto;
}

View File

@@ -20,7 +20,7 @@ const cardConfigStruct = assign(
const SCHEMA: HaFormSchema[] = [
{ name: "title", selector: { text: {} } },
{ name: "content", required: true, selector: { text: { multiline: true } } },
{ name: "content", required: true, selector: { template: {} } },
{ name: "theme", selector: { theme: {} } },
];

View File

@@ -686,6 +686,16 @@ export class BarMediaPlayer extends LitElement {
mwc-list-item[selected] {
font-weight: bold;
}
:host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] {
margin-left: 8px !important;
margin-right: 8px !important;
}
:host-context([style*="direction: rtl;"])
ha-svg-icon[slot="trailingIcon"] {
margin-left: 0px !important;
margin-right: 8px !important;
}
`;
}
}

View File

@@ -278,6 +278,7 @@ class PanelMediaBrowser extends LitElement {
ha-media-player-browse {
height: calc(100vh - (100px + var(--header-height)));
direction: ltr;
}
:host([narrow]) ha-media-player-browse {

View File

@@ -34,6 +34,9 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({
developer_statistics: {
redirect: "/developer-tools/statistics",
},
server_controls: {
redirect: "/developer-tools/yaml",
},
config: {
redirect: "/config/dashboard",
},
@@ -129,10 +132,7 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({
redirect: "/config/users",
},
general: {
redirect: "/config/core",
},
server_controls: {
redirect: "/developer-tools/yaml",
redirect: "/config/general",
},
logs: {
redirect: "/config/logs",
@@ -140,6 +140,27 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({
info: {
redirect: "/config/info",
},
system_health: {
redirect: "/config/system_health",
},
hardware: {
redirect: "/config/hardware",
},
storage: {
redirect: "/config/storage",
},
network: {
redirect: "/config/network",
},
analytics: {
redirect: "/config/analytics",
},
updates: {
redirect: "/config/updates",
},
system_dashboard: {
redirect: "/config/system",
},
customize: {
// customize was removed in 2021.12, fallback to dashboard
redirect: "/config/dashboard",

View File

@@ -1493,7 +1493,7 @@
"unit_system_metric": "Metric",
"imperial_example": "Fahrenheit, pounds",
"metric_example": "Celsius, kilograms",
"find_currency_value": "Find your value",
"find_currency_value": "Find my value",
"save_button": "Save",
"currency": "Currency",
"edit_location": "Edit location",
@@ -1519,7 +1519,7 @@
"caption": "Hardware",
"available_hardware": {
"failed_to_get": "Failed to get available hardware",
"title": "Available hardware",
"title": "All Hardware",
"subsystem": "Subsystem",
"device_path": "Device path",
"id": "ID",
@@ -1675,7 +1675,7 @@
"introduction": "The automation editor allows you to create and edit automations. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.",
"learn_more": "Learn more about automations",
"pick_automation": "Pick automation to edit",
"no_automations": "We couldnt find any automations",
"no_automations": "We couldn't find any automations",
"add_automation": "Create automation",
"only_editable": "Only automations in automations.yaml are editable.",
"dev_only_editable": "Only automations that have a unique ID assigned are debuggable.",
@@ -1961,6 +1961,9 @@
"run_action_error": "Error running action",
"run_action_success": "Action run successfully",
"duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]",
"enable": "Enable",
"disable": "Disable",
"disabled": "Disabled",
"delete": "[%key:ui::panel::mailbox::delete_button%]",
"delete_confirm": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm%]",
"unsupported_action": "No visual editor support for action: {action}",
@@ -2050,6 +2053,9 @@
"label": "Stop",
"stop": "Reason for stopping",
"error": "Stop because of an unexpected error"
},
"parallel": {
"label": "Run in parallel"
}
}
}
@@ -3164,8 +3170,11 @@
"supervisor_stats": "Supervisor Stats"
},
"system_dashboard": {
"confirm_restart": "Are you sure you want to restart Home Assistant?",
"restart_homeassistant": "Restart Home Assistant"
"confirm_restart_text": "Restarting Home Assistant will stop all your active dashboards, automations and scripts.",
"confirm_restart_title": "Restart Home Assistant?",
"restart_homeassistant": "Restart Home Assistant",
"restart_homeassistant_short": "Restart",
"restart_error": "Failed to restart Home Assistant"
}
},
"lovelace": {
@@ -4151,10 +4160,10 @@
"section": {
"validation": {
"heading": "Configuration validation",
"introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid.",
"introduction": "Validate your configuration if you recently made some changes to it and want to make sure that it is all valid.",
"check_config": "Check configuration",
"valid": "Configuration valid!",
"invalid": "Configuration invalid"
"invalid": "Configuration invalid!"
},
"reloading": {
"heading": "YAML configuration reloading",
@@ -4196,12 +4205,12 @@
},
"server_management": {
"heading": "Home Assistant",
"introduction": "Restarting Home Assistant will stop your dashboard and automations. After the reboot, each configuration will be reloaded.",
"confirm_restart_text": "Restarting Home Assistant will stop all your active dashboards, automations and scripts.",
"confirm_restart_title": "Restart Home Assistant?",
"restart": "Restart",
"restart_home_assistant": "Restart Home Assistant",
"confirm_restart": "Are you sure you want to restart Home Assistant?",
"stop": "Stop",
"confirm_stop": "Are you sure you want to stop Home Assistant?"
"confirm_stop": "Are you sure you want to stop Home Assistant?",
"restart_error": "Failed to restart Home Assistant"
}
}
}