mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-15 21:36:36 +00:00
Added Drag & Drop Reordering to Shopping List Card. (#7296)
This commit is contained in:
parent
2fdc746392
commit
5e2ee1a16c
@ -38,3 +38,12 @@ export const addItem = (
|
|||||||
type: "shopping_list/items/add",
|
type: "shopping_list/items/add",
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const reorderItems = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
itemIds: [string]
|
||||||
|
): Promise<ShoppingListItem> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "shopping_list/items/reorder",
|
||||||
|
item_ids: itemIds,
|
||||||
|
});
|
||||||
|
@ -11,9 +11,12 @@ import {
|
|||||||
property,
|
property,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
query,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import { repeat } from "lit-html/directives/repeat";
|
import { repeat } from "lit-html/directives/repeat";
|
||||||
|
import { guard } from "lit-html/directives/guard";
|
||||||
|
import { mdiDrag, mdiSort, mdiPlus, mdiNotificationClearAll } from "@mdi/js";
|
||||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
@ -23,12 +26,15 @@ import {
|
|||||||
fetchItems,
|
fetchItems,
|
||||||
ShoppingListItem,
|
ShoppingListItem,
|
||||||
updateItem,
|
updateItem,
|
||||||
|
reorderItems,
|
||||||
} from "../../../data/shopping-list";
|
} from "../../../data/shopping-list";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
import { SensorCardConfig, ShoppingListCardConfig } from "./types";
|
import { SensorCardConfig, ShoppingListCardConfig } from "./types";
|
||||||
|
|
||||||
|
let Sortable;
|
||||||
|
|
||||||
@customElement("hui-shopping-list-card")
|
@customElement("hui-shopping-list-card")
|
||||||
class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||||
implements LovelaceCard {
|
implements LovelaceCard {
|
||||||
@ -49,6 +55,14 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
|||||||
|
|
||||||
@internalProperty() private _checkedItems?: ShoppingListItem[];
|
@internalProperty() private _checkedItems?: ShoppingListItem[];
|
||||||
|
|
||||||
|
@internalProperty() private _reordering = false;
|
||||||
|
|
||||||
|
@internalProperty() private _renderEmptySortable = false;
|
||||||
|
|
||||||
|
private _sortable?;
|
||||||
|
|
||||||
|
@query("#sortable") private _sortableEl?: 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;
|
||||||
}
|
}
|
||||||
@ -101,15 +115,15 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div class="addRow">
|
<div class="addRow">
|
||||||
<ha-icon
|
<ha-svg-icon
|
||||||
class="addButton"
|
class="addButton"
|
||||||
icon="hass:plus"
|
.path=${mdiPlus}
|
||||||
.title=${this.hass!.localize(
|
.title=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.cards.shopping-list.add_item"
|
"ui.panel.lovelace.cards.shopping-list.add_item"
|
||||||
)}
|
)}
|
||||||
@click=${this._addItem}
|
@click=${this._addItem}
|
||||||
>
|
>
|
||||||
</ha-icon>
|
</ha-svg-icon>
|
||||||
<paper-input
|
<paper-input
|
||||||
no-label-float
|
no-label-float
|
||||||
class="addBox"
|
class="addBox"
|
||||||
@ -118,28 +132,27 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
|||||||
)}
|
)}
|
||||||
@keydown=${this._addKeyPress}
|
@keydown=${this._addKeyPress}
|
||||||
></paper-input>
|
></paper-input>
|
||||||
|
<ha-svg-icon
|
||||||
|
class="reorderButton"
|
||||||
|
.path=${mdiSort}
|
||||||
|
.title=${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.cards.shopping-list.reorder_items"
|
||||||
|
)}
|
||||||
|
@click=${this._toggleReorder}
|
||||||
|
>
|
||||||
|
</ha-svg-icon>
|
||||||
</div>
|
</div>
|
||||||
${repeat(
|
${this._reordering
|
||||||
this._uncheckedItems!,
|
? html`
|
||||||
(item) => item.id,
|
<div id="sortable">
|
||||||
(item) =>
|
${guard([this._uncheckedItems, this._renderEmptySortable], () =>
|
||||||
html`
|
this._renderEmptySortable
|
||||||
<div class="editRow">
|
? ""
|
||||||
<paper-checkbox
|
: this._renderItems(this._uncheckedItems!)
|
||||||
tabindex="0"
|
)}
|
||||||
?checked=${item.complete}
|
|
||||||
.itemId=${item.id}
|
|
||||||
@click=${this._completeItem}
|
|
||||||
></paper-checkbox>
|
|
||||||
<paper-input
|
|
||||||
no-label-float
|
|
||||||
.value=${item.name}
|
|
||||||
.itemId=${item.id}
|
|
||||||
@change=${this._saveEdit}
|
|
||||||
></paper-input>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
)}
|
: this._renderItems(this._uncheckedItems!)}
|
||||||
${this._checkedItems!.length > 0
|
${this._checkedItems!.length > 0
|
||||||
? html`
|
? html`
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
@ -149,16 +162,16 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
|||||||
"ui.panel.lovelace.cards.shopping-list.checked_items"
|
"ui.panel.lovelace.cards.shopping-list.checked_items"
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<ha-icon
|
<ha-svg-icon
|
||||||
class="clearall"
|
class="clearall"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
icon="hass:notification-clear-all"
|
.path=${mdiNotificationClearAll}
|
||||||
.title=${this.hass!.localize(
|
.title=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.cards.shopping-list.clear_items"
|
"ui.panel.lovelace.cards.shopping-list.clear_items"
|
||||||
)}
|
)}
|
||||||
@click=${this._clearItems}
|
@click=${this._clearItems}
|
||||||
>
|
>
|
||||||
</ha-icon>
|
</ha-svg-icon>
|
||||||
</div>
|
</div>
|
||||||
${repeat(
|
${repeat(
|
||||||
this._checkedItems!,
|
this._checkedItems!,
|
||||||
@ -187,6 +200,44 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderItems(items: ShoppingListItem[]) {
|
||||||
|
return html`
|
||||||
|
${repeat(
|
||||||
|
items,
|
||||||
|
(item) => item.id,
|
||||||
|
(item) =>
|
||||||
|
html`
|
||||||
|
<div class="editRow" item-id=${item.id}>
|
||||||
|
<paper-checkbox
|
||||||
|
tabindex="0"
|
||||||
|
?checked=${item.complete}
|
||||||
|
.itemId=${item.id}
|
||||||
|
@click=${this._completeItem}
|
||||||
|
></paper-checkbox>
|
||||||
|
<paper-input
|
||||||
|
no-label-float
|
||||||
|
.value=${item.name}
|
||||||
|
.itemId=${item.id}
|
||||||
|
@change=${this._saveEdit}
|
||||||
|
></paper-input>
|
||||||
|
${this._reordering
|
||||||
|
? html`
|
||||||
|
<ha-svg-icon
|
||||||
|
.title=${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.cards.shopping-list.drag_and_drop"
|
||||||
|
)}
|
||||||
|
class="reorderButton"
|
||||||
|
.path=${mdiDrag}
|
||||||
|
>
|
||||||
|
</ha-svg-icon>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
private async _fetchData(): Promise<void> {
|
private async _fetchData(): Promise<void> {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
@ -248,6 +299,54 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _toggleReorder() {
|
||||||
|
if (!Sortable) {
|
||||||
|
const sortableImport = await import(
|
||||||
|
"sortablejs/modular/sortable.core.esm"
|
||||||
|
);
|
||||||
|
Sortable = sortableImport.Sortable;
|
||||||
|
}
|
||||||
|
this._reordering = !this._reordering;
|
||||||
|
await this.updateComplete;
|
||||||
|
if (this._reordering) {
|
||||||
|
this._createSortable();
|
||||||
|
} else {
|
||||||
|
this._sortable?.destroy();
|
||||||
|
this._sortable = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createSortable() {
|
||||||
|
const sortableEl = this._sortableEl;
|
||||||
|
this._sortable = new Sortable(sortableEl, {
|
||||||
|
animation: 150,
|
||||||
|
fallbackClass: "sortable-fallback",
|
||||||
|
dataIdAttr: "item-id",
|
||||||
|
handle: "ha-svg-icon",
|
||||||
|
onEnd: async (evt) => {
|
||||||
|
// 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()
|
||||||
|
);
|
||||||
|
// 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;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
@ -278,6 +377,11 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reorderButton {
|
||||||
|
padding-left: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
paper-checkbox {
|
paper-checkbox {
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
@ -2401,7 +2401,9 @@
|
|||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"checked_items": "Checked items",
|
"checked_items": "Checked items",
|
||||||
"clear_items": "Clear checked items",
|
"clear_items": "Clear checked items",
|
||||||
"add_item": "Add item"
|
"add_item": "Add item",
|
||||||
|
"reorder_items": "Reorder items",
|
||||||
|
"drag_and_drop": "Drag and drop"
|
||||||
},
|
},
|
||||||
"picture-elements": {
|
"picture-elements": {
|
||||||
"hold": "Hold:",
|
"hold": "Hold:",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user