mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-08 09:56:36 +00:00
Add my panel (#8349)
This commit is contained in:
parent
83de75b689
commit
c8717bfa32
@ -22,6 +22,7 @@
|
|||||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@braintree/sanitize-url": "^5.0.0",
|
||||||
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||||
"@formatjs/intl-pluralrules": "^3.4.10",
|
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||||
"@fullcalendar/common": "5.1.0",
|
"@fullcalendar/common": "5.1.0",
|
||||||
|
@ -6,3 +6,16 @@ export const extractSearchParamsObject = (): Record<string, string> => {
|
|||||||
}
|
}
|
||||||
return query;
|
return query;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extractSearchParam = (param: string): string | null => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get(param);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSearchParam = (params: Record<string, string>): string => {
|
||||||
|
const urlParams = new URLSearchParams();
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
urlParams.append(key, value);
|
||||||
|
});
|
||||||
|
return urlParams.toString();
|
||||||
|
};
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
} from "../../data/device_registry";
|
} from "../../data/device_registry";
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { showAlertDialog } from "../generic/show-dialog-box";
|
||||||
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
|
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
|
||||||
import "./step-flow-abort";
|
import "./step-flow-abort";
|
||||||
import "./step-flow-create-entry";
|
import "./step-flow-create-entry";
|
||||||
@ -105,9 +106,20 @@ class DataEntryFlowDialog extends LitElement {
|
|||||||
|
|
||||||
this._loading = true;
|
this._loading = true;
|
||||||
const curInstance = this._instance;
|
const curInstance = this._instance;
|
||||||
const step = await (params.continueFlowId
|
let step: DataEntryFlowStep;
|
||||||
? params.flowConfig.fetchFlow(this.hass, params.continueFlowId)
|
try {
|
||||||
: params.flowConfig.createFlow(this.hass, params.startFlowHandler!));
|
step = await (params.continueFlowId
|
||||||
|
? params.flowConfig.fetchFlow(this.hass, params.continueFlowId)
|
||||||
|
: params.flowConfig.createFlow(this.hass, params.startFlowHandler!));
|
||||||
|
} catch (err) {
|
||||||
|
this._step = undefined;
|
||||||
|
this._params = undefined;
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: "Error",
|
||||||
|
text: "Config flow could not be loaded",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Happens if second showDialog called
|
// Happens if second showDialog called
|
||||||
if (curInstance !== this._instance) {
|
if (curInstance !== this._instance) {
|
||||||
|
@ -70,10 +70,14 @@ class HassErrorScreen extends LitElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
height: calc(100% - var(--header-height));
|
height: calc(100% - var(--header-height));
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ const COMPONENTS = {
|
|||||||
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
||||||
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
||||||
map: () => import("../panels/map/ha-panel-map"),
|
map: () => import("../panels/map/ha-panel-map"),
|
||||||
|
my: () => import("../panels/my/ha-panel-my"),
|
||||||
profile: () => import("../panels/profile/ha-panel-profile"),
|
profile: () => import("../panels/profile/ha-panel-profile"),
|
||||||
"shopping-list": () =>
|
"shopping-list": () =>
|
||||||
import("../panels/shopping-list/ha-panel-shopping-list"),
|
import("../panels/shopping-list/ha-panel-shopping-list"),
|
||||||
|
@ -39,17 +39,21 @@ class DialogImportBlueprint extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _result?: BlueprintImportResult;
|
@internalProperty() private _result?: BlueprintImportResult;
|
||||||
|
|
||||||
|
@internalProperty() private _url?: string;
|
||||||
|
|
||||||
@query("#input") private _input?: PaperInputElement;
|
@query("#input") private _input?: PaperInputElement;
|
||||||
|
|
||||||
public showDialog(params): void {
|
public showDialog(params): void {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
|
this._url = this._params.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._result = undefined;
|
this._result = undefined;
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
|
this._url = undefined;
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +127,7 @@ class DialogImportBlueprint extends LitElement {
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.blueprint.add.url"
|
"ui.panel.config.blueprint.add.url"
|
||||||
)}
|
)}
|
||||||
|
.value=${this._url}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></paper-input>`}
|
></paper-input>`}
|
||||||
</div>
|
</div>
|
||||||
@ -171,6 +176,7 @@ class DialogImportBlueprint extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _import() {
|
private async _import() {
|
||||||
|
this._url = undefined;
|
||||||
this._importing = true;
|
this._importing = true;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
try {
|
try {
|
||||||
|
@ -7,10 +7,13 @@ import {
|
|||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import { extractSearchParam } from "../../../common/url/search-params";
|
||||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/entity/ha-entity-toggle";
|
import "../../../components/entity/ha-entity-toggle";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
@ -155,6 +158,17 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
if (this.route.path === "/import") {
|
||||||
|
const url = extractSearchParam("blueprint_url");
|
||||||
|
navigate(this, "/config/blueprint/dashboard", true);
|
||||||
|
if (url) {
|
||||||
|
this._addBlueprint(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
@ -228,8 +242,11 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addBlueprint() {
|
private _addBlueprint(url?: string) {
|
||||||
showAddBlueprintDialog(this, { importedCallback: () => this._reload() });
|
showAddBlueprintDialog(this, {
|
||||||
|
url,
|
||||||
|
importedCallback: () => this._reload(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _reload() {
|
private _reload() {
|
||||||
|
@ -18,9 +18,11 @@ import {
|
|||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { navigate } from "../../../common/navigate";
|
||||||
import "../../../common/search/search-input";
|
import "../../../common/search/search-input";
|
||||||
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
|
import { extractSearchParam } from "../../../common/url/search-params";
|
||||||
import { nextRender } from "../../../common/util/render-status";
|
import { nextRender } from "../../../common/util/render-status";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
@ -222,8 +224,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
protected firstUpdated(changed: PropertyValues) {
|
protected firstUpdated(changed: PropertyValues) {
|
||||||
super.firstUpdated(changed);
|
super.firstUpdated(changed);
|
||||||
this._loadConfigEntries();
|
this._loadConfigEntries();
|
||||||
this.hass.loadBackendTranslation("title", undefined, true);
|
const localizePromise = this.hass.loadBackendTranslation(
|
||||||
|
"title",
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
this._fetchManifests();
|
this._fetchManifests();
|
||||||
|
if (this.route.path === "/add") {
|
||||||
|
this._handleAdd(localizePromise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changed: PropertyValues) {
|
protected updated(changed: PropertyValues) {
|
||||||
@ -535,11 +544,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleFlowUpdated() {
|
||||||
|
this._loadConfigEntries();
|
||||||
|
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||||
|
}
|
||||||
|
|
||||||
private _createFlow() {
|
private _createFlow() {
|
||||||
showConfigFlowDialog(this, {
|
showConfigFlowDialog(this, {
|
||||||
dialogClosedCallback: () => {
|
dialogClosedCallback: () => {
|
||||||
this._loadConfigEntries();
|
this._handleFlowUpdated();
|
||||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
|
||||||
},
|
},
|
||||||
showAdvanced: this.showAdvanced,
|
showAdvanced: this.showAdvanced,
|
||||||
});
|
});
|
||||||
@ -551,8 +564,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
showConfigFlowDialog(this, {
|
showConfigFlowDialog(this, {
|
||||||
continueFlowId: (ev.target! as any).flowId,
|
continueFlowId: (ev.target! as any).flowId,
|
||||||
dialogClosedCallback: () => {
|
dialogClosedCallback: () => {
|
||||||
this._loadConfigEntries();
|
this._handleFlowUpdated();
|
||||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -649,6 +661,33 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _handleAdd(localizePromise: Promise<LocalizeFunc>) {
|
||||||
|
const domain = extractSearchParam("domain");
|
||||||
|
navigate(this, "/config/integrations", true);
|
||||||
|
if (!domain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const localize = await localizePromise;
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
title: localize(
|
||||||
|
"ui.panel.config.integrations.confirm_new",
|
||||||
|
"integration",
|
||||||
|
domainToName(localize, domain)
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showConfigFlowDialog(this, {
|
||||||
|
dialogClosedCallback: () => {
|
||||||
|
this._handleFlowUpdated();
|
||||||
|
},
|
||||||
|
startFlowHandler: domain,
|
||||||
|
showAdvanced: this.hass.userData?.showAdvanced,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
132
src/panels/my/ha-panel-my.ts
Normal file
132
src/panels/my/ha-panel-my.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
internalProperty,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||||
|
import { navigate } from "../../common/navigate";
|
||||||
|
import { HomeAssistant, Route } from "../../types";
|
||||||
|
import {
|
||||||
|
createSearchParam,
|
||||||
|
extractSearchParamsObject,
|
||||||
|
} from "../../common/url/search-params";
|
||||||
|
import "../../layouts/hass-error-screen";
|
||||||
|
|
||||||
|
const REDIRECTS = {
|
||||||
|
info: {
|
||||||
|
redirect: "/config/info",
|
||||||
|
},
|
||||||
|
logs: {
|
||||||
|
redirect: "/config/logs",
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
redirect: "/profile/dashboard",
|
||||||
|
},
|
||||||
|
blueprint_import: {
|
||||||
|
redirect: "/config/blueprint/dashboard/import",
|
||||||
|
params: {
|
||||||
|
blueprint_url: "url",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config_flow_start: {
|
||||||
|
redirect: "/config/integrations/add",
|
||||||
|
params: {
|
||||||
|
domain: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type ParamType = "url" | "string";
|
||||||
|
|
||||||
|
interface Redirect {
|
||||||
|
redirect: string;
|
||||||
|
params?: {
|
||||||
|
[key: string]: ParamType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-panel-my")
|
||||||
|
class HaPanelMy extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public route!: Route;
|
||||||
|
|
||||||
|
@internalProperty() public _error = "";
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
const path = this.route.path.substr(1);
|
||||||
|
const redirect: Redirect | undefined = REDIRECTS[path];
|
||||||
|
|
||||||
|
if (!redirect) {
|
||||||
|
this._error = this.hass.localize(
|
||||||
|
"ui.panel.my.not_supported",
|
||||||
|
"link",
|
||||||
|
html`<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
href="https://my.home-assistant.io/faq.html#supported-pages"
|
||||||
|
>${this.hass.localize("ui.panel.my.faq_link")}</a
|
||||||
|
>`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url: string;
|
||||||
|
try {
|
||||||
|
url = this._createRedirectUrl(redirect);
|
||||||
|
} catch (err) {
|
||||||
|
this._error = this.hass.localize("ui.panel.my.error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(this, url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (this._error) {
|
||||||
|
return html`<hass-error-screen
|
||||||
|
.error=${this._error}
|
||||||
|
></hass-error-screen>`;
|
||||||
|
}
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createRedirectUrl(redirect: Redirect): string {
|
||||||
|
const params = this._createRedirectParams(redirect);
|
||||||
|
return `${redirect.redirect}${params}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createRedirectParams(redirect: Redirect): string {
|
||||||
|
const params = extractSearchParamsObject();
|
||||||
|
if (!redirect.params && !Object.keys(params).length) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const resultParams = {};
|
||||||
|
Object.entries(redirect.params || {}).forEach(([key, type]) => {
|
||||||
|
if (!params[key] || !this._checkParamType(type, params[key])) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
resultParams[key] = params[key];
|
||||||
|
});
|
||||||
|
return `?${createSearchParam(resultParams)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _checkParamType(type: ParamType, value: string) {
|
||||||
|
if (type === "string") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type === "url") {
|
||||||
|
return value && value === sanitizeUrl(value);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-panel-my": HaPanelMy;
|
||||||
|
}
|
||||||
|
}
|
@ -804,6 +804,11 @@
|
|||||||
"done": "Done"
|
"done": "Done"
|
||||||
},
|
},
|
||||||
"panel": {
|
"panel": {
|
||||||
|
"my": {
|
||||||
|
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced.",
|
||||||
|
"faq_link": "My Home Assistant FAQ",
|
||||||
|
"error": "An unknown error occured"
|
||||||
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"header": "Configure Home Assistant",
|
"header": "Configure Home Assistant",
|
||||||
"introduction": "In this view it is possible to configure your components and Home Assistant. Not everything is possible to configure from the UI yet, but we're working on it.",
|
"introduction": "In this view it is possible to configure your components and Home Assistant. Not everything is possible to configure from the UI yet, but we're working on it.",
|
||||||
@ -2000,6 +2005,7 @@
|
|||||||
"attention": "Attention required",
|
"attention": "Attention required",
|
||||||
"configured": "Configured",
|
"configured": "Configured",
|
||||||
"new": "Set up a new integration",
|
"new": "Set up a new integration",
|
||||||
|
"confirm_new": "Do you want to set up {integration}?",
|
||||||
"add_integration": "Add integration",
|
"add_integration": "Add integration",
|
||||||
"no_integrations": "Seems like you don't have any integations configured yet. Click on the button below to add your first integration!",
|
"no_integrations": "Seems like you don't have any integations configured yet. Click on the button below to add your first integration!",
|
||||||
"note_about_integrations": "Not all integrations can be configured via the UI yet.",
|
"note_about_integrations": "Not all integrations can be configured via the UI yet.",
|
||||||
|
@ -1900,6 +1900,11 @@
|
|||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@braintree/sanitize-url@^5.0.0":
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-5.0.0.tgz#3ba791f37b90e7f6170d252b63aacfcae943c039"
|
||||||
|
integrity sha512-WmKrB/575EJCzbeSJR3YQ5sET5FaizeljLRw1382qVUeGqzuWBgIS+AF5a0FO51uQTrDpoRgvuHC2IWVsgwkkA==
|
||||||
|
|
||||||
"@formatjs/ecma402-abstract@^1.2.5":
|
"@formatjs/ecma402-abstract@^1.2.5":
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.5.tgz#5a61ac1990ff2df8d1348ab12e186c1ca2a2bd71"
|
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.5.tgz#5a61ac1990ff2df8d1348ab12e186c1ca2a2bd71"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user