Update popup design. Add form popups

This commit is contained in:
Thomas Lovén 2022-08-03 21:21:25 +00:00
parent a641617671
commit bb7b7fa6ff
12 changed files with 289 additions and 131 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
"dependencies": ["panel_custom", "websocket_api", "http", "frontend", "lovelace"],
"codeowners": [],
"requirements": [],
"version": "2.0.0b2",
"version": "2.0.0b3",
"iot_class": "local_push",
"config_flow": true
}

View File

@ -10,16 +10,38 @@ data:
left_button: Left button
```
![Popup](https://user-images.githubusercontent.com/1299821/180668969-c647f301-3f3d-4f3b-a1f8-d95af8b48873.png)
![Dialog Anatomy](https://user-images.githubusercontent.com/1299821/182708739-f89e3b2b-199f-43e0-bf04-e1dfc7075b2a.png)
## Displaying a dashboard card in a popup
## Size
The `size` parameter can be set to `normal`, `wide` and `fullscreen` with results as below (background blur has been exagerated for clarity):
![Normal Size](https://user-images.githubusercontent.com/1299821/182709146-439814f1-d479-4fc7-aab1-e28f5c9a13c7.png)
![Wide Size](https://user-images.githubusercontent.com/1299821/182709172-c98a9c23-5e58-4564-bcb7-1d187842948f.png)
![Fullscreen Size](https://user-images.githubusercontent.com/1299821/182709224-fb2e7b92-8a23-4422-95a0-f0f2835909e0.png)
## HTML content
```yaml
service: browser_mod.popup
data:
title: The title
right_button: Right button
left_button: Left button
title: HTML content
content: |
An <b>HTML</b> string.
<p> Pretty much any HTML works: <ha-icon icon="mdi:lamp" style="color: red;"></ha-icon>
```
![HTML content](https://user-images.githubusercontent.com/1299821/182710044-6fea3ba3-5262-4361-a131-691770340518.png)
## Dashboard card content
```yaml
service: browser_mod.popup
data:
title: HTML content
content:
type: entities
entities:
@ -28,10 +50,53 @@ data:
- light.kitchen_lights
```
![Popup with card](https://user-images.githubusercontent.com/1299821/180669077-bbc86831-3a8a-4e54-b098-d900d62d3508.png)
![Card content](https://user-images.githubusercontent.com/1299821/182710445-f09b74b8-dd53-4d65-8eba-0945fc1d418e.png)
## Form content
`content` can be a list of ha-form schemas and the popup will then contain a form for user input:
```
<ha-form schema>:
name: <string>
[label: <string>]
[default: <any>]
selector: <Home Assistant Selector>
```
| | |
|-|-|
| `name` | A unique parameter name |
| `label` | A description of the parameter |
| `default` | The default value for the parameter |
| `selector` | A [Home Assistant selector](https://www.home-assistant.io/docs/blueprint/selectors) |
The data from the form will be forwarded as data for any `right_button_action` or `left_button_action` of the popup.
```yaml
service: browser_mod.popup
data:
title: Form content
content:
- name: parameter_name
label: Descriptive name
selector:
text: null
- name: another_parameter
label: A number
default: 5
selector:
number:
min: 0
max: 10
slider: true
```
![Form content](https://user-images.githubusercontent.com/1299821/182712670-f3b4fdb7-84a9-49d1-a26f-2cdaa450fa0e.png)
## Actionable popups
Example of a popup with actions opening more popups or calling Home Assistant services:
```yaml
service: browser_mod.popup
data:
@ -62,4 +127,35 @@ data:
entity_id: light.bed_light
```
![Advanced popup](https://user-images.githubusercontent.com/1299821/180670190-18cf8eee-cf18-47b9-84d1-e62ef327c615.gif)
![Multi-level popup](https://user-images.githubusercontent.com/1299821/182713421-708d0026-bcfa-4ba6-bbcd-3b85b584162d.gif)
## Forward form data
The following popup would ask the user for a list of rooms to vacuum and then populate the `params` parameter of the `vacuum.send_command` service call from the result:
```yaml
service: browser_mod.popup
data:
title: Where to vacuum?
right_button: Go!
right_button_action:
service: vacuum.send_command
data:
entity_id: vacuum.xiaomi
command: app_segment_clean
content:
- name: params
label: Rooms to clean
selector:
select:
multiple: true
options:
- label: Kitchen
value: 11
- label: Living room
value: 13
- label: Bedroom
value: 12
```
![Vacuum popup](https://user-images.githubusercontent.com/1299821/182713714-ef4149b1-217a-4d41-9737-714f5320c25c.png)

View File

@ -125,7 +125,7 @@ Display a popup dialog
service: browser_mod.popup
data:
[title: <string>]
content: <string / Dashboard card configuration>
content: <string / Dashboard card configuration / ha-form schema>
[size: <NORMAL/wide/fullscreen>]
[right_button: <string>]
[right_button_action: <service call>]
@ -143,7 +143,7 @@ data:
| | |
|---|---|
|`title` | The title of the popup window.|
|`content`| HTML or a dashboard card configuration to display.|
|`content`| HTML, a dashboard card configuration or ha-form schema to display.|
| `size` | `wide` will make the popup window wider. `fullscreen` will make it cover the entire screen. |
| `right_button`| The text of the right action button.|
| `right_button_action`| Action to perform when the right action button is pressed. |
@ -171,7 +171,9 @@ style:
Note that any Browser Mod services performed as `_action`s here will be performed only on the same Browser as initiated the action unless `browser_id` is given.
For usage examples, see [popups.md](popups.md).
If a ha-form schema is used for `content` the resulting data will be inserted into the `data` for any `_action`.
See [popups.md](popups.md) for more information and usage examples.
## `browser_mod.close_popup`

View File

@ -14,9 +14,7 @@ class BrowserModRegisteredBrowsersCard extends LitElement {
const browserID = ev.currentTarget.browserID;
const unregisterCallback = () => {
console.log(browserID, window.browser_mod.browserID);
if (browserID === window.browser_mod.browserID) {
console.log("Unregister self");
window.browser_mod.registered = false;
} else {
window.browser_mod.connection.sendMessage({

View File

@ -81,9 +81,9 @@ export const loadHaForm = async () => {
await loadLoadCardHelpers();
const helpers = await window.loadCardHelpers();
if (!helpers) return;
const card = await helpers.createCardElement({ type: "entity" });
const card = await helpers.createCardElement({ type: "button" });
if (!card) return;
await card.getConfigElement();
await card.constructor.getConfigElement();
};
// Loads in ha-config-dashboard which is used to copy styling
@ -120,3 +120,15 @@ export const loadDeveloperToolsTemplate = async () => {
await dtRouter?.routerOptions?.routes?.template?.load?.();
await customElements.whenDefined("developer-tools-template");
};
export function throttle(timeout) {
return function (target, propertyKey, descriptor) {
const fn = descriptor.value;
let cooldown = undefined;
descriptor.value = function (...rest) {
if (cooldown) return;
cooldown = setTimeout(() => (cooldown = undefined), timeout);
return fn.bind(this)(...rest);
};
};
}

View File

@ -1,6 +1,5 @@
import "./browser-player";
// import { BrowserModConnection } from "./connection";
import { ConnectionMixin } from "./connection";
import { ScreenSaverMixin } from "./screensaver";
import { MediaPlayerMixin } from "./mediaPlayer";
@ -19,7 +18,8 @@ import { BrowserIDMixin } from "./browserID";
/*
TODO:
- Fix nomenclature
- More pictures for documentation
x Fix nomenclature
x Command -> Service
x Device -> Browser
- Popups
@ -28,6 +28,8 @@ import { BrowserIDMixin } from "./browserID";
X Timeout
X Fullscreen
x Popup-card
x Auto-close
x Forms that are forwarded to service calls
x Motion/occupancy tracker
x Information about interaction requirement
x Information about fullykiosk
@ -39,29 +41,28 @@ import { BrowserIDMixin } from "./browserID";
x ll-custom handling
- Commands
x popup
x Auto-close
x close_popup
x more-info
x navigate
- lovelace-reload?
- Not needed
o lovelace-reload?
o Not needed
x window-reload
- screensaver ?
- Refer to automations instead
o screensaver ?
o Refer to automations instead
x sequence
x delay
x javascript eval
- toast?
- Replaced with popups with timeout
o toast?
o Replaced with popups with timeout
x Redesign services to target devices
x frontend editor for popup cards
- also screensavers
- Saved frontend settings
o also screensavers
x Saved frontend settings
X Framework
x Save sidebar
x Kiosk mode
x Default dashboard
- Screensaver?
o Screensaver?
x Favicon templates
x Title templates
- Tweaks
@ -69,9 +70,9 @@ import { BrowserIDMixin } from "./browserID";
x Card-mod preload
x Video player
x Media_seek
- Screensavers
o Screensavers
x IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
- NOFIX. Home Assistant bug
o NOFIX. Home Assistant bug
X Check functionality with CAST - may need to add frontend part as a lovelace resource
*/
export class BrowserMod extends ServicesMixin(

View File

@ -1,12 +1,11 @@
import { selectTree } from "../helpers";
import { selectTree, throttle } from "../helpers";
export const MediaPlayerMixin = (SuperClass) => {
return class MediaPlayerMixinClass extends SuperClass {
class MediaPlayerMixinClass extends SuperClass {
public player;
private _audio_player;
private _video_player;
private _player_enabled;
private _player_update_cooldown;
constructor() {
super();
@ -24,10 +23,10 @@ export const MediaPlayerMixin = (SuperClass) => {
}
for (const ev of ["timeupdate"]) {
this._audio_player.addEventListener(ev, () =>
this._player_update_choked()
this._player_update_throttled()
);
this._video_player.addEventListener(ev, () =>
this._player_update_choked()
this._player_update_throttled()
);
}
@ -99,12 +98,8 @@ export const MediaPlayerMixin = (SuperClass) => {
}
}
private _player_update_choked() {
if (this._player_update_cooldown) return;
this._player_update_cooldown = window.setTimeout(
() => (this._player_update_cooldown = undefined),
3000
);
@throttle(3000)
_player_update_throttled() {
this._player_update();
}
@ -129,5 +124,7 @@ export const MediaPlayerMixin = (SuperClass) => {
},
});
}
};
}
return MediaPlayerMixinClass;
};

View File

@ -2,6 +2,7 @@ import { LitElement, html, css } from "lit";
import { property, query } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { provideHass, loadLoadCardHelpers, hass_base_el } from "../helpers";
import { loadHaForm } from "../helpers";
class BrowserModPopup extends LitElement {
@property() open;
@ -23,6 +24,7 @@ class BrowserModPopup extends LitElement {
_timeoutStart;
_timeoutTimer;
_resolveClosed;
_formdata;
async closeDialog() {
this.open = false;
@ -76,10 +78,30 @@ class BrowserModPopup extends LitElement {
autoclose = false,
} = {}
) {
this._formdata = undefined;
this.title = title;
if (content && content instanceof HTMLElement) {
this.card = undefined;
if (content && content instanceof HTMLElement) {
this.content = content;
} else if (content && Array.isArray(content)) {
loadHaForm();
const form: any = document.createElement("ha-form");
form.schema = content;
form.computeLabel = (s) => s.label ?? s.name;
form.hass = window.browser_mod.hass;
this._formdata = {};
for (const i of content) {
if (i.name && i.default !== undefined) {
this._formdata[i.name] = i.default;
}
}
form.data = this._formdata;
provideHass(form);
form.addEventListener("value-changed", (ev) => {
this._formdata = { ...ev.detail.value };
form.data = this._formdata;
});
this.content = form;
} else if (content && typeof content === "object") {
// Create a card from config in content
this.card = true;
@ -90,7 +112,6 @@ class BrowserModPopup extends LitElement {
this.content = card;
} else {
// Basic HTML content
this.card = undefined;
this.content = unsafeHTML(content);
}
@ -115,12 +136,12 @@ class BrowserModPopup extends LitElement {
async _primary() {
if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined;
this.dialog?.close();
this._actions?.right_button_action?.();
this._actions?.right_button_action?.(this._formdata);
}
async _secondary() {
if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined;
this.dialog?.close();
this._actions?.left_button_action?.();
this._actions?.left_button_action?.(this._formdata);
}
async _dismiss(ev?) {
this.dialog?.close();
@ -150,16 +171,21 @@ class BrowserModPopup extends LitElement {
: ""}
${this.title
? html`
<app-toolbar slot="heading">
<div slot="heading">
<ha-header-bar>
${this.dismissable
? html`
<ha-icon-button dialogAction="cancel">
<ha-icon-button
dialogAction="cancel"
slot="navigationIcon"
>
<ha-icon .icon=${"mdi:close"}></ha-icon>
</ha-icon-button>
`
: ""}
<div class="main-title">${this.title}</div>
</app-toolbar>
<div slot="title" class="main-title">${this.title}</div>
</ha-header-bar>
</div>
`
: html``}
@ -171,6 +197,7 @@ class BrowserModPopup extends LitElement {
slot="primaryAction"
.label=${this.right_button}
@click=${this._primary}
class="action-button"
></mwc-button>
`
: ""}
@ -180,6 +207,7 @@ class BrowserModPopup extends LitElement {
slot="secondaryAction"
.label=${this.left_button}
@click=${this._secondary}
class="action-button"
></mwc-button>
`
: ""}
@ -195,6 +223,7 @@ class BrowserModPopup extends LitElement {
static get styles() {
return css`
ha-dialog {
--dialog-backdrop-filter: blur(5px);
z-index: 10;
--mdc-dialog-min-width: var(--popup-min-width, 400px);
--mdc-dialog-max-width: var(--popup-max-width, 600px);
@ -202,13 +231,12 @@ class BrowserModPopup extends LitElement {
--mdc-dialog-content-ink-color: var(--primary-text-color);
--justify-action-buttons: space-between;
--mdc-dialog-box-shadow: 0px 0px 0px
--dialog-box-shadow: 0px 0px 0px
var(--popup-border-width, var(--ha-card-border-width, 2px))
var(
--popup-border-color,
var(--ha-card-border-color, var(--divider-color, #e0e0e0))
);
--ha-dialog-border-radius: var(--popup-border-radius, 8px);
--mdc-theme-surface: var(
--popup-background-color,
var(--ha-card-background, var(--card-background-color, white))
@ -224,6 +252,7 @@ class BrowserModPopup extends LitElement {
--mdc-dialog-max-height: 100%;
--mdc-shape-medium: 0px;
--vertial-align-dialog: flex-end;
--ha-dialog-border-radius: 0px;
}
.progress::before {
content: "";
@ -236,23 +265,20 @@ class BrowserModPopup extends LitElement {
z-index: 10;
}
app-toolbar {
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
color: var(--primary-text-color);
background-color: var(
--popup-header-background-color,
var(--popup-background-color, var(--sidebar-background-color))
);
display: block;
}
ha-icon-button > * {
display: flex;
}
.main-title {
margin-left: 16px;
line-height: 1.3em;
max-height: 2.6em;
overflow: hidden;
text-overflow: ellipsis;
cursor: default;
}
.content {
--padding-x: 24px;
@ -269,9 +295,11 @@ class BrowserModPopup extends LitElement {
:host([card]) .content {
--padding-x: 0px;
--padding-y: 0px;
--ha-card-box-shadow: none;
}
:host([actions]) .content {
border-bottom: 1px solid var(--popup-border-color, var(--divider-color));
xborder-bottom: 2px solid
var(--popup-border-color, var(--divider-color));
--footer-height: 54px;
}
:host([wide]) .content {
@ -280,10 +308,14 @@ class BrowserModPopup extends LitElement {
:host([fullscreen]) .content {
height: calc(
100vh - var(--header-height) - var(--footer-height) - 2 *
var(--padding-y)
var(--padding-y) - 16px
);
}
.action-button {
margin-bottom: -24px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-dialog {
--mdc-dialog-min-width: 100vw;

View File

@ -128,7 +128,13 @@ export const ServicesMixin = (SuperClass) => {
const { title, content, ...d } = data;
for (const [k, v] of Object.entries(d)) {
if (k.endsWith("_action")) {
d[k] = () => this._service_action(v as any);
d[k] = (ext_data?) => {
const { service, data } = v as any;
this._service_action({
service,
data: { ...data, ...ext_data },
});
};
}
}
this.showPopup(title, content, d);
@ -147,18 +153,21 @@ export const ServicesMixin = (SuperClass) => {
break;
case "console":
console.log(data.message);
if (
Object.keys(data).length > 1 ||
(data && data.message === undefined)
)
console.dir(data);
else console.log(data.message);
break;
case "javascript":
const code = `
"use strict";
// Insert global definitions here
const hass = (document.querySelector("home-assistant") || document.querySelector("hc-main")).hass;
${data.code}
`;
const fn = new Function(code);
fn();
const fn = new Function("hass", "data", code);
fn(this.hass, data);
break;
}
}

View File

@ -1,7 +1,7 @@
{
"name": "browser_mod",
"private": true,
"version": "2.0.0b2",
"version": "2.0.0b3",
"description": "",
"scripts": {
"build": "rollup -c",