Popup-card
This commit is contained in:
parent
a4085ed3ab
commit
30d8f027dc
File diff suppressed because one or more lines are too long
@ -61,6 +61,15 @@ popup:
|
|||||||
description: "Popup content (Test or lovelace card configuration)"
|
description: "Popup content (Test or lovelace card configuration)"
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
|
size:
|
||||||
|
name: Size
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
mode: dropdown
|
||||||
|
options:
|
||||||
|
- normal
|
||||||
|
- wide
|
||||||
|
- fullscreen
|
||||||
right_button:
|
right_button:
|
||||||
name: Right button
|
name: Right button
|
||||||
description: Text of the right button
|
description: Text of the right button
|
||||||
@ -99,7 +108,7 @@ popup:
|
|||||||
number:
|
number:
|
||||||
mode: box
|
mode: box
|
||||||
timeout_action:
|
timeout_action:
|
||||||
name: Timeout
|
name: Timeout action
|
||||||
description: Action to perform when popup is closed by timeout
|
description: Action to perform when popup is closed by timeout
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
|
@ -75,3 +75,13 @@ export const loadLoadCardHelpers = async () => {
|
|||||||
]);
|
]);
|
||||||
await routes?.routes?.a?.load?.();
|
await routes?.routes?.a?.load?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadHaForm = async () => {
|
||||||
|
if (customElements.get("ha-form")) return;
|
||||||
|
await loadLoadCardHelpers();
|
||||||
|
const helpers = await window.loadCardHelpers();
|
||||||
|
if (!helpers) return;
|
||||||
|
const card = await helpers.createCardElement({ type: "entity" });
|
||||||
|
if (!card) return;
|
||||||
|
await card.getConfigElement();
|
||||||
|
};
|
||||||
|
@ -13,6 +13,7 @@ import { ActivityMixin } from "./activity";
|
|||||||
import "./popups";
|
import "./popups";
|
||||||
import { PopupMixin } from "./popups";
|
import { PopupMixin } from "./popups";
|
||||||
import pjson from "../../package.json";
|
import pjson from "../../package.json";
|
||||||
|
import "./popup-card";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO:
|
TODO:
|
||||||
@ -24,6 +25,7 @@ import pjson from "../../package.json";
|
|||||||
- Card-mod integration
|
- Card-mod integration
|
||||||
X Timeout
|
X Timeout
|
||||||
X Fullscreen
|
X Fullscreen
|
||||||
|
x Popup-card
|
||||||
x Motion/occupancy tracker
|
x Motion/occupancy tracker
|
||||||
x Information about interaction requirement
|
x Information about interaction requirement
|
||||||
x Information about fullykiosk
|
x Information about fullykiosk
|
||||||
|
264
js/plugin/popup-card-editor.ts
Normal file
264
js/plugin/popup-card-editor.ts
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
import { LitElement, html, css } from "lit";
|
||||||
|
import { property, query, state } from "lit/decorators.js";
|
||||||
|
import { loadHaForm } from "../helpers";
|
||||||
|
|
||||||
|
const configSchema = [
|
||||||
|
{
|
||||||
|
name: "entity",
|
||||||
|
label: "Entity",
|
||||||
|
selector: { entity: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
label: "Title",
|
||||||
|
selector: { text: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "size",
|
||||||
|
selector: {
|
||||||
|
select: { mode: "dropdown", options: ["normal", "wide", "fullscreen"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "right_button",
|
||||||
|
label: "Right button",
|
||||||
|
selector: { text: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "left_button",
|
||||||
|
label: "Left button",
|
||||||
|
selector: { text: {} },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "right_button_action",
|
||||||
|
label: "Right button action",
|
||||||
|
selector: { object: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "left_button_action",
|
||||||
|
label: "Left button action",
|
||||||
|
selector: { object: {} },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "dismissable",
|
||||||
|
label: "User dismissable",
|
||||||
|
selector: { boolean: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout",
|
||||||
|
label: "Auto close timeout (ms)",
|
||||||
|
selector: { number: { mode: "box" } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "dismiss_action",
|
||||||
|
label: "Dismiss action",
|
||||||
|
selector: { object: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout_action",
|
||||||
|
label: "Timeout action",
|
||||||
|
selector: { object: {} },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
class PopupCardEditor extends LitElement {
|
||||||
|
@state() _config;
|
||||||
|
|
||||||
|
@property() lovelace;
|
||||||
|
@property() hass;
|
||||||
|
|
||||||
|
@state() _selectedTab = 0;
|
||||||
|
@state() _cardGUIMode = true;
|
||||||
|
@state() _cardGUIModeAvailable = true;
|
||||||
|
|
||||||
|
@query("hui-card-element-editor") private _cardEditorEl?;
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
loadHaForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleSwitchTab(ev: CustomEvent) {
|
||||||
|
this._selectedTab = parseInt(ev.detail.index, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
_configChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._config) return;
|
||||||
|
this._config = { ...ev.detail.value };
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("config-changed", { detail: { config: this._config } })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cardConfigChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._config) return;
|
||||||
|
const card = { ...ev.detail.config };
|
||||||
|
this._config = { ...this._config, card };
|
||||||
|
this._cardGUIModeAvailable = ev.detail.guiModeAvailable;
|
||||||
|
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("config-changed", { detail: { config: this._config } })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_toggleCardMode(ev) {
|
||||||
|
this._cardEditorEl?.toggleMode();
|
||||||
|
}
|
||||||
|
_deleteCard(ev) {
|
||||||
|
if (!this._config) return;
|
||||||
|
this._config = { ...this._config };
|
||||||
|
delete this._config.card;
|
||||||
|
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("config-changed", { detail: { config: this._config } })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_cardGUIModeChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._cardGUIMode = ev.detail.guiMode;
|
||||||
|
this._cardGUIModeAvailable = ev.detail.guiModeAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.hass || !this._config) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="card-config">
|
||||||
|
<div class="toolbar">
|
||||||
|
<mwc-tab-bar
|
||||||
|
.activeIndex=${this._selectedTab}
|
||||||
|
@MDCTabBar:activated=${this._handleSwitchTab}
|
||||||
|
>
|
||||||
|
<mwc-tab .label=${"Settings"}></mwc-tab>
|
||||||
|
<mwc-tab .label=${"Card"}></mwc-tab>
|
||||||
|
</mwc-tab-bar>
|
||||||
|
</div>
|
||||||
|
<div id="editor">
|
||||||
|
${[this._renderSettingsEditor, this._renderCardEditor][
|
||||||
|
this._selectedTab
|
||||||
|
].bind(this)()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderSettingsEditor() {
|
||||||
|
return html`<div class="box">
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this._config}
|
||||||
|
.schema=${configSchema}
|
||||||
|
.computeLabel=${(s) => s.label ?? s.name}
|
||||||
|
@value-changed=${this._configChanged}
|
||||||
|
></ha-form>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderCardEditor() {
|
||||||
|
return html`
|
||||||
|
<div class="box cards">
|
||||||
|
${this._config.card
|
||||||
|
? html`
|
||||||
|
<div class="toolbar">
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._toggleCardMode}
|
||||||
|
.disabled=${!this._cardGUIModeAvailable}
|
||||||
|
class="gui-mode-button"
|
||||||
|
>
|
||||||
|
${!this._cardEditorEl || this._cardGUIMode
|
||||||
|
? "Show code editor"
|
||||||
|
: "Show visual editor"}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button
|
||||||
|
.title=${"Change card type"}
|
||||||
|
@click=${this._deleteCard}
|
||||||
|
>
|
||||||
|
Change card type
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
<hui-card-element-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.lovelace=${this.lovelace}
|
||||||
|
.value=${this._config.card}
|
||||||
|
@config-changed=${this._cardConfigChanged}
|
||||||
|
@GUImode-changed=${this._cardGUIModeChanged}
|
||||||
|
></hui-card-element-editor>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<hui-card-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.lovelace=${this.lovelace}
|
||||||
|
@config-changed=${this._cardConfigChanged}
|
||||||
|
></hui-card-picker>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
mwc-tab-bar {
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
margin-top: 8px;
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.box .toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.gui-mode-button {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
while (!window.browser_mod) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
await window.browser_mod.connectionPromise;
|
||||||
|
|
||||||
|
if (!customElements.get("popup-card-editor"))
|
||||||
|
customElements.define("popup-card-editor", PopupCardEditor);
|
||||||
|
(window as any).customCards = (window as any).customCards || [];
|
||||||
|
(window as any).customCards.push({
|
||||||
|
type: "popup-card",
|
||||||
|
name: "Popup card",
|
||||||
|
preview: false,
|
||||||
|
description:
|
||||||
|
"Replace the more-info dialog for a given entity in the view that includes this card. (Browser Mod)",
|
||||||
|
});
|
||||||
|
})();
|
123
js/plugin/popup-card.ts
Normal file
123
js/plugin/popup-card.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { LitElement, html, css } from "lit";
|
||||||
|
import { property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import "./popup-card-editor";
|
||||||
|
|
||||||
|
class PopupCard extends LitElement {
|
||||||
|
@property() hass;
|
||||||
|
@state() _config;
|
||||||
|
@property({ attribute: "edit-mode", reflect: true }) editMode;
|
||||||
|
@state() _element;
|
||||||
|
|
||||||
|
static getConfigElement() {
|
||||||
|
return document.createElement("popup-card-editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStubConfig(hass, entities) {
|
||||||
|
const entity = entities[0];
|
||||||
|
return {
|
||||||
|
entity,
|
||||||
|
title: "Custom popup",
|
||||||
|
dismissable: true,
|
||||||
|
card: { type: "markdown", content: "This replaces the more-info dialog" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.popup = this.popup.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
this._config = config;
|
||||||
|
(async () => {
|
||||||
|
const ch = await window.loadCardHelpers();
|
||||||
|
this._element = await ch.createCardElement(config.card);
|
||||||
|
this._element.hass = this.hass;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener("hass-more-info", this.popup);
|
||||||
|
|
||||||
|
if (this.parentElement.localName === "hui-card-preview") {
|
||||||
|
this.editMode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
window.removeEventListener("hass-more-info", this.popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup(ev: CustomEvent) {
|
||||||
|
if (ev.detail?.entityId === this._config.entity) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
const config = { ...this._config };
|
||||||
|
delete config.card;
|
||||||
|
|
||||||
|
window.browser_mod?.service("popup", {
|
||||||
|
content: this._config.card,
|
||||||
|
...this._config,
|
||||||
|
});
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("hass-more-info", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
cancelable: false,
|
||||||
|
detail: { entityID: "." },
|
||||||
|
})
|
||||||
|
),
|
||||||
|
50
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (changedProperties.has("hass")) {
|
||||||
|
if (this._element) this._element.hass = this.hass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.editMode) return html``;
|
||||||
|
return html` <ha-card>
|
||||||
|
<div>
|
||||||
|
<h2>${this._config.title}</h2>
|
||||||
|
</div>
|
||||||
|
${this._element}</ha-card
|
||||||
|
>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
:host([edit-mode="true"]) {
|
||||||
|
display: block !important;
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
while (!window.browser_mod) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
await window.browser_mod.connectionPromise;
|
||||||
|
|
||||||
|
if (!customElements.get("popup-card"))
|
||||||
|
customElements.define("popup-card", PopupCard);
|
||||||
|
})();
|
@ -12,6 +12,8 @@ class BrowserModPopup extends LitElement {
|
|||||||
@property() right_button;
|
@property() right_button;
|
||||||
@property() left_button;
|
@property() left_button;
|
||||||
@property() dismissable;
|
@property() dismissable;
|
||||||
|
@property({ reflect: true }) wide;
|
||||||
|
@property({ reflect: true }) fullscreen;
|
||||||
_actions;
|
_actions;
|
||||||
timeout;
|
timeout;
|
||||||
_timeoutStart;
|
_timeoutStart;
|
||||||
@ -47,6 +49,7 @@ class BrowserModPopup extends LitElement {
|
|||||||
dismiss_action = undefined,
|
dismiss_action = undefined,
|
||||||
timeout = undefined,
|
timeout = undefined,
|
||||||
timeout_action = undefined,
|
timeout_action = undefined,
|
||||||
|
size = undefined,
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@ -76,6 +79,8 @@ class BrowserModPopup extends LitElement {
|
|||||||
dismiss_action,
|
dismiss_action,
|
||||||
timeout_action,
|
timeout_action,
|
||||||
};
|
};
|
||||||
|
this.wide = size === "wide" ? "" : undefined;
|
||||||
|
this.fullscreen = size === "fullscreen" ? "" : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
_primary() {
|
_primary() {
|
||||||
|
@ -68,10 +68,7 @@ export const ServicesMixin = (SuperClass) => {
|
|||||||
];
|
];
|
||||||
for (const service of cmds) {
|
for (const service of cmds) {
|
||||||
this.addEventListener(`command-${service}`, (ev) => {
|
this.addEventListener(`command-${service}`, (ev) => {
|
||||||
this._service_action({
|
this.service(service, ev.detail);
|
||||||
service,
|
|
||||||
data: ev.detail,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +79,10 @@ export const ServicesMixin = (SuperClass) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async service(service, data) {
|
||||||
|
this._service_action({ service, data });
|
||||||
|
}
|
||||||
|
|
||||||
async _service_action({ service, data }) {
|
async _service_action({ service, data }) {
|
||||||
let _service: String = service;
|
let _service: String = service;
|
||||||
if (!_service.startsWith("browser_mod.") && _service.includes(".")) {
|
if (!_service.startsWith("browser_mod.") && _service.includes(".")) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user