Easier to use frontend options. Set sidebar title.
This commit is contained in:
parent
39f727206f
commit
fffb017287
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -5,7 +5,7 @@
|
|||||||
"dependencies": ["panel_custom", "websocket_api", "http", "frontend", "lovelace"],
|
"dependencies": ["panel_custom", "websocket_api", "http", "frontend", "lovelace"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "2.0.0b4",
|
"version": "2.0.0b5",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"config_flow": true
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ class SettingsStoreData:
|
|||||||
defaultPanel = attr.ib(type=str, default=None)
|
defaultPanel = attr.ib(type=str, default=None)
|
||||||
sidebarPanelOrder = attr.ib(type=list, default=None)
|
sidebarPanelOrder = attr.ib(type=list, default=None)
|
||||||
sidebarHiddenPanels = attr.ib(type=list, default=None)
|
sidebarHiddenPanels = attr.ib(type=list, default=None)
|
||||||
|
sidebarTitle = attr.ib(type=str, default=None)
|
||||||
faviconTemplate = attr.ib(type=str, default=None)
|
faviconTemplate = attr.ib(type=str, default=None)
|
||||||
titleTemplate = attr.ib(type=str, default=None)
|
titleTemplate = attr.ib(type=str, default=None)
|
||||||
|
|
||||||
|
294
js/config_panel/browser-mod-settings-table.ts
Normal file
294
js/config_panel/browser-mod-settings-table.ts
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import { LitElement, html, css } from "lit";
|
||||||
|
import { property } from "lit/decorators.js";
|
||||||
|
import { selectTree } from "../helpers";
|
||||||
|
|
||||||
|
class BrowserModSettingsTable extends LitElement {
|
||||||
|
@property() settingKey;
|
||||||
|
@property() settingSelector = {
|
||||||
|
template: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
@property() hass;
|
||||||
|
@property() default;
|
||||||
|
|
||||||
|
@property() tableData = [];
|
||||||
|
|
||||||
|
_users = undefined;
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
window.browser_mod.addEventListener("browser-mod-config-update", () =>
|
||||||
|
this.updateTable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
if (changedProperties.has("settingKey")) this.updateTable();
|
||||||
|
if (
|
||||||
|
changedProperties.has("hass") &&
|
||||||
|
changedProperties.get("hass") === undefined
|
||||||
|
)
|
||||||
|
this.updateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchUsers(): Promise<any[]> {
|
||||||
|
if (this._users === undefined)
|
||||||
|
this._users = await this.hass.callWS({ type: "config/auth/list" });
|
||||||
|
return this._users;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSetting(type, target) {
|
||||||
|
const clearSettingCallback = async () => {
|
||||||
|
if (this.settingKey === "sidebarPanelOrder") {
|
||||||
|
const sideBar: any = await selectTree(
|
||||||
|
document,
|
||||||
|
"home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar"
|
||||||
|
);
|
||||||
|
window.browser_mod.setSetting(type, target, {
|
||||||
|
sidebarHiddenPanels: "[]",
|
||||||
|
sidebarPanelOrder: "[]",
|
||||||
|
});
|
||||||
|
window.browser_mod.setSetting(type, target, {
|
||||||
|
sidebarHiddenPanels: undefined,
|
||||||
|
sidebarPanelOrder: undefined,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.default)
|
||||||
|
window.browser_mod.setSetting(type, target, {
|
||||||
|
[this.settingKey]: this.default,
|
||||||
|
});
|
||||||
|
window.browser_mod.setSetting(type, target, {
|
||||||
|
[this.settingKey]: undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.browser_mod?.showPopup(
|
||||||
|
"Are you sure",
|
||||||
|
"Do you wish to clear this setting?",
|
||||||
|
{
|
||||||
|
right_button: "Yes",
|
||||||
|
right_button_action: clearSettingCallback,
|
||||||
|
left_button: "No",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSetting(type, target) {
|
||||||
|
const changeSettingCallback = async (newValue) => {
|
||||||
|
if (this.settingKey === "sidebarPanelOrder") {
|
||||||
|
const sideBar: any = await selectTree(
|
||||||
|
document,
|
||||||
|
"home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar"
|
||||||
|
);
|
||||||
|
|
||||||
|
window.browser_mod.setSetting(type, target, {
|
||||||
|
sidebarHiddenPanels: JSON.stringify(sideBar._hiddenPanels),
|
||||||
|
sidebarPanelOrder: JSON.stringify(sideBar._panelOrder),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(sideBar._hiddenPanels, sideBar._panelOrder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value = newValue.value;
|
||||||
|
window.browser_mod.setSetting(type, target, { [this.settingKey]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const settings = window.browser_mod?.getSetting?.(this.settingKey);
|
||||||
|
const def =
|
||||||
|
(type === "global" ? settings.global : settings[type][target]) ??
|
||||||
|
this.default;
|
||||||
|
window.browser_mod?.showPopup(
|
||||||
|
"Change value",
|
||||||
|
(this.settingSelector as any).plaintext ?? [
|
||||||
|
{
|
||||||
|
name: "value",
|
||||||
|
label: (this.settingSelector as any).label ?? "",
|
||||||
|
default: def,
|
||||||
|
selector: this.settingSelector,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
right_button: "OK",
|
||||||
|
right_button_action: changeSettingCallback,
|
||||||
|
left_button: "Cancel",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addBrowserSetting() {
|
||||||
|
const settings = window.browser_mod?.getSetting?.(this.settingKey);
|
||||||
|
const allBrowsers = window.browser_mod._data.browsers;
|
||||||
|
const browsers = [];
|
||||||
|
for (const target of Object.keys(allBrowsers)) {
|
||||||
|
if (settings.browser[target] == null) browsers.push(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browsers.length === 0) {
|
||||||
|
window.browser_mod.showPopup(
|
||||||
|
"No browsers to configure",
|
||||||
|
"All registered browsers have already been configured.",
|
||||||
|
{ right_button: "OK" }
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.browser_mod.showPopup(
|
||||||
|
"Select browser to configure",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "browser",
|
||||||
|
label: "",
|
||||||
|
selector: {
|
||||||
|
select: { options: browsers },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
right_button: "Next",
|
||||||
|
right_button_action: (value) =>
|
||||||
|
this.changeSetting("browser", value.browser),
|
||||||
|
left_button: "Cancel",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addUserSetting() {
|
||||||
|
const settings = window.browser_mod?.getSetting?.(this.settingKey);
|
||||||
|
const allUsers = await this.fetchUsers();
|
||||||
|
const users = [];
|
||||||
|
for (const target of allUsers) {
|
||||||
|
if (target.username && settings.user[target.id] == null)
|
||||||
|
users.push({ label: target.name, value: target.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
window.browser_mod.showPopup(
|
||||||
|
"No users to configure",
|
||||||
|
"All users have already been configured.",
|
||||||
|
{ right_button: "OK" }
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.browser_mod.showPopup(
|
||||||
|
"Select user to configure",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "user",
|
||||||
|
label: "",
|
||||||
|
selector: {
|
||||||
|
select: { options: users },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
right_button: "Next",
|
||||||
|
right_button_action: (value) => this.changeSetting("user", value.user),
|
||||||
|
left_button: "Cancel",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateTable() {
|
||||||
|
if (this.hass === undefined) return;
|
||||||
|
const users = await this.fetchUsers();
|
||||||
|
const settings = window.browser_mod?.getSetting?.(this.settingKey);
|
||||||
|
const data = [];
|
||||||
|
for (const [k, v] of Object.entries(settings.user)) {
|
||||||
|
const user = users.find((usr) => usr.id === k);
|
||||||
|
data.push({
|
||||||
|
name: `User: ${user.name}`,
|
||||||
|
value: String(v),
|
||||||
|
controls: html`
|
||||||
|
<ha-icon-button @click=${() => this.changeSetting("user", k)}>
|
||||||
|
<ha-icon .icon=${"mdi:pencil"} style="display:flex;"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
<ha-icon-button @click=${() => this.clearSetting("user", k)}>
|
||||||
|
<ha-icon .icon=${"mdi:delete"} style="display:flex;"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
name: "",
|
||||||
|
value: html`
|
||||||
|
<mwc-button @click=${() => this.addUserSetting()}>
|
||||||
|
<ha-icon .icon=${"mdi:plus"}></ha-icon>
|
||||||
|
Add user setting
|
||||||
|
</mwc-button>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(settings.browser)) {
|
||||||
|
data.push({
|
||||||
|
name: `Browser: ${k}`,
|
||||||
|
value: String(v),
|
||||||
|
controls: html`
|
||||||
|
<ha-icon-button @click=${() => this.changeSetting("browser", k)}>
|
||||||
|
<ha-icon .icon=${"mdi:pencil"} style="display:flex;"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
<ha-icon-button @click=${() => this.clearSetting("browser", k)}>
|
||||||
|
<ha-icon .icon=${"mdi:delete"} style="display:flex;"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
name: "",
|
||||||
|
value: html`
|
||||||
|
<mwc-button @click=${() => this.addBrowserSetting()}>
|
||||||
|
<ha-icon .icon=${"mdi:plus"}></ha-icon>
|
||||||
|
Add browser setting
|
||||||
|
</mwc-button>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
name: "GLOBAL",
|
||||||
|
value:
|
||||||
|
settings.global != null
|
||||||
|
? String(settings.global)
|
||||||
|
: html`<span style="color: var(--warning-color);">DEFAULT</span>`,
|
||||||
|
controls: html`
|
||||||
|
<ha-icon-button @click=${() => this.changeSetting("global", null)}>
|
||||||
|
<ha-icon .icon=${"mdi:pencil"} style="display:flex;"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
<ha-icon-button @click=${() => this.clearSetting("global", null)}>
|
||||||
|
<ha-icon .icon=${"mdi:delete"} style="display:flex;"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
this.tableData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const global = window.browser_mod?.global_settings?.[this.settingKey];
|
||||||
|
const columns = {
|
||||||
|
name: {
|
||||||
|
title: "Name",
|
||||||
|
grows: true,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
title: "Value",
|
||||||
|
grows: true,
|
||||||
|
},
|
||||||
|
controls: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-data-table .columns=${columns} .data=${this.tableData} auto-height>
|
||||||
|
</ha-data-table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("browser-mod-settings-table", BrowserModSettingsTable);
|
@ -136,7 +136,7 @@ class BrowserModRegisteredBrowsersCard extends LitElement {
|
|||||||
private _renderInteractionAlert() {
|
private _renderInteractionAlert() {
|
||||||
return html`
|
return html`
|
||||||
<ha-alert title="Interaction requirement">
|
<ha-alert title="Interaction requirement">
|
||||||
For security reasons many browsers require the user to interact with a
|
For privacy reasons many browsers require the user to interact with a
|
||||||
webpage before allowing audio playback or video capture. This may affect
|
webpage before allowing audio playback or video capture. This may affect
|
||||||
the
|
the
|
||||||
<code>media_player</code> and <code>camera</code> components of Browser
|
<code>media_player</code> and <code>camera</code> components of Browser
|
||||||
|
@ -1,38 +1,73 @@
|
|||||||
import { LitElement, html, css } from "lit";
|
import { LitElement, html, css } from "lit";
|
||||||
import { property, state } from "lit/decorators.js";
|
import { property, state } from "lit/decorators.js";
|
||||||
import { loadDeveloperToolsTemplate } from "../helpers";
|
import { loadDeveloperToolsTemplate, selectTree } from "../helpers";
|
||||||
|
|
||||||
|
import "./browser-mod-settings-table";
|
||||||
|
|
||||||
loadDeveloperToolsTemplate();
|
loadDeveloperToolsTemplate();
|
||||||
|
|
||||||
class BrowserModFrontendSettingsCard extends LitElement {
|
class BrowserModFrontendSettingsCard extends LitElement {
|
||||||
@property() hass;
|
@property() hass;
|
||||||
|
|
||||||
@state() _selectedTab = 0;
|
@state() _dashboards = [];
|
||||||
|
|
||||||
|
@state() _editSidebar = false;
|
||||||
|
_savedSidebar = { panelOrder: [], hiddenPanels: [] };
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
window.browser_mod.addEventListener("browser-mod-config-update", () =>
|
window.browser_mod.addEventListener("browser-mod-config-update", () =>
|
||||||
this.requestUpdate()
|
this.requestUpdate()
|
||||||
);
|
);
|
||||||
window.browser_mod.addEventListener("browser-mod-favicon-update", () =>
|
|
||||||
this.requestUpdate()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleSwitchTab(ev: CustomEvent) {
|
updated(changedProperties) {
|
||||||
this._selectedTab = parseInt(ev.detail.index, 10);
|
if (
|
||||||
|
changedProperties.has("hass") &&
|
||||||
|
changedProperties.get("hass") === undefined
|
||||||
|
) {
|
||||||
|
(async () =>
|
||||||
|
(this._dashboards = await this.hass.callWS({
|
||||||
|
type: "lovelace/dashboards/list",
|
||||||
|
})))();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleEditSidebar() {
|
||||||
|
const sideBar: any = await selectTree(
|
||||||
|
document,
|
||||||
|
"home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar"
|
||||||
|
);
|
||||||
|
sideBar.editMode = !sideBar.editMode;
|
||||||
|
this._editSidebar = sideBar.editMode;
|
||||||
|
if (this._editSidebar) {
|
||||||
|
this._savedSidebar = {
|
||||||
|
panelOrder: sideBar._panelOrder,
|
||||||
|
hiddenPanels: sideBar._hiddenPanels,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
sideBar._panelOrder = this._savedSidebar.panelOrder ?? [];
|
||||||
|
sideBar._hiddenPanels = this._savedSidebar.hiddenPanels ?? [];
|
||||||
|
this._savedSidebar = { panelOrder: [], hiddenPanels: [] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const level = ["user", "browser", "global"][this._selectedTab];
|
const db = this._dashboards.map((d) => {
|
||||||
|
return { value: d.url_path, label: d.title };
|
||||||
|
});
|
||||||
|
const dashboardSelector = {
|
||||||
|
select: {
|
||||||
|
options: [{ value: "lovelace", label: "lovelace (default)" }, ...db],
|
||||||
|
custom_value: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
return html`
|
return html`
|
||||||
<ha-card header="Frontend Settings" outlined>
|
<ha-card header="Frontend Settings" outlined>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-alert alert-type="warning">
|
<ha-alert alert-type="warning" title="Please note:">
|
||||||
<p>
|
The settings in this section severely change the way the Home
|
||||||
Please note: The settings in this section severely change the way the Home
|
|
||||||
Assistant frontend works and looks. It is very easy to forget that
|
Assistant frontend works and looks. It is very easy to forget that
|
||||||
you made a setting here when you switch devices or user.
|
you made a setting here when you switch devices or user.
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
Do not report any issues to Home Assistant before clearing
|
Do not report any issues to Home Assistant before clearing
|
||||||
<b>EVERY</b> setting here and thouroghly clearing all your browser
|
<b>EVERY</b> setting here and thouroghly clearing all your browser
|
||||||
@ -41,234 +76,119 @@ class BrowserModFrontendSettingsCard extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
</ha-alert>
|
</ha-alert>
|
||||||
<p>
|
<p>
|
||||||
Global settings are applied for all users and browsers.</br>
|
Settings below are applied by first match. I.e. if a matching User
|
||||||
User settings are applied to the current user and overrides any Global settings.</br>
|
setting exists, it will be applied. Otherwise any matching Browser
|
||||||
Browser settings are applied for the current browser and overrides any User or Global settings.
|
setting and otherwise the GLOBAL setting if that differs from
|
||||||
|
DEFAULT.
|
||||||
</p>
|
</p>
|
||||||
<mwc-tab-bar
|
|
||||||
.activeIndex=${this._selectedTab}
|
|
||||||
@MDCTabBar:activated=${this._handleSwitchTab}
|
|
||||||
>
|
|
||||||
<mwc-tab .label=${"User (" + this.hass.user.name + ")"}></mwc-tab>
|
|
||||||
<ha-icon .icon=${"mdi:chevron-double-right"}></ha-icon>
|
|
||||||
<mwc-tab .label=${"Browser"}></mwc-tab>
|
|
||||||
<ha-icon .icon=${"mdi:chevron-double-right"}></ha-icon>
|
|
||||||
<mwc-tab .label=${"Global"}></mwc-tab>
|
|
||||||
</mwc-tab-bar>
|
|
||||||
|
|
||||||
${this._render_settings(level)}
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_render_settings(level) {
|
|
||||||
const global = window.browser_mod.global_settings;
|
|
||||||
const browser = window.browser_mod.browser_settings;
|
|
||||||
const user = window.browser_mod.user_settings;
|
|
||||||
const current = { global, browser, user }[level];
|
|
||||||
|
|
||||||
const DESC_BOOLEAN = (val) =>
|
|
||||||
({ true: "Enabled", false: "Disabled", undefined: "Unset" }[String(val)]);
|
|
||||||
const DESC_SET_UNSET = (val) => (val === undefined ? "Unset" : "Set");
|
|
||||||
const OVERRIDDEN = (key) => {
|
|
||||||
if (level !== "browser" && browser[key] !== undefined)
|
|
||||||
return html`<br />Overridden by browser setting`;
|
|
||||||
if (level === "global" && user[key] !== undefined)
|
|
||||||
return html`<br />Overridden by user setting`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="box">
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading">Favicon template</span>
|
|
||||||
${OVERRIDDEN("faviconTemplate")}
|
|
||||||
<img src="${window.browser_mod._currentFavicon}" class="favicon" />
|
|
||||||
</ha-settings-row>
|
|
||||||
<ha-code-editor
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${current.faviconTemplate}
|
|
||||||
@value-changed=${(ev) => {
|
|
||||||
const tpl = ev.detail.value || undefined;
|
|
||||||
window.browser_mod.set_setting("faviconTemplate", tpl, level);
|
|
||||||
}}
|
|
||||||
></ha-code-editor>
|
|
||||||
<ha-settings-row>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() =>
|
|
||||||
window.browser_mod.set_setting(
|
|
||||||
"faviconTemplate",
|
|
||||||
undefined,
|
|
||||||
level
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</mwc-button>
|
|
||||||
</ha-settings-row>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading">Title template</span>
|
<span slot="heading">Title template</span>
|
||||||
${OVERRIDDEN("titleTemplate")}
|
<span slot="description">
|
||||||
|
Jinja template for the browser window/tab title
|
||||||
|
</span>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
<ha-code-editor
|
<browser-mod-settings-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${current.titleTemplate}
|
.settingKey=${"titleTemplate"}
|
||||||
@value-changed=${(ev) => {
|
></browser-mod-settings-table>
|
||||||
const tpl = ev.detail.value || undefined;
|
|
||||||
window.browser_mod.set_setting("titleTemplate", tpl, level);
|
|
||||||
}}
|
|
||||||
></ha-code-editor>
|
|
||||||
<ha-settings-row>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() =>
|
|
||||||
window.browser_mod.set_setting("titleTemplate", undefined, level)}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</mwc-button>
|
|
||||||
</ha-settings-row>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading">Hide Sidebar</span>
|
<span slot="heading">Favicon template</span>
|
||||||
<span slot="description">Hide the sidebar and hamburger menu</span>
|
<span slot="description">
|
||||||
Currently: ${DESC_BOOLEAN(current.hideSidebar)}
|
Jinja template for the browser favicon
|
||||||
${OVERRIDDEN("hideSidebar")}
|
</span>
|
||||||
</ha-settings-row>
|
|
||||||
<ha-settings-row>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() =>
|
|
||||||
window.browser_mod.set_setting("hideSidebar", true, level)}
|
|
||||||
>
|
|
||||||
Enable
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() =>
|
|
||||||
window.browser_mod.set_setting("hideSidebar", false, level)}
|
|
||||||
>
|
|
||||||
Disable
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() =>
|
|
||||||
window.browser_mod.set_setting("hideSidebar", undefined, level)}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</mwc-button>
|
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
|
<browser-mod-settings-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.settingKey=${"faviconTemplate"}
|
||||||
|
></browser-mod-settings-table>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading">Hide Header</span>
|
<span slot="heading">Hide sidebar</span>
|
||||||
<span slot="description">Hide the header on all pages</span>
|
<span slot="description">
|
||||||
Currently: ${DESC_BOOLEAN(current.hideHeader)}
|
Completely remove the sidebar from all panels
|
||||||
${OVERRIDDEN("hideHeader")}
|
</span>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
|
<browser-mod-settings-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.settingKey=${"hideSidebar"}
|
||||||
|
.settingSelector=${{ boolean: {}, label: "Hide sidebar" }}
|
||||||
|
></browser-mod-settings-table>
|
||||||
|
|
||||||
|
<div class="separator"></div>
|
||||||
|
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<mwc-button
|
<span slot="heading">Hide header</span>
|
||||||
@click=${() =>
|
<span slot="description">
|
||||||
window.browser_mod.set_setting("hideHeader", true, level)}
|
Completely remove the header from all panels
|
||||||
>
|
</span>
|
||||||
Enable
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() =>
|
|
||||||
window.browser_mod.set_setting("hideHeader", false, level)}
|
|
||||||
>
|
|
||||||
Disable
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() =>
|
|
||||||
window.browser_mod.set_setting("hideHeader", undefined, level)}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</mwc-button>
|
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
|
<browser-mod-settings-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.settingKey=${"hideHeader"}
|
||||||
|
.settingSelector=${{ boolean: {}, label: "Hide header" }}
|
||||||
|
></browser-mod-settings-table>
|
||||||
|
|
||||||
|
<div class="separator"></div>
|
||||||
|
|
||||||
|
<ha-settings-row>
|
||||||
|
<span slot="heading">Default dashboard</span>
|
||||||
|
<span slot="description">
|
||||||
|
The dashboard that is showed when navigating to
|
||||||
|
${location.origin}/
|
||||||
|
</span>
|
||||||
|
</ha-settings-row>
|
||||||
|
<browser-mod-settings-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.settingKey=${"defaultPanel"}
|
||||||
|
.settingSelector=${dashboardSelector}
|
||||||
|
.default=${"lovelace"}
|
||||||
|
></browser-mod-settings-table>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading">Sidebar order</span>
|
<span slot="heading">Sidebar order</span>
|
||||||
<span slot="description">
|
<span slot="description">
|
||||||
Order and visibility of sidebar buttons
|
Order and visibility of sidebar items. <br />Click EDIT and set
|
||||||
|
the sidebar up as you want. Then save the settings and finally
|
||||||
|
click RESTORE.
|
||||||
</span>
|
</span>
|
||||||
Currently: ${DESC_SET_UNSET(current.sidebarPanelOrder)}
|
<mwc-button @click=${() => this.toggleEditSidebar()}>
|
||||||
${OVERRIDDEN("sidebarPanelOrder")}
|
${this._editSidebar ? "Restore" : "Edit"}
|
||||||
</ha-settings-row>
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="description">
|
|
||||||
Clearing this does NOT restore the original default order.
|
|
||||||
</span>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() => {
|
|
||||||
window.browser_mod.set_setting(
|
|
||||||
"sidebarPanelOrder",
|
|
||||||
localStorage.getItem("sidebarPanelOrder"),
|
|
||||||
level
|
|
||||||
);
|
|
||||||
window.browser_mod.set_setting(
|
|
||||||
"sidebarHiddenPanels",
|
|
||||||
localStorage.getItem("sidebarHiddenPanels"),
|
|
||||||
level
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Set
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() => {
|
|
||||||
window.browser_mod.set_setting(
|
|
||||||
"sidebarPanelOrder",
|
|
||||||
undefined,
|
|
||||||
level
|
|
||||||
);
|
|
||||||
window.browser_mod.set_setting(
|
|
||||||
"sidebarHiddenPanels",
|
|
||||||
undefined,
|
|
||||||
level
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
|
<browser-mod-settings-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.settingKey=${"sidebarPanelOrder"}
|
||||||
|
.settingSelector=${{
|
||||||
|
plaintext: "Press OK to store the current sidebar order",
|
||||||
|
}}
|
||||||
|
.default=${"lovelace"}
|
||||||
|
></browser-mod-settings-table>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading">Default dashboard</span>
|
<span slot="heading">Sidebar title</span>
|
||||||
<span slot="description"
|
|
||||||
>The dashboard that's displayed by default</span
|
|
||||||
>
|
|
||||||
Currently: ${DESC_SET_UNSET(current.defaultPanel)}
|
|
||||||
${OVERRIDDEN("defaultPanel")}
|
|
||||||
</ha-settings-row>
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="description">
|
<span slot="description">
|
||||||
Clearing this does NOT restore the original default dashboard.
|
The title at the top of the sidebar
|
||||||
</span>
|
</span>
|
||||||
<mwc-button
|
|
||||||
@click=${() => {
|
|
||||||
window.browser_mod.set_setting(
|
|
||||||
"defaultPanel",
|
|
||||||
localStorage.getItem("defaultPanel"),
|
|
||||||
level
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Set
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${() => {
|
|
||||||
window.browser_mod.set_setting("defaultPanel", undefined, level);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</mwc-button>
|
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
|
<browser-mod-settings-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.settingKey=${"sidebarTitle"}
|
||||||
|
.settingSelector=${{ text: {} }}
|
||||||
|
></browser-mod-settings-table>
|
||||||
</div>
|
</div>
|
||||||
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +200,7 @@ class BrowserModFrontendSettingsCard extends LitElement {
|
|||||||
}
|
}
|
||||||
.separator {
|
.separator {
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
margin: 0 -8px;
|
margin: 16px -16px 0px;
|
||||||
}
|
}
|
||||||
img.favicon {
|
img.favicon {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
|
@ -15,12 +15,13 @@ loadConfigDashboard().then(() => {
|
|||||||
@property() connection;
|
@property() connection;
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
window.browser_mod.addEventListener("browser-mod-config-update", () =>
|
window.addEventListener("browser-mod-config-update", () =>
|
||||||
this.requestUpdate()
|
this.requestUpdate()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (!window.browser_mod) return html``;
|
||||||
return html`
|
return html`
|
||||||
<ha-app-layout>
|
<ha-app-layout>
|
||||||
<app-header slot="header" fixed>
|
<app-header slot="header" fixed>
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
const TIMEOUT_ERROR = "SELECTTREE-TIMEOUT";
|
const TIMEOUT_ERROR = "SELECTTREE-TIMEOUT";
|
||||||
|
|
||||||
async function _await_el(el) {
|
export async function await_element(el, hard = false) {
|
||||||
if (el.localName?.includes("-"))
|
if (el.localName?.includes("-"))
|
||||||
await customElements.whenDefined(el.localName);
|
await customElements.whenDefined(el.localName);
|
||||||
if (el.updateComplete) await el.updateComplete;
|
if (el.updateComplete) await el.updateComplete;
|
||||||
|
if (hard) {
|
||||||
|
if (el.pageRendered) await el.pageRendered;
|
||||||
|
if (el._panelState) {
|
||||||
|
let rounds = 0;
|
||||||
|
while (el._panelState !== "loaded" && rounds++ < 5)
|
||||||
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _selectTree(root, path, all = false) {
|
async function _selectTree(root, path, all = false) {
|
||||||
@ -18,7 +26,7 @@ async function _selectTree(root, path, all = false) {
|
|||||||
|
|
||||||
if (!p.trim().length) continue;
|
if (!p.trim().length) continue;
|
||||||
|
|
||||||
_await_el(e);
|
await_element(e);
|
||||||
el = p === "$" ? [e.shadowRoot] : e.querySelectorAll(p);
|
el = p === "$" ? [e.shadowRoot] : e.querySelectorAll(p);
|
||||||
}
|
}
|
||||||
return all ? el : el[0];
|
return all ? el : el[0];
|
||||||
@ -102,6 +110,7 @@ export const loadConfigDashboard = async () => {
|
|||||||
const configRouter: any = document.createElement("ha-panel-config");
|
const configRouter: any = document.createElement("ha-panel-config");
|
||||||
await configRouter?.routerOptions?.routes?.dashboard?.load?.(); // Load ha-config-dashboard
|
await configRouter?.routerOptions?.routes?.dashboard?.load?.(); // Load ha-config-dashboard
|
||||||
await configRouter?.routerOptions?.routes?.cloud?.load?.(); // Load ha-settings-row
|
await configRouter?.routerOptions?.routes?.cloud?.load?.(); // Load ha-settings-row
|
||||||
|
await configRouter?.routerOptions?.routes?.entities?.load?.(); // Load ha-data-table
|
||||||
await customElements.whenDefined("ha-config-dashboard");
|
await customElements.whenDefined("ha-config-dashboard");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,3 +141,32 @@ export function throttle(timeout) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function runOnce(restart = false) {
|
||||||
|
return function (target, propertyKey, descriptor) {
|
||||||
|
const fn = descriptor.value;
|
||||||
|
let running = undefined;
|
||||||
|
const newfn = function (...rest) {
|
||||||
|
if (restart && running === false) running = true;
|
||||||
|
if (running !== undefined) return;
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
const retval = fn.bind(this)(...rest);
|
||||||
|
if (running) {
|
||||||
|
running = undefined;
|
||||||
|
return newfn.bind(this)(...rest);
|
||||||
|
} else {
|
||||||
|
running = undefined;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
descriptor.value = newfn;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitRepeat(fn, times, delay) {
|
||||||
|
while (times--) {
|
||||||
|
fn();
|
||||||
|
await new Promise((r) => setTimeout(r, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,7 +24,7 @@ export const ConnectionMixin = (SuperClass) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fireEvent(event, detail = undefined) {
|
private fireEvent(event, detail = undefined) {
|
||||||
this.dispatchEvent(new CustomEvent(event, { detail }));
|
this.dispatchEvent(new CustomEvent(event, { detail, bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
private incoming_message(msg) {
|
private incoming_message(msg) {
|
||||||
|
@ -1,25 +1,37 @@
|
|||||||
import { selectTree } from "../helpers";
|
import { await_element, waitRepeat, runOnce, selectTree } from "../helpers";
|
||||||
|
|
||||||
export const AutoSettingsMixin = (SuperClass) => {
|
export const AutoSettingsMixin = (SuperClass) => {
|
||||||
return class AutoSettingsMixinClass extends SuperClass {
|
class AutoSettingsMixinClass extends SuperClass {
|
||||||
_faviconTemplateSubscription;
|
_faviconTemplateSubscription;
|
||||||
_titleTemplateSubscription;
|
_titleTemplateSubscription;
|
||||||
__currentTitle = undefined;
|
__currentTitle = undefined;
|
||||||
|
|
||||||
|
@runOnce()
|
||||||
|
async runHideHeader() {
|
||||||
|
while (!(await this._hideHeader()))
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
@runOnce(true)
|
||||||
|
async runUpdateTitle() {
|
||||||
|
await waitRepeat(() => this._updateTitle(), 3, 500);
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._auto_settings_setup();
|
const runUpdates = async () => {
|
||||||
this.addEventListener("browser-mod-config-update", () =>
|
this.runUpdateTitle();
|
||||||
this._auto_settings_setup()
|
this.runHideHeader();
|
||||||
);
|
};
|
||||||
|
|
||||||
window.addEventListener("location-changed", () => {
|
this._auto_settings_setup();
|
||||||
this._updateTitle();
|
this.addEventListener("browser-mod-config-update", () => {
|
||||||
setTimeout(() => this._updateTitle(), 500);
|
this._auto_settings_setup();
|
||||||
setTimeout(() => this._updateTitle(), 1000);
|
runUpdates();
|
||||||
setTimeout(() => this._updateTitle(), 5000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener("location-changed", runUpdates);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _auto_settings_setup() {
|
async _auto_settings_setup() {
|
||||||
@ -40,7 +52,7 @@ export const AutoSettingsMixin = (SuperClass) => {
|
|||||||
|
|
||||||
// Default panel
|
// Default panel
|
||||||
if (settings.defaultPanel) {
|
if (settings.defaultPanel) {
|
||||||
localStorage.setItem("defaultPanel", settings.defaultPanel);
|
localStorage.setItem("defaultPanel", `"${settings.defaultPanel}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide sidebar
|
// Hide sidebar
|
||||||
@ -55,18 +67,18 @@ export const AutoSettingsMixin = (SuperClass) => {
|
|||||||
).then((el) => el?.remove?.());
|
).then((el) => el?.remove?.());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide header
|
// Sidebar title
|
||||||
if (settings.hideHeader === true) {
|
if (settings.sidebarTitle) {
|
||||||
customElements.whenDefined("app-header-layout").then(() => {
|
selectTree(
|
||||||
const appHeader = customElements.get("app-header").prototype;
|
document,
|
||||||
const _attached = appHeader.attached;
|
"home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar $ .title"
|
||||||
appHeader.attached = function () {
|
).then((el) => {
|
||||||
_attached.bind(this)();
|
if (el) (el as HTMLElement).innerHTML = settings.sidebarTitle;
|
||||||
this.style.setProperty("display", "none");
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide header
|
||||||
|
|
||||||
// Favicon template
|
// Favicon template
|
||||||
if (settings.faviconTemplate !== undefined) {
|
if (settings.faviconTemplate !== undefined) {
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -111,7 +123,6 @@ export const AutoSettingsMixin = (SuperClass) => {
|
|||||||
_updateFavicon({ result }) {
|
_updateFavicon({ result }) {
|
||||||
const link: any = document.head.querySelector("link[rel~='icon']");
|
const link: any = document.head.querySelector("link[rel~='icon']");
|
||||||
link.href = result;
|
link.href = result;
|
||||||
window.browser_mod.fireEvent("browser-mod-favicon-update");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get _currentTitle() {
|
get _currentTitle() {
|
||||||
@ -121,7 +132,77 @@ export const AutoSettingsMixin = (SuperClass) => {
|
|||||||
_updateTitle(data = undefined) {
|
_updateTitle(data = undefined) {
|
||||||
if (data) this.__currentTitle = data.result;
|
if (data) this.__currentTitle = data.result;
|
||||||
if (this.__currentTitle) document.title = this.__currentTitle;
|
if (this.__currentTitle) document.title = this.__currentTitle;
|
||||||
window.browser_mod.fireEvent("browser-mod-favicon-update");
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
async _hideHeader() {
|
||||||
|
if (this.settings.hideHeader !== true) return true;
|
||||||
|
let el = await selectTree(
|
||||||
|
document,
|
||||||
|
"home-assistant $ home-assistant-main $ app-drawer-layout partial-panel-resolver"
|
||||||
|
);
|
||||||
|
if (!el) return false;
|
||||||
|
let steps = 0;
|
||||||
|
while (el && el.localName !== "ha-app-layout" && steps++ < 5) {
|
||||||
|
await await_element(el, true);
|
||||||
|
const next =
|
||||||
|
el.querySelector("ha-app-layout") ??
|
||||||
|
el.firstElementChild ??
|
||||||
|
el.shadowRoot;
|
||||||
|
el = next;
|
||||||
|
}
|
||||||
|
if (el?.localName !== "ha-app-layout") return false;
|
||||||
|
if (el.header) {
|
||||||
|
el.header.style.setProperty("display", "none");
|
||||||
|
setTimeout(() => el._updateLayoutStates(), 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSetting(key) {
|
||||||
|
const retval = { global: undefined, browser: {}, user: {} };
|
||||||
|
retval.global = this._data.settings?.[key];
|
||||||
|
for (const [k, v] of Object.entries(this._data.browsers ?? {})) {
|
||||||
|
if ((v as any).settings?.[key] != null)
|
||||||
|
retval.browser[k] = (v as any).settings[key];
|
||||||
|
}
|
||||||
|
for (const [k, v] of Object.entries(this._data.user_settings ?? {})) {
|
||||||
|
if (v[key] != null) retval.user[k] = v[key];
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSetting(type, target, settings) {
|
||||||
|
if (type === "global") {
|
||||||
|
for (const [key, value] of Object.entries(settings))
|
||||||
|
this.connection.sendMessage({
|
||||||
|
type: "browser_mod/settings",
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
} else if (type === "browser") {
|
||||||
|
const browser = this._data.browsers[target];
|
||||||
|
const newsettings = { ...browser.settings, ...settings };
|
||||||
|
console.log(newsettings);
|
||||||
|
this.connection.sendMessage({
|
||||||
|
type: "browser_mod/register",
|
||||||
|
browserID: target,
|
||||||
|
data: {
|
||||||
|
...browser,
|
||||||
|
settings: newsettings,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (type === "user") {
|
||||||
|
const user = target;
|
||||||
|
for (const [key, value] of Object.entries(settings))
|
||||||
|
this.connection.sendMessage({
|
||||||
|
type: "browser_mod/settings",
|
||||||
|
user,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AutoSettingsMixinClass;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "browser_mod",
|
"name": "browser_mod",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.0b4",
|
"version": "2.0.0b5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
|
@ -17,12 +17,6 @@
|
|||||||
- platform: state
|
- platform: state
|
||||||
entity_id: light.kitchen_lights
|
entity_id: light.kitchen_lights
|
||||||
action:
|
action:
|
||||||
- service: browser_mod.sequence
|
|
||||||
data:
|
|
||||||
sequence:
|
|
||||||
- service: delay
|
|
||||||
data:
|
|
||||||
time: 5000
|
|
||||||
- service: browser_mod.popup
|
- service: browser_mod.popup
|
||||||
data:
|
data:
|
||||||
title: automation
|
title: automation
|
||||||
|
Loading…
x
Reference in New Issue
Block a user