Compare commits

..

4 Commits

Author SHA1 Message Date
Aidan Timson
6aec725908 Move content rendering to dedicated function 2026-04-20 16:21:57 +01:00
Aidan Timson
787e9bde47 Fill tabs 2026-04-20 16:06:41 +01:00
Aidan Timson
64dede3ab4 Use same slot layout 2026-04-20 16:05:40 +01:00
Aidan Timson
670180df90 Setup 2026-04-20 16:00:08 +01:00
37 changed files with 315 additions and 451 deletions

View File

@@ -6,7 +6,6 @@ on:
- cron: "0 * * * *"
permissions:
actions: write
issues: write
pull-requests: write

View File

@@ -107,7 +107,7 @@
"lit": "3.3.2",
"lit-html": "3.3.2",
"luxon": "3.7.2",
"marked": "18.0.2",
"marked": "18.0.1",
"memoize-one": "6.0.0",
"node-vibrant": "4.0.4",
"object-hash": "3.0.0",
@@ -167,7 +167,7 @@
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.4",
"del": "8.0.1",
"eslint": "10.2.1",
"eslint": "10.2.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-webpack": "0.13.11",

View File

@@ -0,0 +1,10 @@
<svg width="75" height="79" viewBox="0 0 75 79" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M73.8393 17.4898C72.6973 9.00165 65.2994 2.31235 56.5296 1.01614C55.05 0.797115 49.4441 0 36.4582 0H36.3612C23.3717 0 20.585 0.797115 19.1054 1.01614C10.5798 2.27644 2.79399 8.28712 0.904997 16.8758C-0.00358524 21.1056 -0.100549 25.7949 0.0682394 30.0965C0.308852 36.2651 0.355538 42.423 0.91577 48.5665C1.30307 52.6474 1.97872 56.6957 2.93763 60.6812C4.73325 68.042 12.0019 74.1676 19.1233 76.6666C26.7478 79.2728 34.9474 79.7055 42.8039 77.9162C43.6682 77.7151 44.5217 77.4817 45.3645 77.216C47.275 76.6092 49.5123 75.9305 51.1571 74.7385C51.1797 74.7217 51.1982 74.7001 51.2112 74.6753C51.2243 74.6504 51.2316 74.6229 51.2325 74.5948V68.6416C51.2321 68.6154 51.2259 68.5896 51.2142 68.5661C51.2025 68.5426 51.1858 68.522 51.1651 68.5058C51.1444 68.4896 51.1204 68.4783 51.0948 68.4726C51.0692 68.4669 51.0426 68.467 51.0171 68.4729C45.9835 69.675 40.8254 70.2777 35.6502 70.2682C26.7439 70.2682 24.3486 66.042 23.6626 64.2826C23.1113 62.762 22.7612 61.1759 22.6212 59.5646C22.6197 59.5375 22.6247 59.5105 22.6357 59.4857C22.6466 59.4609 22.6633 59.4391 22.6843 59.422C22.7053 59.4048 22.73 59.3929 22.7565 59.3871C22.783 59.3813 22.8104 59.3818 22.8367 59.3886C27.7864 60.5826 32.8604 61.1853 37.9522 61.1839C39.1768 61.1839 40.3978 61.1839 41.6224 61.1516C46.7435 61.008 52.1411 60.7459 57.1796 59.7621C57.3053 59.7369 57.431 59.7154 57.5387 59.6831C65.4861 58.157 73.0493 53.3672 73.8178 41.2381C73.8465 40.7606 73.9184 36.2364 73.9184 35.7409C73.9219 34.0569 74.4606 23.7949 73.8393 17.4898Z" fill="url(#paint0_linear_549_34)"/>
<path d="M61.2484 27.0263V48.114H52.8916V27.6475C52.8916 23.3388 51.096 21.1413 47.4437 21.1413C43.4287 21.1413 41.4177 23.7409 41.4177 28.8755V40.0782H33.1111V28.8755C33.1111 23.7409 31.0965 21.1413 27.0815 21.1413C23.4507 21.1413 21.6371 23.3388 21.6371 27.6475V48.114H13.2839V27.0263C13.2839 22.7176 14.384 19.2946 16.5843 16.7572C18.8539 14.2258 21.8311 12.926 25.5264 12.926C29.8036 12.926 33.0357 14.5705 35.1905 17.8559L37.2698 21.346L39.3527 17.8559C41.5074 14.5705 44.7395 12.926 49.0095 12.926C52.7013 12.926 55.6784 14.2258 57.9553 16.7572C60.1531 19.2922 61.2508 22.7152 61.2484 27.0263Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_549_34" x1="37.0692" y1="0" x2="37.0692" y2="79" gradientUnits="userSpaceOnUse">
<stop stop-color="#6364FF"/>
<stop offset="1" stop-color="#563ACC"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -13,9 +13,9 @@ import {
} from "../../data/entity/entity";
import { forwardHaptic } from "../../data/haptics";
import type { HomeAssistant } from "../../types";
import "../ha-control-switch";
import "../ha-formfield";
import "../ha-icon-button";
import "../ha-switch";
const isOn = (stateObj?: HassEntity) =>
stateObj !== undefined &&
@@ -35,7 +35,7 @@ export class HaEntityToggle extends LitElement {
protected render(): TemplateResult {
if (!this.stateObj) {
return html`<ha-control-switch disabled></ha-control-switch> `;
return html` <ha-switch disabled></ha-switch> `;
}
if (
@@ -62,14 +62,14 @@ export class HaEntityToggle extends LitElement {
`;
}
const switchTemplate = html`<ha-control-switch
const switchTemplate = html`<ha-switch
aria-label=${`Toggle ${computeStateName(this.stateObj)} ${
this._isOn ? "off" : "on"
}`}
.checked=${this._isOn}
.disabled=${this.stateObj.state === UNAVAILABLE}
@change=${this._toggleChanged}
></ha-control-switch>`;
></ha-switch>`;
if (!this.label) {
return switchTemplate;
@@ -163,9 +163,6 @@ export class HaEntityToggle extends LitElement {
white-space: nowrap;
min-width: 38px;
}
ha-control-switch {
--control-switch-thickness: 20px;
}
ha-icon-button {
--ha-icon-button-size: 40px;
color: var(--ha-icon-button-inactive-color, var(--primary-text-color));
@@ -174,6 +171,9 @@ export class HaEntityToggle extends LitElement {
ha-icon-button.state-active {
color: var(--ha-icon-button-active-color, var(--primary-color));
}
ha-switch {
padding: 13px 5px;
}
`;
}

View File

@@ -116,29 +116,11 @@ export class HaControlSwitch extends LitElement {
}
private _keydown(ev: any) {
const supportedKeys = ["Enter", " "];
if (this.vertical) {
supportedKeys.push("ArrowUp", "ArrowDown");
} else {
supportedKeys.push("ArrowLeft", "ArrowRight");
}
if (!supportedKeys.includes(ev.key)) {
if (ev.key !== "Enter" && ev.key !== " ") {
return;
}
ev.preventDefault();
if (
ev.key === "Enter" ||
ev.key === " " ||
(this.vertical &&
((this.checked && ev.key === "ArrowDown") ||
(!this.checked && ev.key === "ArrowUp"))) ||
(!this.vertical &&
((!this.checked && ev.key === "ArrowRight") ||
(this.checked && ev.key === "ArrowLeft")))
) {
this._toggle();
}
this._toggle();
}
protected render(): TemplateResult {
@@ -150,7 +132,7 @@ export class HaControlSwitch extends LitElement {
aria-checked=${this.checked ? "true" : "false"}
aria-label=${ifDefined(this.label)}
role="switch"
tabindex=${ifDefined(this.disabled ? undefined : "0")}
tabindex="0"
?checked=${this.checked}
?disabled=${this.disabled}
>
@@ -174,7 +156,6 @@ export class HaControlSwitch extends LitElement {
--control-switch-on-color: var(--primary-color);
--control-switch-off-color: var(--disabled-color);
--control-switch-background-opacity: 0.2;
--control-switch-hover-background-opacity: 0.4;
--control-switch-thickness: 40px;
--control-switch-border-radius: var(--ha-border-radius-lg);
--control-switch-padding: 4px;
@@ -186,10 +167,10 @@ export class HaControlSwitch extends LitElement {
transition: box-shadow 180ms ease-in-out;
-webkit-tap-highlight-color: transparent;
}
.switch:not([disabled]):focus-visible {
.switch:focus-visible {
box-shadow: 0 0 0 2px var(--control-switch-off-color);
}
.switch[checked]:not([disabled]):focus-visible {
.switch[checked]:focus-visible {
box-shadow: 0 0 0 2px var(--control-switch-on-color);
}
.switch {
@@ -218,10 +199,6 @@ export class HaControlSwitch extends LitElement {
transition: background-color 180ms ease-in-out;
opacity: var(--control-switch-background-opacity);
}
.switch:not([disabled]):focus-visible .background,
.switch:not([disabled]):hover .background {
opacity: var(--control-switch-hover-background-opacity);
}
.switch .button {
width: 50%;
height: 100%;

View File

@@ -124,7 +124,7 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
@state()
@consume({ context: internationalizationContext, subscribe: true })
private _i18n?: ContextType<typeof internationalizationContext>;
private _i18n!: ContextType<typeof internationalizationContext>;
// disabled till iOS app fix the "focus_element" implementation
// @state()
@@ -176,7 +176,7 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
@wa-after-hide=${this._handleAfterHide}
>
${!this.withoutHeader
? html`<slot name="header">
? html` <slot name="header">
<ha-dialog-header
.subtitlePosition=${this.headerSubtitlePosition}
.showBorder=${this._bodyScrolled}
@@ -184,8 +184,7 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
<slot name="headerNavigationIcon" slot="navigationIcon">
<ha-icon-button
data-dialog="close"
.label=${this._i18n?.localize?.("ui.common.close") ??
"Close"}
.label=${this._i18n?.localize("ui.common.close") ?? "Close"}
.path=${mdiClose}
></ha-icon-button>
</slot>

View File

@@ -163,7 +163,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
@state()
@consume({ context: internationalizationContext, subscribe: true })
private i18n?: ContextType<typeof internationalizationContext>;
private i18n!: ContextType<typeof internationalizationContext>;
@state() private _items: PickerComboBoxItem[] = [];
@@ -218,9 +218,9 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
const searchLabel =
this.label ??
(this.allowCustomValue
? (this.i18n?.localize?.("ui.components.combo-box.search_or_custom") ??
? (this.i18n.localize?.("ui.components.combo-box.search_or_custom") ??
"Search | Add custom value")
: (this.i18n?.localize?.("ui.common.search") ?? "Search"));
: (this.i18n.localize?.("ui.common.search") ?? "Search"));
return html`<ha-input-search
appearance="outlined"
@@ -347,7 +347,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
return caseInsensitiveStringCompare(
sortLabelA,
sortLabelB,
this.i18n?.locale?.language ?? navigator.language
this.i18n.locale?.language ?? navigator.language
);
});
}
@@ -364,7 +364,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
id: this._search,
primary:
this.customValueLabel ??
this.i18n?.localize?.("ui.components.combo-box.add_custom_item") ??
this.i18n.localize?.("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${this._search}"`,
icon_path: mdiPlus,
@@ -398,10 +398,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
? typeof this.notFoundLabel === "function"
? this.notFoundLabel(this._search)
: this.notFoundLabel ||
this.i18n?.localize?.("ui.components.combo-box.no_match") ||
this.i18n.localize?.("ui.components.combo-box.no_match") ||
"No matching items found"
: this.emptyLabel ||
this.i18n?.localize?.("ui.components.combo-box.no_items") ||
this.i18n.localize?.("ui.components.combo-box.no_items") ||
"No items available"}</span
>
</ha-combo-box-item>
@@ -493,7 +493,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
id: searchString,
primary:
this.customValueLabel ??
this.i18n?.localize?.("ui.components.combo-box.add_custom_item") ??
this.i18n.localize?.("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${searchString}"`,
icon_path: mdiPlus,

View File

@@ -454,7 +454,8 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
if (!this.hass.user?.is_admin) {
return nothing;
}
const isSelected = selectedPanel === "config";
const isSelected =
selectedPanel === "config" || this.route.path?.startsWith("/hassio/");
return html`
<ha-md-list-item
class="configuration ${classMap({ selected: isSelected })}"

View File

@@ -607,8 +607,6 @@ export interface TriggerSidebarConfig extends BaseSidebarConfig {
description?: TriggerDescription;
yamlMode: boolean;
uiSupported: boolean;
paste: () => void;
pasteAvailable: () => boolean;
}
export interface ConditionSidebarConfig extends BaseSidebarConfig {
@@ -625,8 +623,6 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig {
description?: ConditionDescription;
yamlMode: boolean;
uiSupported: boolean;
paste: () => void;
pasteAvailable: () => boolean;
}
export interface ActionSidebarConfig extends BaseSidebarConfig {
@@ -645,8 +641,6 @@ export interface ActionSidebarConfig extends BaseSidebarConfig {
};
yamlMode: boolean;
uiSupported: boolean;
paste: () => void;
pasteAvailable: () => boolean;
}
export interface OptionSidebarConfig extends BaseSidebarConfig {

View File

@@ -2,12 +2,15 @@ import type { Connection } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../../../types";
import type { LovelaceResource } from "../resource";
import type { LovelaceStrategyConfig } from "./strategy";
import type { LovelaceViewRawConfig } from "./view";
import type {
LovelaceDashboardBackgroundConfig,
LovelaceViewRawConfig,
} from "./view";
export interface LovelaceDashboardBaseConfig {}
export interface LovelaceConfig extends LovelaceDashboardBaseConfig {
background?: string;
background?: LovelaceDashboardBackgroundConfig;
views: LovelaceViewRawConfig[];
}

View File

@@ -31,6 +31,10 @@ export interface LovelaceViewBackgroundConfig {
attachment?: "scroll" | "fixed";
}
export type LovelaceDashboardBackgroundConfig =
| string
| LovelaceViewBackgroundConfig;
export interface LovelaceViewHeaderConfig {
card?: LovelaceCardConfig;
layout?: "start" | "center" | "responsive";
@@ -60,7 +64,7 @@ export interface LovelaceBaseViewConfig {
show_icon_and_title?: boolean;
theme?: string;
panel?: boolean;
background?: string | LovelaceViewBackgroundConfig;
background?: LovelaceDashboardBackgroundConfig;
visible?: boolean | ShowViewConfig[];
subview?: boolean;
back_path?: string;

View File

@@ -1,4 +1,4 @@
import { mdiAccountGroup, mdiOpenInNew } from "@mdi/js";
import { mdiOpenInNew } from "@mdi/js";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
@@ -92,8 +92,12 @@ class DialogCommunity extends LitElement {
href="https://fosstodon.org/@homeassistant"
>
<ha-list-item hasMeta graphic="icon">
<ha-svg-icon .path=${mdiAccountGroup} slot="graphic"></ha-svg-icon>
${this.localize("ui.panel.page-onboarding.welcome.social_media")}
<img
src="/static/images/logo_mastodon.svg"
slot="graphic"
alt="Mastodon Logo"
/>
${this.localize("ui.panel.page-onboarding.welcome.mastodon")}
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item>
</a>

View File

@@ -10,7 +10,6 @@ import {
mdiCheckboxOutline,
mdiContentCopy,
mdiContentCut,
mdiContentPaste,
mdiDelete,
mdiDotsVertical,
mdiPlay,
@@ -386,31 +385,6 @@ export default class HaAutomationActionRow extends LitElement {
)}
</ha-dropdown-item>
${this._pasteAvailable()
? html`
<ha-dropdown-item value="paste">
<ha-svg-icon slot="icon" .path=${mdiContentPaste}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.actions.paste"
),
html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>V</span>
</span>`
)}
</ha-dropdown-item>
`
: nothing}
${!this.optionsInSidebar
? html`
<ha-dropdown-item
@@ -795,9 +769,6 @@ export default class HaAutomationActionRow extends LitElement {
private _copyAction = () => {
this._setClipboard();
if (this._selected && this.optionsInSidebar) {
this.openSidebar(); // refresh sidebar
}
showEditorToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.actions.copied_to_clipboard"
@@ -820,15 +791,6 @@ export default class HaAutomationActionRow extends LitElement {
});
};
private _pasteAction = () => {
const action = this._clipboard?.action;
if (!action) return;
fireEvent(this, "paste", { item: action });
};
private _pasteAvailable = () => !!this._clipboard?.action;
private _moveUp = () => {
fireEvent(this, "move-up");
};
@@ -906,8 +868,6 @@ export default class HaAutomationActionRow extends LitElement {
delete: this._onDelete,
copy: this._copyAction,
cut: this._cutAction,
paste: this._pasteAction,
pasteAvailable: this._pasteAvailable,
duplicate: this._duplicateAction,
insertAfter: this._insertAfter,
run: this._runAction,
@@ -1001,9 +961,6 @@ export default class HaAutomationActionRow extends LitElement {
case "cut":
this._cutAction();
break;
case "paste":
this._pasteAction();
break;
case "move_up":
this._moveUp();
break;

View File

@@ -80,7 +80,6 @@ export default class HaAutomationAction extends AutomationSortableListMixin<Acti
.narrow=${this.narrow}
.disabled=${this.disabled}
@duplicate=${this.duplicateItem}
@paste=${this.pasteItem}
@insert-after=${this.insertAfter}
@move-down=${this.moveDown}
@move-up=${this.moveUp}

View File

@@ -6,7 +6,6 @@ import {
mdiArrowUp,
mdiContentCopy,
mdiContentCut,
mdiContentPaste,
mdiDelete,
mdiDotsVertical,
mdiFlask,
@@ -279,31 +278,6 @@ export default class HaAutomationConditionRow extends LitElement {
)}
</ha-dropdown-item>
${this._pasteAvailable()
? html`
<ha-dropdown-item value="paste">
<ha-svg-icon slot="icon" .path=${mdiContentPaste}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.actions.paste"
),
html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>V</span>
</span>`
)}
</ha-dropdown-item>
`
: nothing}
${!this.optionsInSidebar
? html`
<ha-dropdown-item
@@ -691,9 +665,6 @@ export default class HaAutomationConditionRow extends LitElement {
private _copyCondition = () => {
this._setClipboard();
if (this._selected && this.optionsInSidebar) {
this.openSidebar(); // refresh sidebar
}
showEditorToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.conditions.copied_to_clipboard"
@@ -716,15 +687,6 @@ export default class HaAutomationConditionRow extends LitElement {
});
};
private _pasteCondition = () => {
const condition = this._clipboard?.condition;
if (!condition) return;
fireEvent(this, "paste", { item: condition });
};
private _pasteAvailable = () => !!this._clipboard?.condition;
private _moveUp = () => {
fireEvent(this, "move-up");
};
@@ -828,8 +790,6 @@ export default class HaAutomationConditionRow extends LitElement {
insertAfter: this._insertAfter,
copy: this._copyCondition,
cut: this._cutCondition,
paste: this._pasteCondition,
pasteAvailable: this._pasteAvailable,
test: this._testCondition,
config: sidebarCondition,
uiSupported: this._uiSupported(
@@ -898,9 +858,6 @@ export default class HaAutomationConditionRow extends LitElement {
case "cut":
this._cutCondition();
break;
case "paste":
this._pasteCondition();
break;
case "move_up":
this._moveUp();
break;

View File

@@ -226,7 +226,6 @@ export default class HaAutomationCondition extends AutomationSortableListMixin<C
.disabled=${this.disabled}
.narrow=${this.narrow}
@duplicate=${this.duplicateItem}
@paste=${this.pasteItem}
@insert-after=${this.insertAfter}
@move-down=${this.moveDown}
@move-up=${this.moveUp}

View File

@@ -104,9 +104,6 @@ declare global {
value: Trigger | Condition | Action | Trigger[] | Condition[] | Action[];
};
"save-automation": undefined;
paste: {
item: Trigger | Condition | Action;
};
}
}

View File

@@ -165,21 +165,6 @@ export const AutomationSortableListMixin = <T extends object>(
});
}
protected pasteItem(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.item) return;
const index = (ev.target as any).index;
const clonedItem = deepClone(ev.detail.item);
this.setHighlightedItems(ensureArray(clonedItem));
fireEvent(this, "value-changed", {
// @ts-expect-error Requires library bump to ES2023
value: this.items.toSpliced(index + 1, 0, clonedItem),
});
}
protected insertAfter(ev: CustomEvent) {
ev.stopPropagation();
const index = (ev.target as any).index;

View File

@@ -5,7 +5,6 @@ import {
mdiCheckboxOutline,
mdiContentCopy,
mdiContentCut,
mdiContentPaste,
mdiDelete,
mdiPlay,
mdiPlayCircleOutline,
@@ -229,37 +228,6 @@ export default class HaAutomationSidebarAction extends LitElement {
: nothing}
</div>
</ha-dropdown-item>
${this.config.pasteAvailable()
? html`
<ha-dropdown-item
slot="menu-items"
value="paste"
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentPaste}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.paste"
)}
${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>V</span>
</span>`
: nothing}
</div>
</ha-dropdown-item>
`
: nothing}
<ha-dropdown-item
slot="menu-items"
value="toggle_yaml_mode"
@@ -423,9 +391,6 @@ export default class HaAutomationSidebarAction extends LitElement {
case "cut":
this.config.cut();
break;
case "paste":
this.config.paste();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;

View File

@@ -3,7 +3,6 @@ import {
mdiAppleKeyboardCommand,
mdiContentCopy,
mdiContentCut,
mdiContentPaste,
mdiDelete,
mdiFlask,
mdiPlayCircleOutline,
@@ -222,37 +221,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
: nothing}
</div>
</ha-dropdown-item>
${this.config.pasteAvailable()
? html`
<ha-dropdown-item
slot="menu-items"
value="paste"
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentPaste}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.paste"
)}
${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>V</span>
</span>`
: nothing}
</div>
</ha-dropdown-item>
`
: nothing}
<ha-dropdown-item
slot="menu-items"
value="toggle_yaml_mode"
@@ -468,9 +436,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
case "cut":
this.config.cut();
break;
case "paste":
this.config.paste();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;

View File

@@ -3,7 +3,6 @@ import {
mdiAppleKeyboardCommand,
mdiContentCopy,
mdiContentCut,
mdiContentPaste,
mdiDelete,
mdiIdentifier,
mdiPlayCircleOutline,
@@ -210,37 +209,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
: nothing}
</div>
</ha-dropdown-item>
${this.config.pasteAvailable()
? html`
<ha-dropdown-item
slot="menu-items"
value="paste"
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentPaste}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.paste"
)}
${!this.narrow
? html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>V</span>
</span>`
: nothing}
</div>
</ha-dropdown-item>
`
: nothing}
<ha-dropdown-item
slot="menu-items"
value="toggle_yaml_mode"
@@ -383,9 +351,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
case "cut":
this.config.cut();
break;
case "paste":
this.config.paste();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;

View File

@@ -6,7 +6,6 @@ import {
mdiArrowUp,
mdiContentCopy,
mdiContentCut,
mdiContentPaste,
mdiDelete,
mdiDotsVertical,
mdiInformationOutline,
@@ -315,31 +314,6 @@ export default class HaAutomationTriggerRow extends LitElement {
)}
</ha-dropdown-item>
${this._pasteAvailable()
? html`
<ha-dropdown-item value="paste">
<ha-svg-icon slot="icon" .path=${mdiContentPaste}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.actions.paste"
),
html`<span class="shortcut">
<span
>${isMac
? html`<ha-svg-icon
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
"ui.panel.config.automation.editor.ctrl"
)}</span
>
<span>+</span>
<span>V</span>
</span>`
)}
</ha-dropdown-item>
`
: nothing}
${!this.optionsInSidebar
? html`
<ha-dropdown-item
@@ -648,8 +622,6 @@ export default class HaAutomationTriggerRow extends LitElement {
copy: this._copyTrigger,
duplicate: this._duplicateTrigger,
cut: this._cutTrigger,
paste: this._pasteTrigger,
pasteAvailable: this._pasteAvailable,
insertAfter: this._insertAfter,
config: trigger,
uiSupported: this._uiSupported(
@@ -790,9 +762,6 @@ export default class HaAutomationTriggerRow extends LitElement {
private _copyTrigger = () => {
this._setClipboard();
if (this._selected && this.optionsInSidebar) {
this.openSidebar(); // refresh sidebar
}
showEditorToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.triggers.copied_to_clipboard"
@@ -815,15 +784,6 @@ export default class HaAutomationTriggerRow extends LitElement {
});
};
private _pasteTrigger = () => {
const trigger = this._clipboard?.trigger;
if (!trigger) return;
fireEvent(this, "paste", { item: trigger });
};
private _pasteAvailable = () => !!this._clipboard?.trigger;
private _moveUp = () => {
fireEvent(this, "move-up");
};
@@ -896,9 +856,6 @@ export default class HaAutomationTriggerRow extends LitElement {
case "cut":
this._cutTrigger();
break;
case "paste":
this._pasteTrigger();
break;
case "move_up":
this._moveUp();
break;

View File

@@ -137,7 +137,6 @@ export default class HaAutomationTrigger extends AutomationSortableListMixin<Tri
.trigger=${trg}
.triggerDescriptions=${this._triggerDescriptions}
@duplicate=${this.duplicateItem}
@paste=${this.pasteItem}
@insert-after=${this.insertAfter}
@move-down=${this.moveDown}
@move-up=${this.moveUp}

View File

@@ -13,15 +13,14 @@ import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-menu-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-tip";
import "../../../components/ha-tooltip";
import "../../../components/ha-top-app-bar-fixed";
import "../../../components/ha-tooltip";
import type { CloudStatus } from "../../../data/cloud";
import type { RepairsIssue } from "../../../data/repairs";
import {
@@ -48,6 +47,7 @@ import { configSections } from "../ha-panel-config";
import "../repairs/ha-config-repairs";
import "./ha-config-navigation";
import "./ha-config-updates";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
const randomTip = (openFn: any, hass: HomeAssistant, narrow: boolean) => {
const weighted: string[] = [];
@@ -60,11 +60,23 @@ const randomTip = (openFn: any, hass: HomeAssistant, narrow: boolean) => {
rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_forums")}</a
>`,
social_media: html`<a
href=${documentationUrl(hass, `/socials`)}
twitter: html`<a
href=${documentationUrl(hass, `/twitter`)}
target="_blank"
rel="noreferrer"
>${hass.localize("ui.panel.config.tips.social_media")}</a
>${hass.localize("ui.panel.config.tips.join_x")}</a
>`,
mastodon: html`<a
href=${documentationUrl(hass, `/mastodon`)}
target="_blank"
rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_mastodon")}</a
>`,
bluesky: html`<a
href=${documentationUrl(hass, `/bluesky`)}
target="_blank"
rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_bluesky")}</a
>`,
discord: html`<a
href=${documentationUrl(hass, `/join-chat`)}

View File

@@ -1,6 +1,8 @@
import type { CSSResultGroup } from "lit";
import { mdiClose } from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, 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 { slugify } from "../../../../common/string/slugify";
@@ -8,17 +10,28 @@ import "../../../../components/ha-button";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-form/ha-form";
import "../../../../components/ha-dialog";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-tab-group";
import "../../../../components/ha-tab-group-tab";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type {
LovelaceDashboard,
LovelaceDashboardCreateParams,
LovelaceDashboardMutableParams,
} from "../../../../data/lovelace/dashboard";
import { haStyleDialog } from "../../../../resources/styles";
import {
haStyleDialog,
haStyleDialogFixedTop,
} from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "../../../lovelace/editor/view-editor/hui-view-background-editor";
import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
import { pickAvailableDashboardUrlPath } from "./pick-available-dashboard-url-path";
const TABS = ["tab-settings", "tab-background"] as const;
@customElement("dialog-lovelace-dashboard-detail")
export class DialogLovelaceDashboardDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -35,10 +48,16 @@ export class DialogLovelaceDashboardDetail extends LitElement {
@state() private _submitting = false;
@state() private _currTab: (typeof TABS)[number] = TABS[0];
@state() private _backgroundConfig?: LovelaceConfig;
public showDialog(params: LovelaceDashboardDetailsDialogParams): void {
this._params = params;
this._error = undefined;
this._urlPathChanged = false;
this._currTab = TABS[0];
this._backgroundConfig = params.lovelaceConfig;
this._open = true;
if (this._params.dashboard) {
this._data = this._params.dashboard;
@@ -64,6 +83,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
private _dialogClosed(): void {
this._params = undefined;
this._data = undefined;
this._backgroundConfig = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -73,6 +93,8 @@ export class DialogLovelaceDashboardDetail extends LitElement {
}
const titleInvalid = !this._data.title || !this._data.title.trim();
const showBackgroundTab =
Boolean(this._params.lovelaceConfig) && Boolean(this._params.saveConfig);
const cancelButton = html`
<ha-button
@@ -88,38 +110,51 @@ export class DialogLovelaceDashboardDetail extends LitElement {
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._params.urlPath
? this._data.title ||
this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard"
)
: this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
)}
width=${showBackgroundTab ? "large" : "medium"}
prevent-scrim-close
@closed=${this._dialogClosed}
class=${classMap({
"has-background-tab": showBackgroundTab,
})}
>
<div>
${this._params.dashboard?.mode === "yaml"
? this.hass.localize(
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
)
: html`
<ha-form
autofocus
.schema=${this._schema(
this._params,
this._data?.require_admin
${showBackgroundTab
? html`
<ha-dialog-header show-border slot="header">
<ha-icon-button
slot="navigationIcon"
@click=${this.closeDialog}
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<h2 slot="title">
${this._params.urlPath
? this._data.title ||
this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard"
)
: this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
)}
</h2>
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
${TABS.map(
(tab) => html`
<ha-tab-group-tab
slot="nav"
.panel=${tab}
.active=${this._currTab === tab}
>
${this.hass.localize(
`ui.panel.lovelace.editor.edit_view.${tab.replace("-", "_")}`
)}
</ha-tab-group-tab>
`
)}
.data=${this._data}
.hass=${this.hass}
.error=${this._error}
.computeLabel=${this._computeLabel}
.computeHelper=${this._computeHelper}
@value-changed=${this._valueChanged}
></ha-form>
`}
</div>
</ha-tab-group>
</ha-dialog-header>
`
: nothing}
<div>${this._renderContent(showBackgroundTab)}</div>
<ha-dialog-footer slot="footer">
${this._params.urlPath
? html`
@@ -163,6 +198,39 @@ export class DialogLovelaceDashboardDetail extends LitElement {
`;
}
private _renderContent(
showBackgroundTab: boolean
): string | TemplateResult<1> | typeof nothing {
if (this._params?.dashboard?.mode === "yaml") {
return this.hass.localize(
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
);
}
if (this._currTab === "tab-background" && showBackgroundTab) {
return html`
<hui-view-background-editor
.hass=${this.hass}
.config=${this._backgroundConfig}
@background-config-changed=${this._backgroundConfigChanged}
></hui-view-background-editor>
`;
}
return html`
<ha-form
autofocus
.schema=${this._schema(this._params!, this._data?.require_admin)}
.data=${this._data}
.hass=${this.hass}
.error=${this._error}
.computeLabel=${this._computeLabel}
.computeHelper=${this._computeHelper}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
private _schema = memoizeOne(
(params: LovelaceDashboardDetailsDialogParams, requireAdmin?: boolean) =>
[
@@ -254,6 +322,12 @@ export class DialogLovelaceDashboardDetail extends LitElement {
}
}
private _backgroundConfigChanged(
ev: CustomEvent<{ config: LovelaceConfig }>
) {
this._backgroundConfig = ev.detail.config;
}
private _fillUrlPath(title: string) {
if (this._urlPathChanged || !title) {
return;
@@ -280,6 +354,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
}
this._submitting = true;
try {
if (
this._backgroundConfig &&
this._params?.saveConfig &&
this._params.lovelaceConfig &&
this._backgroundConfig.background !==
this._params.lovelaceConfig.background
) {
await this._params.saveConfig(this._backgroundConfig);
}
if (this._params!.dashboard) {
const values: Partial<LovelaceDashboardMutableParams> = {
require_admin: this._data!.require_admin,
@@ -314,6 +397,18 @@ export class DialogLovelaceDashboardDetail extends LitElement {
}
}
private _handleTabChanged(
ev: CustomEvent<{
name: (typeof TABS)[number];
}>
): void {
const newTab = ev.detail.name;
if (newTab === this._currTab) {
return;
}
this._currTab = newTab;
}
private async _deleteDashboard() {
this._submitting = true;
try {
@@ -326,7 +421,30 @@ export class DialogLovelaceDashboardDetail extends LitElement {
}
static get styles(): CSSResultGroup {
return [haStyleDialog, css``];
return [
haStyleDialog,
haStyleDialogFixedTop,
css`
ha-dialog {
--dialog-content-padding: var(--ha-space-6);
}
h2 {
margin: 0;
font-size: inherit;
font-weight: inherit;
}
ha-tab-group-tab {
flex: 1;
}
ha-tab-group-tab::part(base) {
width: 100%;
justify-content: center;
}
`,
];
}
}

View File

@@ -1,4 +1,5 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type {
LovelaceDashboard,
LovelaceDashboardCreateParams,
@@ -17,6 +18,8 @@ export interface LovelaceDashboardDetailsDialogParams {
* auto-generated paths avoid collisions by appending -2, -3, and so on.
*/
takenUrlPaths?: ReadonlySet<string>;
lovelaceConfig?: LovelaceConfig;
saveConfig?: (config: LovelaceConfig) => Promise<void>;
createDashboard?: (values: LovelaceDashboardCreateParams) => Promise<unknown>;
updateDashboard: (
updates: Partial<LovelaceDashboardMutableParams>

View File

@@ -1,4 +1,3 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -7,6 +6,7 @@ import { ifDefined } from "lit/directives/if-defined";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-card";
import type { CameraEntity } from "../../../data/camera";
import type { ImageEntity } from "../../../data/image";
import { computeImageUrl } from "../../../data/image";
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
@@ -72,8 +72,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
!["camera", "image", "person"].includes(computeDomain(config.entity)) &&
!config.image &&
!config.state_image &&
!config.camera_image &&
!config.show_entity_picture
!config.camera_image
) {
throw new Error("No image source configured");
}
@@ -115,7 +114,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
return nothing;
}
const stateObj: HassEntity | undefined =
const stateObj: CameraEntity | ImageEntity | PersonEntity | undefined =
this.hass.states[this._config.entity];
if (!stateObj) {
@@ -144,23 +143,10 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
}
const domain: string = computeDomain(this._config.entity);
let image: string | undefined;
// When show_entity_picture is enabled, try entity_picture first
if (this._config.show_entity_picture) {
image =
stateObj.attributes.entity_picture_local ||
stateObj.attributes.entity_picture;
}
// Fall back to configured image
if (!image) {
image =
(typeof this._config?.image === "object" &&
this._config.image.media_content_id) ||
(this._config.image as string | undefined);
}
let image: string | undefined =
(typeof this._config?.image === "object" &&
this._config.image.media_content_id) ||
(this._config.image as string | undefined);
if (!image) {
switch (domain) {
case "image":

View File

@@ -531,7 +531,6 @@ export interface PictureElementsCardConfig extends LovelaceCardConfig {
export interface PictureEntityCardConfig extends LovelaceCardConfig {
entity: string;
show_entity_picture?: boolean;
name?: string | EntityNameItem | EntityNameItem[];
image?: string | MediaSelectorValue;
camera_image?: string;

View File

@@ -35,7 +35,6 @@ const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(string()),
show_entity_picture: optional(boolean()),
image: optional(union([string(), object()])),
name: optional(entityNameStruct),
camera_image: optional(string()),
@@ -69,7 +68,6 @@ export class HuiPictureEntityCardEditor
(localize: LocalizeFunc) =>
[
{ name: "entity", required: true, selector: { entity: {} } },
{ name: "show_entity_picture", selector: { boolean: {} } },
{
name: "name",
selector: {
@@ -216,8 +214,6 @@ export class HuiPictureEntityCardEditor
config.entity !== this._config?.entity &&
(computeDomain(config.entity) === "image" ||
(computeDomain(config.entity) === "person" &&
this.hass?.states[config.entity]?.attributes.entity_picture) ||
(config.show_entity_picture &&
this.hass?.states[config.entity]?.attributes.entity_picture)) &&
config.image === STUB_IMAGE
) {
@@ -251,10 +247,6 @@ export class HuiPictureEntityCardEditor
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
switch (schema.name) {
case "show_entity_picture":
return this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.show_entity_picture_helper`
);
case "aspect_ratio":
return typeof this._config?.grid_options?.rows === "number"
? this.hass!.localize(

View File

@@ -183,7 +183,7 @@ export class HuiDialogEditView extends LitElement {
<hui-view-background-editor
.hass=${this.hass}
.config=${this._config}
@view-config-changed=${this._viewConfigChanged}
@background-config-changed=${this._viewConfigChanged}
></hui-view-background-editor>
`;
break;

View File

@@ -5,7 +5,7 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { LovelaceDashboardBackgroundConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import type { LocalizeFunc } from "../../../../common/translations/localize";
@@ -14,18 +14,18 @@ import {
resolveMediaSource,
} from "../../../../data/media_source";
export interface BackgroundConfigTarget {
background?: LovelaceDashboardBackgroundConfig;
}
@customElement("hui-view-background-editor")
export class HuiViewBackgroundEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _config!: LovelaceViewConfig;
@property({ attribute: false }) public config!: BackgroundConfigTarget;
@state({ attribute: false }) private _resolvedImage?: string;
set config(config: LovelaceViewConfig) {
this._config = config;
}
private _localizeValueCallback = (key: string) =>
this.hass.localize(key as any);
@@ -125,12 +125,12 @@ export class HuiViewBackgroundEditor extends LitElement {
protected updated(changedProps: PropertyValues) {
if (
this._config &&
this.config &&
this.hass &&
(changedProps.has("_config") ||
(changedProps.has("config") ||
(changedProps.has("hass") && !changedProps.get("hass")))
) {
const background = this._backgroundData(this._config);
const background = this._backgroundData(this.config);
this.style.setProperty(
"--picture-opacity",
`${(background.opacity ?? 100) / 100}`
@@ -156,7 +156,7 @@ export class HuiViewBackgroundEditor extends LitElement {
return nothing;
}
const background = this._backgroundData(this._config);
const background = this._backgroundData(this.config);
return html`
${this._resolvedImage
@@ -181,7 +181,7 @@ export class HuiViewBackgroundEditor extends LitElement {
}
private _backgroundData = memoizeOne(
(backgroundConfig?: LovelaceViewConfig) => {
(backgroundConfig?: BackgroundConfigTarget) => {
let background = backgroundConfig?.background;
if (typeof background === "string") {
const backgroundUrl = background.match(
@@ -220,10 +220,10 @@ export class HuiViewBackgroundEditor extends LitElement {
private _valueChanged(ev: CustomEvent): void {
const config = {
...this._config,
...this.config,
background: ev.detail.value,
};
fireEvent(this, "view-config-changed", { config });
fireEvent(this, "background-config-changed", { config });
}
private _computeLabelCallback = (
@@ -290,4 +290,10 @@ declare global {
interface HTMLElementTagNameMap {
"hui-view-background-editor": HuiViewBackgroundEditor;
}
interface HASSDomEvents {
"background-config-changed": {
config: BackgroundConfigTarget;
};
}
}

View File

@@ -1045,6 +1045,8 @@ class HUIRoot extends LitElement {
showDashboardDetailDialog(this, {
dashboard,
urlPath,
lovelaceConfig: this.lovelace?.config,
saveConfig: this.lovelace?.saveConfig,
updateDashboard: async (values) => {
await updateDashboard(this.hass!, dashboard!.id, values);
},

View File

@@ -336,15 +336,12 @@ export const getMyRedirects = (): Redirects => ({
redirect: "/config/info",
},
supervisor_store: {
component: "hassio",
redirect: "/config/apps/available",
},
supervisor_addons: {
component: "hassio",
redirect: "/config/apps",
},
supervisor_app: {
component: "hassio",
redirect: "/config/app",
params: {
app: "string",
@@ -354,7 +351,6 @@ export const getMyRedirects = (): Redirects => ({
},
},
supervisor_addon: {
component: "hassio",
redirect: "/config/app",
params: {
addon: "string",
@@ -364,7 +360,6 @@ export const getMyRedirects = (): Redirects => ({
},
},
supervisor_add_addon_repository: {
component: "hassio",
redirect: "/config/apps/available",
params: {
repository_url: "url",
@@ -415,9 +410,21 @@ class HaPanelMy extends LitElement {
1,
this.route.path.endsWith("/") ? this.route.path.length - 1 : undefined
);
const hasSupervisor = isComponentLoaded(this.hass.config, "hassio");
this._redirect = getRedirect(path);
if (path.startsWith("supervisor") && this._redirect === undefined) {
if (!hasSupervisor) {
this._error = "no_supervisor";
return;
}
navigate(`/hassio/_my_redirect/${path}${window.location.search}`, {
replace: true,
});
return;
}
if (!this._redirect) {
this._error = "not_supported";
return;
@@ -437,10 +444,7 @@ class HaPanelMy extends LitElement {
!isComponentLoaded(this.hass.config, this._redirect.component)
) {
this.hass.loadBackendTranslation("title", this._redirect.component);
this._error =
this._redirect.component === "hassio"
? "no_supervisor"
: "no_component";
this._error = "no_component";
const component = this._redirect.component;
if ((PROTOCOL_INTEGRATIONS as readonly string[]).includes(component)) {
const params = extractSearchParamsObject();

View File

@@ -340,6 +340,8 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
if (repo && repo.source !== "local") {
myParams.append("repository_url", repo.source);
}
} else if (redirect.redirect === "/hassio/addon") {
myParams.append("addon", targetPath.split("/")[3]);
}
window.open(
`https://my.home-assistant.io/create-link/?${myParams.toString()}`,

View File

@@ -2526,7 +2526,7 @@
"my": {
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced.",
"component_not_loaded": "This redirect is not supported by your Home Assistant instance. You need the integration {integration} to use this redirect.",
"no_supervisor": "This redirect is not supported by your Home Assistant installation. It needs the Home Assistant OS installation method. For more information, see the {docs_link}.",
"no_supervisor": "This redirect is not supported by your Home Assistant installation. It needs either the Home Assistant OS or Home Assistant Supervised installation method. For more information, see the {docs_link}.",
"not_app": "This redirect only works from a mobile device that has the Home Assistant Companion app installed. {link}.",
"url_error": "The provided URL is invalid.",
"documentation": "documentation",
@@ -8011,8 +8011,10 @@
},
"tips": {
"tip": "Tip!",
"join": "Join the community on our {forums}, {social_media}, {discord}, {blog} or {newsletter}",
"social_media": "Social Media",
"join": "Join the community on our {forums}, {mastodon}, {bluesky}, {twitter}, {discord}, {blog} or {newsletter}",
"join_x": "X (formerly Twitter)",
"join_mastodon": "Mastodon",
"join_bluesky": "Bluesky",
"join_forums": "Forums",
"join_chat": "Chat",
"join_blog": "Blog",
@@ -9364,8 +9366,6 @@
"attribute": "Attribute",
"camera_image": "Camera entity",
"image_entity": "Image entity",
"show_entity_picture": "Show entity picture",
"show_entity_picture_helper": "Use the entity's picture if available; falls back to the configured image",
"camera_view": "Camera view",
"camera_view_options": {
"auto": "Auto",
@@ -10591,7 +10591,8 @@
"forums": "Home Assistant forums",
"open_home_newsletter": "Building the Open Home newsletter",
"discord": "Discord chat",
"social_media": "Social Media",
"x": "[%key:ui::panel::config::tips::join_x%]",
"mastodon": "Mastodon",
"playstore": "Get it on Google Play",
"appstore": "Download on the App Store"
},
@@ -10890,7 +10891,7 @@
"forums": "[%key:ui::panel::page-onboarding::welcome::forums%]",
"open_home_newsletter": "[%key:ui::panel::page-onboarding::welcome::open_home_newsletter%]",
"discord": "[%key:ui::panel::page-onboarding::welcome::discord%]",
"social_media": "[%key:ui::panel::page-onboarding::welcome::social_media%]",
"mastodon": "[%key:ui::panel::page-onboarding::welcome::mastodon%]",
"playstore": "[%key:ui::panel::page-onboarding::welcome::playstore%]",
"appstore": "[%key:ui::panel::page-onboarding::welcome::appstore%]"
},

View File

@@ -1501,23 +1501,23 @@ __metadata:
languageName: node
linkType: hard
"@eslint/config-array@npm:^0.23.5":
version: 0.23.5
resolution: "@eslint/config-array@npm:0.23.5"
"@eslint/config-array@npm:^0.23.4":
version: 0.23.4
resolution: "@eslint/config-array@npm:0.23.4"
dependencies:
"@eslint/object-schema": "npm:^3.0.5"
"@eslint/object-schema": "npm:^3.0.4"
debug: "npm:^4.3.1"
minimatch: "npm:^10.2.4"
checksum: 10/0e05be2b5c8b9f9fb8094948fd2d35591a32091b9d39205181f2ed9bec0e2c8b2969f019f40a0388755a025408b98929e2d0beccb4fbd6609c1c0d6c9e9a14f0
checksum: 10/a42bdf8d66d52703596d5a791eedb1ad79e232dad58e0450b65d8ad48c2fdf50ee62d26fe0e823092384e93378c39adeea30eb1cd12af7ee207a142f9e1996eb
languageName: node
linkType: hard
"@eslint/config-helpers@npm:^0.5.5":
version: 0.5.5
resolution: "@eslint/config-helpers@npm:0.5.5"
"@eslint/config-helpers@npm:^0.5.4":
version: 0.5.4
resolution: "@eslint/config-helpers@npm:0.5.4"
dependencies:
"@eslint/core": "npm:^1.2.1"
checksum: 10/19072449502b928a716df87b2d9b13c7befb21974b0f93fdbea705ddba098792142808105170ef2183c28ce13ac9fa1713ef0599749f8469434ac2b914fc8f4d
"@eslint/core": "npm:^1.2.0"
checksum: 10/277c951ac82b28e2f56adae3cf64ddcfa4e0fb07e8beaa853c2070e07fcb6062fc48a257ddfa12027aaead698f031bc81c343e0c9ddf7a8f59b724e119552897
languageName: node
linkType: hard
@@ -1530,12 +1530,12 @@ __metadata:
languageName: node
linkType: hard
"@eslint/core@npm:^1.2.1":
version: 1.2.1
resolution: "@eslint/core@npm:1.2.1"
"@eslint/core@npm:^1.2.0":
version: 1.2.0
resolution: "@eslint/core@npm:1.2.0"
dependencies:
"@types/json-schema": "npm:^7.0.15"
checksum: 10/e1f9f5534f495b74a4c13c372e8f2feaf0c67f5dd666111c849c97c221d4ba730c98333a2ca94dd28cd7c24e3b1016bd868ca03c42e070732c047053f854cb13
checksum: 10/b4e459ea69935cbed0b99f2160631fcabb3489d950dde86989b1da395f7649bedb14c702afc82e7cb64b6d7c62a6a10f8dae8dcf4c92dc3035bcbe531e65603e
languageName: node
linkType: hard
@@ -1568,10 +1568,10 @@ __metadata:
languageName: node
linkType: hard
"@eslint/object-schema@npm:^3.0.5":
version: 3.0.5
resolution: "@eslint/object-schema@npm:3.0.5"
checksum: 10/42e9ec2551d7cafe1825f20494576c9a867dfd26e728b66620f55d954cd5c4c9c4987755d147893985b8d39b49dace31117e59e7bc9564eb411b397e579a50e7
"@eslint/object-schema@npm:^3.0.4":
version: 3.0.4
resolution: "@eslint/object-schema@npm:3.0.4"
checksum: 10/16daaf207aea472852465619fd29db29e0ab897353347020c52d526e27d8ceb44516506d4da61ff0a89cb8bd5ad935b882aca255ff1bc44ea9a59b1bd8de75bb
languageName: node
linkType: hard
@@ -1585,13 +1585,13 @@ __metadata:
languageName: node
linkType: hard
"@eslint/plugin-kit@npm:^0.7.1":
version: 0.7.1
resolution: "@eslint/plugin-kit@npm:0.7.1"
"@eslint/plugin-kit@npm:^0.7.0":
version: 0.7.0
resolution: "@eslint/plugin-kit@npm:0.7.0"
dependencies:
"@eslint/core": "npm:^1.2.1"
"@eslint/core": "npm:^1.2.0"
levn: "npm:^0.4.1"
checksum: 10/8f923f4cdadadd215e0c2028e6a53101bb148a7780cdb4dc8cd69b0c77fc88496742e87e0605b12905ff715e2c7ad6cbd2d92c5653cdbf91cca1e229b5022c1f
checksum: 10/3e435cc19db205f4a70ecba060e22b9b7efa2809426c0b020347b3aea0ae5e3507628486c1bc1a18a32fbad1e8cf0fd2ec11ca6ca6a6b958d141291426257c15
languageName: node
linkType: hard
@@ -7748,16 +7748,16 @@ __metadata:
languageName: node
linkType: hard
"eslint@npm:10.2.1":
version: 10.2.1
resolution: "eslint@npm:10.2.1"
"eslint@npm:10.2.0":
version: 10.2.0
resolution: "eslint@npm:10.2.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.8.0"
"@eslint-community/regexpp": "npm:^4.12.2"
"@eslint/config-array": "npm:^0.23.5"
"@eslint/config-helpers": "npm:^0.5.5"
"@eslint/core": "npm:^1.2.1"
"@eslint/plugin-kit": "npm:^0.7.1"
"@eslint/config-array": "npm:^0.23.4"
"@eslint/config-helpers": "npm:^0.5.4"
"@eslint/core": "npm:^1.2.0"
"@eslint/plugin-kit": "npm:^0.7.0"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
"@humanwhocodes/retry": "npm:^0.4.2"
@@ -7789,7 +7789,7 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
checksum: 10/954658c846696dc501a2b8e5fb268713e231dd81375dc25d76cd2fb4a1c73094ea73c64197634ece6fca8a54536e89eb44548a11be672544522e7a50eb8aae95
checksum: 10/583589ed96922aad9507c94339e843c8929c297d505ae7d70579cef56b435a10d8a48d24616eb4fb53fbe75d8655adb8e44add5d5b2bca100148d31d890ab3a4
languageName: node
linkType: hard
@@ -8995,7 +8995,7 @@ __metadata:
dialog-polyfill: "npm:0.5.6"
echarts: "npm:6.0.0"
element-internals-polyfill: "npm:3.0.2"
eslint: "npm:10.2.1"
eslint: "npm:10.2.0"
eslint-config-airbnb-base: "npm:15.0.0"
eslint-config-prettier: "npm:10.1.8"
eslint-import-resolver-webpack: "npm:0.13.11"
@@ -9036,7 +9036,7 @@ __metadata:
lodash.template: "npm:4.5.0"
luxon: "npm:3.7.2"
map-stream: "npm:0.0.7"
marked: "npm:18.0.2"
marked: "npm:18.0.1"
memoize-one: "npm:6.0.0"
node-vibrant: "npm:4.0.4"
object-hash: "npm:3.0.0"
@@ -10727,12 +10727,12 @@ __metadata:
languageName: node
linkType: hard
"marked@npm:18.0.2":
version: 18.0.2
resolution: "marked@npm:18.0.2"
"marked@npm:18.0.1":
version: 18.0.1
resolution: "marked@npm:18.0.1"
bin:
marked: bin/marked.js
checksum: 10/3e4953789360ff42be633337a1ad5f440d1941a328f8c23f8ce3fb686554d67c4f5815eb4b7225af370fd3c01f61f3dcf07dda1487ec47dbf8017eaa7e358028
checksum: 10/200d99ff0a45c503fe8c0fdcdbae32106dcebb17e06b68aa06ad26eb9c38f84c71c34d239a001d34688dc58a38fb47c7c7edf8bd9d3f31672b3c3980f84bfad6
languageName: node
linkType: hard