mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 21:37:21 +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)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.0",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
|
@ -6,3 +6,16 @@ export const extractSearchParamsObject = (): Record<string, string> => {
|
||||
}
|
||||
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";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showAlertDialog } from "../generic/show-dialog-box";
|
||||
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
|
||||
import "./step-flow-abort";
|
||||
import "./step-flow-create-entry";
|
||||
@ -105,9 +106,20 @@ class DataEntryFlowDialog extends LitElement {
|
||||
|
||||
this._loading = true;
|
||||
const curInstance = this._instance;
|
||||
const step = await (params.continueFlowId
|
||||
? params.flowConfig.fetchFlow(this.hass, params.continueFlowId)
|
||||
: params.flowConfig.createFlow(this.hass, params.startFlowHandler!));
|
||||
let step: DataEntryFlowStep;
|
||||
try {
|
||||
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
|
||||
if (curInstance !== this._instance) {
|
||||
|
@ -70,10 +70,14 @@ class HassErrorScreen extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
height: calc(100% - var(--header-height));
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ const COMPONENTS = {
|
||||
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
||||
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
||||
map: () => import("../panels/map/ha-panel-map"),
|
||||
my: () => import("../panels/my/ha-panel-my"),
|
||||
profile: () => import("../panels/profile/ha-panel-profile"),
|
||||
"shopping-list": () =>
|
||||
import("../panels/shopping-list/ha-panel-shopping-list"),
|
||||
|
@ -39,17 +39,21 @@ class DialogImportBlueprint extends LitElement {
|
||||
|
||||
@internalProperty() private _result?: BlueprintImportResult;
|
||||
|
||||
@internalProperty() private _url?: string;
|
||||
|
||||
@query("#input") private _input?: PaperInputElement;
|
||||
|
||||
public showDialog(params): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._url = this._params.url;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._error = undefined;
|
||||
this._result = undefined;
|
||||
this._params = undefined;
|
||||
this._url = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@ -123,6 +127,7 @@ class DialogImportBlueprint extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.url"
|
||||
)}
|
||||
.value=${this._url}
|
||||
dialogInitialFocus
|
||||
></paper-input>`}
|
||||
</div>
|
||||
@ -171,6 +176,7 @@ class DialogImportBlueprint extends LitElement {
|
||||
}
|
||||
|
||||
private async _import() {
|
||||
this._url = undefined;
|
||||
this._importing = true;
|
||||
this._error = undefined;
|
||||
try {
|
||||
|
@ -7,10 +7,13 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
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 "../../../components/entity/ha-entity-toggle";
|
||||
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 {
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
@ -228,8 +242,11 @@ class HaBlueprintOverview extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addBlueprint() {
|
||||
showAddBlueprintDialog(this, { importedCallback: () => this._reload() });
|
||||
private _addBlueprint(url?: string) {
|
||||
showAddBlueprintDialog(this, {
|
||||
url,
|
||||
importedCallback: () => this._reload(),
|
||||
});
|
||||
}
|
||||
|
||||
private _reload() {
|
||||
|
@ -18,9 +18,11 @@ import {
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../common/search/search-input";
|
||||
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
@ -222,8 +224,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
protected firstUpdated(changed: PropertyValues) {
|
||||
super.firstUpdated(changed);
|
||||
this._loadConfigEntries();
|
||||
this.hass.loadBackendTranslation("title", undefined, true);
|
||||
const localizePromise = this.hass.loadBackendTranslation(
|
||||
"title",
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
this._fetchManifests();
|
||||
if (this.route.path === "/add") {
|
||||
this._handleAdd(localizePromise);
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changed: PropertyValues) {
|
||||
@ -535,11 +544,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleFlowUpdated() {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
showAdvanced: this.showAdvanced,
|
||||
});
|
||||
@ -551,8 +564,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: (ev.target! as any).flowId,
|
||||
dialogClosedCallback: () => {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -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[] {
|
||||
return [
|
||||
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"
|
||||
},
|
||||
"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": {
|
||||
"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.",
|
||||
@ -2000,6 +2005,7 @@
|
||||
"attention": "Attention required",
|
||||
"configured": "Configured",
|
||||
"new": "Set up a new integration",
|
||||
"confirm_new": "Do you want to set up {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!",
|
||||
"note_about_integrations": "Not all integrations can be configured via the UI yet.",
|
||||
|
@ -1900,6 +1900,11 @@
|
||||
lodash "^4.17.13"
|
||||
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":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.5.tgz#5a61ac1990ff2df8d1348ab12e186c1ca2a2bd71"
|
||||
|
Loading…
x
Reference in New Issue
Block a user