mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20231026.0 (#18431)
This commit is contained in:
commit
4e6e924a40
@ -45,8 +45,8 @@ gulp.task(
|
|||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-app",
|
"copy-static-app",
|
||||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
||||||
|
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod"),
|
||||||
// Don't compress running tests
|
// Don't compress running tests
|
||||||
...(env.isTestBuild() ? [] : ["compress-app"]),
|
...(env.isTestBuild() ? [] : ["compress-app"])
|
||||||
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod")
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20231025.1"
|
version = "20231026.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -223,7 +223,7 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this._computedStartTime = new Date(
|
this._computedStartTime = new Date(
|
||||||
this.historyData.timeline.reduce(
|
(this.historyData?.timeline ?? []).reduce(
|
||||||
(minTime, stateInfo) =>
|
(minTime, stateInfo) =>
|
||||||
Math.min(
|
Math.min(
|
||||||
minTime,
|
minTime,
|
||||||
|
@ -10,7 +10,8 @@ export class HaSlider extends MdSlider {
|
|||||||
:host {
|
:host {
|
||||||
--md-sys-color-primary: var(--primary-color);
|
--md-sys-color-primary: var(--primary-color);
|
||||||
--md-sys-color-outline: var(--outline-color);
|
--md-sys-color-outline: var(--outline-color);
|
||||||
|
--md-slider-handle-width: 14px;
|
||||||
|
--md-slider-handle-height: 14px;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
min-inline-size: 100px;
|
min-inline-size: 100px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
|
@ -136,6 +136,11 @@ export class HaTextField extends TextFieldBase {
|
|||||||
text-align: var(--text-field-text-align, start);
|
text-align: var(--text-field-text-align, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Edge, hide reveal password icon */
|
||||||
|
::-ms-reveal {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Chrome, Safari, Edge, Opera */
|
/* Chrome, Safari, Edge, Opera */
|
||||||
:host([no-spinner]) input::-webkit-outer-spin-button,
|
:host([no-spinner]) input::-webkit-outer-spin-button,
|
||||||
:host([no-spinner]) input::-webkit-inner-spin-button {
|
:host([no-spinner]) input::-webkit-inner-spin-button {
|
||||||
|
@ -348,11 +348,6 @@ export const getLegacyLovelaceCollection = (conn: Connection) =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface WindowWithLovelaceProm extends Window {
|
|
||||||
llConfProm?: Promise<LovelaceConfig>;
|
|
||||||
llResProm?: Promise<LovelaceResource[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ActionHandlerOptions {
|
export interface ActionHandlerOptions {
|
||||||
hasHold?: boolean;
|
hasHold?: boolean;
|
||||||
hasDoubleClick?: boolean;
|
hasDoubleClick?: boolean;
|
||||||
|
8
src/data/preloads.ts
Normal file
8
src/data/preloads.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { LovelaceConfig, LovelaceResource } from "./lovelace";
|
||||||
|
import { RecorderInfo } from "./recorder";
|
||||||
|
|
||||||
|
export interface WindowWithPreloads extends Window {
|
||||||
|
llConfProm?: Promise<LovelaceConfig>;
|
||||||
|
llResProm?: Promise<LovelaceResource[]>;
|
||||||
|
recorderInfoProm?: Promise<RecorderInfo>;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Connection } from "home-assistant-js-websocket";
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { HaDurationData } from "../components/ha-duration-input";
|
import { HaDurationData } from "../components/ha-duration-input";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@ -115,8 +116,8 @@ export interface StatisticsValidationResults {
|
|||||||
[statisticId: string]: StatisticsValidationResult[];
|
[statisticId: string]: StatisticsValidationResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRecorderInfo = (hass: HomeAssistant) =>
|
export const getRecorderInfo = (conn: Connection) =>
|
||||||
hass.callWS<RecorderInfo>({
|
conn.sendMessagePromise<RecorderInfo>({
|
||||||
type: "recorder/info",
|
type: "recorder/info",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export const enum TodoItemStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TodoItem {
|
export interface TodoItem {
|
||||||
uid?: string;
|
uid: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
status: TodoItemStatus;
|
status: TodoItemStatus;
|
||||||
}
|
}
|
||||||
@ -95,11 +95,11 @@ export const moveItem = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_id: string,
|
entity_id: string,
|
||||||
uid: string,
|
uid: string,
|
||||||
pos: number
|
previous_uid: string | undefined
|
||||||
): Promise<void> =>
|
): Promise<void> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "todo/item/move",
|
type: "todo/item/move",
|
||||||
entity_id,
|
entity_id,
|
||||||
uid,
|
uid,
|
||||||
pos,
|
previous_uid,
|
||||||
});
|
});
|
||||||
|
@ -13,12 +13,9 @@ import {
|
|||||||
import { loadTokens, saveTokens } from "../common/auth/token_storage";
|
import { loadTokens, saveTokens } from "../common/auth/token_storage";
|
||||||
import { hassUrl } from "../data/auth";
|
import { hassUrl } from "../data/auth";
|
||||||
import { isExternal } from "../data/external";
|
import { isExternal } from "../data/external";
|
||||||
|
import { getRecorderInfo } from "../data/recorder";
|
||||||
import { subscribeFrontendUserData } from "../data/frontend";
|
import { subscribeFrontendUserData } from "../data/frontend";
|
||||||
import {
|
import { fetchConfig, fetchResources } from "../data/lovelace";
|
||||||
fetchConfig,
|
|
||||||
fetchResources,
|
|
||||||
WindowWithLovelaceProm,
|
|
||||||
} from "../data/lovelace";
|
|
||||||
import { subscribePanels } from "../data/ws-panels";
|
import { subscribePanels } from "../data/ws-panels";
|
||||||
import { subscribeThemes } from "../data/ws-themes";
|
import { subscribeThemes } from "../data/ws-themes";
|
||||||
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
||||||
@ -27,6 +24,7 @@ import type { ExternalAuth } from "../external_app/external_auth";
|
|||||||
import "../resources/array.flat.polyfill";
|
import "../resources/array.flat.polyfill";
|
||||||
import "../resources/safari-14-attachshadow-patch";
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
import { MAIN_WINDOW_NAME } from "../data/main_window";
|
import { MAIN_WINDOW_NAME } from "../data/main_window";
|
||||||
|
import { WindowWithPreloads } from "../data/preloads";
|
||||||
|
|
||||||
window.name = MAIN_WINDOW_NAME;
|
window.name = MAIN_WINDOW_NAME;
|
||||||
(window as any).frontendVersion = __VERSION__;
|
(window as any).frontendVersion = __VERSION__;
|
||||||
@ -124,12 +122,14 @@ window.hassConnection.then(({ conn }) => {
|
|||||||
subscribeFrontendUserData(conn, "core", noop);
|
subscribeFrontendUserData(conn, "core", noop);
|
||||||
subscribeRepairsIssueRegistry(conn, noop);
|
subscribeRepairsIssueRegistry(conn, noop);
|
||||||
|
|
||||||
|
const preloadWindow = window as WindowWithPreloads;
|
||||||
|
preloadWindow.recorderInfoProm = getRecorderInfo(conn);
|
||||||
|
|
||||||
if (location.pathname === "/" || location.pathname.startsWith("/lovelace/")) {
|
if (location.pathname === "/" || location.pathname.startsWith("/lovelace/")) {
|
||||||
const llWindow = window as WindowWithLovelaceProm;
|
preloadWindow.llConfProm = fetchConfig(conn, null, false);
|
||||||
llWindow.llConfProm = fetchConfig(conn, null, false);
|
preloadWindow.llConfProm.catch(() => {
|
||||||
llWindow.llConfProm.catch(() => {
|
|
||||||
// Ignore it, it is handled by Lovelace panel.
|
// Ignore it, it is handled by Lovelace panel.
|
||||||
});
|
});
|
||||||
llWindow.llResProm = fetchResources(conn);
|
preloadWindow.llResProm = fetchResources(conn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,11 +3,12 @@ import { customElement, state } from "lit/decorators";
|
|||||||
import { isNavigationClick } from "../common/dom/is-navigation-click";
|
import { isNavigationClick } from "../common/dom/is-navigation-click";
|
||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import { getStorageDefaultPanelUrlPath } from "../data/panel";
|
import { getStorageDefaultPanelUrlPath } from "../data/panel";
|
||||||
import { getRecorderInfo } from "../data/recorder";
|
import { getRecorderInfo, RecorderInfo } from "../data/recorder";
|
||||||
import "../resources/custom-card-support";
|
import "../resources/custom-card-support";
|
||||||
import { HassElement } from "../state/hass-element";
|
import { HassElement } from "../state/hass-element";
|
||||||
import QuickBarMixin from "../state/quick-bar-mixin";
|
import QuickBarMixin from "../state/quick-bar-mixin";
|
||||||
import { HomeAssistant, Route } from "../types";
|
import { HomeAssistant, Route } from "../types";
|
||||||
|
import { WindowWithPreloads } from "../data/preloads";
|
||||||
import { storeState } from "../util/ha-pref-storage";
|
import { storeState } from "../util/ha-pref-storage";
|
||||||
import {
|
import {
|
||||||
renderLaunchScreenInfoBox,
|
renderLaunchScreenInfoBox,
|
||||||
@ -204,7 +205,15 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
|||||||
|
|
||||||
protected async checkDataBaseMigration() {
|
protected async checkDataBaseMigration() {
|
||||||
if (this.hass?.config?.components.includes("recorder")) {
|
if (this.hass?.config?.components.includes("recorder")) {
|
||||||
const info = await getRecorderInfo(this.hass);
|
let recorderInfoProm: Promise<RecorderInfo> | undefined;
|
||||||
|
const preloadWindow = window as WindowWithPreloads;
|
||||||
|
// On first load, we speed up loading page by having recorderInfoProm ready
|
||||||
|
if (preloadWindow.recorderInfoProm) {
|
||||||
|
recorderInfoProm = preloadWindow.recorderInfoProm;
|
||||||
|
preloadWindow.recorderInfoProm = undefined;
|
||||||
|
}
|
||||||
|
const info = await (recorderInfoProm ||
|
||||||
|
getRecorderInfo(this.hass.connection));
|
||||||
this._databaseMigration =
|
this._databaseMigration =
|
||||||
info.migration_in_progress && !info.migration_is_live;
|
info.migration_in_progress && !info.migration_is_live;
|
||||||
if (this._databaseMigration) {
|
if (this._databaseMigration) {
|
||||||
|
@ -93,6 +93,20 @@ class HaScheduleForm extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this.calendar?.destroy();
|
||||||
|
this.calendar = undefined;
|
||||||
|
this.renderRoot.querySelector("style[data-fullcalendar]")?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this.hasUpdated && !this.calendar) {
|
||||||
|
this.setupCalendar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.updateComplete.then(
|
this.updateComplete.then(
|
||||||
() =>
|
() =>
|
||||||
@ -165,6 +179,10 @@ class HaScheduleForm extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(): void {
|
protected firstUpdated(): void {
|
||||||
|
this.setupCalendar();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupCalendar(): void {
|
||||||
const config: CalendarOptions = {
|
const config: CalendarOptions = {
|
||||||
...defaultFullCalendarConfig,
|
...defaultFullCalendarConfig,
|
||||||
locale: this.hass.language,
|
locale: this.hass.language,
|
||||||
|
@ -21,6 +21,7 @@ import "../../../components/ha-checkbox";
|
|||||||
import "../../../components/ha-list-item";
|
import "../../../components/ha-list-item";
|
||||||
import "../../../components/ha-select";
|
import "../../../components/ha-select";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-textfield";
|
import "../../../components/ha-textfield";
|
||||||
import type { HaTextField } from "../../../components/ha-textfield";
|
import type { HaTextField } from "../../../components/ha-textfield";
|
||||||
import {
|
import {
|
||||||
@ -37,8 +38,10 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import type { SortableInstance } from "../../../resources/sortable";
|
import type { SortableInstance } from "../../../resources/sortable";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
import { TodoListCardConfig } from "./types";
|
import { TodoListCardConfig } from "./types";
|
||||||
|
import { isUnavailableState } from "../../../data/entity";
|
||||||
|
|
||||||
@customElement("hui-todo-list-card")
|
@customElement("hui-todo-list-card")
|
||||||
export class HuiTodoListCard
|
export class HuiTodoListCard
|
||||||
@ -74,7 +77,7 @@ export class HuiTodoListCard
|
|||||||
|
|
||||||
@state() private _entityId?: string;
|
@state() private _entityId?: string;
|
||||||
|
|
||||||
@state() private _items?: Record<string, TodoItem>;
|
@state() private _items?: TodoItem[];
|
||||||
|
|
||||||
@state() private _reordering = false;
|
@state() private _reordering = false;
|
||||||
|
|
||||||
@ -104,22 +107,16 @@ export class HuiTodoListCard
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getCheckedItems = memoizeOne(
|
private _getCheckedItems = memoizeOne((items?: TodoItem[]): TodoItem[] =>
|
||||||
(items?: Record<string, TodoItem>): TodoItem[] =>
|
items
|
||||||
items
|
? items.filter((item) => item.status === TodoItemStatus.Completed)
|
||||||
? Object.values(items).filter(
|
: []
|
||||||
(item) => item.status === TodoItemStatus.Completed
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private _getUncheckedItems = memoizeOne(
|
private _getUncheckedItems = memoizeOne((items?: TodoItem[]): TodoItem[] =>
|
||||||
(items?: Record<string, TodoItem>): TodoItem[] =>
|
items
|
||||||
items
|
? items.filter((item) => item.status === TodoItemStatus.NeedsAction)
|
||||||
? Object.values(items).filter(
|
: []
|
||||||
(item) => item.status === TodoItemStatus.NeedsAction
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
);
|
);
|
||||||
|
|
||||||
public willUpdate(
|
public willUpdate(
|
||||||
@ -169,6 +166,18 @@ export class HuiTodoListCard
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this._entityId];
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return html`
|
||||||
|
<hui-warning>
|
||||||
|
${createEntityNotFoundWarning(this.hass, this._entityId)}
|
||||||
|
</hui-warning>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unavailable = isUnavailableState(stateObj.state);
|
||||||
|
|
||||||
const checkedItems = this._getCheckedItems(this._items);
|
const checkedItems = this._getCheckedItems(this._items);
|
||||||
const uncheckedItems = this._getUncheckedItems(this._items);
|
const uncheckedItems = this._getUncheckedItems(this._items);
|
||||||
|
|
||||||
@ -182,39 +191,44 @@ export class HuiTodoListCard
|
|||||||
<div class="addRow">
|
<div class="addRow">
|
||||||
${this.todoListSupportsFeature(TodoListEntityFeature.CREATE_TODO_ITEM)
|
${this.todoListSupportsFeature(TodoListEntityFeature.CREATE_TODO_ITEM)
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-icon-button
|
||||||
class="addButton"
|
class="addButton"
|
||||||
.path=${mdiPlus}
|
.path=${mdiPlus}
|
||||||
.title=${this.hass!.localize(
|
.title=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.cards.todo-list.add_item"
|
"ui.panel.lovelace.cards.todo-list.add_item"
|
||||||
)}
|
)}
|
||||||
|
.disabled=${unavailable}
|
||||||
@click=${this._addItem}
|
@click=${this._addItem}
|
||||||
>
|
>
|
||||||
</ha-svg-icon>
|
</ha-icon-button>
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
class="addBox"
|
class="addBox"
|
||||||
.placeholder=${this.hass!.localize(
|
.placeholder=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.cards.todo-list.add_item"
|
"ui.panel.lovelace.cards.todo-list.add_item"
|
||||||
)}
|
)}
|
||||||
@keydown=${this._addKeyPress}
|
@keydown=${this._addKeyPress}
|
||||||
|
.disabled=${unavailable}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.todoListSupportsFeature(TodoListEntityFeature.MOVE_TODO_ITEM)
|
${this.todoListSupportsFeature(TodoListEntityFeature.MOVE_TODO_ITEM)
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-icon-button
|
||||||
class="reorderButton"
|
class="reorderButton"
|
||||||
.path=${mdiSort}
|
.path=${mdiSort}
|
||||||
.title=${this.hass!.localize(
|
.title=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.cards.todo-list.reorder_items"
|
"ui.panel.lovelace.cards.todo-list.reorder_items"
|
||||||
)}
|
)}
|
||||||
@click=${this._toggleReorder}
|
@click=${this._toggleReorder}
|
||||||
|
.disabled=${unavailable}
|
||||||
>
|
>
|
||||||
</ha-svg-icon>
|
</ha-icon-button>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
<div id="unchecked">${this._renderItems(uncheckedItems)}</div>
|
<div id="unchecked">
|
||||||
|
${this._renderItems(uncheckedItems, unavailable)}
|
||||||
|
</div>
|
||||||
${checkedItems.length
|
${checkedItems.length
|
||||||
? html`
|
? html`
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
@ -235,6 +249,7 @@ export class HuiTodoListCard
|
|||||||
"ui.panel.lovelace.cards.todo-list.clear_items"
|
"ui.panel.lovelace.cards.todo-list.clear_items"
|
||||||
)}
|
)}
|
||||||
@click=${this._clearCompletedItems}
|
@click=${this._clearCompletedItems}
|
||||||
|
.disabled=${unavailable}
|
||||||
>
|
>
|
||||||
</ha-svg-icon>`
|
</ha-svg-icon>`
|
||||||
: nothing}
|
: nothing}
|
||||||
@ -247,16 +262,18 @@ export class HuiTodoListCard
|
|||||||
${this.todoListSupportsFeature(
|
${this.todoListSupportsFeature(
|
||||||
TodoListEntityFeature.UPDATE_TODO_ITEM
|
TodoListEntityFeature.UPDATE_TODO_ITEM
|
||||||
)
|
)
|
||||||
? html` <ha-checkbox
|
? html`<ha-checkbox
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
.checked=${item.status === TodoItemStatus.Completed}
|
.checked=${item.status === TodoItemStatus.Completed}
|
||||||
.itemId=${item.uid}
|
.itemId=${item.uid}
|
||||||
@change=${this._completeItem}
|
@change=${this._completeItem}
|
||||||
|
.disabled=${unavailable}
|
||||||
></ha-checkbox>`
|
></ha-checkbox>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
class="item"
|
class="item"
|
||||||
.disabled=${!this.todoListSupportsFeature(
|
.disabled=${unavailable ||
|
||||||
|
!this.todoListSupportsFeature(
|
||||||
TodoListEntityFeature.UPDATE_TODO_ITEM
|
TodoListEntityFeature.UPDATE_TODO_ITEM
|
||||||
)}
|
)}
|
||||||
.value=${item.summary}
|
.value=${item.summary}
|
||||||
@ -272,7 +289,7 @@ export class HuiTodoListCard
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderItems(items: TodoItem[]) {
|
private _renderItems(items: TodoItem[], unavailable = false) {
|
||||||
return html`
|
return html`
|
||||||
${repeat(
|
${repeat(
|
||||||
items,
|
items,
|
||||||
@ -282,16 +299,18 @@ export class HuiTodoListCard
|
|||||||
${this.todoListSupportsFeature(
|
${this.todoListSupportsFeature(
|
||||||
TodoListEntityFeature.UPDATE_TODO_ITEM
|
TodoListEntityFeature.UPDATE_TODO_ITEM
|
||||||
)
|
)
|
||||||
? html` <ha-checkbox
|
? html`<ha-checkbox
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
.checked=${item.status === TodoItemStatus.Completed}
|
.checked=${item.status === TodoItemStatus.Completed}
|
||||||
.itemId=${item.uid}
|
.itemId=${item.uid}
|
||||||
|
.disabled=${unavailable}
|
||||||
@change=${this._completeItem}
|
@change=${this._completeItem}
|
||||||
></ha-checkbox>`
|
></ha-checkbox>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
class="item"
|
class="item"
|
||||||
.disabled=${!this.todoListSupportsFeature(
|
.disabled=${unavailable ||
|
||||||
|
!this.todoListSupportsFeature(
|
||||||
TodoListEntityFeature.UPDATE_TODO_ITEM
|
TodoListEntityFeature.UPDATE_TODO_ITEM
|
||||||
)}
|
)}
|
||||||
.value=${item.summary}
|
.value=${item.summary}
|
||||||
@ -325,16 +344,21 @@ export class HuiTodoListCard
|
|||||||
if (!this.hass || !this._entityId) {
|
if (!this.hass || !this._entityId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const items = await fetchItems(this.hass!, this._entityId!);
|
if (!(this._entityId in this.hass.states)) {
|
||||||
const records: Record<string, TodoItem> = {};
|
return;
|
||||||
items.forEach((item) => {
|
}
|
||||||
records[item.uid!] = item;
|
this._items = await fetchItems(this.hass!, this._entityId!);
|
||||||
});
|
}
|
||||||
this._items = records;
|
|
||||||
|
private _getItem(itemId: string) {
|
||||||
|
return this._items?.find((item) => item.uid === itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _completeItem(ev): void {
|
private _completeItem(ev): void {
|
||||||
const item = this._items![ev.target.itemId];
|
const item = this._getItem(ev.target.itemId);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
updateItem(this.hass!, this._entityId!, {
|
updateItem(this.hass!, this._entityId!, {
|
||||||
...item,
|
...item,
|
||||||
status: ev.target.checked
|
status: ev.target.checked
|
||||||
@ -346,7 +370,10 @@ export class HuiTodoListCard
|
|||||||
private _saveEdit(ev): void {
|
private _saveEdit(ev): void {
|
||||||
// If name is not empty, update the item otherwise remove it
|
// If name is not empty, update the item otherwise remove it
|
||||||
if (ev.target.value) {
|
if (ev.target.value) {
|
||||||
const item = this._items![ev.target.itemId];
|
const item = this._getItem(ev.target.itemId);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
updateItem(this.hass!, this._entityId!, {
|
updateItem(this.hass!, this._entityId!, {
|
||||||
...item,
|
...item,
|
||||||
summary: ev.target.value,
|
summary: ev.target.value,
|
||||||
@ -368,7 +395,7 @@ export class HuiTodoListCard
|
|||||||
}
|
}
|
||||||
const deleteActions: Array<Promise<any>> = [];
|
const deleteActions: Array<Promise<any>> = [];
|
||||||
this._getCheckedItems(this._items).forEach((item: TodoItem) => {
|
this._getCheckedItems(this._items).forEach((item: TodoItem) => {
|
||||||
deleteActions.push(deleteItem(this.hass!, this._entityId!, item.uid!));
|
deleteActions.push(deleteItem(this.hass!, this._entityId!, item.uid));
|
||||||
});
|
});
|
||||||
await Promise.all(deleteActions).finally(() => this._fetchData());
|
await Promise.all(deleteActions).finally(() => this._fetchData());
|
||||||
}
|
}
|
||||||
@ -438,11 +465,37 @@ export class HuiTodoListCard
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _moveItem(oldIndex, newIndex) {
|
private async _moveItem(oldIndex: number, newIndex: number) {
|
||||||
const item = this._getUncheckedItems(this._items)[oldIndex];
|
const uncheckedItems = this._getUncheckedItems(this._items);
|
||||||
await moveItem(this.hass!, this._entityId!, item.uid!, newIndex).finally(
|
const item = uncheckedItems[oldIndex];
|
||||||
() => this._fetchData()
|
let prevItem: TodoItem | undefined;
|
||||||
);
|
if (newIndex > 0) {
|
||||||
|
if (newIndex < oldIndex) {
|
||||||
|
prevItem = uncheckedItems[newIndex - 1];
|
||||||
|
} else {
|
||||||
|
prevItem = uncheckedItems[newIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimistic change
|
||||||
|
const itemIndex = this._items!.findIndex((itm) => itm.uid === item.uid);
|
||||||
|
this._items!.splice(itemIndex, 1);
|
||||||
|
if (newIndex === 0) {
|
||||||
|
this._items!.unshift(item);
|
||||||
|
} else {
|
||||||
|
const prevIndex = this._items!.findIndex(
|
||||||
|
(itm) => itm.uid === prevItem!.uid
|
||||||
|
);
|
||||||
|
this._items!.splice(prevIndex + 1, 0, item);
|
||||||
|
}
|
||||||
|
this._items = [...this._items!];
|
||||||
|
|
||||||
|
await moveItem(
|
||||||
|
this.hass!,
|
||||||
|
this._entityId!,
|
||||||
|
item.uid,
|
||||||
|
prevItem?.uid
|
||||||
|
).finally(() => this._fetchData());
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@ -470,16 +523,14 @@ export class HuiTodoListCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
.addButton {
|
.addButton {
|
||||||
padding-right: 16px;
|
margin-left: -12px;
|
||||||
padding-inline-end: 16px;
|
margin-inline-start: -12px;
|
||||||
cursor: pointer;
|
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reorderButton {
|
.reorderButton {
|
||||||
padding-left: 16px;
|
margin-right: -12px;
|
||||||
padding-inline-start: 16px;
|
margin-inline-end: -12px;
|
||||||
cursor: pointer;
|
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,8 @@ export const loadLovelaceResources = (
|
|||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
) => {
|
) => {
|
||||||
// Don't load ressources on safe mode
|
// Don't load ressources on safe mode
|
||||||
if (hass.config.safe_mode) {
|
// Sometimes, hass.config is null but it should not.
|
||||||
|
if (hass.config?.safe_mode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resources.forEach((resource) => {
|
resources.forEach((resource) => {
|
||||||
|
@ -48,6 +48,7 @@ export class HaCardConditionNumericState extends LitElement {
|
|||||||
name: "above",
|
name: "above",
|
||||||
selector: {
|
selector: {
|
||||||
number: {
|
number: {
|
||||||
|
step: "any",
|
||||||
mode: "box",
|
mode: "box",
|
||||||
unit_of_measurement: stateObj?.attributes.unit_of_measurement,
|
unit_of_measurement: stateObj?.attributes.unit_of_measurement,
|
||||||
},
|
},
|
||||||
@ -57,6 +58,7 @@ export class HaCardConditionNumericState extends LitElement {
|
|||||||
name: "below",
|
name: "below",
|
||||||
selector: {
|
selector: {
|
||||||
number: {
|
number: {
|
||||||
|
step: "any",
|
||||||
mode: "box",
|
mode: "box",
|
||||||
unit_of_measurement: stateObj?.attributes.unit_of_measurement,
|
unit_of_measurement: stateObj?.attributes.unit_of_measurement,
|
||||||
},
|
},
|
||||||
|
@ -186,11 +186,15 @@ export class HuiTileCardEditor
|
|||||||
multiple: true,
|
multiple: true,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: "State",
|
label: localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.state_content_options.state`
|
||||||
|
),
|
||||||
value: "state",
|
value: "state",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Last changed",
|
label: localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.state_content_options.last-changed`
|
||||||
|
),
|
||||||
value: "last-changed",
|
value: "last-changed",
|
||||||
},
|
},
|
||||||
...Object.keys(stateObj?.attributes ?? {})
|
...Object.keys(stateObj?.attributes ?? {})
|
||||||
|
@ -15,8 +15,8 @@ import {
|
|||||||
LovelaceConfig,
|
LovelaceConfig,
|
||||||
saveConfig,
|
saveConfig,
|
||||||
subscribeLovelaceUpdates,
|
subscribeLovelaceUpdates,
|
||||||
WindowWithLovelaceProm,
|
|
||||||
} from "../../data/lovelace";
|
} from "../../data/lovelace";
|
||||||
|
import { WindowWithPreloads } from "../../data/preloads";
|
||||||
import "../../layouts/hass-error-screen";
|
import "../../layouts/hass-error-screen";
|
||||||
import "../../layouts/hass-loading-screen";
|
import "../../layouts/hass-loading-screen";
|
||||||
import { HomeAssistant, PanelInfo, Route } from "../../types";
|
import { HomeAssistant, PanelInfo, Route } from "../../types";
|
||||||
@ -220,18 +220,18 @@ export class LovelacePanel extends LitElement {
|
|||||||
let rawConf: LovelaceConfig | undefined;
|
let rawConf: LovelaceConfig | undefined;
|
||||||
let confMode: Lovelace["mode"] = this.panel!.config.mode;
|
let confMode: Lovelace["mode"] = this.panel!.config.mode;
|
||||||
let confProm: Promise<LovelaceConfig> | undefined;
|
let confProm: Promise<LovelaceConfig> | undefined;
|
||||||
const llWindow = window as WindowWithLovelaceProm;
|
const preloadWindow = window as WindowWithPreloads;
|
||||||
|
|
||||||
// On first load, we speed up loading page by having LL promise ready
|
// On first load, we speed up loading page by having LL promise ready
|
||||||
if (llWindow.llConfProm) {
|
if (preloadWindow.llConfProm) {
|
||||||
confProm = llWindow.llConfProm;
|
confProm = preloadWindow.llConfProm;
|
||||||
llWindow.llConfProm = undefined;
|
preloadWindow.llConfProm = undefined;
|
||||||
}
|
}
|
||||||
if (!resourcesLoaded) {
|
if (!resourcesLoaded) {
|
||||||
resourcesLoaded = true;
|
resourcesLoaded = true;
|
||||||
const resources = await (llWindow.llResProm ||
|
(preloadWindow.llResProm || fetchResources(this.hass!.connection)).then(
|
||||||
fetchResources(this.hass!.connection));
|
(resources) => loadLovelaceResources(resources, this.hass!)
|
||||||
loadLovelaceResources(resources, this.hass!);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.urlPath !== null || !confProm) {
|
if (this.urlPath !== null || !confProm) {
|
||||||
|
@ -56,6 +56,7 @@ import {
|
|||||||
BrowserMediaPlayer,
|
BrowserMediaPlayer,
|
||||||
ERR_UNSUPPORTED_MEDIA,
|
ERR_UNSUPPORTED_MEDIA,
|
||||||
} from "./browser-media-player";
|
} from "./browser-media-player";
|
||||||
|
import { debounce } from "../../common/util/debounce";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -118,8 +119,14 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
public showResolvingNewMediaPicked() {
|
public showResolvingNewMediaPicked() {
|
||||||
this._tearDownBrowserPlayer();
|
this._tearDownBrowserPlayer();
|
||||||
this._newMediaExpected = true;
|
this._newMediaExpected = true;
|
||||||
|
// Sometimes the state does not update when playing media, like with TTS, so we wait max 2 secs and then stop waiting
|
||||||
|
this._debouncedResetMediaExpected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _debouncedResetMediaExpected = debounce(() => {
|
||||||
|
this._newMediaExpected = false;
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
public hideResolvingNewMediaPicked() {
|
public hideResolvingNewMediaPicked() {
|
||||||
this._newMediaExpected = false;
|
this._newMediaExpected = false;
|
||||||
}
|
}
|
||||||
@ -154,7 +161,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
protected render() {
|
protected render() {
|
||||||
if (this._newMediaExpected) {
|
if (this._newMediaExpected) {
|
||||||
return html`
|
return html`
|
||||||
<div class="controls-progress">
|
<div class="controls-progress buffering">
|
||||||
${until(
|
${until(
|
||||||
// Only show spinner after 500ms
|
// Only show spinner after 500ms
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
@ -240,9 +247,13 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-progress">
|
<div
|
||||||
|
class="controls-progress ${stateObj.state === "buffering"
|
||||||
|
? "buffering"
|
||||||
|
: ""}"
|
||||||
|
>
|
||||||
${stateObj.state === "buffering"
|
${stateObj.state === "buffering"
|
||||||
? html` <ha-circular-progress active></ha-circular-progress> `
|
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||||
: html`
|
: html`
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
${controls === undefined
|
${controls === undefined
|
||||||
@ -541,7 +552,8 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 100px;
|
height: 100px;
|
||||||
|
box-sizing: border-box;
|
||||||
background: var(
|
background: var(
|
||||||
--ha-card-background,
|
--ha-card-background,
|
||||||
var(--card-background-color, white)
|
var(--card-background-color, white)
|
||||||
@ -627,12 +639,11 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: 100px;
|
height: 100%;
|
||||||
max-width: 100px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app img {
|
.app img {
|
||||||
max-height: 68px;
|
height: 68px;
|
||||||
margin: 16px 0 16px 16px;
|
margin: 16px 0 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,8 +652,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([narrow]) {
|
:host([narrow]) {
|
||||||
min-height: 56px;
|
height: 57px;
|
||||||
max-height: 56px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([narrow]) .controls-progress {
|
:host([narrow]) .controls-progress {
|
||||||
@ -650,6 +660,10 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
min-width: 48px;
|
min-width: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host([narrow]) .controls-progress.buffering {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
:host([narrow]) .media-info {
|
:host([narrow]) .media-info {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
@ -672,16 +686,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([narrow]) img {
|
|
||||||
max-height: 56px;
|
|
||||||
max-width: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([narrow]) .blank-image {
|
|
||||||
height: 56px;
|
|
||||||
width: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([narrow]) mwc-linear-progress {
|
:host([narrow]) mwc-linear-progress {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -286,7 +286,7 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([narrow]) ha-media-player-browse {
|
:host([narrow]) ha-media-player-browse {
|
||||||
height: calc(100vh - (80px + var(--header-height)));
|
height: calc(100vh - (57px + var(--header-height)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-bar-media-player {
|
ha-bar-media-player {
|
||||||
|
@ -2,7 +2,9 @@ import { ResizeController } from "@lit-labs/observers/resize-controller";
|
|||||||
import "@material/mwc-list";
|
import "@material/mwc-list";
|
||||||
import {
|
import {
|
||||||
mdiChevronDown,
|
mdiChevronDown,
|
||||||
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
|
mdiInformationOutline,
|
||||||
mdiMicrophone,
|
mdiMicrophone,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
@ -19,6 +21,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
import { storage } from "../../common/decorators/storage";
|
import { storage } from "../../common/decorators/storage";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import "../../components/ha-button";
|
import "../../components/ha-button";
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
@ -27,15 +30,21 @@ import "../../components/ha-menu-button";
|
|||||||
import "../../components/ha-state-icon";
|
import "../../components/ha-state-icon";
|
||||||
import "../../components/ha-svg-icon";
|
import "../../components/ha-svg-icon";
|
||||||
import "../../components/ha-two-pane-top-app-bar-fixed";
|
import "../../components/ha-two-pane-top-app-bar-fixed";
|
||||||
|
import { deleteConfigEntry } from "../../data/config_entries";
|
||||||
|
import { getExtendedEntityRegistryEntry } from "../../data/entity_registry";
|
||||||
|
import { fetchIntegrationManifest } from "../../data/integration";
|
||||||
import { getTodoLists } from "../../data/todo";
|
import { getTodoLists } from "../../data/todo";
|
||||||
|
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
|
import {
|
||||||
|
showAlertDialog,
|
||||||
|
showConfirmationDialog,
|
||||||
|
} from "../../dialogs/generic/show-dialog-box";
|
||||||
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
|
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { HuiErrorCard } from "../lovelace/cards/hui-error-card";
|
import { HuiErrorCard } from "../lovelace/cards/hui-error-card";
|
||||||
import { createCardElement } from "../lovelace/create-element/create-card-element";
|
import { createCardElement } from "../lovelace/create-element/create-card-element";
|
||||||
import { LovelaceCard } from "../lovelace/types";
|
import { LovelaceCard } from "../lovelace/types";
|
||||||
import { fetchIntegrationManifest } from "../../data/integration";
|
|
||||||
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
|
|
||||||
|
|
||||||
@customElement("ha-panel-todo")
|
@customElement("ha-panel-todo")
|
||||||
class PanelTodo extends LitElement {
|
class PanelTodo extends LitElement {
|
||||||
@ -92,6 +101,10 @@ class PanelTodo extends LitElement {
|
|||||||
protected willUpdate(changedProperties: PropertyValues): void {
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
super.willUpdate(changedProperties);
|
super.willUpdate(changedProperties);
|
||||||
|
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this.hass.loadFragmentTranslation("lovelace");
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.hasUpdated && !this._entityId) {
|
if (!this.hasUpdated && !this._entityId) {
|
||||||
this._entityId = getTodoLists(this.hass)[0]?.entity_id;
|
this._entityId = getTodoLists(this.hass)[0]?.entity_id;
|
||||||
} else if (!this.hasUpdated) {
|
} else if (!this.hasUpdated) {
|
||||||
@ -124,6 +137,9 @@ class PanelTodo extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
const entityRegistryEntry = this._entityId
|
||||||
|
? this.hass.entities[this._entityId]
|
||||||
|
: undefined;
|
||||||
const showPane = this._showPaneController.value ?? !this.narrow;
|
const showPane = this._showPaneController.value ?? !this.narrow;
|
||||||
const listItems = getTodoLists(this.hass).map(
|
const listItems = getTodoLists(this.hass).map(
|
||||||
(list) =>
|
(list) =>
|
||||||
@ -158,7 +174,9 @@ class PanelTodo extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-button slot="trigger">
|
<ha-button slot="trigger">
|
||||||
${this._entityId
|
${this._entityId
|
||||||
? computeStateName(this.hass.states[this._entityId])
|
? this._entityId in this.hass.states
|
||||||
|
? computeStateName(this.hass.states[this._entityId])
|
||||||
|
: this._entityId
|
||||||
: ""}
|
: ""}
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
slot="trailingIcon"
|
slot="trailingIcon"
|
||||||
@ -188,13 +206,36 @@ class PanelTodo extends LitElement {
|
|||||||
${this._conversation(this.hass.config.components)
|
${this._conversation(this.hass.config.components)
|
||||||
? html`<ha-list-item
|
? html`<ha-list-item
|
||||||
graphic="icon"
|
graphic="icon"
|
||||||
@click=${this._showVoiceCommandDialog}
|
@click=${this._showMoreInfoDialog}
|
||||||
|
.disabled=${!this._entityId}
|
||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiMicrophone} slot="graphic">
|
<ha-svg-icon .path=${mdiInformationOutline} slot="graphic">
|
||||||
</ha-svg-icon>
|
</ha-svg-icon>
|
||||||
${this.hass.localize("ui.panel.todo.start_conversation")}
|
${this.hass.localize("ui.panel.todo.information")}
|
||||||
</ha-list-item>`
|
</ha-list-item>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
<li divider role="separator"></li>
|
||||||
|
<ha-list-item graphic="icon" @click=${this._showVoiceCommandDialog}>
|
||||||
|
<ha-svg-icon .path=${mdiMicrophone} slot="graphic"> </ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.panel.todo.start_conversation")}
|
||||||
|
</ha-list-item>
|
||||||
|
${entityRegistryEntry?.platform === "local_todo"
|
||||||
|
? html` <li divider role="separator"></li>
|
||||||
|
<ha-list-item
|
||||||
|
graphic="icon"
|
||||||
|
@click=${this._deleteList}
|
||||||
|
class="warning"
|
||||||
|
.disabled=${!this._entityId}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiDelete}
|
||||||
|
slot="graphic"
|
||||||
|
class="warning"
|
||||||
|
>
|
||||||
|
</ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.panel.todo.delete_list")}
|
||||||
|
</ha-list-item>`
|
||||||
|
: nothing}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
<div id="columns">
|
<div id="columns">
|
||||||
<div class="column">${this._card}</div>
|
<div class="column">${this._card}</div>
|
||||||
@ -215,6 +256,60 @@ class PanelTodo extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _showMoreInfoDialog(): void {
|
||||||
|
if (!this._entityId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "hass-more-info", { entityId: this._entityId });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deleteList(): Promise<void> {
|
||||||
|
if (!this._entityId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityRegistryEntry = await getExtendedEntityRegistryEntry(
|
||||||
|
this.hass,
|
||||||
|
this._entityId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (entityRegistryEntry.platform !== "local_todo") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryId = entityRegistryEntry.config_entry_id;
|
||||||
|
|
||||||
|
if (!entryId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize("ui.panel.todo.delete_confirm_title", {
|
||||||
|
name:
|
||||||
|
this._entityId in this.hass.states
|
||||||
|
? computeStateName(this.hass.states[this._entityId])
|
||||||
|
: this._entityId,
|
||||||
|
}),
|
||||||
|
text: this.hass.localize("ui.panel.todo.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);
|
||||||
|
|
||||||
|
this._entityId = getTodoLists(this.hass)[0]?.entity_id;
|
||||||
|
|
||||||
|
if (result.require_restart) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize("ui.panel.todo.restart_confirm"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _showVoiceCommandDialog(): void {
|
private _showVoiceCommandDialog(): void {
|
||||||
showVoiceCommandDialog(this, this.hass, { pipeline_id: "last_used" });
|
showVoiceCommandDialog(this, this.hass, { pipeline_id: "last_used" });
|
||||||
}
|
}
|
||||||
|
@ -5114,6 +5114,10 @@
|
|||||||
"vertical": "Vertical",
|
"vertical": "Vertical",
|
||||||
"hide_state": "Hide state",
|
"hide_state": "Hide state",
|
||||||
"state_content": "State content",
|
"state_content": "State content",
|
||||||
|
"state_content_options": {
|
||||||
|
"state": "State",
|
||||||
|
"last-changed": "Last changed"
|
||||||
|
},
|
||||||
"features": {
|
"features": {
|
||||||
"name": "Features",
|
"name": "Features",
|
||||||
"not_compatible": "Not compatible",
|
"not_compatible": "Not compatible",
|
||||||
@ -5512,7 +5516,12 @@
|
|||||||
},
|
},
|
||||||
"todo": {
|
"todo": {
|
||||||
"start_conversation": "Start conversation",
|
"start_conversation": "Start conversation",
|
||||||
"create_list": "Create list"
|
"create_list": "Create list",
|
||||||
|
"delete_list": "Delete list",
|
||||||
|
"information": "Information",
|
||||||
|
"delete_confirm_title": "Remove {name}?",
|
||||||
|
"delete_confirm_text": "Are you sure you want to remove this list and all of its items?",
|
||||||
|
"restart_confirm": "Restart Home Assistant to finish removing this to-do list"
|
||||||
},
|
},
|
||||||
"page-authorize": {
|
"page-authorize": {
|
||||||
"initializing": "Initializing",
|
"initializing": "Initializing",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user