mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-20 07:46:37 +00:00
New Card: Shopping List (#1970)
* New Card: Shopping List Following features: - Add item - Edit item - Complete item - Clear items * Address Travis complaint * Addressed review comments * Update translation variable name * Line up input row text * Taking MVP to heart Addressed review comments and scaled this back to just get a simple shopping list card out there and we can discuss/debate how best to add the additional pieces with smaller PRs * Remove calling connected in set hass
This commit is contained in:
parent
935639e5e0
commit
6432207bf1
@ -29,6 +29,12 @@ export default (elements, { initialStates = {} } = {}) => {
|
|||||||
resources: demoResources,
|
resources: demoResources,
|
||||||
states: initialStates,
|
states: initialStates,
|
||||||
themes: {},
|
themes: {},
|
||||||
|
connection: {
|
||||||
|
subscribeEvents: async (callback, event) => {
|
||||||
|
console.log("subscribeEvents", event);
|
||||||
|
return () => console.log("unsubscribeEvents", event);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Mock properties
|
// Mock properties
|
||||||
mockEntities: entities,
|
mockEntities: entities,
|
||||||
|
55
gallery/src/demos/demo-hui-shopping-list-card.js
Normal file
55
gallery/src/demos/demo-hui-shopping-list-card.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
|
import provideHass from "../data/provide_hass";
|
||||||
|
import "../components/demo-cards";
|
||||||
|
|
||||||
|
const CONFIGS = [
|
||||||
|
{
|
||||||
|
heading: "List example",
|
||||||
|
config: `
|
||||||
|
- type: shopping-list
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "List with title example",
|
||||||
|
config: `
|
||||||
|
- type: shopping-list
|
||||||
|
title: Shopping List
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
class DemoShoppingListEntity extends PolymerElement {
|
||||||
|
static get template() {
|
||||||
|
return html`
|
||||||
|
<demo-cards
|
||||||
|
id='demos'
|
||||||
|
configs="[[_configs]]"
|
||||||
|
></demo-cards>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
_configs: {
|
||||||
|
type: Object,
|
||||||
|
value: CONFIGS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ready() {
|
||||||
|
super.ready();
|
||||||
|
const hass = provideHass(this.$.demos);
|
||||||
|
|
||||||
|
hass.mockAPI("shopping_list", () => [
|
||||||
|
{ name: "list", id: 1, complete: false },
|
||||||
|
{ name: "all", id: 2, complete: false },
|
||||||
|
{ name: "the", id: 3, complete: false },
|
||||||
|
{ name: "things", id: 4, complete: true },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("demo-hui-shopping-list-card", DemoShoppingListEntity);
|
28
src/data/shopping-list.ts
Normal file
28
src/data/shopping-list.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface ShoppingListItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
complete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchItems = (hass: HomeAssistant): Promise<ShoppingListItem[]> =>
|
||||||
|
hass.callApi("GET", "shopping_list");
|
||||||
|
|
||||||
|
export const saveEdit = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
itemId: number,
|
||||||
|
name: string
|
||||||
|
): Promise<ShoppingListItem> =>
|
||||||
|
hass.callApi("POST", "shopping_list/item/" + itemId, {
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const completeItem = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
itemId: number,
|
||||||
|
complete: boolean
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callApi("POST", "shopping_list/item/" + itemId, {
|
||||||
|
complete,
|
||||||
|
});
|
165
src/panels/lovelace/cards/hui-shopping-list-card.ts
Normal file
165
src/panels/lovelace/cards/hui-shopping-list-card.ts
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { html, LitElement } from "@polymer/lit-element";
|
||||||
|
import { repeat } from "lit-html/directives/repeat";
|
||||||
|
import { TemplateResult } from "lit-html";
|
||||||
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
|
||||||
|
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { LovelaceCard, LovelaceConfig } from "../types";
|
||||||
|
import {
|
||||||
|
fetchItems,
|
||||||
|
completeItem,
|
||||||
|
saveEdit,
|
||||||
|
ShoppingListItem,
|
||||||
|
} from "../../../data/shopping-list";
|
||||||
|
|
||||||
|
interface Config extends LovelaceConfig {
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HuiShoppingListCard extends hassLocalizeLitMixin(LitElement)
|
||||||
|
implements LovelaceCard {
|
||||||
|
private _hass?: HomeAssistant;
|
||||||
|
private _config?: Config;
|
||||||
|
private _items?: ShoppingListItem[];
|
||||||
|
private _unsubEvents?: Promise<() => Promise<void>>;
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
_config: {},
|
||||||
|
_items: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass: HomeAssistant) {
|
||||||
|
this._hass = hass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCardSize(): number {
|
||||||
|
return (
|
||||||
|
(this._config ? (this._config.title ? 1 : 0) : 0) +
|
||||||
|
(this._items ? this._items.length : 3)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config: Config): void {
|
||||||
|
this._config = config;
|
||||||
|
this._items = [];
|
||||||
|
this._fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
if (this._hass) {
|
||||||
|
this._unsubEvents = this._hass.connection.subscribeEvents(
|
||||||
|
() => this._fetchData(),
|
||||||
|
"shopping_list_updated"
|
||||||
|
);
|
||||||
|
this._fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
|
||||||
|
if (this._unsubEvents) {
|
||||||
|
this._unsubEvents.then((unsub) => unsub());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._config || !this._hass) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${this.renderStyle()}
|
||||||
|
<ha-card .header="${this._config.title}">
|
||||||
|
${repeat(
|
||||||
|
this._items!,
|
||||||
|
(item) => item.id,
|
||||||
|
(item, index) =>
|
||||||
|
html`
|
||||||
|
<div class="editRow">
|
||||||
|
<paper-checkbox
|
||||||
|
slot="item-icon"
|
||||||
|
id=${index}
|
||||||
|
?checked=${item.complete}
|
||||||
|
.itemId=${item.id}
|
||||||
|
@click=${this._completeItem}
|
||||||
|
tabindex='0'
|
||||||
|
></paper-checkbox>
|
||||||
|
<paper-item-body>
|
||||||
|
<paper-input
|
||||||
|
no-label-float
|
||||||
|
.value='${item.name}'
|
||||||
|
.itemId=${item.id}
|
||||||
|
@change=${this._saveEdit}
|
||||||
|
></paper-input>
|
||||||
|
</paper-item-body>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderStyle(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
.editRow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
paper-checkbox {
|
||||||
|
padding: 11px 11px 11px 18px;
|
||||||
|
}
|
||||||
|
paper-input {
|
||||||
|
--paper-input-container-underline: {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
--paper-input-container-underline-focus: {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
--paper-input-container-underline-disabled: {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchData(): Promise<void> {
|
||||||
|
if (this._hass) {
|
||||||
|
this._items = await fetchItems(this._hass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _completeItem(ev): void {
|
||||||
|
completeItem(this._hass!, ev.target.itemId, ev.target.checked).catch(() =>
|
||||||
|
this._fetchData()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _saveEdit(ev): void {
|
||||||
|
saveEdit(this._hass!, ev.target.itemId, ev.target.value).catch(() =>
|
||||||
|
this._fetchData()
|
||||||
|
);
|
||||||
|
|
||||||
|
ev.target.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-shopping-list-card": HuiShoppingListCard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("hui-shopping-list-card", HuiShoppingListCard);
|
@ -21,6 +21,7 @@ import "../cards/hui-picture-glance-card";
|
|||||||
import "../cards/hui-plant-status-card";
|
import "../cards/hui-plant-status-card";
|
||||||
import "../cards/hui-sensor-card";
|
import "../cards/hui-sensor-card";
|
||||||
import "../cards/hui-vertical-stack-card.ts";
|
import "../cards/hui-vertical-stack-card.ts";
|
||||||
|
import "../cards/hui-shopping-list-card";
|
||||||
import "../cards/hui-thermostat-card.ts";
|
import "../cards/hui-thermostat-card.ts";
|
||||||
import "../cards/hui-weather-forecast-card";
|
import "../cards/hui-weather-forecast-card";
|
||||||
import "../cards/hui-gauge-card";
|
import "../cards/hui-gauge-card";
|
||||||
@ -49,6 +50,7 @@ const CARD_TYPES = new Set([
|
|||||||
"picture-glance",
|
"picture-glance",
|
||||||
"plant-status",
|
"plant-status",
|
||||||
"sensor",
|
"sensor",
|
||||||
|
"shopping-list",
|
||||||
"thermostat",
|
"thermostat",
|
||||||
"vertical-stack",
|
"vertical-stack",
|
||||||
"weather-forecast",
|
"weather-forecast",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user