Migrate mailbox to LitElement (#17690)

* migrate mailbox to lit

* Make some methods private

* Clean up
This commit is contained in:
Simon Lamon 2023-08-30 15:21:20 +02:00 committed by GitHub
parent c0793fad83
commit e06bd41b5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 435 additions and 409 deletions

View File

@ -1,158 +0,0 @@
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../components/ha-dialog";
import "../../components/ha-circular-progress";
import "../../components/ha-icon";
import "../../components/ha-icon-button";
import LocalizeMixin from "../../mixins/localize-mixin";
import "../../styles/polymer-ha-style-dialog";
/*
* @appliesMixin LocalizeMixin
*/
class HaDialogShowAudioMessage extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style-dialog">
.error {
color: red;
}
p {
color: var(--secondary-text-color);
}
.icon {
text-align: var(--float-end);
}
</style>
<ha-dialog
open="[[_opened]]"
on-closed="closeDialog"
heading="[[localize('ui.panel.mailbox.playback_title')]]"
>
<div>
<div class="icon">
<template is="dom-if" if="[[_loading]]">
<ha-circular-progress active></ha-circular-progress>
</template>
<ha-icon-button id="delicon" on-click="openDeleteDialog">
<ha-icon icon="hass:delete"></ha-icon>
</ha-icon-button>
</div>
<div id="transcribe"></div>
<div>
<template is="dom-if" if="[[_errorMsg]]">
<div class="error">[[_errorMsg]]</div>
</template>
<audio id="mp3" preload="none" controls>
<source id="mp3src" src="" type="audio/mpeg" />
</audio>
</div>
</div>
</ha-dialog>
`;
}
static get properties() {
return {
hass: Object,
_currentMessage: Object,
// Error message when can't talk to server etc
_errorMsg: String,
_loading: {
type: Boolean,
value: false,
},
_opened: {
type: Boolean,
value: false,
},
};
}
showDialog({ hass, message }) {
this.hass = hass;
this._errorMsg = null;
this._currentMessage = message;
this._opened = true;
this.$.transcribe.innerText = message.message;
const platform = message.platform;
const mp3 = this.$.mp3;
if (platform.has_media) {
mp3.style.display = "";
this._showLoading(true);
mp3.src = null;
const url = `/api/mailbox/media/${platform.name}/${message.sha}`;
this.hass
.fetchWithAuth(url)
.then((response) => {
if (response.ok) {
return response.blob();
}
return Promise.reject({
status: response.status,
statusText: response.statusText,
});
})
.then((blob) => {
this._showLoading(false);
mp3.src = window.URL.createObjectURL(blob);
mp3.play();
})
.catch((err) => {
this._showLoading(false);
this._errorMsg = `Error loading audio: ${err.statusText}`;
});
} else {
mp3.style.display = "none";
this._showLoading(false);
}
}
openDeleteDialog() {
if (confirm(this.localize("ui.panel.mailbox.delete_prompt"))) {
this.deleteSelected();
}
}
deleteSelected() {
const msg = this._currentMessage;
this.hass.callApi(
"DELETE",
`mailbox/delete/${msg.platform.name}/${msg.sha}`
);
this._dialogDone();
}
_dialogDone() {
this.$.mp3.pause();
this.setProperties({
_currentMessage: null,
_errorMsg: null,
_loading: false,
_opened: false,
});
}
closeDialog() {
this._dialogDone();
}
_showLoading(displayed) {
const delicon = this.$.delicon;
if (displayed) {
this._loading = true;
delicon.style.display = "none";
} else {
const platform = this._currentMessage.platform;
this._loading = false;
delicon.style.display = platform.can_delete ? "" : "none";
}
}
}
customElements.define("ha-dialog-show-audio-message", HaDialogShowAudioMessage);

View File

@ -0,0 +1,153 @@
import "@material/mwc-button";
import "../../components/ha-dialog";
import "../../components/ha-circular-progress";
import "../../components/ha-icon";
import "../../components/ha-icon-button";
import {
CSSResultGroup,
LitElement,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { HomeAssistant } from "../../types";
import { haStyleDialog } from "../../resources/styles";
@customElement("ha-dialog-show-audio-message")
class HaDialogShowAudioMessage extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _currentMessage?: any;
@state() private _errorMsg?: string;
@state() private _loading: boolean = false;
@state() private _opened: boolean = false;
@state() private _blobUrl?: string;
protected render(): TemplateResult {
return html`
<ha-dialog
.open=${this._opened}
@closed=${this._closeDialog}
heading=${this.hass.localize("ui.panel.mailbox.playback_title")}
>
${this._loading
? html`<ha-circular-progress active></ha-circular-progress>`
: html`<div class="icon">
<ha-icon-button id="delicon" @click=${this._openDeleteDialog}>
<ha-icon icon="hass:delete"></ha-icon>
</ha-icon-button>
</div>
${
this._currentMessage
? html`<div id="transcribe">
${this._currentMessage?.message}
</div>`
: nothing
}
${
this._errorMsg
? html`<div class="error">${this._errorMsg}</div>`
: nothing
}
${
this._blobUrl
? html` <audio id="mp3" preload="none" controls autoplay>
<source
id="mp3src"
src=${this._blobUrl}
type="audio/mpeg"
/>
</audio>`
: nothing
}
</div>`}
</ha-dialog>
`;
}
showDialog({ hass, message }) {
this.hass = hass;
this._errorMsg = undefined;
this._currentMessage = message;
this._opened = true;
const platform = message.platform;
if (platform.has_media) {
this._loading = true;
const url = `/api/mailbox/media/${platform.name}/${message.sha}`;
this.hass
.fetchWithAuth(url)
.then((response) => {
if (response.ok) {
return response.blob();
}
return Promise.reject({
status: response.status,
statusText: response.statusText,
});
})
.then((blob) => {
this._loading = false;
this._blobUrl = window.URL.createObjectURL(blob);
})
.catch((err) => {
this._loading = false;
this._errorMsg = `Error loading audio: ${err.statusText}`;
});
} else {
this._loading = false;
}
}
private _openDeleteDialog() {
if (confirm(this.hass.localize("ui.panel.mailbox.delete_prompt"))) {
this._deleteSelected();
}
}
private _deleteSelected() {
const msg = this._currentMessage;
this.hass.callApi(
"DELETE",
`mailbox/delete/${msg.platform.name}/${msg.sha}`
);
this._closeDialog();
}
private _closeDialog() {
const mp3 = this.shadowRoot!.querySelector("#mp3")! as any;
mp3.pause();
this._currentMessage = undefined;
this._errorMsg = undefined;
this._loading = false;
this._opened = false;
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
.error {
color: red;
}
p {
color: var(--secondary-text-color);
}
.icon {
text-align: var(--float-end);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-dialog-show-audio-message": HaDialogShowAudioMessage;
}
}

View File

@ -1,251 +0,0 @@
import "@material/mwc-button";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-tabs/paper-tab";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { formatDateTime } from "../../common/datetime/format_date_time";
import "../../components/ha-card";
import "../../components/ha-menu-button";
import "../../components/ha-tabs";
import "../../layouts/ha-app-layout";
import { EventsMixin } from "../../mixins/events-mixin";
import LocalizeMixin from "../../mixins/localize-mixin";
import "../../styles/polymer-ha-style";
let registeredDialog = false;
/*
* @appliesMixin LocalizeMixin
*/
class HaPanelMailbox extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style include="ha-style">
:host {
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.content {
padding: 16px;
max-width: 600px;
margin: 0 auto;
}
ha-card {
overflow: hidden;
}
paper-item {
cursor: pointer;
}
ha-tabs {
margin-left: max(env(safe-area-inset-left), 24px);
margin-right: max(env(safe-area-inset-right), 24px);
--paper-tabs-selection-bar-color: #fff;
text-transform: uppercase;
}
.empty {
text-align: center;
color: var(--secondary-text-color);
}
.header {
@apply --paper-font-title;
}
.row {
display: flex;
justify-content: space-between;
}
@media all and (max-width: 450px) {
.content {
width: auto;
padding: 0;
}
}
.tip {
color: var(--secondary-text-color);
font-size: 14px;
}
.date {
color: var(--primary-text-color);
}
</style>
<ha-app-layout>
<app-header slot="header" fixed>
<app-toolbar>
<ha-menu-button
hass="[[hass]]"
narrow="[[narrow]]"
></ha-menu-button>
<div main-title>[[localize('panel.mailbox')]]</div>
</app-toolbar>
<div sticky hidden$="[[areTabsHidden(platforms)]]">
<ha-tabs
scrollable
selected="[[_currentPlatform]]"
on-iron-activate="handlePlatformSelected"
>
<template is="dom-repeat" items="[[platforms]]">
<paper-tab data-entity="[[item]]">
[[getPlatformName(item)]]
</paper-tab>
</template>
</ha-tabs>
</div>
</app-header>
<div class="content">
<ha-card>
<template is="dom-if" if="[[!_messages.length]]">
<div class="card-content empty">
[[localize('ui.panel.mailbox.empty')]]
</div>
</template>
<template is="dom-repeat" items="[[_messages]]">
<paper-item on-click="openMP3Dialog">
<paper-item-body style="width:100%" two-line>
<div class="row">
<div>[[item.caller]]</div>
<div class="tip">
[[localize('ui.duration.second', 'count', item.duration)]]
</div>
</div>
<div secondary>
<span class="date">[[item.timestamp]]</span> -
[[item.message]]
</div>
</paper-item-body>
</paper-item>
</template>
</ha-card>
</div>
</ha-app-layout>
`;
}
static get properties() {
return {
hass: Object,
narrow: Boolean,
platforms: {
type: Array,
},
_messages: {
type: Array,
},
_currentPlatform: {
type: Number,
value: 0,
},
};
}
connectedCallback() {
super.connectedCallback();
if (!registeredDialog) {
registeredDialog = true;
this.fire("register-dialog", {
dialogShowEvent: "show-audio-message-dialog",
dialogTag: "ha-dialog-show-audio-message",
dialogImport: () => import("./ha-dialog-show-audio-message"),
});
}
this.hassChanged = this.hassChanged.bind(this);
this.hass.connection
.subscribeEvents(this.hassChanged, "mailbox_updated")
.then((unsub) => {
this._unsubEvents = unsub;
});
this.computePlatforms().then((platforms) => {
this.platforms = platforms;
this.hassChanged();
});
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubEvents) this._unsubEvents();
}
hassChanged() {
if (!this._messages) {
this._messages = [];
}
this.getMessages().then((items) => {
this._messages = items;
});
}
openMP3Dialog(event) {
this.fire("show-audio-message-dialog", {
hass: this.hass,
message: event.model.item,
});
}
getMessages() {
const platform = this.platforms[this._currentPlatform];
return this.hass
.callApi("GET", `mailbox/messages/${platform.name}`)
.then((values) => {
const platformItems = [];
const arrayLength = values.length;
for (let i = 0; i < arrayLength; i++) {
const datetime = formatDateTime(
new Date(values[i].info.origtime * 1000),
this.hass.locale,
this.hass.config
);
platformItems.push({
timestamp: datetime,
caller: values[i].info.callerid,
message: values[i].text,
sha: values[i].sha,
duration: values[i].info.duration,
platform: platform,
});
}
return platformItems.sort(
(a, b) => new Date(b.timestamp) - new Date(a.timestamp)
);
});
}
computePlatforms() {
return this.hass.callApi("GET", "mailbox/platforms");
}
handlePlatformSelected(ev) {
const newPlatform = ev.detail.selected;
if (newPlatform !== this._currentPlatform) {
this._currentPlatform = newPlatform;
this.hassChanged();
}
}
areTabsHidden(platforms) {
return !platforms || platforms.length < 2;
}
getPlatformName(item) {
const entity = `mailbox.${item.name}`;
const stateObj = this.hass.states[entity.toLowerCase()];
return stateObj.attributes.friendly_name;
}
}
customElements.define("ha-panel-mailbox", HaPanelMailbox);

