Update todo list card (#18396)

This commit is contained in:
Bram Kragten 2023-10-25 13:57:50 +02:00 committed by GitHub
parent 8d2ec8098c
commit 0c2531a7ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 59 deletions

View File

@ -302,6 +302,7 @@ export abstract class TopAppBarBaseBase extends BaseElement {
} }
.pane .footer { .pane .footer {
border-top: 1px solid var(--divider-color); border-top: 1px solid var(--divider-color);
padding-bottom: 8px;
} }
.main { .main {
min-height: 100%; min-height: 100%;

View File

@ -11,8 +11,9 @@ import {
} from "lit"; } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { guard } from "lit/directives/guard";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import type { SortableEvent } from "sortablejs";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-card"; import "../../../components/ha-card";
@ -75,17 +76,11 @@ export class HuiTodoListCard
@state() private _items?: Record<string, TodoItem>; @state() private _items?: Record<string, TodoItem>;
@state() private _uncheckedItems?: TodoItem[];
@state() private _checkedItems?: TodoItem[];
@state() private _reordering = false; @state() private _reordering = false;
@state() private _renderEmptySortable = false;
private _sortable?: SortableInstance; private _sortable?: SortableInstance;
@query("#sortable") private _sortableEl?: HTMLElement; @query("#unchecked") private _uncheckedContainer?: HTMLElement;
public getCardSize(): number { public getCardSize(): number {
return (this._config ? (this._config.title ? 2 : 0) : 0) + 3; return (this._config ? (this._config.title ? 2 : 0) : 0) + 3;
@ -109,6 +104,24 @@ export class HuiTodoListCard
return undefined; return undefined;
} }
private _getCheckedItems = memoizeOne(
(items?: Record<string, TodoItem>): TodoItem[] =>
items
? Object.values(items).filter(
(item) => item.status === TodoItemStatus.Completed
)
: []
);
private _getUncheckedItems = memoizeOne(
(items?: Record<string, TodoItem>): TodoItem[] =>
items
? Object.values(items).filter(
(item) => item.status === TodoItemStatus.NeedsAction
)
: []
);
public willUpdate( public willUpdate(
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown> changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
): void { ): void {
@ -118,8 +131,7 @@ export class HuiTodoListCard
} }
this._fetchData(); this._fetchData();
} else if (changedProperties.has("_entityId") || !this._items) { } else if (changedProperties.has("_entityId") || !this._items) {
this._uncheckedItems = []; this._items = undefined;
this._checkedItems = [];
this._fetchData(); this._fetchData();
} }
} }
@ -157,6 +169,9 @@ export class HuiTodoListCard
return nothing; return nothing;
} }
const checkedItems = this._getCheckedItems(this._items);
const uncheckedItems = this._getUncheckedItems(this._items);
return html` return html`
<ha-card <ha-card
.header=${this._config.title} .header=${this._config.title}
@ -199,20 +214,8 @@ export class HuiTodoListCard
` `
: nothing} : nothing}
</div> </div>
${this._reordering <div id="unchecked">${this._renderItems(uncheckedItems)}</div>
? html` ${checkedItems.length
<div id="sortable">
${guard(
[this._uncheckedItems, this._renderEmptySortable],
() =>
this._renderEmptySortable
? ""
: this._renderItems(this._uncheckedItems!)
)}
</div>
`
: this._renderItems(this._uncheckedItems!)}
${this._checkedItems!.length > 0
? html` ? html`
<div class="divider"></div> <div class="divider"></div>
<div class="checked"> <div class="checked">
@ -224,7 +227,7 @@ export class HuiTodoListCard
${this.todoListSupportsFeature( ${this.todoListSupportsFeature(
TodoListEntityFeature.DELETE_TODO_ITEM TodoListEntityFeature.DELETE_TODO_ITEM
) )
? html` <ha-svg-icon ? html`<ha-svg-icon
class="clearall" class="clearall"
tabindex="0" tabindex="0"
.path=${mdiNotificationClearAll} .path=${mdiNotificationClearAll}
@ -237,7 +240,7 @@ export class HuiTodoListCard
: nothing} : nothing}
</div> </div>
${repeat( ${repeat(
this._checkedItems!, checkedItems,
(item) => item.uid, (item) => item.uid,
(item) => html` (item) => html`
<div class="editRow"> <div class="editRow">
@ -322,21 +325,12 @@ export class HuiTodoListCard
if (!this.hass || !this._entityId) { if (!this.hass || !this._entityId) {
return; return;
} }
const checkedItems: TodoItem[] = [];
const uncheckedItems: TodoItem[] = [];
const items = await fetchItems(this.hass!, this._entityId!); const items = await fetchItems(this.hass!, this._entityId!);
const records: Record<string, TodoItem> = {}; const records: Record<string, TodoItem> = {};
items.forEach((item) => { items.forEach((item) => {
records[item.uid!] = item; records[item.uid!] = item;
if (item.status === TodoItemStatus.Completed) {
checkedItems.push(item);
} else {
uncheckedItems.push(item);
}
}); });
this._items = records; this._items = records;
this._checkedItems = checkedItems;
this._uncheckedItems = uncheckedItems;
} }
private _completeItem(ev): void { private _completeItem(ev): void {
@ -373,7 +367,7 @@ export class HuiTodoListCard
return; return;
} }
const deleteActions: Array<Promise<any>> = []; const deleteActions: Array<Promise<any>> = [];
this._checkedItems!.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());
@ -416,43 +410,41 @@ export class HuiTodoListCard
private async _createSortable() { private async _createSortable() {
const Sortable = (await import("../../../resources/sortable")).default; const Sortable = (await import("../../../resources/sortable")).default;
const sortableEl = this._sortableEl; this._sortable = new Sortable(this._uncheckedContainer!, {
this._sortable = new Sortable(sortableEl!, {
animation: 150, animation: 150,
fallbackClass: "sortable-fallback", fallbackClass: "sortable-fallback",
dataIdAttr: "item-id", dataIdAttr: "item-id",
handle: "ha-svg-icon", handle: "ha-svg-icon",
onEnd: async (evt) => { onChoose: (evt: SortableEvent) => {
(evt.item as any).placeholder =
document.createComment("sort-placeholder");
evt.item.after((evt.item as any).placeholder);
},
onEnd: (evt: SortableEvent) => {
// put back in original location
if ((evt.item as any).placeholder) {
(evt.item as any).placeholder.replaceWith(evt.item);
delete (evt.item as any).placeholder;
}
if (evt.newIndex === undefined || evt.oldIndex === undefined) { if (evt.newIndex === undefined || evt.oldIndex === undefined) {
return; return;
} }
// Since this is `onEnd` event, it's possible that // Since this is `onEnd` event, it's possible that
// an item wa dragged away and was put back to its original position. // an item was dragged away and was put back to its original position.
if (evt.oldIndex !== evt.newIndex) { if (evt.oldIndex !== evt.newIndex) {
const item = this._uncheckedItems![evt.oldIndex]; this._moveItem(evt.oldIndex, evt.newIndex);
moveItem(
this.hass!,
this._entityId!,
item.uid!,
evt.newIndex
).finally(() => this._fetchData());
// Move the shopping list item in memory.
this._uncheckedItems!.splice(
evt.newIndex,
0,
this._uncheckedItems!.splice(evt.oldIndex, 1)[0]
);
} }
this._renderEmptySortable = true;
await this.updateComplete;
while (sortableEl?.lastElementChild) {
sortableEl.removeChild(sortableEl.lastElementChild);
}
this._renderEmptySortable = false;
}, },
}); });
} }
private async _moveItem(oldIndex, newIndex) {
const item = this._getUncheckedItems(this._items)[oldIndex];
await moveItem(this.hass!, this._entityId!, item.uid!, newIndex).finally(
() => this._fetchData()
);
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
ha-card { ha-card {