diff --git a/setup.py b/setup.py index be9a26d294..65ef259734 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20190215.0", + version="20190216.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 4cc5aaddf9..82cf0585ab 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -6,7 +6,6 @@ import { PropertyValues, property, } from "lit-element"; -import { classMap } from "lit-html/directives/class-map"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-item/paper-icon-item"; @@ -14,27 +13,12 @@ import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import "./ha-icon"; +import "../components/user/ha-user-badge"; import isComponentLoaded from "../common/config/is_component_loaded"; import { HomeAssistant, Panel } from "../types"; import { fireEvent } from "../common/dom/fire_event"; import { DEFAULT_PANEL } from "../common/const"; -const computeInitials = (name: string) => { - if (!name) { - return "user"; - } - return ( - name - .trim() - // Split by space and take first 3 words - .split(" ") - .slice(0, 3) - // Of each word, take first letter - .map((s) => s.substr(0, 1)) - .join("") - ); -}; - const computeUrl = (urlPath) => `/${urlPath}`; const computePanels = (hass: HomeAssistant) => { @@ -93,22 +77,13 @@ class HaSidebar extends LitElement { return html``; } - const initials = hass.user ? computeInitials(hass.user.name) : ""; - return html`
Home Assistant
${hass.user ? html` - 2, - })}" - > - - ${initials} + + ` : ""} @@ -344,23 +319,6 @@ class HaSidebar extends LitElement { .dev-tools a { color: var(--sidebar-icon-color); } - - .profile-badge { - /* for ripple */ - position: relative; - box-sizing: border-box; - width: 40px; - line-height: 40px; - border-radius: 50%; - text-align: center; - background-color: var(--light-primary-color); - text-decoration: none; - color: var(--primary-text-color); - } - - .profile-badge.long { - font-size: 80%; - } `; } } diff --git a/src/components/user/ha-user-badge.ts b/src/components/user/ha-user-badge.ts new file mode 100644 index 0000000000..a4bb8a5a31 --- /dev/null +++ b/src/components/user/ha-user-badge.ts @@ -0,0 +1,77 @@ +import { + LitElement, + TemplateResult, + css, + CSSResult, + html, + property, + customElement, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import { User } from "../../data/auth"; +import { CurrentUser } from "../../types"; + +const computeInitials = (name: string) => { + if (!name) { + return "user"; + } + return ( + name + .trim() + // Split by space and take first 3 words + .split(" ") + .slice(0, 3) + // Of each word, take first letter + .map((s) => s.substr(0, 1)) + .join("") + ); +}; + +@customElement("ha-user-badge") +class StateBadge extends LitElement { + @property() public user?: User | CurrentUser; + + protected render(): TemplateResult | void { + const user = this.user; + + const initials = user ? computeInitials(user.name) : "?"; + + return html` +
2, + })}" + > + ${initials} +
+ `; + } + + static get styles(): CSSResult { + return css` + .profile-badge { + display: inline-block; + box-sizing: border-box; + width: 40px; + line-height: 40px; + border-radius: 50%; + text-align: center; + background-color: var(--light-primary-color); + text-decoration: none; + color: var(--primary-text-color); + overflow: hidden; + } + + .profile-badge.long { + font-size: 80%; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-user-badge": StateBadge; + } +} diff --git a/src/components/user/ha-user-picker.ts b/src/components/user/ha-user-picker.ts new file mode 100644 index 0000000000..9fcc6db530 --- /dev/null +++ b/src/components/user/ha-user-picker.ts @@ -0,0 +1,104 @@ +import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-icon-item"; +import "@polymer/paper-item/paper-item-body"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; +import "@polymer/paper-listbox/paper-listbox"; +import memoizeOne from "memoize-one"; +import { + LitElement, + TemplateResult, + html, + css, + CSSResult, + property, +} from "lit-element"; +import { HomeAssistant } from "../../types"; +import { fireEvent } from "../../common/dom/fire_event"; +import { User, fetchUsers } from "../../data/auth"; +import compare from "../../common/string/compare"; + +class HaEntityPicker extends LitElement { + public hass?: HomeAssistant; + @property() public label?: string; + @property() public value?: string; + @property() public users?: User[]; + + private _sortedUsers = memoizeOne((users?: User[]) => { + if (!users || users.length === 1) { + return users || []; + } + const sorted = [...users]; + sorted.sort((a, b) => compare(a.name, b.name)); + return sorted; + }); + + protected render(): TemplateResult | void { + return html` + + + + No user + + ${this._sortedUsers(this.users).map( + (user) => html` + + + ${user.name} + + ` + )} + + + `; + } + + private get _value() { + return this.value || ""; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + if (this.users === undefined) { + fetchUsers(this.hass!).then((users) => { + this.users = users; + }); + } + } + + private _userChanged(ev) { + const newValue = ev.detail.item.dataset.userId; + + if (newValue !== this._value) { + this.value = ev.detail.value; + setTimeout(() => { + fireEvent(this, "value-changed", { value: newValue }); + fireEvent(this, "change"); + }, 0); + } + } + + static get styles(): CSSResult { + return css` + :host { + display: inline-block; + } + paper-dropdown-menu-light { + display: block; + } + paper-listbox { + min-width: 200px; + } + paper-icon-item { + cursor: pointer; + } + `; + } +} + +customElements.define("ha-user-picker", HaEntityPicker); diff --git a/src/data/auth.ts b/src/data/auth.ts index 52c3af9099..4ec1e57146 100644 --- a/src/data/auth.ts +++ b/src/data/auth.ts @@ -1,5 +1,26 @@ +import { HomeAssistant } from "../types"; + export interface AuthProvider { name: string; id: string; type: string; } + +interface Credential { + type: string; +} + +export interface User { + id: string; + name: string; + is_owner: boolean; + is_active: boolean; + system_generated: boolean; + group_ids: string[]; + credentials: Credential[]; +} + +export const fetchUsers = async (hass: HomeAssistant) => + hass.callWS({ + type: "config/auth/list", + }); diff --git a/src/data/ws-user.ts b/src/data/ws-user.ts index 66be784eb8..2e171b8dab 100644 --- a/src/data/ws-user.ts +++ b/src/data/ws-user.ts @@ -3,12 +3,17 @@ import { Connection, getCollection, } from "home-assistant-js-websocket"; -import { User } from "../types"; +import { CurrentUser } from "../types"; export const userCollection = (conn: Connection) => - getCollection(conn, "_usr", () => getUser(conn) as Promise, undefined); + getCollection( + conn, + "_usr", + () => getUser(conn) as Promise, + undefined + ); export const subscribeUser = ( conn: Connection, - onChange: (user: User) => void + onChange: (user: CurrentUser) => void ) => userCollection(conn).subscribe(onChange); diff --git a/src/dialogs/more-info/controls/more-info-weather.js b/src/dialogs/more-info/controls/more-info-weather.js index 38ee714986..cc47677f73 100644 --- a/src/dialogs/more-info/controls/more-info-weather.js +++ b/src/dialogs/more-info/controls/more-info-weather.js @@ -105,8 +105,11 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) { -
[[computeDateTime(item.datetime)]]
+