From d9b988d66b867b847b65266e89175e1883d7063c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 19 Oct 2023 17:03:04 +0200 Subject: [PATCH] Add support for todo component --- demo/src/stubs/shopping_list.ts | 2 +- package.json | 1 + src/common/const.ts | 2 + src/components/ha-sidebar.ts | 4 +- src/data/shopping-list.ts | 58 ---- src/data/todo.ts | 104 +++++++ src/layouts/partial-panel-resolver.ts | 3 +- .../lovelace/cards/hui-shopping-list-card.ts | 277 ++++++++++++------ src/panels/lovelace/cards/types.ts | 1 + .../hui-shopping-list-editor.ts | 10 + .../shopping-list/ha-panel-shopping-list.ts | 106 ------- src/panels/todo/ha-panel-todo.ts | 257 ++++++++++++++++ src/translations/en.json | 5 +- yarn.lock | 26 ++ 14 files changed, 590 insertions(+), 266 deletions(-) delete mode 100644 src/data/shopping-list.ts create mode 100644 src/data/todo.ts delete mode 100644 src/panels/shopping-list/ha-panel-shopping-list.ts create mode 100644 src/panels/todo/ha-panel-todo.ts diff --git a/demo/src/stubs/shopping_list.ts b/demo/src/stubs/shopping_list.ts index 7b714a6e0199..54e951fad8a0 100644 --- a/demo/src/stubs/shopping_list.ts +++ b/demo/src/stubs/shopping_list.ts @@ -1,4 +1,4 @@ -import { ShoppingListItem } from "../../../src/data/shopping-list"; +import { ShoppingListItem } from "../../../src/data/todo"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; let items: ShoppingListItem[] = [ diff --git a/package.json b/package.json index bf2fcc4dc548..cd2a8cca36ff 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@lezer/highlight": "1.1.6", "@lit-labs/context": "0.4.1", "@lit-labs/motion": "1.0.4", + "@lit-labs/observers": "2.0.1", "@lit-labs/virtualizer": "2.0.7", "@lrnwebcomponents/simple-tooltip": "7.0.18", "@material/chips": "=14.0.0-canary.53b3cad2f.0", diff --git a/src/common/const.ts b/src/common/const.ts index daead2ada95f..dbb3481e6c59 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -16,6 +16,7 @@ import { mdiCarCoolantLevel, mdiCash, mdiChatSleep, + mdiClipboardCheck, mdiClock, mdiCloudUpload, mdiCog, @@ -120,6 +121,7 @@ export const FIXED_DOMAIN_ICONS = { siren: mdiBullhorn, stt: mdiMicrophoneMessage, text: mdiFormTextbox, + todo: mdiClipboardCheck, time: mdiClock, timer: mdiTimerOutline, tts: mdiSpeakerMessage, diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index be3c2b6227b9..68216dee3936 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -2,9 +2,9 @@ import "@material/mwc-button/mwc-button"; import { mdiBell, mdiCalendar, - mdiCart, mdiCellphoneCog, mdiChartBox, + mdiClipboardList, mdiClose, mdiCog, mdiFormatListBulletedType, @@ -81,7 +81,7 @@ const PANEL_ICONS = { lovelace: mdiViewDashboard, map: mdiTooltipAccount, "media-browser": mdiPlayBoxMultiple, - "shopping-list": mdiCart, + todo: mdiClipboardList, }; const panelSorter = ( diff --git a/src/data/shopping-list.ts b/src/data/shopping-list.ts deleted file mode 100644 index 7a1d60a1ca32..000000000000 --- a/src/data/shopping-list.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { HomeAssistant } from "../types"; - -export interface ShoppingListItem { - id: number; - name: string; - complete: boolean; -} - -export const fetchItems = (hass: HomeAssistant): Promise => - hass.callWS({ - type: "shopping_list/items", - }); - -export const updateItem = ( - hass: HomeAssistant, - itemId: number, - item: { - name?: string; - complete?: boolean; - } -): Promise => - hass.callWS({ - type: "shopping_list/items/update", - item_id: itemId, - ...item, - }); - -export const clearItems = (hass: HomeAssistant): Promise => - hass.callWS({ - type: "shopping_list/items/clear", - }); - -export const addItem = ( - hass: HomeAssistant, - name: string -): Promise => - hass.callWS({ - type: "shopping_list/items/add", - name, - }); - -export const removeItem = ( - hass: HomeAssistant, - item_id: string -): Promise => - hass.callWS({ - type: "shopping_list/items/remove", - item_id, - }); - -export const reorderItems = ( - hass: HomeAssistant, - itemIds: string[] -): Promise => - hass.callWS({ - type: "shopping_list/items/reorder", - item_ids: itemIds, - }); diff --git a/src/data/todo.ts b/src/data/todo.ts new file mode 100644 index 000000000000..9038e1560ad0 --- /dev/null +++ b/src/data/todo.ts @@ -0,0 +1,104 @@ +import { HomeAssistant, ServiceCallResponse } from "../types"; +import { computeDomain } from "../common/entity/compute_domain"; +import { computeStateName } from "../common/entity/compute_state_name"; +import { isUnavailableState } from "./entity"; + +export enum TodoItemStatus { + NeedsAction = "needs-action", + Completed = "completed", +} + +export interface TodoList { + entity_id: string; + name: string; +} + +export interface TodoItem { + uid?: string; + summary: string; + status: TodoItemStatus; +} + +export const enum TodoListEntityFeature { + CREATE_TODO_ITEM = 1, + DELETE_TODO_ITEM = 2, + UPDATE_TODO_ITEM = 4, + MOVE_TODO_ITEM = 8, +} + +export const getTodoLists = (hass: HomeAssistant): TodoList[] => + Object.keys(hass.states) + .filter( + (entityId) => + computeDomain(entityId) === "todo" && + !isUnavailableState(hass.states[entityId].state) + ) + .sort() + .map((entityId) => ({ + ...hass.states[entityId], + entity_id: entityId, + name: computeStateName(hass.states[entityId]), + })); + +export interface TodoItems { + items: TodoItem[]; +} + +export const fetchItems = async ( + hass: HomeAssistant, + entityId: string +): Promise => { + const result = await hass.callWS({ + type: "todo/item/list", + entity_id: entityId, + }); + return result.items; +}; + +export const updateItem = ( + hass: HomeAssistant, + entity_id: string, + item: TodoItem +): Promise => + hass.callService("todo", "update_item", item, { entity_id }); + +export const createItem = ( + hass: HomeAssistant, + entity_id: string, + summary: string +): Promise => + hass.callService( + "todo", + "create_item", + { + summary, + }, + { entity_id } + ); + +export const deleteItem = ( + hass: HomeAssistant, + entity_id: string, + uid: string +): Promise => + hass.callService( + "todo", + "delete_item", + { + uid, + }, + { entity_id } + ); + +export const moveItem = ( + hass: HomeAssistant, + entity_id: string, + uid: string, + pos: number +): Promise => + hass.callWS({ + type: "todo/item/move", + entity_id, + uid, + pos, + }); diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts index 48b44a0b2844..c2bfefd981f5 100644 --- a/src/layouts/partial-panel-resolver.ts +++ b/src/layouts/partial-panel-resolver.ts @@ -34,8 +34,7 @@ const COMPONENTS = { map: () => import("../panels/map/ha-panel-map"), my: () => import("../panels/my/ha-panel-my"), profile: () => import("../panels/profile/ha-panel-profile"), - "shopping-list": () => - import("../panels/shopping-list/ha-panel-shopping-list"), + todo: () => import("../panels/todo/ha-panel-todo"), "media-browser": () => import("../panels/media-browser/ha-panel-media-browser"), }; diff --git a/src/panels/lovelace/cards/hui-shopping-list-card.ts b/src/panels/lovelace/cards/hui-shopping-list-card.ts index 1b7dc10a5651..f10fb6e05d49 100644 --- a/src/panels/lovelace/cards/hui-shopping-list-card.ts +++ b/src/panels/lovelace/cards/hui-shopping-list-card.ts @@ -1,11 +1,12 @@ import { mdiDrag, mdiNotificationClearAll, mdiPlus, mdiSort } from "@mdi/js"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, + PropertyValueMap, PropertyValues, + css, + html, nothing, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; @@ -13,28 +14,34 @@ import { classMap } from "lit/directives/class-map"; import { guard } from "lit/directives/guard"; import { repeat } from "lit/directives/repeat"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-card"; import "../../../components/ha-checkbox"; +import "../../../components/ha-list-item"; +import "../../../components/ha-select"; import "../../../components/ha-svg-icon"; import "../../../components/ha-textfield"; import type { HaTextField } from "../../../components/ha-textfield"; + import { - addItem, - clearItems, + TodoItem, + TodoItemStatus, + TodoListEntityFeature, + createItem, + deleteItem, fetchItems, - removeItem, - reorderItems, - ShoppingListItem, + getTodoLists, + moveItem, updateItem, -} from "../../../data/shopping-list"; +} from "../../../data/todo"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { - loadSortable, SortableInstance, + loadSortable, } from "../../../resources/sortable.ondemand"; import { HomeAssistant } from "../../../types"; import { LovelaceCard, LovelaceCardEditor } from "../types"; -import { SensorCardConfig, ShoppingListCardConfig } from "./types"; +import { ShoppingListCardConfig } from "./types"; @customElement("hui-shopping-list-card") class HuiShoppingListCard @@ -54,9 +61,13 @@ class HuiShoppingListCard @state() private _config?: ShoppingListCardConfig; - @state() private _uncheckedItems?: ShoppingListItem[]; + @state() private _entityId?: string; - @state() private _checkedItems?: ShoppingListItem[]; + @state() private _items: Record = {}; + + @state() private _uncheckedItems?: TodoItem[]; + + @state() private _checkedItems?: TodoItem[]; @state() private _reordering = false; @@ -74,10 +85,27 @@ class HuiShoppingListCard this._config = config; this._uncheckedItems = []; this._checkedItems = []; + + if (this._config!.entity) { + this._entityId = this._config!.entity; + } + } + + public willUpdate( + _changedProperties: PropertyValueMap | Map + ): void { + if (!this.hasUpdated) { + if (!this._entityId) { + const todoLists = getTodoLists(this.hass!); + if (todoLists.length > 0) { + this._entityId = todoLists[0].entity_id; + } + } + this._fetchData(); + } } public hassSubscribe(): Promise[] { - this._fetchData(); return [ this.hass!.connection.subscribeEvents( () => this._fetchData(), @@ -94,7 +122,7 @@ class HuiShoppingListCard const oldHass = changedProps.get("hass") as HomeAssistant | undefined; const oldConfig = changedProps.get("_config") as - | SensorCardConfig + | ShoppingListCardConfig | undefined; if ( @@ -106,7 +134,7 @@ class HuiShoppingListCard } protected render() { - if (!this._config || !this.hass) { + if (!this._config || !this.hass || !this._entityId) { return nothing; } @@ -118,31 +146,39 @@ class HuiShoppingListCard })} >
- - - - - + ${this.todoListSupportsFeature(TodoListEntityFeature.CREATE_TODO_ITEM) + ? html` + + + + ` + : nothing} + ${this.todoListSupportsFeature(TodoListEntityFeature.MOVE_TODO_ITEM) + ? html` + + + ` + : nothing}
${this._reordering ? html` @@ -166,32 +202,43 @@ class HuiShoppingListCard "ui.panel.lovelace.cards.shopping-list.checked_items" )} - - + ${this.todoListSupportsFeature( + TodoListEntityFeature.DELETE_TODO_ITEM + ) + ? html` + ` + : nothing} ${repeat( this._checkedItems!, - (item) => item.id, + (item) => item.uid, (item) => html`
- + ${this.todoListSupportsFeature( + TodoListEntityFeature.UPDATE_TODO_ITEM + ) + ? html` ` + : nothing}
@@ -203,23 +250,30 @@ class HuiShoppingListCard `; } - private _renderItems(items: ShoppingListItem[]) { + private _renderItems(items: TodoItem[]) { return html` ${repeat( items, - (item) => item.id, + (item) => item.uid, (item) => html` -
- +
+ ${this.todoListSupportsFeature( + TodoListEntityFeature.UPDATE_TODO_ITEM + ) + ? html` ` + : nothing} ${this._reordering @@ -240,47 +294,70 @@ class HuiShoppingListCard `; } + private todoListSupportsFeature(feature: number): boolean { + const entityStateObj = this.hass!.states[this._entityId!]; + return entityStateObj && supportsFeature(entityStateObj, feature); + } + private async _fetchData(): Promise { - if (!this.hass) { + if (!this.hass || !this._entityId) { return; } - const checkedItems: ShoppingListItem[] = []; - const uncheckedItems: ShoppingListItem[] = []; - const items = await fetchItems(this.hass); - for (const key in items) { - if (items[key].complete) { - checkedItems.push(items[key]); + const checkedItems: TodoItem[] = []; + const uncheckedItems: TodoItem[] = []; + const items = await fetchItems(this.hass!, this._entityId!); + const records: Record = {}; + items.forEach((item) => { + records[item.uid!] = item; + if (item.status === TodoItemStatus.Completed) { + checkedItems.push(item); } else { - uncheckedItems.push(items[key]); + uncheckedItems.push(item); } - } + }); + this._items = records; this._checkedItems = checkedItems; this._uncheckedItems = uncheckedItems; } private _completeItem(ev): void { - updateItem(this.hass!, ev.target.itemId, { - complete: ev.target.checked, - }).catch(() => this._fetchData()); + const item = this._items[ev.target.itemId]; + updateItem(this.hass!, this._entityId!, { + ...item, + status: ev.target.checked + ? TodoItemStatus.Completed + : TodoItemStatus.NeedsAction, + }).finally(() => this._fetchData()); } private _saveEdit(ev): void { // If name is not empty, update the item otherwise remove it if (ev.target.value) { - updateItem(this.hass!, ev.target.itemId, { - name: ev.target.value, - }).catch(() => this._fetchData()); - } else { - removeItem(this.hass!, ev.target.itemId).catch(() => this._fetchData()); + const item = this._items[ev.target.itemId]; + updateItem(this.hass!, this._entityId!, { + ...item, + summary: ev.target.value, + }).finally(() => this._fetchData()); + } else if ( + this.todoListSupportsFeature(TodoListEntityFeature.DELETE_TODO_ITEM) + ) { + deleteItem(this.hass!, this._entityId!, ev.target.itemId).finally(() => + this._fetchData() + ); } ev.target.blur(); } - private _clearItems(): void { - if (this.hass) { - clearItems(this.hass).catch(() => this._fetchData()); + private async _clearCompletedItems(): Promise { + if (!this.hass) { + return; } + const deleteActions: Array> = []; + this._checkedItems!.forEach((item: TodoItem) => { + deleteActions.push(deleteItem(this.hass!, this._entityId!, item.uid!)); + }); + await Promise.all(deleteActions).finally(() => this._fetchData()); } private get _newItem(): HaTextField { @@ -289,9 +366,10 @@ class HuiShoppingListCard private _addItem(ev): void { const newItem = this._newItem; - if (newItem.value!.length > 0) { - addItem(this.hass!, newItem.value!).catch(() => this._fetchData()); + createItem(this.hass!, this._entityId!, newItem.value!).finally(() => + this._fetchData() + ); } newItem.value = ""; @@ -332,9 +410,13 @@ class HuiShoppingListCard // Since this is `onEnd` event, it's possible that // an item wa dragged away and was put back to its original position. if (evt.oldIndex !== evt.newIndex) { - reorderItems(this.hass!, this._sortable!.toArray()).catch(() => - this._fetchData() - ); + const item = this._uncheckedItems![evt.oldIndex]; + moveItem( + this.hass!, + this._entityId!, + item.uid!, + evt.newIndex + ).finally(() => this._fetchData()); // Move the shopping list item in memory. this._uncheckedItems!.splice( evt.newIndex, @@ -419,6 +501,11 @@ class HuiShoppingListCard .clearall { cursor: pointer; } + + .todoList { + display: block; + padding: 8px; + } `; } } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index d1aae334080a..148bd327987e 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -430,6 +430,7 @@ export interface SensorCardConfig extends LovelaceCardConfig { export interface ShoppingListCardConfig extends LovelaceCardConfig { title?: string; theme?: string; + entity?: string; } export interface StackCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-shopping-list-editor.ts b/src/panels/lovelace/editor/config-elements/hui-shopping-list-editor.ts index ed0be3e4a89f..f20c1a7b62d4 100644 --- a/src/panels/lovelace/editor/config-elements/hui-shopping-list-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-shopping-list-editor.ts @@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-textfield"; import "../../../../components/ha-theme-picker"; import { HomeAssistant } from "../../../../types"; @@ -16,6 +17,7 @@ const cardConfigStruct = assign( object({ title: optional(string()), theme: optional(string()), + entity: optional(string()), }) ); @@ -67,6 +69,14 @@ export class HuiShoppingListEditor .configValue=${"title"} @input=${this._valueChanged} > + + - isComponentLoaded(this.hass, "conversation") - ); - - protected firstUpdated(changedProperties: PropertyValues): void { - super.firstUpdated(changedProperties); - - this._card = createCardElement({ type: "shopping-list" }) as LovelaceCard; - this._card.hass = this.hass; - } - - protected updated(changedProperties: PropertyValues): void { - super.updated(changedProperties); - - if (changedProperties.has("hass")) { - this._card.hass = this.hass; - } - } - - protected render(): TemplateResult { - return html` - - -
${this.hass.localize("panel.shopping_list")}
- ${this._conversation(this.hass.config.components) - ? html` - - ` - : ""} -
-
${this._card}
-
-
- `; - } - - private _showVoiceCommandDialog(): void { - showVoiceCommandDialog(this, this.hass, { pipeline_id: "last_used" }); - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - #columns { - display: flex; - flex-direction: row; - justify-content: center; - margin: 8px; - } - .column { - flex: 1 0 0; - max-width: 500px; - min-width: 0; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-panel-shopping-list": PanelShoppingList; - } -} diff --git a/src/panels/todo/ha-panel-todo.ts b/src/panels/todo/ha-panel-todo.ts new file mode 100644 index 000000000000..5e79de67f5ba --- /dev/null +++ b/src/panels/todo/ha-panel-todo.ts @@ -0,0 +1,257 @@ +import { ResizeController } from "@lit-labs/observers/resize-controller"; +import "@material/mwc-list"; +import { mdiChevronDown, mdiDotsVertical, mdiMicrophone } from "@mdi/js"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + TemplateResult, + css, + html, + nothing, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { isComponentLoaded } from "../../common/config/is_component_loaded"; +import { storage } from "../../common/decorators/storage"; +import { computeDomain } from "../../common/entity/compute_domain"; +import { computeStateName } from "../../common/entity/compute_state_name"; +import "../../components/ha-button"; +import "../../components/ha-icon-button"; +import "../../components/ha-list-item"; +import "../../components/ha-menu-button"; +import "../../components/ha-state-icon"; +import "../../components/ha-svg-icon"; +import "../../components/ha-two-pane-top-app-bar-fixed"; +import { getTodoLists } from "../../data/todo"; +import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; +import { haStyle } from "../../resources/styles"; +import { HomeAssistant } from "../../types"; +import { HuiErrorCard } from "../lovelace/cards/hui-error-card"; +import { createCardElement } from "../lovelace/create-element/create-card-element"; +import { LovelaceCard } from "../lovelace/types"; + +@customElement("ha-panel-todo") +class PanelTodo extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean, reflect: true }) public narrow!: boolean; + + @property({ type: Boolean, reflect: true }) public mobile = false; + + @state() private _card?: LovelaceCard | HuiErrorCard; + + @storage({ + key: "selectedTodoEntity", + state: true, + }) + private _entityId?: string; + + private _headerHeight = 56; + + private _showPaneController = new ResizeController(this, { + callback: (entries: ResizeObserverEntry[]) => + entries[0]?.contentRect.width > 750, + }); + + private _mql?: MediaQueryList; + + private _conversation = memoizeOne((_components) => + isComponentLoaded(this.hass, "conversation") + ); + + public connectedCallback() { + super.connectedCallback(); + this._mql = window.matchMedia( + "(max-width: 450px), all and (max-height: 500px)" + ); + this._mql.addListener(this._setIsMobile); + this.mobile = this._mql.matches; + const computedStyles = getComputedStyle(this); + this._headerHeight = Number( + computedStyles.getPropertyValue("--header-height").replace("px", "") + ); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._mql?.removeListener(this._setIsMobile!); + this._mql = undefined; + } + + private _setIsMobile = (ev: MediaQueryListEvent) => { + this.mobile = ev.matches; + }; + + protected willUpdate(changedProperties: PropertyValues): void { + super.willUpdate(changedProperties); + + if (!this.hasUpdated && !this._entityId) { + this._entityId = Object.keys(this.hass.states).find( + (entityId) => computeDomain(entityId) === "todo" + ); + } else if (!this.hasUpdated) { + this._createCard(); + } + } + + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + + if (changedProperties.has("_entityId")) { + this._createCard(); + } + + if (changedProperties.has("hass") && this._card) { + this._card.hass = this.hass; + } + } + + private _createCard(): void { + if (!this._entityId) { + this._card = undefined; + return; + } + this._card = createCardElement({ + type: "shopping-list", + entity: this._entityId, + }) as LovelaceCard; + this._card.hass = this.hass; + } + + protected render(): TemplateResult { + const showPane = this._showPaneController.value ?? !this.narrow; + const listItems = getTodoLists(this.hass).map( + (list) => + html` + ${list.name} + ` + ); + return html` + + +
+ ${!showPane + ? html` + + ${this._entityId + ? computeStateName(this.hass.states[this._entityId]) + : ""} + + + ${listItems} +
  • +
    ` + : "Lists"} +
    + ${listItems} + + + ${this._conversation(this.hass.config.components) + ? html` + + + ${this.hass.localize("ui.panel.todo.start_conversation")} + ` + : nothing} + +
    +
    ${this._card}
    +
    +
    + `; + } + + private _handleEntityPicked(ev) { + this._entityId = ev.currentTarget.entityId; + } + + private _showVoiceCommandDialog(): void { + showVoiceCommandDialog(this, this.hass, { pipeline_id: "last_used" }); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + :host { + display: block; + } + #columns { + display: flex; + flex-direction: row; + justify-content: center; + margin: 8px; + } + .column { + flex: 1 0 0; + max-width: 500px; + min-width: 0; + } + :host([mobile]) .lists { + --mdc-menu-min-width: 100vw; + } + :host([mobile]) ha-button-menu { + --mdc-shape-medium: 0 0 var(--mdc-shape-medium) + var(--mdc-shape-medium); + } + ha-button-menu ha-button { + --mdc-theme-primary: currentColor; + --mdc-typography-button-text-transform: none; + --mdc-typography-button-font-size: var( + --mdc-typography-headline6-font-size, + 1.25rem + ); + --mdc-typography-button-font-weight: var( + --mdc-typography-headline6-font-weight, + 500 + ); + --mdc-typography-button-letter-spacing: var( + --mdc-typography-headline6-letter-spacing, + 0.0125em + ); + --mdc-typography-button-line-height: var( + --mdc-typography-headline6-line-height, + 2rem + ); + --button-height: 40px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-panel-todo": PanelTodo; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index d48e089221ac..fa28374e3354 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -8,7 +8,7 @@ "logbook": "Logbook", "history": "History", "mailbox": "Mailbox", - "shopping_list": "Shopping list", + "todo": "To-do Lists", "developer_tools": "Developer tools", "media_browser": "Media", "profile": "Profile" @@ -4467,6 +4467,7 @@ "never_triggered": "Never triggered" }, "shopping-list": { + "lists": "To-do Lists", "checked_items": "Checked items", "clear_items": "Clear checked items", "add_item": "Add item", @@ -5444,7 +5445,7 @@ "qr_code_image": "QR code for token {name}" } }, - "shopping_list": { + "todo": { "start_conversation": "Start conversation" }, "page-authorize": { diff --git a/yarn.lock b/yarn.lock index 93a6c35c13f3..4019a1725497 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2106,6 +2106,15 @@ __metadata: languageName: node linkType: hard +"@lit-labs/observers@npm:2.0.1": + version: 2.0.1 + resolution: "@lit-labs/observers@npm:2.0.1" + dependencies: + "@lit/reactive-element": ^2.0.0 + checksum: 6c4518ee37678d86b263799590edd5c202a9f6800b52b52d2a0fc47e572d029c587fa9722108e449ab6bc7b1b1aee9e780b3e42ca2e91d63a43fc96541a80f98 + languageName: node + linkType: hard + "@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0": version: 1.1.1 resolution: "@lit-labs/ssr-dom-shim@npm:1.1.1" @@ -2113,6 +2122,13 @@ __metadata: languageName: node linkType: hard +"@lit-labs/ssr-dom-shim@npm:^1.1.2-pre.0": + version: 1.1.2 + resolution: "@lit-labs/ssr-dom-shim@npm:1.1.2" + checksum: 73fd787893851d4ec4aaa5c775405ed2aae4ca0891b2dd3c973b32c2f4bf70ada5481dd0224e52b786d037aa8a00052186ad1623c44551affd66f6409cca8da6 + languageName: node + linkType: hard + "@lit-labs/virtualizer@npm:2.0.7": version: 2.0.7 resolution: "@lit-labs/virtualizer@npm:2.0.7" @@ -2132,6 +2148,15 @@ __metadata: languageName: node linkType: hard +"@lit/reactive-element@npm:^2.0.0": + version: 2.0.0 + resolution: "@lit/reactive-element@npm:2.0.0" + dependencies: + "@lit-labs/ssr-dom-shim": ^1.1.2-pre.0 + checksum: afa12f1cf72e8735cb7eaa51d428610785ee796882ca52108310e75ac54bbf5690da718c8bf85d042060f98c139ff0d5efd54f677a9d3fc4d794ad2e0f7a12c5 + languageName: node + linkType: hard + "@lokalise/node-api@npm:12.0.0": version: 12.0.0 resolution: "@lokalise/node-api@npm:12.0.0" @@ -9626,6 +9651,7 @@ __metadata: "@lezer/highlight": 1.1.6 "@lit-labs/context": 0.4.1 "@lit-labs/motion": 1.0.4 + "@lit-labs/observers": 2.0.1 "@lit-labs/virtualizer": 2.0.7 "@lokalise/node-api": 12.0.0 "@lrnwebcomponents/simple-tooltip": 7.0.18