Alow setting up integrations during onboarding (#3163)
* Allow setting up integrations during onboarding * Fix compress static * Don't compress static files in CI * Remove unused file * Fix static compress disabled in CI build * Work with new integration step * Import fix * Lint * Upgrade HAWS to 4.1.1
This commit is contained in:
parent
8c904fb012
commit
82e8ca2754
|
@ -37,7 +37,11 @@ gulp.task(
|
|||
"clean",
|
||||
gulp.parallel("gen-icons", "build-translations"),
|
||||
"copy-static",
|
||||
gulp.parallel("webpack-prod-app", "compress-static"),
|
||||
gulp.parallel(
|
||||
"webpack-prod-app",
|
||||
// Do not compress static files in CI, it's SLOW.
|
||||
...(process.env.CI === "true" ? [] : ["compress-static"])
|
||||
),
|
||||
gulp.parallel(
|
||||
"gen-pages-prod",
|
||||
"gen-index-html-prod",
|
||||
|
|
|
@ -95,7 +95,7 @@ gulp.task("copy-static", (done) => {
|
|||
done();
|
||||
});
|
||||
|
||||
gulp.task("compress-static", () => compressStatic(paths.root));
|
||||
gulp.task("compress-static", () => compressStatic(paths.static));
|
||||
|
||||
gulp.task("copy-static-demo", (done) => {
|
||||
// Copy app static files
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
"es6-object-assign": "^1.1.0",
|
||||
"fecha": "^3.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "^3.4.0",
|
||||
"home-assistant-js-websocket": "^4.1.1",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"jquery": "^3.3.1",
|
||||
"js-yaml": "^3.13.0",
|
||||
|
|
|
@ -14,6 +14,8 @@ export interface SignedPath {
|
|||
path: string;
|
||||
}
|
||||
|
||||
export const hassUrl = `${location.protocol}//${location.host}`;
|
||||
|
||||
export const getSignedPath = (
|
||||
hass: HomeAssistant,
|
||||
path: string
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
import { HomeAssistant } from "../types";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
|
||||
export interface ConfigEntry {
|
||||
entry_id: string;
|
||||
domain: string;
|
||||
title: string;
|
||||
source: string;
|
||||
state: string;
|
||||
connection_class: string;
|
||||
supports_options: boolean;
|
||||
}
|
||||
|
||||
export interface FieldSchema {
|
||||
name: string;
|
||||
|
@ -11,7 +22,10 @@ export interface FieldSchema {
|
|||
export interface ConfigFlowProgress {
|
||||
flow_id: string;
|
||||
handler: string;
|
||||
context: { [key: string]: any };
|
||||
context: {
|
||||
title_placeholders: { [key: string]: string };
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ConfigFlowStepForm {
|
||||
|
@ -106,3 +120,23 @@ export const subscribeConfigFlowInProgress = (
|
|||
hass.connection,
|
||||
onChange
|
||||
);
|
||||
|
||||
export const getConfigEntries = (hass: HomeAssistant) =>
|
||||
hass.callApi<ConfigEntry[]>("GET", "config/config_entries/entry");
|
||||
|
||||
export const localizeConfigFlowTitle = (
|
||||
localize: LocalizeFunc,
|
||||
flow: ConfigFlowProgress
|
||||
) => {
|
||||
const placeholders = flow.context.title_placeholders || {};
|
||||
const placeholderKeys = Object.keys(placeholders);
|
||||
if (placeholderKeys.length === 0) {
|
||||
return localize(`component.${flow.handler}.config.title`);
|
||||
}
|
||||
const args: string[] = [];
|
||||
placeholderKeys.forEach((key) => {
|
||||
args.push(key);
|
||||
args.push(placeholders[key]);
|
||||
});
|
||||
return localize(`component.${flow.handler}.config.flow_title`, ...args);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface OnboardingUserStepResponse {
|
||||
auth_code: string;
|
||||
}
|
||||
|
||||
export interface OnboardingIntegrationStepResponse {
|
||||
auth_code: string;
|
||||
}
|
||||
|
||||
export interface OnboardingResponses {
|
||||
user: OnboardingUserStepResponse;
|
||||
bla: number;
|
||||
integration: OnboardingIntegrationStepResponse;
|
||||
}
|
||||
|
||||
export type ValidOnboardingStep = keyof OnboardingResponses;
|
||||
|
@ -24,6 +29,7 @@ export const onboardUserStep = (params: {
|
|||
name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
language: string;
|
||||
}) =>
|
||||
handleFetchPromise<OnboardingUserStepResponse>(
|
||||
fetch("/api/onboarding/users", {
|
||||
|
@ -32,3 +38,13 @@ export const onboardUserStep = (params: {
|
|||
body: JSON.stringify(params),
|
||||
})
|
||||
);
|
||||
|
||||
export const onboardIntegrationStep = (
|
||||
hass: HomeAssistant,
|
||||
params: { client_id: string }
|
||||
) =>
|
||||
hass.callApi<OnboardingIntegrationStepResponse>(
|
||||
"POST",
|
||||
"onboarding/integration",
|
||||
params
|
||||
);
|
||||
|
|
|
@ -169,6 +169,18 @@ class StepFlowCreateEntry extends LitElement {
|
|||
.buttons > *:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
paper-dropdown-menu-light {
|
||||
cursor: pointer;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
.device {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { subscribePanels } from "../data/ws-panels";
|
|||
import { subscribeThemes } from "../data/ws-themes";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { hassUrl } from "../data/auth";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -21,7 +22,6 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
const hassUrl = `${location.protocol}//${location.host}`;
|
||||
const isExternal = location.search.includes("external_auth=1");
|
||||
|
||||
const authProm = isExternal
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
Home Assistant
|
||||
</div>
|
||||
|
||||
<ha-onboarding>Initializing</ha-onboarding>
|
||||
<ha-onboarding></ha-onboarding>
|
||||
</div>
|
||||
|
||||
<%= renderTemplate('_js_base') %>
|
||||
|
|
|
@ -31,10 +31,10 @@ export const localizeLiteBaseMixin = (superClass) =>
|
|||
return;
|
||||
}
|
||||
|
||||
this._updateResources();
|
||||
this._downloadResources();
|
||||
}
|
||||
|
||||
private async _updateResources() {
|
||||
private async _downloadResources() {
|
||||
const { language, data } = await getTranslation(
|
||||
this.translationFragment,
|
||||
this.language
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
import {
|
||||
LitElement,
|
||||
html,
|
||||
PropertyValues,
|
||||
customElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import {
|
||||
getAuth,
|
||||
createConnection,
|
||||
genClientId,
|
||||
Auth,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import {
|
||||
OnboardingStep,
|
||||
ValidOnboardingStep,
|
||||
OnboardingResponses,
|
||||
fetchOnboardingOverview,
|
||||
} from "../data/onboarding";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import "./onboarding-create-user";
|
||||
import "./onboarding-loading";
|
||||
import { hassUrl } from "../data/auth";
|
||||
import { HassElement } from "../state/hass-element";
|
||||
|
||||
interface OnboardingEvent<T extends ValidOnboardingStep> {
|
||||
type: T;
|
||||
|
@ -34,43 +41,55 @@ declare global {
|
|||
}
|
||||
|
||||
@customElement("ha-onboarding")
|
||||
class HaOnboarding extends litLocalizeLiteMixin(LitElement) {
|
||||
class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
public translationFragment = "page-onboarding";
|
||||
|
||||
@property() private _loading = false;
|
||||
@property() private _steps?: OnboardingStep[];
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this._steps) {
|
||||
const step = this._curStep()!;
|
||||
|
||||
if (this._loading || !step) {
|
||||
return html`
|
||||
<onboarding-loading></onboarding-loading>
|
||||
`;
|
||||
}
|
||||
|
||||
const step = this._steps.find((stp) => !stp.done)!;
|
||||
|
||||
if (step.step === "user") {
|
||||
} else if (step.step === "user") {
|
||||
return html`
|
||||
<onboarding-create-user
|
||||
.localize=${this.localize}
|
||||
.language=${this.language}
|
||||
></onboarding-create-user>
|
||||
`;
|
||||
} else if (step.step === "integration") {
|
||||
return html`
|
||||
<onboarding-integrations
|
||||
.hass=${this.hass}
|
||||
.onboardingLocalize=${this.localize}
|
||||
></onboarding-integrations>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchOnboardingSteps();
|
||||
import("./onboarding-integrations");
|
||||
registerServiceWorker(false);
|
||||
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
|
||||
}
|
||||
|
||||
private _curStep() {
|
||||
return this._steps ? this._steps.find((stp) => !stp.done) : undefined;
|
||||
}
|
||||
|
||||
private async _fetchOnboardingSteps() {
|
||||
try {
|
||||
const response = await window.stepsPromise;
|
||||
const response = await (window.stepsPromise || fetchOnboardingOverview());
|
||||
|
||||
if (response.status === 404) {
|
||||
// We don't load the component when onboarding is done
|
||||
document.location.href = "/";
|
||||
document.location.assign("/");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -78,7 +97,16 @@ class HaOnboarding extends litLocalizeLiteMixin(LitElement) {
|
|||
|
||||
if (steps.every((step) => step.done)) {
|
||||
// Onboarding is done!
|
||||
document.location.href = "/";
|
||||
document.location.assign("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (steps[0].done) {
|
||||
// First step is already done, so we need to get auth somewhere else.
|
||||
const auth = await getAuth({
|
||||
hassUrl,
|
||||
});
|
||||
await this._connectHass(auth);
|
||||
}
|
||||
|
||||
this._steps = steps;
|
||||
|
@ -91,20 +119,52 @@ class HaOnboarding extends litLocalizeLiteMixin(LitElement) {
|
|||
ev: HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>
|
||||
) {
|
||||
const stepResult = ev.detail;
|
||||
this._steps = this._steps!.map((step) =>
|
||||
step.step === stepResult.type ? { ...step, done: true } : step
|
||||
);
|
||||
|
||||
if (stepResult.type === "user") {
|
||||
const result = stepResult.result as OnboardingResponses["user"];
|
||||
this._loading = true;
|
||||
try {
|
||||
const auth = await getAuth({
|
||||
hassUrl,
|
||||
authCode: result.auth_code,
|
||||
});
|
||||
await this._connectHass(auth);
|
||||
} catch (err) {
|
||||
alert("Ah snap, something went wrong!");
|
||||
location.reload();
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
} else if (stepResult.type === "integration") {
|
||||
const result = stepResult.result as OnboardingResponses["integration"];
|
||||
this._loading = true;
|
||||
|
||||
// Revoke current auth token.
|
||||
await this.hass!.auth.revoke();
|
||||
|
||||
const state = btoa(
|
||||
JSON.stringify({
|
||||
hassUrl: `${location.protocol}//${location.host}`,
|
||||
clientId: genClientId(),
|
||||
})
|
||||
);
|
||||
document.location.href = `/?auth_callback=1&code=${encodeURIComponent(
|
||||
result.auth_code
|
||||
)}&state=${state}`;
|
||||
document.location.assign(
|
||||
`/?auth_callback=1&code=${encodeURIComponent(
|
||||
result.auth_code
|
||||
)}&state=${state}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _connectHass(auth: Auth) {
|
||||
const conn = await createConnection({ auth });
|
||||
this.initializeHass(auth, conn);
|
||||
// Load config strings for integrations
|
||||
(this as any)._loadFragmentTranslations(this.hass!.language, "config");
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
property,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "../components/ha-icon";
|
||||
|
||||
@customElement("integration-badge")
|
||||
class IntegrationBadge extends LitElement {
|
||||
@property() public icon!: string;
|
||||
@property() public title!: string;
|
||||
@property() public badgeIcon?: string;
|
||||
@property({ type: Boolean, reflect: true }) public clickable = false;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<div class="icon">
|
||||
<iron-icon .icon=${this.icon}></iron-icon>
|
||||
${this.badgeIcon
|
||||
? html`
|
||||
<ha-icon class="badge" .icon=${this.badgeIcon}></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="title">${this.title}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
:host([clickable]) {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
margin: 0 auto 8px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--secondary-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host([clickable]) .icon {
|
||||
border-color: var(--primary-color);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
color: var(--primary-color);
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
min-height: 2.3em;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"integration-badge": IntegrationBadge;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||
@customElement("onboarding-create-user")
|
||||
class OnboardingCreateUser extends LitElement {
|
||||
@property() public localize!: LocalizeFunc;
|
||||
@property() public language!: string;
|
||||
|
||||
@property() private _name = "";
|
||||
@property() private _username = "";
|
||||
|
@ -173,6 +174,7 @@ class OnboardingCreateUser extends LitElement {
|
|||
name: this._name,
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
language: this.language,
|
||||
});
|
||||
|
||||
fireEvent(this, "onboarding-step", {
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
property,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
loadConfigFlowDialog,
|
||||
showConfigFlowDialog,
|
||||
} from "../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { HomeAssistant } from "../types";
|
||||
import {
|
||||
getConfigFlowsInProgress,
|
||||
getConfigEntries,
|
||||
ConfigEntry,
|
||||
ConfigFlowProgress,
|
||||
localizeConfigFlowTitle,
|
||||
} from "../data/config_entries";
|
||||
import { compare } from "../common/string/compare";
|
||||
import "./integration-badge";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { onboardIntegrationStep } from "../data/onboarding";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
|
||||
@customElement("onboarding-integrations")
|
||||
class OnboardingIntegrations extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public onboardingLocalize!: LocalizeFunc;
|
||||
@property() private _entries?: ConfigEntry[];
|
||||
@property() private _discovered?: ConfigFlowProgress[];
|
||||
private _unsubEvents?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.hass.connection
|
||||
.subscribeEvents(
|
||||
debounce(() => this._loadData(), 500),
|
||||
"config_entry_discovered"
|
||||
)
|
||||
.then((unsub) => {
|
||||
this._unsubEvents = unsub;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubEvents) {
|
||||
this._unsubEvents();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this._entries || !this._discovered) {
|
||||
return html``;
|
||||
}
|
||||
// Render discovered and existing entries together sorted by localized title.
|
||||
const entries: Array<[string, TemplateResult]> = this._entries.map(
|
||||
(entry) => {
|
||||
const title = this.hass.localize(
|
||||
`component.${entry.domain}.config.title`
|
||||
);
|
||||
return [
|
||||
title,
|
||||
html`
|
||||
<integration-badge
|
||||
.title=${title}
|
||||
icon="hass:check"
|
||||
></integration-badge>
|
||||
`,
|
||||
];
|
||||
}
|
||||
);
|
||||
const discovered: Array<[string, TemplateResult]> = this._discovered.map(
|
||||
(flow) => {
|
||||
const title = localizeConfigFlowTitle(this.hass.localize, flow);
|
||||
return [
|
||||
title,
|
||||
html`
|
||||
<button .flowId=${flow.flow_id} @click=${this._continueFlow}>
|
||||
<integration-badge
|
||||
clickable
|
||||
.title=${title}
|
||||
icon="hass:plus"
|
||||
></integration-badge>
|
||||
</button>
|
||||
`,
|
||||
];
|
||||
}
|
||||
);
|
||||
const content = [...entries, ...discovered]
|
||||
.sort((a, b) => compare(a[0], b[0]))
|
||||
.map((item) => item[1]);
|
||||
|
||||
return html`
|
||||
<p>
|
||||
${this.onboardingLocalize("ui.panel.page-onboarding.integration.intro")}
|
||||
</p>
|
||||
<div class="badges">
|
||||
${content}
|
||||
<button @click=${this._createFlow}>
|
||||
<integration-badge
|
||||
clickable
|
||||
title=${this.onboardingLocalize(
|
||||
"ui.panel.page-onboarding.integration.more_integrations"
|
||||
)}
|
||||
icon="hass:dots-horizontal"
|
||||
></integration-badge>
|
||||
</button>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<mwc-button @click=${this._finish}>
|
||||
${this.onboardingLocalize(
|
||||
"ui.panel.page-onboarding.integration.finish"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadConfigFlowDialog();
|
||||
this._loadData();
|
||||
/* polyfill for paper-dropdown */
|
||||
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
private _continueFlow(ev) {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: ev.currentTarget.flowId,
|
||||
dialogClosedCallback: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
const [discovered, entries] = await Promise.all([
|
||||
getConfigFlowsInProgress(this.hass!),
|
||||
getConfigEntries(this.hass!),
|
||||
]);
|
||||
this._discovered = discovered;
|
||||
this._entries = entries;
|
||||
}
|
||||
|
||||
private async _finish() {
|
||||
const result = await onboardIntegrationStep(this.hass, {
|
||||
client_id: genClientId(),
|
||||
});
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "integration",
|
||||
result,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.badges {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.badges > * {
|
||||
width: 24%;
|
||||
min-width: 90px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
button {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: 0;
|
||||
font: inherit;
|
||||
}
|
||||
.footer {
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"onboarding-integrations": OnboardingIntegrations;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,64 @@
|
|||
import { LitElement, TemplateResult, html, customElement } from "lit-element";
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
|
||||
@customElement("onboarding-loading")
|
||||
class OnboardingLoading extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
Loading…
|
||||
<div class="loader"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
/* MIT License (MIT). Copyright (c) 2014 Luke Haas */
|
||||
.loader,
|
||||
.loader:after {
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.loader {
|
||||
margin: 60px auto;
|
||||
font-size: 4px;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
border-top: 1.1em solid rgba(3, 169, 244, 0.2);
|
||||
border-right: 1.1em solid rgba(3, 169, 244, 0.2);
|
||||
border-bottom: 1.1em solid rgba(3, 169, 244, 0.2);
|
||||
border-left: 1.1em solid rgb(3, 168, 244);
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation: load8 1.4s infinite linear;
|
||||
animation: load8 1.4s infinite linear;
|
||||
}
|
||||
@-webkit-keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
loadConfigFlowDialog,
|
||||
showConfigFlowDialog,
|
||||
} from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { localizeConfigFlowTitle } from "../../../data/config_entries";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
|
@ -207,21 +208,8 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
|||
return localize(`component.${integration}.config.title`);
|
||||
}
|
||||
|
||||
_computeActiveFlowTitle(localize, integration) {
|
||||
const placeholders = integration.context.title_placeholders || {};
|
||||
const placeholderKeys = Object.keys(placeholders);
|
||||
if (placeholderKeys.length === 0) {
|
||||
return localize(`component.${integration.handler}.config.title`);
|
||||
}
|
||||
const args = [];
|
||||
placeholderKeys.forEach((key) => {
|
||||
args.push(key);
|
||||
args.push(placeholders[key]);
|
||||
});
|
||||
return localize(
|
||||
`component.${integration.handler}.config.flow_title`,
|
||||
...args
|
||||
);
|
||||
_computeActiveFlowTitle(localize, flow) {
|
||||
return localizeConfigFlowTitle(localize, flow);
|
||||
}
|
||||
|
||||
_computeConfigEntryEntities(hass, configEntry, entities) {
|
||||
|
|
|
@ -107,6 +107,7 @@ export const connectionMixin = (
|
|||
return resp;
|
||||
},
|
||||
...getState(),
|
||||
...this._pendingHass,
|
||||
};
|
||||
|
||||
this.hassConnected();
|
||||
|
|
|
@ -10,6 +10,7 @@ import { HomeAssistant } from "../types";
|
|||
|
||||
export class HassBaseEl {
|
||||
protected hass?: HomeAssistant;
|
||||
protected _pendingHass: Partial<HomeAssistant> = {};
|
||||
protected initializeHass(_auth: Auth, _conn: Connection) {}
|
||||
protected hassConnected() {}
|
||||
protected hassReconnected() {}
|
||||
|
@ -23,6 +24,7 @@ export class HassBaseEl {
|
|||
export default <T>(superClass: Constructor<T>): Constructor<T & HassBaseEl> =>
|
||||
// @ts-ignore
|
||||
class extends superClass {
|
||||
protected _pendingHass: Partial<HomeAssistant> = {};
|
||||
private __provideHass: HTMLElement[] = [];
|
||||
// @ts-ignore
|
||||
@property() protected hass: HomeAssistant;
|
||||
|
@ -55,7 +57,11 @@ export default <T>(superClass: Constructor<T>): Constructor<T & HassBaseEl> =>
|
|||
el.hass = this.hass;
|
||||
}
|
||||
|
||||
protected async _updateHass(obj) {
|
||||
protected async _updateHass(obj: Partial<HomeAssistant>) {
|
||||
if (!this.hass) {
|
||||
this._pendingHass = { ...this._pendingHass, ...obj };
|
||||
return;
|
||||
}
|
||||
this.hass = { ...this.hass, ...obj };
|
||||
}
|
||||
};
|
||||
|
|
|
@ -115,7 +115,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||
},
|
||||
};
|
||||
const changes: Partial<HomeAssistant> = { resources };
|
||||
if (language === this.hass!.language) {
|
||||
if (this.hass && language === this.hass.language) {
|
||||
changes.localize = computeLocalize(this, language, resources);
|
||||
}
|
||||
this._updateHass(changes);
|
||||
|
|
|
@ -1172,6 +1172,11 @@
|
|||
"required_fields": "Fill in all required fields",
|
||||
"password_not_match": "Passwords don't match"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"intro": "Devices and services are represented in Home Assistant as integrations. You can set them up now, or do it later from the configuration screen.",
|
||||
"more_integrations": "More",
|
||||
"finish": "Finish"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7237,10 +7237,10 @@ hoek@6.x.x:
|
|||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
|
||||
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
|
||||
|
||||
home-assistant-js-websocket@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.4.0.tgz#3ba47cc8f8b7620619a675e7488d6108e8733a70"
|
||||
integrity sha512-Uq5/KIAh4kF13MKzMyd0efBDoU+pNF0O1CfdGpSmT3La3tpt5h+ykpUYlq/vEBj6WwzU6iv3Czt4UK1o0IJHcA==
|
||||
home-assistant-js-websocket@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-4.1.1.tgz#b85152c223a20bfe8827b817b927fd97cc021157"
|
||||
integrity sha512-hNk8bj9JObd3NpgQ1+KtQCbSoz/TWockC8T/L8KvsPrDtkl1oQddajirumaMDgrJg/su4QsxFNUcDPGJyJ05UA==
|
||||
|
||||
homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
|
||||
version "1.0.3"
|
||||
|
|
Loading…
Reference in New Issue