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}
+