From 7002697f69b989b5ae9ede5178c01572d66bc354 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 12 Jun 2026 18:35:07 +0300 Subject: [PATCH 1/3] fix(iot-hub): append /preview suffix on card image URL when missing --- .../home/components/iot-hub/iot-hub-item-card.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.ts index bdf50acddf..2d9c13d8d7 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.ts @@ -62,8 +62,8 @@ export class TbIotHubItemCardComponent { return null; } const [path, query] = this.item.image.split('?'); - const original = path.endsWith('/preview') ? path.slice(0, -'/preview'.length) : path; - const resolved = query ? `${original}?${query}` : original; + const withPreview = path.endsWith('/preview') ? path : `${path}/preview`; + const resolved = query ? `${withPreview}?${query}` : withPreview; return this.iotHubApiService.resolveResourceUrl(resolved); } From 71182fea387733d26ce0a3b9650e2b4df56e5588 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 12 Jun 2026 18:35:13 +0300 Subject: [PATCH 2/3] refactor(iot-hub): fetch device package zip inside install dialog Move the zip fetch from IotHubActionsService into TbDeviceInstallDialogComponent.ngOnInit so the dialog opens immediately and shows its own loading spinner instead of blocking the caller. Drops the now-unused fetchZipData helper and zipData field from DeviceInstallDialogData. --- .../device-install-dialog.component.html | 4 ++-- .../device-install-dialog.component.ts | 7 ++++-- .../iot-hub/iot-hub-actions.service.ts | 22 +++++-------------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html index 733ed0a906..402381ad67 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.html @@ -164,8 +164,8 @@ @if (loading) { -
- +
+
} @else {
diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts index 53dc3ac523..e4df929fd2 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts @@ -53,10 +53,10 @@ import { InstallStepType, stepTypeAliasMap } from '@shared/models/iot-hub/device-package.models'; +import { mergeMap } from 'rxjs/operators'; export interface DeviceInstallDialogData { item: MpItemVersionView; - zipData: ArrayBuffer; reviewMode?: boolean; selectedInstallMethod?: string; installState?: Record; @@ -138,8 +138,11 @@ export class TbDeviceInstallDialogComponent extends DialogComponent { try { + const zipData = await firstValueFrom(this.iotHubApiService.getVersionFileData(this.data.item.id, { ignoreLoading: true }).pipe( + mergeMap((blob: Blob) => blob.arrayBuffer()) + )); const JSZip = (await import('jszip')).default; - const zip = await JSZip.loadAsync(this.data.zipData); + const zip = await JSZip.loadAsync(zipData); const imageExtensions = new Set(['png', 'jpg', 'jpeg', 'gif', 'svg']); for (const [path, entry] of Object.entries(zip.files) as [string, any][]) { if (!entry.dir) { diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts index 4fd1565c07..bae080a464 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts @@ -129,34 +129,24 @@ export class IotHubActionsService { } installDevice(item: MpItemVersionView): Observable { - return this.fetchZipData(item).pipe( - mergeMap((zipData: ArrayBuffer) => this.openDeviceInstallDialog(item, zipData)) - ); + return this.openDeviceInstallDialog(item); } reviewDevice(item: MpItemVersionView, deviceDescriptor: DeviceInstalledItemDescriptor): Observable { - return this.fetchZipData(item).pipe( - mergeMap((zipData: ArrayBuffer) => this.openDeviceInstallDialog(item, zipData, { + return this.openDeviceInstallDialog(item, { reviewMode: true, selectedInstallMethod: deviceDescriptor.selectedInstallMethod, installState: deviceDescriptor.installState - })) - ); - } - - private fetchZipData(item: MpItemVersionView): Observable { - return this.iotHubApiService.getVersionFileData(item.id as string, { ignoreLoading: true }).pipe( - mergeMap((blob: Blob) => blob.arrayBuffer()) - ); + }); } - private openDeviceInstallDialog(item: MpItemVersionView, zipData: ArrayBuffer, + private openDeviceInstallDialog(item: MpItemVersionView, options?: { reviewMode?: boolean; selectedInstallMethod?: string; installState?: any }): Observable { - return this.dialog.open(TbDeviceInstallDialogComponent, { + return this.dialog.open(TbDeviceInstallDialogComponent, { panelClass: ['tb-dialog', 'tb-fullscreen-dialog-lt-md'], disableClose: true, autoFocus: false, - data: { item, zipData, ...options } as DeviceInstallDialogData + data: { item, ...options } }).afterClosed(); } } From 1afd2f273b58e5e91ad9e041e32c04ae1d9a8e55 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 12 Jun 2026 18:35:19 +0300 Subject: [PATCH 3/3] style(iot-hub): detail dialog mobile close button + meta-grid overflow polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the detail dialog close button into desktop (inline in header) and mobile (absolute, top-right) variants. Switch meta-grid first column to minmax(0, …) so the creator section can shrink, and truncate the creator name with a tooltip. Add 8px right margin to the card type label. --- .../iot-hub/iot-hub-item-card.component.scss | 1 + .../iot-hub-item-detail-dialog.component.html | 10 ++++--- .../iot-hub-item-detail-dialog.component.scss | 27 ++++++++++++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.scss b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.scss index d46124d91e..71f3ea051d 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.scss +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.scss @@ -285,6 +285,7 @@ font-weight: 500; color: rgba(0, 0, 0, 0.54); text-transform: uppercase; + margin-right: 8px; tb-icon { font-size: 12px; diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html index 3827599242..e42f1521aa 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html @@ -69,12 +69,16 @@ } } -
+ +
@@ -163,7 +167,7 @@
@if (showCreator) { -
+
{{ 'iot-hub.creator' | translate }}
verified }
- {{ item.creatorDisplayName }} + {{ item.creatorDisplayName }}
} diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.scss b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.scss index 0622ce4af5..5b7a182a01 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.scss @@ -54,6 +54,21 @@ } +.dlg-close-btn-desktop { + @media #{$mat-lt-sm} { + display: none; + } +} + +.dlg-close-btn-mobile { + position: absolute; + top: 16px; + right: 12px; + @media #{$mat-gt-xs} { + display: none; + } +} + // Header icon for CF/RC — Design: 64px, rounded-8, colored bg, white icon 36px .dlg-header-icon { width: 64px; @@ -250,21 +265,25 @@ display: grid; gap: 24px; padding: 16px 24px 12px; - grid-template-columns: max-content minmax(0, max-content) minmax(auto, max-content) minmax(auto, 1fr) max-content; + grid-template-columns: minmax(0, max-content) minmax(0, max-content) minmax(auto, max-content) minmax(auto, 1fr) max-content; &.dlg-meta-4-col { - grid-template-columns: max-content minmax(auto, max-content) minmax(auto, 1fr) max-content; + grid-template-columns: minmax(0, max-content) minmax(auto, max-content) minmax(auto, 1fr) max-content; } .dlg-meta-details { display: none; } + .dlg-meta-creator-section { + min-width: 0; + } + @media #{$mat-lt-md} { - grid-template-columns: max-content max-content max-content; + grid-template-columns: minmax(0, 1fr) max-content max-content; &.dlg-meta-4-col { - grid-template-columns: max-content max-content max-content; + grid-template-columns: minmax(0, 1fr) max-content max-content; } .dlg-meta-details-section {