Skip to content

Commit

Permalink
Update todo list card
Browse files Browse the repository at this point in the history
  • Loading branch information
bramkragten committed Oct 25, 2023
1 parent d2caed2 commit 55d9dc9
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 59 deletions.
1 change: 1 addition & 0 deletions src/components/ha-two-pane-top-app-bar-fixed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ export abstract class TopAppBarBaseBase extends BaseElement {
}
.pane .footer {
border-top: 1px solid var(--divider-color);
padding-bottom: 8px;
}
.main {
min-height: 100%;
Expand Down
110 changes: 51 additions & 59 deletions src/panels/lovelace/cards/hui-todo-list-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { guard } from "lit/directives/guard";
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 { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-card";
Expand Down Expand Up @@ -75,17 +76,11 @@ export class HuiTodoListCard

@state() private _items?: Record<string, TodoItem>;

@state() private _uncheckedItems?: TodoItem[];

@state() private _checkedItems?: TodoItem[];

@state() private _reordering = false;

@state() private _renderEmptySortable = false;

private _sortable?: SortableInstance;

@query("#sortable") private _sortableEl?: HTMLElement;
@query("#unchecked") private _uncheckedContainer?: HTMLElement;

public getCardSize(): number {
return (this._config ? (this._config.title ? 2 : 0) : 0) + 3;
Expand All @@ -109,6 +104,24 @@ export class HuiTodoListCard
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(
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
): void {
Expand All @@ -118,8 +131,7 @@ export class HuiTodoListCard
}
this._fetchData();
} else if (changedProperties.has("_entityId") || !this._items) {
this._uncheckedItems = [];
this._checkedItems = [];
this._items = undefined;
this._fetchData();
}
}
Expand Down Expand Up @@ -157,6 +169,9 @@ export class HuiTodoListCard
return nothing;
}

const checkedItems = this._getCheckedItems(this._items);
const uncheckedItems = this._getUncheckedItems(this._items);

return html`
<ha-card
.header=${this._config.title}
Expand Down Expand Up @@ -199,20 +214,8 @@ export class HuiTodoListCard
`
: nothing}
</div>
${this._reordering
? html`
<div id="sortable">
${guard(
[this._uncheckedItems, this._renderEmptySortable],
() =>
this._renderEmptySortable
? ""
: this._renderItems(this._uncheckedItems!)
)}
</div>
`
: this._renderItems(this._uncheckedItems!)}
${this._checkedItems!.length > 0
<div id="unchecked">${this._renderItems(uncheckedItems)}</div>
${checkedItems.length
? html`
<div class="divider"></div>
<div class="checked">
Expand All @@ -224,7 +227,7 @@ export class HuiTodoListCard
${this.todoListSupportsFeature(
TodoListEntityFeature.DELETE_TODO_ITEM
)
? html` <ha-svg-icon
? html`<ha-svg-icon
class="clearall"
tabindex="0"
.path=${mdiNotificationClearAll}
Expand All @@ -237,7 +240,7 @@ export class HuiTodoListCard
: nothing}
</div>
${repeat(
this._checkedItems!,
checkedItems,
(item) => item.uid,
(item) => html`
<div class="editRow">
Expand Down Expand Up @@ -322,21 +325,12 @@ export class HuiTodoListCard
if (!this.hass || !this._entityId) {
return;
}
const checkedItems: TodoItem[] = [];
const uncheckedItems: TodoItem[] = [];
const items = await fetchItems(this.hass!, this._entityId!);
const records: Record<string, TodoItem> = {};
items.forEach((item) => {
records[item.uid!] = item;
if (item.status === TodoItemStatus.Completed) {
checkedItems.push(item);
} else {
uncheckedItems.push(item);
}
});
this._items = records;
this._checkedItems = checkedItems;
this._uncheckedItems = uncheckedItems;
}

private _completeItem(ev): void {
Expand Down Expand Up @@ -373,7 +367,7 @@ export class HuiTodoListCard
return;
}
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!));
});
await Promise.all(deleteActions).finally(() => this._fetchData());
Expand Down Expand Up @@ -416,43 +410,41 @@ export class HuiTodoListCard

private async _createSortable() {
const Sortable = (await import("../../../resources/sortable")).default;
const sortableEl = this._sortableEl;
this._sortable = new Sortable(sortableEl!, {
this._sortable = new Sortable(this._uncheckedContainer!, {
animation: 150,
fallbackClass: "sortable-fallback",
dataIdAttr: "item-id",
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) {
return;
}
// 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) {
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,
0,
this._uncheckedItems!.splice(evt.oldIndex, 1)[0]
);
}
this._renderEmptySortable = true;
await this.updateComplete;
while (sortableEl?.lastElementChild) {
sortableEl.removeChild(sortableEl.lastElementChild);
this._moveItem(evt.oldIndex, evt.newIndex);
}
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 {
return css`
ha-card {
Expand Down

0 comments on commit 55d9dc9

Please sign in to comment.