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:
Ian Richardson 2018-11-06 03:47:24 -06:00 committed by Paulus Schoutsen
parent 935639e5e0
commit 6432207bf1
5 changed files with 256 additions and 0 deletions

View File

@ -29,6 +29,12 @@ export default (elements, { initialStates = {} } = {}) => {
resources: demoResources,
states: initialStates,
themes: {},
connection: {
subscribeEvents: async (callback, event) => {
console.log("subscribeEvents", event);
return () => console.log("unsubscribeEvents", event);
},
},
// Mock properties
mockEntities: entities,

View 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
View 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,
});

View 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);

View File

@ -21,6 +21,7 @@ import "../cards/hui-picture-glance-card";
import "../cards/hui-plant-status-card";
import "../cards/hui-sensor-card";
import "../cards/hui-vertical-stack-card.ts";
import "../cards/hui-shopping-list-card";
import "../cards/hui-thermostat-card.ts";
import "../cards/hui-weather-forecast-card";
import "../cards/hui-gauge-card";
@ -49,6 +50,7 @@ const CARD_TYPES = new Set([
"picture-glance",
"plant-status",
"sensor",
"shopping-list",
"thermostat",
"vertical-stack",
"weather-forecast",