mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-26 11:17:21 +00:00
Compare commits
5 Commits
helpers-en
...
ha-icon-pi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d77c2b60a | ||
|
|
e4b4c809c9 | ||
|
|
0c51b100b6 | ||
|
|
017bc712b0 | ||
|
|
b034f57384 |
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
||||
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -36,14 +36,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -57,4 +57,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
|
||||
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
||||
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
|
||||
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
|
||||
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
||||
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
||||
@@ -11,7 +11,7 @@ A compact, accessible dropdown menu for choosing actions or settings. `ha-dropdo
|
||||
### Example usage (composition)
|
||||
|
||||
```html
|
||||
<ha-dropdown>
|
||||
<ha-dropdown open>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
|
||||
@@ -28,7 +28,7 @@ export class DemoHaDropdown extends LitElement {
|
||||
<div class=${mode}>
|
||||
<ha-card header="ha-button in ${mode}">
|
||||
<div class="card-content">
|
||||
<ha-dropdown>
|
||||
<ha-dropdown open>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-alert";
|
||||
import "../components/ha-button";
|
||||
@@ -59,8 +59,7 @@ export class HaAuthFlow extends LitElement {
|
||||
willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated && this.clientId === genClientId()) {
|
||||
// Preselect store token when logging in to own instance
|
||||
if (!this.hasUpdated) {
|
||||
this._storeToken = this.initStoreToken;
|
||||
}
|
||||
|
||||
@@ -118,9 +117,6 @@ export class HaAuthFlow extends LitElement {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.action ha-button {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<form>${this._renderForm()}</form>
|
||||
`;
|
||||
|
||||
@@ -597,15 +597,10 @@ export class HaChartBase extends LitElement {
|
||||
aria: { show: true },
|
||||
dataZoom: this._getDataZoomConfig(),
|
||||
toolbox: {
|
||||
top: Number.MAX_SAFE_INTEGER,
|
||||
left: Number.MAX_SAFE_INTEGER,
|
||||
top: Infinity,
|
||||
left: Infinity,
|
||||
feature: {
|
||||
dataZoom: {
|
||||
show: true,
|
||||
yAxisIndex: false,
|
||||
filterMode: "none",
|
||||
showTitle: false,
|
||||
},
|
||||
dataZoom: { show: true, yAxisIndex: false, filterMode: "none" },
|
||||
},
|
||||
iconStyle: { opacity: 0 },
|
||||
},
|
||||
|
||||
@@ -129,6 +129,10 @@ export class HaGenericPicker extends LitElement {
|
||||
// helper to set new value after closing picker, to avoid flicker
|
||||
private _newValue?: string;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public invalid = false;
|
||||
|
||||
private _unsubscribeTinyKeys?: () => void;
|
||||
|
||||
protected render() {
|
||||
@@ -163,6 +167,8 @@ export class HaGenericPicker extends LitElement {
|
||||
.value=${this.value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.errorMessage=${this.errorMessage}
|
||||
.invalid=${this.invalid}
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
.valueRenderer=${this.valueRenderer}
|
||||
>
|
||||
@@ -234,11 +240,16 @@ export class HaGenericPicker extends LitElement {
|
||||
}
|
||||
|
||||
private _renderHelper() {
|
||||
return this.helper
|
||||
? html`<ha-input-helper-text .disabled=${this.disabled}
|
||||
>${this.helper}</ha-input-helper-text
|
||||
>`
|
||||
: nothing;
|
||||
const showError = this.invalid && this.errorMessage;
|
||||
const showHelper = !showError && this.helper;
|
||||
|
||||
if (!showError && !showHelper) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`<ha-input-helper-text .disabled=${this.disabled}>
|
||||
${showError ? this.errorMessage : this.helper}
|
||||
</ha-input-helper-text>`;
|
||||
}
|
||||
|
||||
private _dialogOpened = () => {
|
||||
@@ -337,6 +348,9 @@ export class HaGenericPicker extends LitElement {
|
||||
display: block;
|
||||
margin: var(--ha-space-2) 0 0;
|
||||
}
|
||||
:host([invalid]) ha-input-helper-text {
|
||||
color: var(--mdc-theme-error, var(--error-color, #b00020));
|
||||
}
|
||||
|
||||
wa-popover {
|
||||
--wa-space-l: var(--ha-space-0);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type {
|
||||
ComboBoxDataProviderCallback,
|
||||
ComboBoxDataProviderParams,
|
||||
} from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -10,9 +6,10 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { customIcons } from "../data/custom_icons";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import "./ha-combo-box";
|
||||
import "./ha-icon";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-generic-picker";
|
||||
import "./ha-icon";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
|
||||
interface IconItem {
|
||||
icon: string;
|
||||
@@ -21,7 +18,7 @@ interface IconItem {
|
||||
}
|
||||
|
||||
interface RankedIcon {
|
||||
icon: string;
|
||||
item: PickerComboBoxItem;
|
||||
rank: number;
|
||||
}
|
||||
|
||||
@@ -67,13 +64,18 @@ const loadCustomIconItems = async (iconsetPrefix: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<IconItem | RankedIcon> = (item) => html`
|
||||
const rowRenderer: RenderItemFunction<PickerComboBoxItem> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<ha-icon .icon=${item.icon} slot="start"></ha-icon>
|
||||
${item.icon}
|
||||
<ha-icon .icon=${item.id} slot="start"></ha-icon>
|
||||
${item.id}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
const valueRenderer = (value: string) => html`
|
||||
<ha-icon .icon=${value} slot="start"></ha-icon>
|
||||
<span slot="headline">${value}</span>
|
||||
`;
|
||||
|
||||
@customElement("ha-icon-picker")
|
||||
export class HaIconPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@@ -96,13 +98,11 @@ export class HaIconPicker extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-combo-box
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
item-value-path="icon"
|
||||
item-label-path="icon"
|
||||
.value=${this._value}
|
||||
allow-custom-value
|
||||
.dataProvider=${ICONS_LOADED ? this._iconProvider : undefined}
|
||||
.getItems=${this._getItems}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@@ -110,69 +110,85 @@ export class HaIconPicker extends LitElement {
|
||||
.placeholder=${this.placeholder}
|
||||
.errorMessage=${this.errorMessage}
|
||||
.invalid=${this.invalid}
|
||||
.renderer=${rowRenderer}
|
||||
icon
|
||||
@opened-changed=${this._openedChanged}
|
||||
.rowRenderer=${rowRenderer}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchFn=${this._filterIcons}
|
||||
.notFoundLabel=${this.hass?.localize(
|
||||
"ui.components.icon-picker.no_match"
|
||||
)}
|
||||
popover-placement="bottom-start"
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
${this._value || this.placeholder
|
||||
? html`
|
||||
<ha-icon .icon=${this._value || this.placeholder} slot="icon">
|
||||
</ha-icon>
|
||||
`
|
||||
: html`<slot slot="icon" name="fallback"></slot>`}
|
||||
</ha-combo-box>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
// Filter can take a significant chunk of frame (up to 3-5 ms)
|
||||
private _filterIcons = memoizeOne(
|
||||
(filter: string, iconItems: IconItem[] = ICONS) => {
|
||||
(filter: string, items: PickerComboBoxItem[]): PickerComboBoxItem[] => {
|
||||
if (!filter) {
|
||||
return iconItems;
|
||||
return items;
|
||||
}
|
||||
|
||||
const filteredItems: RankedIcon[] = [];
|
||||
const addIcon = (icon: string, rank: number) =>
|
||||
filteredItems.push({ icon, rank });
|
||||
const addIcon = (item: PickerComboBoxItem, rank: number) =>
|
||||
filteredItems.push({ item, rank });
|
||||
|
||||
// Filter and rank such that exact matches rank higher, and prefer icon name matches over keywords
|
||||
for (const item of iconItems) {
|
||||
if (item.parts.has(filter)) {
|
||||
addIcon(item.icon, 1);
|
||||
} else if (item.keywords.includes(filter)) {
|
||||
addIcon(item.icon, 2);
|
||||
} else if (item.icon.includes(filter)) {
|
||||
addIcon(item.icon, 3);
|
||||
} else if (item.keywords.some((word) => word.includes(filter))) {
|
||||
addIcon(item.icon, 4);
|
||||
for (const item of items) {
|
||||
const iconName = item.id.split(":")[1] || item.id;
|
||||
const parts = iconName.split("-");
|
||||
const keywords = item.search_labels?.slice(1) || [];
|
||||
|
||||
if (parts.includes(filter)) {
|
||||
addIcon(item, 1);
|
||||
} else if (keywords.includes(filter)) {
|
||||
addIcon(item, 2);
|
||||
} else if (item.id.includes(filter)) {
|
||||
addIcon(item, 3);
|
||||
} else if (keywords.some((word) => word.includes(filter))) {
|
||||
addIcon(item, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow preview for custom icon not in list
|
||||
if (filteredItems.length === 0) {
|
||||
addIcon(filter, 0);
|
||||
addIcon(
|
||||
{
|
||||
id: filter,
|
||||
primary: filter,
|
||||
icon: filter,
|
||||
search_labels: [filter],
|
||||
sorting_label: filter,
|
||||
},
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
return filteredItems.sort((itemA, itemB) => itemA.rank - itemB.rank);
|
||||
return filteredItems
|
||||
.sort((itemA, itemB) => itemA.rank - itemB.rank)
|
||||
.map((item) => item.item);
|
||||
}
|
||||
);
|
||||
|
||||
private _iconProvider = (
|
||||
params: ComboBoxDataProviderParams,
|
||||
callback: ComboBoxDataProviderCallback<IconItem | RankedIcon>
|
||||
) => {
|
||||
const filteredItems = this._filterIcons(params.filter.toLowerCase(), ICONS);
|
||||
const iStart = params.page * params.pageSize;
|
||||
const iEnd = iStart + params.pageSize;
|
||||
callback(filteredItems.slice(iStart, iEnd), filteredItems.length);
|
||||
};
|
||||
private _getItems = (): PickerComboBoxItem[] =>
|
||||
ICONS.map((icon: IconItem) => ({
|
||||
id: icon.icon,
|
||||
primary: icon.icon,
|
||||
icon: icon.icon,
|
||||
search_labels: [
|
||||
icon.icon.split(":")[1] || icon.icon,
|
||||
...Array.from(icon.parts),
|
||||
...icon.keywords,
|
||||
],
|
||||
sorting_label: icon.icon,
|
||||
}));
|
||||
|
||||
private async _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
const opened = ev.detail.value;
|
||||
if (opened && !ICONS_LOADED) {
|
||||
await loadIcons();
|
||||
this.requestUpdate();
|
||||
protected firstUpdated() {
|
||||
if (!ICONS_LOADED) {
|
||||
loadIcons().then(() => {
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,15 +215,9 @@ export class HaIconPicker extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
*[slot="icon"] {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
}
|
||||
*[slot="prefix"] {
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
ha-generic-picker {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ export class HaPickerField extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public valueRenderer?: PickerValueRenderer;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public invalid = false;
|
||||
|
||||
@query("ha-combo-box-item", true) public item!: HaComboBoxItem;
|
||||
|
||||
public async focus() {
|
||||
@@ -142,6 +146,11 @@ export class HaPickerField extends LitElement {
|
||||
background-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
:host([invalid]) ha-combo-box-item:after {
|
||||
height: 2px;
|
||||
background-color: var(--mdc-theme-error, var(--error-color, #b00020));
|
||||
}
|
||||
|
||||
.clear {
|
||||
margin: 0 -8px;
|
||||
--mdc-icon-button-size: 32px;
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { formatTime } from "../common/datetime/format_time";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import { fileDownload } from "../util/file_download";
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
import type { BackupManagerState, ManagerStateEvent } from "./backup_manager";
|
||||
@@ -415,7 +414,7 @@ ${hass.auth.data.hassUrl}
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.encryption_key")}
|
||||
${encryptionKey}
|
||||
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.more_info", { link: documentationUrl(hass, "/more-info/backup-emergency-kit") })}`);
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.more_info", { link: "https://www.home-assistant.io/more-info/backup-emergency-kit" })}`);
|
||||
|
||||
export const geneateEmergencyKitFileName = (
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Connection } from "home-assistant-js-websocket";
|
||||
export interface CoreFrontendUserData {
|
||||
showAdvanced?: boolean;
|
||||
showEntityIdPicker?: boolean;
|
||||
default_panel?: string;
|
||||
defaultPanel?: string;
|
||||
}
|
||||
|
||||
export interface SidebarFrontendUserData {
|
||||
@@ -12,11 +12,7 @@ export interface SidebarFrontendUserData {
|
||||
}
|
||||
|
||||
export interface CoreFrontendSystemData {
|
||||
default_panel?: string;
|
||||
}
|
||||
|
||||
export interface HomeFrontendSystemData {
|
||||
favorite_entities?: string[];
|
||||
defaultPanel?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -26,7 +22,6 @@ declare global {
|
||||
}
|
||||
interface FrontendSystemData {
|
||||
core: CoreFrontendSystemData;
|
||||
home: HomeFrontendSystemData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ export const getLegacyDefaultPanelUrlPath = (): string | null => {
|
||||
};
|
||||
|
||||
export const getDefaultPanelUrlPath = (hass: HomeAssistant): string =>
|
||||
hass.userData?.default_panel ||
|
||||
hass.systemData?.default_panel ||
|
||||
hass.userData?.defaultPanel ||
|
||||
hass.systemData?.defaultPanel ||
|
||||
getLegacyDefaultPanelUrlPath() ||
|
||||
DEFAULT_PANEL;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import type {
|
||||
import { showToast } from "../../util/toast";
|
||||
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-more-info-add-to")
|
||||
export class HaMoreInfoAddTo extends LitElement {
|
||||
@@ -52,7 +51,6 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
app_payload: action.app_payload,
|
||||
},
|
||||
});
|
||||
fireEvent(this, "add-to-action-selected");
|
||||
} catch (err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
@@ -151,8 +149,4 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-add-to": HaMoreInfoAddTo;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"add-to-action-selected": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +645,6 @@ export class MoreInfoDialog extends LitElement {
|
||||
<ha-more-info-add-to
|
||||
.hass=${this.hass}
|
||||
.entityId=${entityId}
|
||||
@add-to-action-selected=${this._goBack}
|
||||
></ha-more-info-add-to>
|
||||
`
|
||||
: nothing
|
||||
|
||||
@@ -5,7 +5,6 @@ import { atLeastVersion } from "../common/config/version";
|
||||
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
|
||||
import "../components/ha-card";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./hass-subpage";
|
||||
|
||||
@@ -58,7 +57,7 @@ class SupervisorErrorScreen extends LitElement {
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/help/")}
|
||||
href="https://www.home-assistant.io/help/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-card";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { showAppDialog } from "./dialogs/show-app-dialog";
|
||||
import { showCommunityDialog } from "./dialogs/show-community-dialog";
|
||||
@@ -23,10 +22,7 @@ class OnboardingWelcomeLinks extends LitElement {
|
||||
return html`<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/blog/2016/01/19/perfect-home-automation/"
|
||||
)}
|
||||
href="https://www.home-assistant.io/blog/2016/01/19/perfect-home-automation/"
|
||||
>
|
||||
<onboarding-welcome-link
|
||||
noninteractive
|
||||
|
||||
@@ -188,7 +188,6 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
class="handle ${this._resizing ? "resizing" : ""}"
|
||||
@mousedown=${this._handleMouseDown}
|
||||
@touchstart=${this._handleMouseDown}
|
||||
@dblclick=${this._handleDoubleClick}
|
||||
@focus=${this._startKeyboardResizing}
|
||||
@blur=${this._stopKeyboardResizing}
|
||||
tabindex="0"
|
||||
@@ -259,17 +258,6 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
);
|
||||
};
|
||||
|
||||
private _handleDoubleClick = (ev: MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this._unregisterResizeHandlers();
|
||||
this._tinykeysUnsub?.();
|
||||
this._tinykeysUnsub = undefined;
|
||||
this._resizing = false;
|
||||
document.body.style.removeProperty("cursor");
|
||||
fireEvent(this, "sidebar-reset-size");
|
||||
};
|
||||
|
||||
private _startResizing(clientX: number) {
|
||||
// register event listeners for drag handling
|
||||
document.addEventListener("mousemove", this._handleMouseMove);
|
||||
@@ -434,6 +422,5 @@ declare global {
|
||||
deltaInPx: number;
|
||||
};
|
||||
"sidebar-resizing-stopped": undefined;
|
||||
"sidebar-reset-size": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,6 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
@sidebar-resized=${this._resizeSidebar}
|
||||
@sidebar-resizing-stopped=${this._stopResizeSidebar}
|
||||
@sidebar-reset-size=${this._resetSidebarWidth}
|
||||
></ha-automation-sidebar>
|
||||
</div>
|
||||
</div>
|
||||
@@ -701,16 +700,6 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
this._prevSidebarWidthPx = undefined;
|
||||
}
|
||||
|
||||
private _resetSidebarWidth(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._prevSidebarWidthPx = undefined;
|
||||
this._sidebarWidthPx = SIDEBAR_DEFAULT_WIDTH;
|
||||
this.style.setProperty(
|
||||
"--sidebar-dynamic-width",
|
||||
`${this._sidebarWidthPx}px`
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
saveFabStyles,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { mdiClose, mdiOpenInNew } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-code-editor";
|
||||
@@ -141,7 +140,7 @@ class DialogImportBlueprint extends LitElement {
|
||||
<ha-button
|
||||
size="small"
|
||||
appearance="plain"
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
href="https://www.home-assistant.io/get-blueprints"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
|
||||
@@ -43,7 +43,6 @@ import {
|
||||
} from "../../../data/blueprint";
|
||||
import { showScriptEditor } from "../../../data/script";
|
||||
import { findRelated } from "../../../data/search";
|
||||
import "../../../components/chips/ha-assist-chip";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -61,7 +60,6 @@ type BlueprintMetaDataPath = BlueprintMetaData & {
|
||||
error: boolean;
|
||||
type: "automation" | "script";
|
||||
fullpath: string;
|
||||
usageCount?: number;
|
||||
};
|
||||
|
||||
const createNewFunctions = {
|
||||
@@ -130,20 +128,14 @@ class HaBlueprintOverview extends LitElement {
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@state() private _usageCounts: Record<string, number> = {};
|
||||
|
||||
private _usageCountRequest = 0;
|
||||
|
||||
private _processedBlueprints = memoizeOne(
|
||||
(
|
||||
blueprints: Record<string, Blueprints>,
|
||||
localize: LocalizeFunc,
|
||||
usageCounts: Record<string, number>
|
||||
localize: LocalizeFunc
|
||||
): BlueprintMetaDataPath[] => {
|
||||
const result: any[] = [];
|
||||
Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
|
||||
Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
|
||||
const fullpath = `${type}/${path}`;
|
||||
if ("error" in blueprint) {
|
||||
result.push({
|
||||
name: blueprint.error,
|
||||
@@ -153,8 +145,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
),
|
||||
error: true,
|
||||
path,
|
||||
fullpath,
|
||||
usageCount: 0,
|
||||
fullpath: `${type}/${path}`,
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
@@ -165,8 +156,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
),
|
||||
error: false,
|
||||
path,
|
||||
fullpath,
|
||||
usageCount: usageCounts[fullpath] || 0,
|
||||
fullpath: `${type}/${path}`,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -199,34 +189,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
filterable: true,
|
||||
flex: 2,
|
||||
},
|
||||
usage_count: {
|
||||
title: localize(
|
||||
"ui.panel.config.blueprint.overview.headers.usage_count"
|
||||
),
|
||||
sortable: true,
|
||||
valueColumn: "usageCount",
|
||||
type: "numeric",
|
||||
minWidth: "100px",
|
||||
maxWidth: "120px",
|
||||
template: (blueprint) => {
|
||||
const count = blueprint.usageCount ?? 0;
|
||||
return html`
|
||||
<ha-assist-chip
|
||||
filled
|
||||
.active=${count > 0}
|
||||
label=${String(count)}
|
||||
title=${blueprint.error
|
||||
? String(count)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.blueprint.overview.view_${blueprint.type}`
|
||||
)}
|
||||
?disabled=${blueprint.error}
|
||||
data-fullpath=${blueprint.fullpath}
|
||||
@click=${this._handleUsageClick}
|
||||
></ha-assist-chip>
|
||||
`;
|
||||
},
|
||||
},
|
||||
fullpath: {
|
||||
title: "fullpath",
|
||||
hidden: true,
|
||||
@@ -304,7 +266,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._loadUsageCounts();
|
||||
if (this.route.path === "/import") {
|
||||
const url = extractSearchParam("blueprint_url");
|
||||
navigate("/config/blueprint/dashboard", { replace: true });
|
||||
@@ -314,13 +275,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("blueprints")) {
|
||||
this._loadUsageCounts();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
@@ -330,11 +284,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.automations}
|
||||
.columns=${this._columns(this.hass.localize)}
|
||||
.data=${this._processedBlueprints(
|
||||
this.blueprints,
|
||||
this.hass.localize,
|
||||
this._usageCounts
|
||||
)}
|
||||
.data=${this._processedBlueprints(this.blueprints, this.hass.localize)}
|
||||
id="fullpath"
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.no_blueprints"
|
||||
@@ -349,7 +299,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
href="https://www.home-assistant.io/get-blueprints"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
size="small"
|
||||
@@ -430,51 +380,10 @@ class HaBlueprintOverview extends LitElement {
|
||||
fireEvent(this, "reload-blueprints");
|
||||
}
|
||||
|
||||
private async _loadUsageCounts() {
|
||||
if (!this.blueprints) {
|
||||
return;
|
||||
}
|
||||
|
||||
const request = ++this._usageCountRequest;
|
||||
const usageCounts: Record<string, number> = {};
|
||||
|
||||
const blueprintList = this._processedBlueprints(
|
||||
this.blueprints,
|
||||
this.hass.localize,
|
||||
{}
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
blueprintList.map(async (blueprint) => {
|
||||
if (blueprint.error) {
|
||||
usageCounts[blueprint.fullpath] = 0;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const related = await findRelated(
|
||||
this.hass,
|
||||
`${blueprint.domain}_blueprint`,
|
||||
blueprint.path
|
||||
);
|
||||
const count =
|
||||
(related.automation?.length || 0) + (related.script?.length || 0);
|
||||
usageCounts[blueprint.fullpath] = count;
|
||||
} catch (_err) {
|
||||
usageCounts[blueprint.fullpath] = 0;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (request === this._usageCountRequest) {
|
||||
this._usageCounts = usageCounts;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const blueprint = this._processedBlueprints(
|
||||
this.blueprints,
|
||||
this.hass.localize,
|
||||
this._usageCounts
|
||||
this.hass.localize
|
||||
).find((b) => b.fullpath === ev.detail.id)!;
|
||||
if (blueprint.error) {
|
||||
showAlertDialog(this, {
|
||||
@@ -488,25 +397,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
this._createNew(blueprint);
|
||||
}
|
||||
|
||||
private _handleUsageClick = (ev: Event) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
const target = ev.currentTarget as HTMLElement | null;
|
||||
const fullpath = target?.dataset.fullpath;
|
||||
if (!fullpath) {
|
||||
return;
|
||||
}
|
||||
const blueprint = this._processedBlueprints(
|
||||
this.blueprints,
|
||||
this.hass.localize,
|
||||
this._usageCounts
|
||||
).find((item) => item.fullpath === fullpath);
|
||||
if (!blueprint || blueprint.error) {
|
||||
return;
|
||||
}
|
||||
this._showUsed(blueprint);
|
||||
};
|
||||
|
||||
private _showUsed = (blueprint: BlueprintMetaDataPath) => {
|
||||
navigate(
|
||||
`/config/${blueprint.domain}/dashboard?blueprint=${encodeURIComponent(
|
||||
|
||||
@@ -2,7 +2,9 @@ import { mdiDotsVertical, mdiDownload } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import "../../../layouts/hass-subpage";
|
||||
@@ -12,8 +14,6 @@ import {
|
||||
downloadFileSupported,
|
||||
fileDownload,
|
||||
} from "../../../util/file_download";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-dropdown";
|
||||
|
||||
@customElement("ha-config-section-analytics")
|
||||
class HaConfigSectionAnalytics extends LitElement {
|
||||
@@ -33,19 +33,22 @@ class HaConfigSectionAnalytics extends LitElement {
|
||||
>
|
||||
${downloadFileSupported(this.hass)
|
||||
? html`
|
||||
<ha-dropdown
|
||||
@wa-select=${this._handleOverflowAction}
|
||||
<ha-button-menu
|
||||
@action=${this._handleOverflowAction}
|
||||
slot="toolbar-icon"
|
||||
>
|
||||
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
|
||||
</ha-icon-button>
|
||||
<ha-dropdown-item .value=${"download_device_info"}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
||||
<ha-list-item graphic="icon">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiDownload}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.analytics.download_device_info"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
`
|
||||
: nothing}
|
||||
<div class="content">
|
||||
@@ -55,16 +58,9 @@ class HaConfigSectionAnalytics extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _handleOverflowAction(
|
||||
ev: CustomEvent<{ item: { value: string } }>
|
||||
): Promise<void> {
|
||||
if (ev.detail.item.value === "download_device_info") {
|
||||
const signedPath = await getSignedPath(
|
||||
this.hass,
|
||||
"/api/analytics/devices"
|
||||
);
|
||||
fileDownload(signedPath.path);
|
||||
}
|
||||
private async _handleOverflowAction(): Promise<void> {
|
||||
const signedPath = await getSignedPath(this.hass, "/api/analytics/devices");
|
||||
fileDownload(signedPath.path);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
@@ -5,9 +6,13 @@ import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-bar";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-metric";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import type {
|
||||
@@ -28,9 +33,6 @@ import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../dashboard/ha-config-updates";
|
||||
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
|
||||
@customElement("ha-config-section-updates")
|
||||
class HaConfigSectionUpdates extends LitElement {
|
||||
@@ -71,25 +73,24 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
.path=${mdiRefresh}
|
||||
@click=${this._checkUpdates}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown @wa-select=${this._handleOverflowAction}>
|
||||
<ha-button-menu multi>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-dropdown-item
|
||||
type="checkbox"
|
||||
value="show_skipped"
|
||||
.checked=${this._showSkipped}
|
||||
<ha-check-list-item
|
||||
left
|
||||
@request-selected=${this._toggleSkipped}
|
||||
.selected=${this._showSkipped}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.updates.show_skipped")}
|
||||
</ha-dropdown-item>
|
||||
</ha-check-list-item>
|
||||
${this._supervisorInfo
|
||||
? html`
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item
|
||||
value="toggle_beta"
|
||||
<li divider role="separator"></li>
|
||||
<ha-list-item
|
||||
@request-selected=${this._toggleBeta}
|
||||
.disabled=${this._supervisorInfo.channel === "dev"}
|
||||
>
|
||||
${this._supervisorInfo.channel === "stable"
|
||||
@@ -97,10 +98,10 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.updates.leave_beta"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
: ""}
|
||||
</ha-dropdown>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
@@ -132,19 +133,27 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
}
|
||||
|
||||
private async _handleOverflowAction(
|
||||
ev: CustomEvent<{ item: { value: string } }>
|
||||
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (ev.detail.source !== "property") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showSkipped = !this._showSkipped;
|
||||
}
|
||||
|
||||
private async _toggleBeta(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (ev.detail.item.value === "toggle_beta") {
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
} else if (ev.detail.item.value === "show_skipped") {
|
||||
this._showSkipped = !this._showSkipped;
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-alert";
|
||||
import type { EnergyValidationIssue } from "../../../../data/energy";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-energy-validation-result")
|
||||
@@ -30,10 +29,7 @@ class EnergyValidationMessage extends LitElement {
|
||||
)}
|
||||
${issue.type === "recorder_untracked"
|
||||
? html`(<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/recorder#configure-filter"
|
||||
)}
|
||||
href="https://www.home-assistant.io/integrations/recorder#configure-filter"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>${this.hass.localize("ui.panel.config.common.learn_more")}</a
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
mdiCancel,
|
||||
mdiChevronRight,
|
||||
mdiCog,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiMenuDown,
|
||||
mdiPencilOff,
|
||||
@@ -25,10 +24,6 @@ import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import {
|
||||
DEFAULT_ENTITY_NAME,
|
||||
type EntityNameItem,
|
||||
} from "../../../common/entity/compute_entity_name_display";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import type {
|
||||
LocalizeFunc,
|
||||
@@ -114,11 +109,10 @@ import { configSections } from "../ha-panel-config";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import { renderConfigEntryError } from "../integrations/ha-config-integration-page";
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { isHelperDomain, type HelperDomain } from "./const";
|
||||
import { isHelperDomain } from "./const";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { HELPERS_CRUD } from "../../../data/helpers_crud";
|
||||
import {
|
||||
fetchDiagnosticHandlers,
|
||||
getConfigEntryDiagnosticsDownloadUrl,
|
||||
@@ -126,11 +120,6 @@ import {
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
|
||||
const HELPER_ENTITY_NAME: EntityNameItem[] = [
|
||||
{ type: "area" },
|
||||
...DEFAULT_ENTITY_NAME,
|
||||
];
|
||||
|
||||
interface HelperItem {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -462,19 +451,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(helper.editable && helper.entity
|
||||
? [
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
path: mdiDelete,
|
||||
label: this.hass.localize("ui.common.delete"),
|
||||
warning: true,
|
||||
action: () => this._deleteHelper(helper),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
@@ -514,7 +490,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
|
||||
return {
|
||||
id: entityState.entity_id,
|
||||
name: this._formatHelperName(entityState),
|
||||
name: entityState.attributes.friendly_name || "",
|
||||
entity_id: entityState.entity_id,
|
||||
editable:
|
||||
configEntry !== undefined || entityState.attributes.editable,
|
||||
@@ -593,14 +569,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
);
|
||||
|
||||
private _formatHelperName(stateObj: HassEntity): string {
|
||||
const formatted =
|
||||
this.hass.formatEntityName(stateObj, HELPER_ENTITY_NAME) || "";
|
||||
return (
|
||||
formatted || stateObj.attributes.friendly_name || stateObj.entity_id || ""
|
||||
);
|
||||
}
|
||||
|
||||
private _labelsForEntity(entityId: string): string[] {
|
||||
return (
|
||||
this.hass.entities[entityId]?.labels ||
|
||||
@@ -1312,62 +1280,6 @@ ${rejected
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteHelper(helper: HelperItem) {
|
||||
if (!helper.entity_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.delete_confirm_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.delete_confirm_text",
|
||||
{ name: helper.name }
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// For old-style helpers (input_boolean, etc.), use HELPERS_CRUD
|
||||
if (isHelperDomain(helper.type)) {
|
||||
const entityReg = this._entityReg.find(
|
||||
(e) => e.entity_id === helper.entity_id
|
||||
);
|
||||
if (
|
||||
!entityReg?.unique_id ||
|
||||
!isComponentLoaded(this.hass, helper.type)
|
||||
) {
|
||||
throw new Error(
|
||||
this.hass.localize("ui.panel.config.helpers.picker.delete_failed")
|
||||
);
|
||||
}
|
||||
await HELPERS_CRUD[helper.type as HelperDomain].delete(
|
||||
this.hass,
|
||||
entityReg.unique_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For config entry-based helpers, delete the config entry
|
||||
if (helper.configEntry) {
|
||||
await deleteConfigEntry(this.hass, helper.configEntry.entry_id);
|
||||
}
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
text:
|
||||
err.message ||
|
||||
this.hass.localize("ui.panel.config.helpers.picker.delete_failed"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _createHelper() {
|
||||
showHelperDetailDialog(this, {});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
mdiBookshelf,
|
||||
mdiCog,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiOpenInNew,
|
||||
} from "@mdi/js";
|
||||
import { mdiBookshelf, mdiCog, mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -13,11 +7,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-list-item";
|
||||
import {
|
||||
deleteApplicationCredential,
|
||||
fetchApplicationCredentialsConfigEntry,
|
||||
} from "../../../data/application_credential";
|
||||
import { deleteConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
ATTENTION_SOURCES,
|
||||
DISCOVERY_SOURCES,
|
||||
@@ -26,10 +15,7 @@ import {
|
||||
} from "../../../data/config_flow";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||
@@ -74,7 +60,7 @@ export class HaConfigFlowCard extends LitElement {
|
||||
: "ui.common.add"
|
||||
)}
|
||||
</ha-button>
|
||||
${this.flow.context.configuration_url || this.manifest || attention
|
||||
${this.flow.context.configuration_url || this.manifest
|
||||
? html`<ha-button-menu slot="header-button">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -132,22 +118,6 @@ export class HaConfigFlowCard extends LitElement {
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${attention
|
||||
? html`<ha-list-item
|
||||
class="warning"
|
||||
graphic="icon"
|
||||
@click=${this._handleDelete}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
</ha-integration-action-card>
|
||||
@@ -205,109 +175,6 @@ export class HaConfigFlowCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
// Return an application credentials id for this config entry to prompt the
|
||||
// user for removal. This is best effort so we don't stop overall removal
|
||||
// if the integration isn't loaded or there is some other error.
|
||||
private async _fetchApplicationCredentials(entryId: string) {
|
||||
try {
|
||||
return (await fetchApplicationCredentialsConfigEntry(this.hass, entryId))
|
||||
.application_credentials_id;
|
||||
} catch (_err: any) {
|
||||
// We won't prompt the user to remove credentials
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async _removeApplicationCredential(applicationCredentialsId: string) {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_title"
|
||||
),
|
||||
text: html`${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_prompt"
|
||||
)},
|
||||
<br />
|
||||
<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_detail"
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/application_credentials"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.learn_more"
|
||||
)}
|
||||
</a>`,
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteApplicationCredential(this.hass, applicationCredentialsId);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_error_title"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleDelete() {
|
||||
const entryId = this.flow.context.entry_id;
|
||||
|
||||
if (!entryId) {
|
||||
// This shouldn't happen for reauth flows, but handle gracefully
|
||||
return;
|
||||
}
|
||||
|
||||
const applicationCredentialsId =
|
||||
await this._fetchApplicationCredentials(entryId);
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_title",
|
||||
{ title: localizeConfigFlowTitle(this.hass.localize, this.flow) }
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_text"
|
||||
),
|
||||
confirmText: this.hass!.localize("ui.common.delete"),
|
||||
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await deleteConfigEntry(this.hass, entryId);
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (applicationCredentialsId) {
|
||||
this._removeApplicationCredential(applicationCredentialsId);
|
||||
}
|
||||
|
||||
this._handleFlowUpdated();
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
@@ -324,9 +191,6 @@ export class HaConfigFlowCard extends LitElement {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
--ha-card-border-color: var(--error-color);
|
||||
}
|
||||
.warning {
|
||||
--mdc-theme-text-primary-on-background: var(--error-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
color:
|
||||
route.route_status === "Active"
|
||||
? primaryColor
|
||||
: style.getPropertyValue("--dark-primary-color"),
|
||||
: style.getPropertyValue("--disabled-color"),
|
||||
type: ["Child", "Parent"].includes(neighbor.relationship)
|
||||
? "solid"
|
||||
: "dotted",
|
||||
@@ -335,7 +335,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
symbolSize: 5,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: style.getPropertyValue("--dark-primary-color"),
|
||||
color: style.getPropertyValue("--disabled-color"),
|
||||
type: "dotted",
|
||||
},
|
||||
ignoreForceLayout: true,
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { showLabsPreviewFeatureEnableDialog } from "./show-dialog-labs-preview-feature-enable";
|
||||
import {
|
||||
@@ -101,7 +100,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
||||
? html`
|
||||
<a
|
||||
slot="toolbar-icon"
|
||||
href=${documentationUrl(this.hass, "/integrations/labs/")}
|
||||
href="https://www.home-assistant.io/integrations/labs/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
.title=${this.hass.localize("ui.common.help")}
|
||||
@@ -125,7 +124,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.labs.empty.description"
|
||||
)}
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/integrations/labs/")}
|
||||
href="https://www.home-assistant.io/integrations/labs/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -62,7 +62,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
const defaultPanelUrlPath =
|
||||
this.hass.systemData?.default_panel || DEFAULT_PANEL;
|
||||
this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
|
||||
const titleInvalid = !this._data.title || !this._data.title.trim();
|
||||
|
||||
return html`
|
||||
@@ -260,7 +260,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
|
||||
const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
|
||||
// Add warning dialog to saying that this will change the default dashboard for all users
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
@@ -284,7 +284,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
|
||||
saveFrontendSystemData(this.hass.connection, "core", {
|
||||
...this.hass.systemData,
|
||||
default_panel: urlPath === defaultPanel ? undefined : urlPath,
|
||||
defaultPanel: urlPath === defaultPanel ? undefined : urlPath,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -309,20 +309,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
let localizedErrorMessage: string | undefined;
|
||||
if (err?.translation_domain && err?.translation_key) {
|
||||
const localize = await this.hass.loadBackendTranslation(
|
||||
"exceptions",
|
||||
err.translation_domain
|
||||
);
|
||||
localizedErrorMessage = localize(
|
||||
`component.${err.translation_domain}.exceptions.${err.translation_key}.message`,
|
||||
err.translation_placeholders
|
||||
);
|
||||
}
|
||||
this._error = {
|
||||
base: localizedErrorMessage || err?.message || "Unknown error",
|
||||
};
|
||||
this._error = { base: err?.message || "Unknown error" };
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||
}
|
||||
|
||||
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
|
||||
const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
|
||||
@@ -270,7 +270,6 @@ export class HaManualScriptEditor extends LitElement {
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
@sidebar-resized=${this._resizeSidebar}
|
||||
@sidebar-resizing-stopped=${this._stopResizeSidebar}
|
||||
@sidebar-reset-size=${this._resetSidebarWidth}
|
||||
></ha-automation-sidebar>
|
||||
</div>
|
||||
</div>
|
||||
@@ -619,16 +618,6 @@ export class HaManualScriptEditor extends LitElement {
|
||||
this._prevSidebarWidthPx = undefined;
|
||||
}
|
||||
|
||||
private _resetSidebarWidth(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._prevSidebarWidthPx = undefined;
|
||||
this._sidebarWidthPx = SIDEBAR_DEFAULT_WIDTH;
|
||||
this.style.setProperty(
|
||||
"--sidebar-dynamic-width",
|
||||
`${this._sidebarWidthPx}px`
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
saveFabStyles,
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
@@ -15,6 +14,8 @@ import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { TagDetailDialogParams } from "./show-dialog-tag-detail";
|
||||
|
||||
const TAG_BASE = "https://www.home-assistant.io/tag/";
|
||||
|
||||
@customElement("dialog-tag-detail")
|
||||
class DialogTagDetail
|
||||
extends LitElement
|
||||
@@ -121,7 +122,7 @@ class DialogTagDetail
|
||||
</div>
|
||||
<div id="qr">
|
||||
<ha-qr-code
|
||||
.data=${`${documentationUrl(this.hass, "/tag/")}${this._params!.entry!.id}`}
|
||||
.data=${`${TAG_BASE}${this._params!.entry!.id}`}
|
||||
center-image="/static/icons/favicon-192x192.png"
|
||||
error-correction-level="quartile"
|
||||
scale="5"
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-wa-dialog";
|
||||
import type { HomeFrontendSystemData } from "../../../data/frontend";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { EditHomeDialogParams } from "./show-dialog-edit-home";
|
||||
|
||||
@customElement("dialog-edit-home")
|
||||
export class DialogEditHome
|
||||
extends LitElement
|
||||
implements HassDialog<EditHomeDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: EditHomeDialogParams;
|
||||
|
||||
@state() private _config?: HomeFrontendSystemData;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
public showDialog(params: EditHomeDialogParams): void {
|
||||
this._params = params;
|
||||
this._config = { ...params.config };
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): boolean {
|
||||
this._open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
this._config = undefined;
|
||||
this._submitting = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
.headerTitle=${this.hass.localize("ui.panel.home.editor.title")}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<p class="description">
|
||||
${this.hass.localize("ui.panel.home.editor.description")}
|
||||
</p>
|
||||
|
||||
<ha-entities-picker
|
||||
autofocus
|
||||
.hass=${this.hass}
|
||||
.value=${this._config?.favorite_entities || []}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.strategy.home.favorite_entities"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.strategy.home.add_favorite_entity"
|
||||
)}
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.home.editor.favorite_entities_helper"
|
||||
)}
|
||||
reorder
|
||||
allow-custom-entity
|
||||
@value-changed=${this._favoriteEntitiesChanged}
|
||||
></ha-entities-picker>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _favoriteEntitiesChanged(ev: CustomEvent): void {
|
||||
const entities = ev.detail.value as string[];
|
||||
this._config = {
|
||||
...this._config,
|
||||
favorite_entities: entities.length > 0 ? entities : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._params || !this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
try {
|
||||
await this._params.saveConfig(this._config);
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to save home configuration:", err);
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: var(--ha-space-6);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0 0 var(--ha-space-4) 0;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-entities-picker {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-edit-home": DialogEditHome;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HomeFrontendSystemData } from "../../../data/frontend";
|
||||
|
||||
export interface EditHomeDialogParams {
|
||||
config: HomeFrontendSystemData;
|
||||
saveConfig: (config: HomeFrontendSystemData) => Promise<void>;
|
||||
}
|
||||
|
||||
export const loadEditHomeDialog = () => import("./dialog-edit-home");
|
||||
|
||||
export const showEditHomeDialog = (
|
||||
element: HTMLElement,
|
||||
params: EditHomeDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-edit-home",
|
||||
dialogImport: loadEditHomeDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@@ -3,18 +3,18 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { deepEqual } from "../../common/util/deep-equal";
|
||||
import {
|
||||
fetchFrontendSystemData,
|
||||
saveFrontendSystemData,
|
||||
type HomeFrontendSystemData,
|
||||
} from "../../data/frontend";
|
||||
import type { LovelaceDashboardStrategyConfig } from "../../data/lovelace/config/types";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../../types";
|
||||
import { showToast } from "../../util/toast";
|
||||
import "../lovelace/hui-root";
|
||||
import { generateLovelaceDashboardStrategy } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import { showEditHomeDialog } from "./dialogs/show-dialog-edit-home";
|
||||
import { showAlertDialog } from "../lovelace/custom-card-helpers";
|
||||
|
||||
const HOME_LOVELACE_CONFIG: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
type: "home",
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-panel-home")
|
||||
class PanelHome extends LitElement {
|
||||
@@ -28,14 +28,12 @@ class PanelHome extends LitElement {
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _config: FrontendSystemData["home"] = {};
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
// Initial setup
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
this._loadConfig();
|
||||
this._setLovelace();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,28 +95,9 @@ class PanelHome extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _loadConfig() {
|
||||
try {
|
||||
const data = await fetchFrontendSystemData(this.hass.connection, "home");
|
||||
this._config = data || {};
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to load favorites:", err);
|
||||
this._config = {};
|
||||
}
|
||||
this._setLovelace();
|
||||
}
|
||||
|
||||
private async _setLovelace() {
|
||||
const strategyConfig: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
type: "home",
|
||||
favorite_entities: this._config.favorite_entities,
|
||||
},
|
||||
};
|
||||
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
strategyConfig,
|
||||
HOME_LOVELACE_CONFIG,
|
||||
this.hass
|
||||
);
|
||||
|
||||
@@ -142,34 +121,15 @@ class PanelHome extends LitElement {
|
||||
}
|
||||
|
||||
private _setEditMode = () => {
|
||||
showEditHomeDialog(this, {
|
||||
config: this._config,
|
||||
saveConfig: async (config) => {
|
||||
await this._saveConfig(config);
|
||||
},
|
||||
// For now, we just show an alert that edit mode is not supported.
|
||||
// This will be expanded in the future.
|
||||
showAlertDialog(this, {
|
||||
title: "Edit mode not available",
|
||||
text: "The Home panel does not support edit mode.",
|
||||
confirmText: this.hass.localize("ui.common.ok"),
|
||||
});
|
||||
};
|
||||
|
||||
private async _saveConfig(config: HomeFrontendSystemData): Promise<void> {
|
||||
try {
|
||||
await saveFrontendSystemData(this.hass.connection, "home", config);
|
||||
this._config = config || {};
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to save home configuration:", err);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.panel.home.editor.save_failed"),
|
||||
duration: 0,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.successfully_saved"),
|
||||
});
|
||||
this._setLovelace();
|
||||
}
|
||||
|
||||
static readonly styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
@@ -7,7 +7,6 @@ import "../../components/ha-settings-row";
|
||||
import "../../components/ha-switch";
|
||||
import type { CoreFrontendUserData } from "../../data/frontend";
|
||||
import { saveFrontendUserData } from "../../data/frontend";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-advanced-mode-row")
|
||||
@@ -32,10 +31,7 @@ class AdvancedModeRow extends LitElement {
|
||||
<span slot="description">
|
||||
${this.hass.localize("ui.panel.profile.advanced_mode.description")}
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/blog/2019/07/17/release-96/#advanced-mode"
|
||||
)}
|
||||
href="https://www.home-assistant.io/blog/2019/07/17/release-96/#advanced-mode"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.panel.profile.advanced_mode.link_promo")}
|
||||
|
||||
@@ -25,7 +25,7 @@ class HaPickDashboardRow extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const value = this.hass.userData?.default_panel || USE_SYSTEM_VALUE;
|
||||
const value = this.hass.userData?.defaultPanel || USE_SYSTEM_VALUE;
|
||||
return html`
|
||||
<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">
|
||||
@@ -84,12 +84,12 @@ class HaPickDashboardRow extends LitElement {
|
||||
return;
|
||||
}
|
||||
const urlPath = value === USE_SYSTEM_VALUE ? undefined : value;
|
||||
if (urlPath === this.hass.userData?.default_panel) {
|
||||
if (urlPath === this.hass.userData?.defaultPanel) {
|
||||
return;
|
||||
}
|
||||
saveFrontendUserData(this.hass.connection, "core", {
|
||||
...this.hass.userData,
|
||||
default_panel: urlPath,
|
||||
defaultPanel: urlPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,6 +767,9 @@
|
||||
"no_match": "No languages found for {term}",
|
||||
"no_languages": "No languages available"
|
||||
},
|
||||
"icon-picker": {
|
||||
"no_match": "No matching icons found"
|
||||
},
|
||||
"tts-picker": {
|
||||
"tts": "Text-to-speech",
|
||||
"none": "None"
|
||||
@@ -2220,14 +2223,6 @@
|
||||
"migrate_to_user_data": "This will change the sidebar on all the devices you are logged in to. To create a sidebar per device, you should use a different user for that device."
|
||||
},
|
||||
"panel": {
|
||||
"home": {
|
||||
"editor": {
|
||||
"title": "Edit home page",
|
||||
"description": "Configure your home page display preferences.",
|
||||
"favorite_entities_helper": "Display your favorite entities. Home Assistant will still suggest based on commonly used up to 8 slots.",
|
||||
"save_failed": "Failed to save home page configuration"
|
||||
}
|
||||
},
|
||||
"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.",
|
||||
@@ -3265,10 +3260,7 @@
|
||||
"create_helper": "Create helper",
|
||||
"no_helpers": "Looks like you don't have any helpers yet!",
|
||||
"search": "Search {number} {number, plural,\n one {helper}\n other {helpers}\n}",
|
||||
"error_information": "Error information",
|
||||
"delete_confirm_title": "Delete helper?",
|
||||
"delete_confirm_text": "Are you sure you want to delete {name}?",
|
||||
"delete_failed": "Failed to delete helper"
|
||||
"error_information": "Error information"
|
||||
},
|
||||
"dialog": {
|
||||
"create": "Create",
|
||||
@@ -4796,8 +4788,7 @@
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
"type": "Type",
|
||||
"file_name": "File name",
|
||||
"usage_count": "In use"
|
||||
"file_name": "File name"
|
||||
},
|
||||
"types": {
|
||||
"automation": "Automation",
|
||||
|
||||
Reference in New Issue
Block a user