Add my panel (#8349)

This commit is contained in:
Bram Kragten 2021-02-08 14:48:54 +01:00 committed by GitHub
parent 83de75b689
commit c8717bfa32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 246 additions and 10 deletions

View File

@ -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",

View File

@ -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();
};

View File

@ -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
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) {

View File

@ -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);
}
`,
];
}

View File

@ -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"),

View File

@ -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 {

View File

@ -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() {

View File

@ -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,

View 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;
}
}

View File

@ -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.",

View File

@ -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"