Add iframe strategy (#20068)
* Add iframe strategy with editor * Unify sandbox parameters * Update translations * Remove title from editor * Add editor when creating iframe strategy * Update src/translations/en.json
This commit is contained in:
parent
c30b9cdfcf
commit
90e9f79841
|
@ -1,5 +1,5 @@
|
|||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiMap, mdiPencilOutline, mdiShape } from "@mdi/js";
|
||||
import { mdiMap, mdiPencilOutline, mdiShape, mdiWeb } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
@ -25,6 +25,10 @@ const STRATEGIES = [
|
|||
type: "map",
|
||||
iconPath: mdiMap,
|
||||
},
|
||||
{
|
||||
type: "iframe",
|
||||
iconPath: mdiWeb,
|
||||
},
|
||||
] as const satisfies Strategy[];
|
||||
|
||||
@customElement("ha-dialog-new-dashboard")
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { LovelaceStrategyConfig } from "../../../../data/lovelace/config/strategy";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "../../../lovelace/editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor";
|
||||
import { LovelaceDashboardConfigureStrategyDialogParams } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
||||
|
||||
@customElement("dialog-lovelace-dashboard-configure-strategy")
|
||||
export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: LovelaceDashboardConfigureStrategyDialogParams;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _data?: LovelaceStrategyConfig;
|
||||
|
||||
public showDialog(
|
||||
params: LovelaceDashboardConfigureStrategyDialogParams
|
||||
): void {
|
||||
this._params = params;
|
||||
this._data = params.config.strategy;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this._data = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params || !this._data) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<hui-dashboard-strategy-element-editor
|
||||
.hass=${this.hass}
|
||||
.lovelace=${this._params.config}
|
||||
.value=${this._data}
|
||||
@config-changed=${this._handleConfigChanged}
|
||||
dialogInitialFocus
|
||||
></hui-dashboard-strategy-element-editor>
|
||||
</div>
|
||||
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.next")}
|
||||
</ha-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleConfigChanged(ev: CustomEvent): void {
|
||||
this._data = ev.detail.config;
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
if (!this._data) {
|
||||
return;
|
||||
}
|
||||
this._submitting = true;
|
||||
await this._params!.saveConfig({
|
||||
...this._params!.config,
|
||||
strategy: this._data,
|
||||
});
|
||||
this._submitting = false;
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [haStyleDialog, css``];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-lovelace-dashboard-configure-strategy": DialogLovelaceDashboardDetail;
|
||||
}
|
||||
}
|
|
@ -24,7 +24,8 @@ import "../../../../components/ha-icon-button";
|
|||
import "../../../../components/ha-svg-icon";
|
||||
import { LovelacePanelConfig } from "../../../../data/lovelace";
|
||||
import {
|
||||
LovelaceConfig,
|
||||
LovelaceRawConfig,
|
||||
isStrategyDashboard,
|
||||
saveConfig,
|
||||
} from "../../../../data/lovelace/config/types";
|
||||
import {
|
||||
|
@ -39,8 +40,10 @@ import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-
|
|||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../../types";
|
||||
import { getLovelaceStrategy } from "../../../lovelace/strategies/get-strategy";
|
||||
import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboard";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||
|
||||
type DataTableItem = Pick<
|
||||
|
@ -64,6 +67,12 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||
|
||||
@state() private _dashboards: LovelaceDashboard[] = [];
|
||||
|
||||
public willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
}
|
||||
}
|
||||
|
||||
private _columns = memoize(
|
||||
(narrow: boolean, _language, dashboards): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer<DataTableItem> = {
|
||||
|
@ -339,7 +348,25 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||
|
||||
private async _addDashboard() {
|
||||
showNewDashboardDialog(this, {
|
||||
selectConfig: (config) => {
|
||||
selectConfig: async (config) => {
|
||||
if (config && isStrategyDashboard(config)) {
|
||||
const strategyType = config.strategy.type;
|
||||
const strategyClass = await getLovelaceStrategy(
|
||||
"dashboard",
|
||||
strategyType
|
||||
);
|
||||
|
||||
if (strategyClass.configRequired) {
|
||||
showDashboardConfigureStrategyDialog(this, {
|
||||
config: config,
|
||||
saveConfig: async (updatedConfig) => {
|
||||
this._openDetailDialog(undefined, undefined, updatedConfig);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._openDetailDialog(undefined, undefined, config);
|
||||
},
|
||||
});
|
||||
|
@ -348,7 +375,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||
private async _openDetailDialog(
|
||||
dashboard?: LovelaceDashboard,
|
||||
urlPath?: string,
|
||||
defaultConfig?: LovelaceConfig
|
||||
defaultConfig?: LovelaceRawConfig
|
||||
): Promise<void> {
|
||||
showDashboardDetailDialog(this, {
|
||||
dashboard,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { LovelaceDashboardStrategyConfig } from "../../../../data/lovelace/config/types";
|
||||
|
||||
export interface LovelaceDashboardConfigureStrategyDialogParams {
|
||||
config: LovelaceDashboardStrategyConfig;
|
||||
saveConfig: (values: LovelaceDashboardStrategyConfig) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export const loadDashboardConfigureStrategyDialog = () =>
|
||||
import("./dialog-lovelace-dashboard-configure-strategy");
|
||||
|
||||
export const showDashboardConfigureStrategyDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: LovelaceDashboardConfigureStrategyDialogParams
|
||||
) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-lovelace-dashboard-configure-strategy",
|
||||
dialogImport: loadDashboardConfigureStrategyDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
|
@ -4,6 +4,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
|||
import "../../layouts/hass-error-screen";
|
||||
import "../../layouts/hass-subpage";
|
||||
import { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { IFRAME_SANDBOX } from "../../util/iframe";
|
||||
|
||||
@customElement("ha-panel-iframe")
|
||||
class HaPanelIframe extends LitElement {
|
||||
|
@ -40,7 +41,7 @@ class HaPanelIframe extends LitElement {
|
|||
this.panel.title === null ? undefined : this.panel.title
|
||||
)}
|
||||
src=${this.panel.config.url}
|
||||
sandbox="allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts allow-modals allow-downloads"
|
||||
.sandbox=${IFRAME_SANDBOX}
|
||||
allow="fullscreen"
|
||||
></iframe>
|
||||
</hass-subpage>
|
||||
|
|
|
@ -6,6 +6,7 @@ import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
|||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-card";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { IFRAME_SANDBOX } from "../../../util/iframe";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { IframeCardConfig } from "./types";
|
||||
|
||||
|
@ -96,7 +97,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
|||
<iframe
|
||||
title=${ifDefined(this._config.title)}
|
||||
src=${this._config.url}
|
||||
sandbox="${sandbox_user_params} allow-forms allow-modals allow-popups allow-pointer-lock allow-same-origin allow-scripts"
|
||||
.sandbox=${`${sandbox_user_params} ${IFRAME_SANDBOX}`}
|
||||
allow="fullscreen"
|
||||
></iframe>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { IframeDashboardStrategyConfig } from "../../strategies/iframe/iframe-dashboard-strategy";
|
||||
import { LovelaceStrategyEditor } from "../../strategies/types";
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
name: "url",
|
||||
selector: {
|
||||
text: {
|
||||
type: "url",
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[];
|
||||
|
||||
@customElement("hui-iframe-dashboard-strategy-editor")
|
||||
export class HuiIframeDashboarStrategyEditor
|
||||
extends LitElement
|
||||
implements LovelaceStrategyEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state()
|
||||
private _config?: IframeDashboardStrategyConfig;
|
||||
|
||||
public setConfig(config: IframeDashboardStrategyConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.schema=${SCHEMA}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const data = ev.detail.value;
|
||||
fireEvent(this, "config-changed", { config: data });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
|
||||
switch (schema.name) {
|
||||
case "url":
|
||||
return this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.strategy.iframe.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-iframe-dashboard-strategy-editor": HuiIframeDashboarStrategyEditor;
|
||||
}
|
||||
}
|
|
@ -25,12 +25,14 @@ const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
|
|||
"original-states": () =>
|
||||
import("./original-states/original-states-dashboard-strategy"),
|
||||
map: () => import("./map/map-dashboard-strategy"),
|
||||
iframe: () => import("./iframe/iframe-dashboard-strategy"),
|
||||
},
|
||||
view: {
|
||||
"original-states": () =>
|
||||
import("./original-states/original-states-view-strategy"),
|
||||
energy: () => import("../../energy/strategies/energy-view-strategy"),
|
||||
map: () => import("./map/map-view-strategy"),
|
||||
iframe: () => import("./iframe/iframe-view-strategy"),
|
||||
},
|
||||
section: {},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import { LovelaceStrategyEditor } from "../types";
|
||||
import { IframeViewStrategyConfig } from "./iframe-view-strategy";
|
||||
|
||||
export type IframeDashboardStrategyConfig = IframeViewStrategyConfig;
|
||||
|
||||
@customElement("iframe-dashboard-strategy")
|
||||
export class IframeDashboardStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
config: IframeDashboardStrategyConfig
|
||||
): Promise<LovelaceConfig> {
|
||||
return {
|
||||
title: config.title,
|
||||
views: [
|
||||
{
|
||||
strategy: config,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceStrategyEditor> {
|
||||
await import(
|
||||
"../../editor/dashboard-strategy-editor/hui-iframe-dashboard-strategy-editor"
|
||||
);
|
||||
return document.createElement("hui-iframe-dashboard-strategy-editor");
|
||||
}
|
||||
|
||||
static configRequired = true;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"iframe-dashboard-strategy": IframeDashboardStrategy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import { IframeCardConfig } from "../../cards/types";
|
||||
|
||||
export type IframeViewStrategyConfig = {
|
||||
type: "iframe";
|
||||
url: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
@customElement("iframe-view-strategy")
|
||||
export class IframeViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
config: IframeViewStrategyConfig
|
||||
): Promise<LovelaceViewConfig> {
|
||||
return {
|
||||
type: "panel",
|
||||
title: config.title,
|
||||
cards: [
|
||||
{
|
||||
type: "iframe",
|
||||
url: config.url,
|
||||
} as IframeCardConfig,
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"iframe-view-strategy": IframeViewStrategy;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ export type LovelaceStrategy<T = any> = {
|
|||
generate(config: LovelaceStrategyConfig, hass: HomeAssistant): Promise<T>;
|
||||
getConfigElement?: () => LovelaceStrategyEditor;
|
||||
noEditor?: boolean;
|
||||
configRequired?: boolean;
|
||||
};
|
||||
|
||||
export interface LovelaceDashboardStrategy
|
||||
|
|
|
@ -2212,6 +2212,10 @@
|
|||
"map": {
|
||||
"title": "[%key:panel::map%]",
|
||||
"description": "Display people and your devices on a map"
|
||||
},
|
||||
"iframe": {
|
||||
"title": "Webpage",
|
||||
"description": "Integrate a webpage as a dashboard."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5785,6 +5789,9 @@
|
|||
"areas": "Areas",
|
||||
"hide_entities_without_area": "Hide entities without area",
|
||||
"hide_energy": "Hide energy"
|
||||
},
|
||||
"iframe": {
|
||||
"url": "URL"
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export const IFRAME_SANDBOX =
|
||||
"allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts allow-modals allow-downloads";
|
Loading…
Reference in New Issue