diff --git a/src/onboarding/action-badge.ts b/src/onboarding/action-badge.ts new file mode 100644 index 0000000000..c53f57ca52 --- /dev/null +++ b/src/onboarding/action-badge.ts @@ -0,0 +1,85 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../components/ha-svg-icon"; + +@customElement("action-badge") +class ActionBadge 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 { + return html` +
+ + ${this.badgeIcon + ? html`` + : ""} +
+
${this.title}
+ `; + } + + static get styles(): CSSResultGroup { + 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; + box-sizing: border-box; + 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; + word-break: break-word; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "action-badge": ActionBadge; + } +} diff --git a/src/onboarding/integration-badge.ts b/src/onboarding/integration-badge.ts index e888460046..fc440b1589 100644 --- a/src/onboarding/integration-badge.ts +++ b/src/onboarding/integration-badge.ts @@ -9,6 +9,8 @@ class IntegrationBadge extends LitElement { @property() public title!: string; + @property() public badgeIcon?: string; + @property({ type: Boolean }) public darkOptimizedIcon?: boolean; @property({ type: Boolean, reflect: true }) public clickable = false; @@ -25,6 +27,12 @@ class IntegrationBadge extends LitElement { })} referrerpolicy="no-referrer" /> + ${this.badgeIcon + ? html`` + : ""}
${this.title}
`; @@ -39,6 +47,10 @@ class IntegrationBadge extends LitElement { color: var(--primary-text-color); } + :host([clickable]) { + color: var(--primary-text-color); + } + img { max-width: 100%; max-height: 100%; @@ -54,6 +66,18 @@ class IntegrationBadge extends LitElement { justify-content: center; } + .badge { + position: absolute; + color: white; + bottom: -7px; + right: -10px; + background-color: var(--label-badge-green); + border-radius: 50%; + display: block; + --mdc-icon-size: 18px; + border: 2px solid white; + } + .title { min-height: 2.3em; word-break: break-word; diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index 6475ae6695..c2c145359a 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -1,12 +1,14 @@ import "@material/mwc-button/mwc-button"; +import { mdiCheck, mdiDotsHorizontal } from "@mdi/js"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { - CSSResultGroup, - LitElement, - PropertyValues, css, + CSSResultGroup, html, + LitElement, nothing, + PropertyValues, + TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../common/config/is_component_loaded"; @@ -14,12 +16,22 @@ import { fireEvent } from "../common/dom/fire_event"; import { stringCompare } from "../common/string/compare"; import { LocalizeFunc } from "../common/translations/localize"; import { ConfigEntry, subscribeConfigEntries } from "../data/config_entries"; -import { subscribeConfigFlowInProgress } from "../data/config_flow"; +import { + getConfigFlowInProgressCollection, + localizeConfigFlowTitle, + subscribeConfigFlowInProgress, +} from "../data/config_flow"; import { DataEntryFlowProgress } from "../data/data_entry_flow"; import { domainToName } from "../data/integration"; import { scanUSBDevices } from "../data/usb"; +import { + loadConfigFlowDialog, + showConfigFlowDialog, +} from "../dialogs/config-flow/show-dialog-config-flow"; import { SubscribeMixin } from "../mixins/subscribe-mixin"; +import { showAddIntegrationDialog } from "../panels/config/integrations/show-add-integration-dialog"; import { HomeAssistant } from "../types"; +import "./action-badge"; import "./integration-badge"; const HIDDEN_DOMAINS = new Set([ @@ -51,7 +63,7 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) { integrations.add(flow.handler); } } - this.hass.loadBackendTranslation("title", Array.from(integrations)); + this.hass.loadBackendTranslation("config", Array.from(integrations)); }), subscribeConfigEntries( this.hass, @@ -97,65 +109,62 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) { return nothing; } // Render discovered and existing entries together sorted by localized title. - const entries: Array<[string, string]> = this._entries.map((entry) => [ - entry.domain, - domainToName(this.hass.localize, entry.domain), - ]); - const discovered: Array<[string, string]> = this._discovered.map((flow) => [ - flow.handler, - domainToName(this.hass.localize, flow.handler), - ]); - let domains = [...entries, ...discovered].sort((a, b) => - stringCompare(a[0], b[0], this.hass.locale.language) + const entries: Array<[string, TemplateResult]> = this._entries.map( + (entry) => { + const title = + entry.title || + domainToName(this.hass.localize, entry.domain) || + entry.domain; + return [ + title, + html` + + `, + ]; + } ); - - const foundDevices = domains.length; - - if (domains.length > 12) { - const uniqueDomains: Set = new Set(); - domains.forEach(([domain]) => { - uniqueDomains.add(domain); - }); - if (uniqueDomains.size < domains.length) { - domains = domains.filter(([domain]) => { - if (uniqueDomains.has(domain)) { - uniqueDomains.delete(domain); - return true; - } - return false; - }); + const discovered: Array<[string, TemplateResult]> = this._discovered.map( + (flow) => { + const title = localizeConfigFlowTitle(this.hass.localize, flow); + return [ + title, + html` + + `, + ]; } - if (domains.length > 12) { - domains = domains.slice(0, 11); - } - } + ); + const content = [...entries, ...discovered] + .sort((a, b) => stringCompare(a[0], b[0], this.hass.locale.language)) + .map((item) => item[1]); return html` -

- ${this.onboardingLocalize( - "ui.panel.page-onboarding.integration.header" - )} -

${this.onboardingLocalize("ui.panel.page-onboarding.integration.intro")}

- ${domains.map( - ([domain, title]) => - html`` - )} - ${foundDevices > domains.length - ? html`
- ${this.onboardingLocalize( - "ui.panel.page-onboarding.integration.more_integrations", - { count: foundDevices - domains.length } - )} -
` - : nothing} + ${content} +