View File

@ -0,0 +1,282 @@
import {
CSSResultGroup,
LitElement,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import "@material/mwc-button";
import { formatDateTime } from "../../common/datetime/format_date_time";
import "../../components/ha-card";
import "../../components/ha-menu-button";
import "../../components/ha-tabs";
import "../../layouts/ha-app-layout";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-tabs/paper-tab";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import { haStyle } from "../../resources/styles";
import "../../components/ha-top-app-bar-fixed";
let registeredDialog = false;
interface MailboxMessage {
info: {
origtime: number;
callerid: string;
duration: string;
};
text: string;
sha: string;
}
@customElement("ha-panel-mailbox")
class HaPanelMailbox extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public narrow!: boolean;
@property({ attribute: false }) public platforms?: any[];
@state() private _messages?: any[];
@state() private _currentPlatform: number = 0;
private _unsubEvents?;
protected render(): TemplateResult {
return html`
<ha-top-app-bar-fixed>
<ha-menu-button
slot="navigationIcon"
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
<div slot="title">${this.hass.localize("panel.mailbox")}</div>
${!this._areTabsHidden(this.platforms)
? html`<div sticky>
<ha-tabs
scrollable
.selected=${this._currentPlatform}
@iron-activate=${this._handlePlatformSelected}
>
${this.platforms?.map(
(platform) =>
html` <paper-tab data-entity=${platform}>
${this._getPlatformName(platform)}
</paper-tab>`
)}
</ha-tabs>
</div>`
: ""}
</ha-top-app-bar-fixed>
<div class="content">
<ha-card>
${!this._messages?.length
? html`<div class="card-content empty">
${this.hass.localize("ui.panel.mailbox.empty")}
</div>`
: nothing}
${this._messages?.map(
(message) =>
html` <paper-item
.message=${message}
@click=${this._openMP3Dialog}
>
<paper-item-body style="width:100%" two-line>
<div class="row">
<div>${message.caller}</div>
<div class="tip">
${this.hass.localize(
"ui.duration.second",
"count",
message.duration
)}
</div>
</div>
<div secondary>
<span class="date">${message.timestamp}</span> -
${message.message}
</div>
</paper-item-body>
</paper-item>`
)}
</ha-card>
</div>
`;
}
connectedCallback() {
super.connectedCallback();
if (!registeredDialog) {
registeredDialog = true;
fireEvent(this, "register-dialog", {
dialogShowEvent: "show-audio-message-dialog",
dialogTag: "ha-dialog-show-audio-message",
dialogImport: () => import("./ha-dialog-show-audio-message"),
});
}
this.hassChanged = this.hassChanged.bind(this);
this.hass.connection
.subscribeEvents(this.hassChanged, "mailbox_updated")
.then((unsub) => {
this._unsubEvents = unsub;
});
this._computePlatforms().then((platforms) => {
this.platforms = platforms;
this.hassChanged();
});
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubEvents) this._unsubEvents();
}
hassChanged() {
if (!this._messages) {
this._messages = [];
}
this._getMessages().then((items) => {
this._messages = items;
});
}
private _openMP3Dialog(ev) {
const message: any = (ev.currentTarget! as any).message;
fireEvent(this, "show-audio-message-dialog", {
hass: this.hass,
message: message,
});
}
private _getMessages() {
const platform = this.platforms![this._currentPlatform];
return this.hass
.callApi<MailboxMessage[]>("GET", `mailbox/messages/${platform.name}`)
.then((values) => {
const platformItems: any[] = [];
const arrayLength = values.length;
for (let i = 0; i < arrayLength; i++) {
const datetime = formatDateTime(
new Date(values[i].info.origtime * 1000),
this.hass.locale,
this.hass.config
);
platformItems.push({
timestamp: datetime,
caller: values[i].info.callerid,
message: values[i].text,
sha: values[i].sha,
duration: values[i].info.duration,
platform: platform,
});
}
return platformItems.sort((a, b) => b.timestamp - a.timestamp);
});
}
private _computePlatforms(): Promise<any[]> {
return this.hass.callApi<any[]>("GET", "mailbox/platforms");
}
private _handlePlatformSelected(ev) {
const newPlatform = ev.detail.selected;
if (newPlatform !== this._currentPlatform) {
this._currentPlatform = newPlatform;
this.hassChanged();
}
}
private _areTabsHidden(platforms) {
return !platforms || platforms.length < 2;
}
private _getPlatformName(item) {
const entity = `mailbox.${item.name}`;
const stateObj = this.hass.states[entity.toLowerCase()];
return stateObj.attributes.friendly_name;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.content {
padding: 16px;
max-width: 600px;
margin: 0 auto;
}
ha-card {
overflow: hidden;
}
paper-item {
cursor: pointer;
}
ha-tabs {
margin-left: max(env(safe-area-inset-left), 24px);
margin-right: max(env(safe-area-inset-right), 24px);
--paper-tabs-selection-bar-color: #fff;
text-transform: uppercase;
}
.empty {
text-align: center;
color: var(--secondary-text-color);
}
.header {
@apply --paper-font-title;
}
.row {
display: flex;
justify-content: space-between;
}
@media all and (max-width: 450px) {
.content {
width: auto;
padding: 0;
}
}
.tip {
color: var(--secondary-text-color);
font-size: 14px;
}
.date {
color: var(--primary-text-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-panel-mailbox": HaPanelMailbox;
}
}
declare global {
// for fire event
interface HASSDomEvents {
"show-audio-message-dialog": {
hass: HomeAssistant;
message: string;
};
}
}