Browse Source

feat(iot-hub): refine widget select empty state, filter panel, and add-item dialog sizing

- MpItemVersionQuery switched to options-object pattern (no more positional undefined params)
- Remove widgets-bundle-search and widget-types-panel components (inlined / merged into filter popover)
- Widget select empty state: shared #noData template with tb-no-data-bg illustration + 'Clear all filters' button (clears both search and filters); Installed->All widgets adds 'Browse IoT Hub' banner that navigates to IoT Hub->All widgets
- Filter popover: immediate-apply (no Apply/Cancel), Clear all + close in header, tbPopoverPlacement=bottomRight, tbPopoverShowCloseButton=false; installed mode uses same expansion-panel style as iot hub with widget type badge and Include Deprecated (shown for bundle and allWidgets)
- Sort mat-menu (Most installed / Newest / Name) with iconPositionEnd; client-side sort for installed widgets, scada items first when scadaFirst=true
- Replace all-widgets/installed-from-iot-hub placeholder SVGs with high-fidelity PNG@2x assets (incl. separate all-iot-hub-widgets.png for IoT Hub mode)
- IoT Hub browse: merge add-mode styles into embedded class, embedded always uses mobile filter drawer; compact grid 1 col default / 2 cols gt-md; regular 3 cols gt-sm; empty state uses tb-no-data-bg
- iot-hub-search: empty state uses tb-no-data-bg; add-item-dialog: tb-fullscreen-dialog-lt-md panel class, content 70vh with widths 80vw/1000px/1200px at gt-sm/gt-md/gt-xmd
pull/15508/head
Igor Kulikov 2 months ago
parent
commit
6f466cdf43
  1. 16
      ui-ngx/src/app/core/http/iot-hub-api.service.ts
  2. 56
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html
  3. 79
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss
  4. 38
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  5. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts
  6. 9
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-add-item-dialog.component.scss
  7. 6
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html
  8. 52
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.scss
  9. 27
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts
  10. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.html
  11. 11
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.scss
  12. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.ts
  13. 2
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts
  14. 86
      ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts
  15. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  16. BIN
      ui-ngx/src/assets/widget/all-iot-hub-widgets.png
  17. BIN
      ui-ngx/src/assets/widget/all-widgets.png
  18. 7
      ui-ngx/src/assets/widget/all-widgets.svg
  19. BIN
      ui-ngx/src/assets/widget/installed-from-iot-hub.png
  20. 9
      ui-ngx/src/assets/widget/installed-from-iot-hub.svg

16
ui-ngx/src/app/core/http/iot-hub-api.service.ts

@ -73,11 +73,11 @@ export class IotHubApiService {
}
public getPublishedVersions(query: MpItemVersionQuery, config?: IotHubRequestConfig): Observable<PageData<MpItemVersionView>> {
if (query.tbVersion == null) {
query.tbVersion = tbVersionToInt(env.tbVersion);
if (query.options.tbVersion == null) {
query.options.tbVersion = tbVersionToInt(env.tbVersion);
}
if (query.peOnly == null) {
query.peOnly = false;
if (query.options.peOnly == null) {
query.options.peOnly = false;
}
return this.http.get<PageData<MpItemVersionView>>(
`${this.baseUrl}/api/versions/published${query.toQuery()}`,
@ -129,14 +129,6 @@ export class IotHubApiService {
});
}
public reportVersionInstalled(versionId: string, config?: IotHubRequestConfig): Observable<void> {
return this.http.post<void>(
`${this.baseUrl}/api/versions/${versionId}/install`,
null,
{ params: this.buildParams(config) }
);
}
public getConnectivitySettings(config?: IotHubRequestConfig): Observable<DeviceConnectivitySettings> {
return this.http.get<DeviceConnectivitySettings>(
`/api/iot-hub/connectivity`,

56
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html

@ -106,7 +106,7 @@
[itemCard]="installedDefaultCard"
[loadingCell]="widgetLoadingCard"
[dataLoading]="loadingWidgetBundles"
[noData]="noWidgetBundles">
[noData]="noData">
</tb-scroll-grid>
<ng-template #installedDefaultCard let-item="item">
@switch (item.__logical) {
@ -119,7 +119,7 @@
</div>
<div class="preview-container">
<div class="preview-spacer"></div>
<img class="preview" src="/assets/widget/all-widgets.svg" alt="{{ 'widget.all-widgets' | translate }}">
<img class="preview" src="/assets/widget/all-widgets.png" alt="{{ 'widget.all-widgets' | translate }}">
</div>
</mat-card>
}
@ -152,10 +152,6 @@
<mat-spinner strokeWidth="5"></mat-spinner>
</div>
</ng-template>
<ng-template #noWidgetBundles>
<span translate
class="mat-headline-5 tb-absolute-fill flex items-center justify-center">widgets-bundle.no-widgets-bundles-text</span>
</ng-template>
}
}
@case ('iotHub') {
@ -169,7 +165,7 @@
[itemCard]="iotHubDefaultCard"
[loadingCell]="widgetLoadingCard"
[dataLoading]="loadingCategories"
[noData]="noCategories">
[noData]="noData">
</tb-scroll-grid>
<ng-template #iotHubDefaultCard let-item="item">
@switch (item.__logical) {
@ -182,7 +178,7 @@
</div>
<div class="preview-container">
<div class="preview-spacer"></div>
<img class="preview" src="/assets/widget/all-widgets.svg" alt="{{ 'widget.all-widgets' | translate }}">
<img class="preview" src="/assets/widget/all-iot-hub-widgets.png" alt="{{ 'widget.all-widgets' | translate }}">
</div>
</mat-card>
}
@ -195,7 +191,7 @@
</div>
<div class="preview-container">
<div class="preview-spacer"></div>
<img class="preview" src="/assets/widget/installed-from-iot-hub.svg" alt="{{ 'iot-hub.installed-from-iot-hub' | translate }}">
<img class="preview" src="/assets/widget/installed-from-iot-hub.png" alt="{{ 'iot-hub.installed-from-iot-hub' | translate }}">
</div>
</mat-card>
}
@ -222,9 +218,6 @@
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
</div>
</ng-template>
<ng-template #noCategories>
<span class="mat-headline-5 tb-absolute-fill flex items-center justify-center">{{ 'iot-hub.no-items-found' | translate }}</span>
</ng-template>
}
@case ('installed') {
<ng-container *ngTemplateOutlet="iotHubInstalledGrid"></ng-container>
@ -246,7 +239,7 @@
[itemCard]="widgetCard"
[loadingCell]="widgetLoadingCard"
[dataLoading]="loadingWidgets"
[noData]="noWidgets">
[noData]="noData">
</tb-scroll-grid>
<ng-template #widgetCard let-item="item">
<mat-card class="tb-widget-preview-card flex flex-col gap-2" appearance="raised" (click)="onWidgetClicked($event, item)">
@ -278,9 +271,6 @@
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
</div>
</ng-template>
<ng-template #noWidgets>
<span class="mat-headline-5 tb-absolute-fill flex items-center justify-center">{{ 'widget.no-widgets-text' | translate }}</span>
</ng-template>
</ng-template>
<ng-template #widgetLoadingCard>
@ -302,7 +292,7 @@
[itemCard]="iotHubWidgetCard"
[loadingCell]="widgetLoadingCard"
[dataLoading]="loadingIotHubWidgets"
[noData]="noIotHubWidgets">
[noData]="noData">
</tb-scroll-grid>
<ng-template #iotHubWidgetCard let-item="item">
<ng-container *ngTemplateOutlet="iotHubWidgetCardTpl; context: { $implicit: item, showInstalledBadge: true }"></ng-container>
@ -315,9 +305,6 @@
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
</div>
</ng-template>
<ng-template #noIotHubWidgets>
<span class="mat-headline-5 tb-absolute-fill flex items-center justify-center">{{ 'iot-hub.no-items-found' | translate }}</span>
</ng-template>
</ng-template>
<ng-template #iotHubInstalledGrid>
@ -329,7 +316,7 @@
[itemCard]="iotHubInstalledWidgetCard"
[loadingCell]="widgetLoadingCard"
[dataLoading]="loadingIotHubInstalledWidgets"
[noData]="noIotHubInstalledWidgets">
[noData]="noData">
</tb-scroll-grid>
<ng-template #iotHubInstalledWidgetCard let-item="item">
<ng-container *ngTemplateOutlet="iotHubWidgetCardTpl; context: { $implicit: item, showInstalledBadge: false }"></ng-container>
@ -342,9 +329,30 @@
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
</div>
</ng-template>
<ng-template #noIotHubInstalledWidgets>
<span class="mat-headline-5 tb-absolute-fill flex items-center justify-center">{{ 'iot-hub.no-items-found' | translate }}</span>
</ng-template>
</ng-template>
<ng-template #noData>
<div class="tb-widget-select-empty-state tb-absolute-fill flex flex-col items-center">
<div class="tb-widget-select-empty-inner flex flex-col items-center">
<div class="tb-no-data-bg"></div>
<h3>{{ 'iot-hub.no-widgets-found' | translate }}</h3>
<p>{{ 'iot-hub.no-items-found-text' | translate }}</p>
@if (hasActiveSearchOrFilters()) {
<button mat-flat-button color="primary" (click)="clearSearchAndFilters()">
{{ 'iot-hub.clear-all-filters' | translate }}
</button>
}
</div>
@if (selectWidgetMode === 'installed' && installedSubMode === 'allWidgets') {
<div class="tb-widget-select-browse-tip" (click)="navigateToIotHubAllWidgets()">
<mat-icon class="tb-widget-select-browse-tip-icon">storefront</mat-icon>
<p class="tb-widget-select-browse-tip-text">
<span>{{ 'iot-hub.browse-tip-prefix' | translate }} </span><span class="tb-widget-select-browse-tip-link">{{ 'iot-hub.browse-tip-link' | translate }}</span><span> {{ 'iot-hub.browse-tip-suffix' | translate }}</span>
</p>
<mat-icon class="tb-widget-select-browse-tip-arrow">arrow_forward</mat-icon>
</div>
}
</div>
</ng-template>
<ng-template #iotHubWidgetCardTpl let-item let-showInstalledBadge="showInstalledBadge">

79
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../scss/constants";
tb-dashboard-widget-select {
display: flex;
flex-direction: column;
@ -71,7 +73,7 @@ tb-dashboard-widget-select {
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 8px;
caret-color: #00695c;
caret-color: $tb-primary-color;
color: rgba(255, 255, 255, 0.84);
mat-icon {
@ -103,7 +105,7 @@ tb-dashboard-widget-select {
&.focus {
background: #fff;
border-color: rgba(255, 255, 255, 0.15);
caret-color: #00695c;
caret-color: $tb-primary-color;
mat-icon {
color: rgba(0, 0, 0, 0.54);
@ -153,6 +155,79 @@ tb-dashboard-widget-select {
}
}
.tb-widget-select-empty-state {
padding: 40px 12px;
text-align: center;
gap: 40px;
.tb-widget-select-empty-inner {
gap: 20px;
.tb-no-data-bg {
flex: auto;
height: 100px;
}
h3 {
font-size: 20px;
font-weight: 600;
line-height: 24px;
letter-spacing: 0.1px;
color: rgba(0, 0, 0, 0.87);
margin: 0;
}
p {
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.76);
margin: 0;
}
}
.tb-widget-select-browse-tip {
display: flex;
width: 100%;
gap: 16px;
align-items: center;
padding: 16px 20px;
border: 1px solid $tb-primary-color;
border-radius: 4px;
background: #fff;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background: rgba($tb-primary-color, 0.06);
}
.tb-widget-select-browse-tip-icon,
.tb-widget-select-browse-tip-arrow {
flex-shrink: 0;
color: $tb-primary-color;
}
.tb-widget-select-browse-tip-text {
flex: 1;
text-align: left;
margin: 0;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0.15px;
color: rgba(0, 0, 0, 0.76);
.tb-widget-select-browse-tip-link {
font-weight: 500;
letter-spacing: 0.25px;
color: $tb-primary-color;
}
}
}
}
.mat-mdc-card.tb-widget-preview-card {
cursor: pointer;
transition: box-shadow 0.2s;

38
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts

@ -319,19 +319,13 @@ export class DashboardWidgetSelectComponent {
const effectiveCategories = this.iotHubSelectedCategory
? [this.iotHubSelectedCategory]
: (this.iotHubAppliedCategories.size > 0 ? Array.from(this.iotHubAppliedCategories) : undefined);
const query = new MpItemVersionQuery(pageLink, ItemType.WIDGET,
undefined, undefined,
effectiveCategories,
this.iotHubAppliedUseCases.size > 0 ? Array.from(this.iotHubAppliedUseCases) : undefined,
undefined,
this.iotHubAppliedWidgetTypes.size > 0 ? Array.from(this.iotHubAppliedWidgetTypes) : undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
this.scadaFirst ? true : undefined
);
const query = new MpItemVersionQuery(pageLink, {
type: ItemType.WIDGET,
categories: effectiveCategories,
useCases: this.iotHubAppliedUseCases.size > 0 ? Array.from(this.iotHubAppliedUseCases) : undefined,
widgetTypes: this.iotHubAppliedWidgetTypes.size > 0 ? Array.from(this.iotHubAppliedWidgetTypes) : undefined,
scadaFirst: this.scadaFirst ? true : undefined
});
return this.iotHubApiService.getPublishedVersions(query, { ignoreLoading: true });
};
@ -641,6 +635,22 @@ export class DashboardWidgetSelectComponent {
}
}
hasActiveSearchOrFilters(): boolean {
return !!this.searchSubject.value?.trim() || this.hasActiveFilters();
}
clearSearchAndFilters(): void {
this.searchSubject.next('');
if (this.hasActiveFilters()) {
this.clearAllFilters();
}
}
navigateToIotHubAllWidgets(): void {
this.selectWidgetMode = 'iotHub';
this.iotHubSubMode = 'allWidgets';
}
get hideCategoriesFilterSection(): boolean {
return this.selectWidgetMode === 'iotHub' && this.iotHubSubMode === 'category';
}
@ -680,7 +690,7 @@ export class DashboardWidgetSelectComponent {
return this.translate.instant('iot-hub.installed-from-iot-hub');
}
if (this.iotHubSubMode === 'allWidgets') {
return this.translate.instant('widget.all-widgets');
return this.translate.instant('iot-hub.all-iot-hub-widgets');
}
return '';
}

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts

@ -49,7 +49,7 @@ export class IotHubActionsService {
addItem(itemType: ItemType, options?: { itemSubType?: string; entityId?: EntityId }): Observable<IotHubAddItemDialogResult> {
return this.dialog.open(TbIotHubAddItemDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
panelClass: ['tb-dialog', 'tb-fullscreen-dialog-lt-md'],
disableClose: true,
autoFocus: false,
data: {

9
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-add-item-dialog.component.scss

@ -21,19 +21,24 @@
position: relative;
padding: 0;
overflow-x: hidden;
height: 80vh;
height: 70vh;
max-height: 70vh;
tb-iot-hub-browse {
height: 100%;
}
@media #{$mat-gt-xs} {
@media #{$mat-gt-sm} {
width: 80vw;
}
@media #{$mat-gt-md} {
width: 1000px;
}
@media #{$mat-gt-xmd} {
width: 1200px;
}
}
.mat-mdc-dialog-actions {

6
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div class="tb-iot-hub-browse" [class.embedded]="embedded" [class.tabs-hidden]="hideTabs" [class.add-mode]="mode === 'add'">
<div class="tb-iot-hub-browse" [class.embedded]="embedded" [class.tabs-hidden]="hideTabs">
<!-- Type tabs -->
<nav mat-tab-nav-bar class="tb-iot-hub-tabs" [tabPanel]="tabPanel" [class.hidden]="hideTabs">
<a mat-tab-link
@ -431,7 +431,7 @@
<!-- Error -->
@if (hasError && !isLoading) {
<div class="tb-iot-hub-empty-state">
<mat-icon class="tb-iot-hub-empty-icon">error_outline</mat-icon>
<div class="tb-no-data-bg"></div>
<h3>{{ 'iot-hub.unable-to-load' | translate }}</h3>
<p>{{ 'iot-hub.unable-to-load-text' | translate }}</p>
<button mat-flat-button color="primary" (click)="loadItems()">
@ -443,7 +443,7 @@
<!-- Empty state -->
@if (!isLoading && !hasError && items.length === 0) {
<div class="tb-iot-hub-empty-state">
<mat-icon class="tb-iot-hub-empty-icon">search_off</mat-icon>
<div class="tb-no-data-bg"></div>
<h3>{{ 'iot-hub.no-items-found' | translate }}</h3>
<p>{{ 'iot-hub.no-items-found-text' | translate }}</p>
@if (hasActiveFilters()) {

52
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.scss

@ -25,7 +25,7 @@
flex-direction: column;
height: 100%;
&.tabs-hidden:not(.add-mode) {
&.tabs-hidden:not(.embedded) {
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
}
@ -57,7 +57,7 @@
::ng-deep .mat-drawer-backdrop {
bottom: -48px;
.add-mode & {
.embedded & {
bottom: 0;
}
@ -76,7 +76,7 @@
padding-bottom: 48px;
margin-bottom: -48px;
.add-mode & {
.embedded & {
padding-bottom: 0;
margin-bottom: 0;
}
@ -131,6 +131,24 @@
flex-shrink: 0;
}
// Embedded mode always use mobile filter layout (no left sidebar, filter button + drawer)
.tb-iot-hub-browse.embedded {
.tb-iot-hub-filters-desktop {
display: none;
}
.tb-iot-hub-filter-toggle-btn {
display: inline-flex;
}
.tb-iot-hub-sort-row {
.tb-iot-hub-active-filters,
.tb-iot-hub-clear-btn {
display: none;
}
}
}
// Filter drawer right-side overlay
.tb-iot-hub-filter-drawer {
width: 60%;
@ -138,7 +156,7 @@
min-width: 300px;
margin-bottom: -48px;
.add-mode & {
.embedded & {
margin-bottom: 0;
}
}
@ -347,7 +365,7 @@
overflow-y: auto;
padding: 0 40px 0 20px;
.add-mode & {
.embedded & {
padding-bottom: 20px;
}
}
@ -495,6 +513,11 @@
padding: 80px 0;
text-align: center;
.tb-no-data-bg {
flex: auto;
height: 100px;
}
h3 {
font-size: 18px;
font-weight: 500;
@ -509,13 +532,6 @@
}
}
.tb-iot-hub-empty-icon {
font-size: 48px;
width: 48px;
height: 48px;
color: rgba(0, 0, 0, 0.2);
}
// Pagination Design: centered page numbers, items per page on the right
.tb-iot-hub-pagination {
display: flex;
@ -694,21 +710,21 @@
}
}
// Add mode responsive overrides
.add-mode {
// Embedded mode responsive overrides
.embedded {
.tb-iot-hub-card-grid.tb-iot-hub-card-grid-compact {
grid-template-columns: repeat(1, 1fr);
}
@media #{$mat-gt-sm} {
.tb-iot-hub-card-grid {
grid-template-columns: repeat(2, 1fr);
grid-template-columns: repeat(3, 1fr);
}
}
@media #{$mat-lt-md} {
.tb-iot-hub-card-grid {
grid-template-columns: repeat(1, 1fr);
@media #{$mat-gt-md} {
.tb-iot-hub-card-grid.tb-iot-hub-card-grid-compact {
grid-template-columns: repeat(2, 1fr);
}
}
}

27
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts

@ -707,21 +707,18 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
const sort = this.sortOptions[this.selectedSortIndex];
const sortOrder: SortOrder = { property: sort.value, direction: sort.direction };
const pageLink = new PageLink(this.pageSize, this.pageIndex, this.textSearch || null, sortOrder);
const query = new MpItemVersionQuery(
pageLink,
this.activeType,
undefined,
this.creatorId || undefined,
this.activeCategories.size > 0 ? Array.from(this.activeCategories) : undefined,
this.activeUseCases.size > 0 ? Array.from(this.activeUseCases) : undefined,
this.activeCfTypes.size > 0 ? Array.from(this.activeCfTypes) : undefined,
this.activeWidgetTypes.size > 0 ? Array.from(this.activeWidgetTypes) : undefined,
this.activeRuleChainTypes.size > 0 ? Array.from(this.activeRuleChainTypes) : undefined,
undefined,
this.activeHardwareTypes.size > 0 ? Array.from(this.activeHardwareTypes) : undefined,
this.activeConnectivity.size > 0 ? Array.from(this.activeConnectivity) : undefined,
this.activeVendors.size > 0 ? Array.from(this.activeVendors) : undefined
);
const query = new MpItemVersionQuery(pageLink, {
type: this.activeType,
creatorId: this.creatorId || undefined,
categories: this.activeCategories.size > 0 ? Array.from(this.activeCategories) : undefined,
useCases: this.activeUseCases.size > 0 ? Array.from(this.activeUseCases) : undefined,
cfTypes: this.activeCfTypes.size > 0 ? Array.from(this.activeCfTypes) : undefined,
widgetTypes: this.activeWidgetTypes.size > 0 ? Array.from(this.activeWidgetTypes) : undefined,
ruleChainTypes: this.activeRuleChainTypes.size > 0 ? Array.from(this.activeRuleChainTypes) : undefined,
hardwareTypes: this.activeHardwareTypes.size > 0 ? Array.from(this.activeHardwareTypes) : undefined,
connectivity: this.activeConnectivity.size > 0 ? Array.from(this.activeConnectivity) : undefined,
vendors: this.activeVendors.size > 0 ? Array.from(this.activeVendors) : undefined
});
this.iotHubApiService.getPublishedVersions(
query,
{ ignoreLoading: true, ignoreErrors: true }

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.html

@ -103,7 +103,7 @@
<!-- No results -->
@if (!isLoading && totalElements === 0 && searchText?.trim()) {
<div class="tb-search-empty">
<mat-icon class="tb-search-empty-icon">search_off</mat-icon>
<div class="tb-no-data-bg"></div>
<h3>{{ 'iot-hub.no-items-found' | translate }}</h3>
<p>{{ 'iot-hub.try-adjusting-search' | translate }}</p>
</div>

11
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.scss

@ -196,14 +196,11 @@
align-items: center;
padding: 40px 0;
margin-top: -20px;
}
.tb-search-empty-icon {
font-size: 100px;
width: 100px;
height: 100px;
color: rgba(0, 0, 0, 0.38);
margin-bottom: 20px;
.tb-no-data-bg {
flex: auto;
height: 100px;
}
}
.tb-search-empty h3 {

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.ts

@ -275,7 +275,7 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
const sort = this.sortOptions[this.selectedSortIndex];
const sortOrder: SortOrder = { property: sort.value, direction: sort.direction };
const pageLink = new PageLink(this.pageSize, this.pageIndex, text.trim() || null, sortOrder);
const query = new MpItemVersionQuery(pageLink, undefined, undefined, this.creatorId || undefined);
const query = new MpItemVersionQuery(pageLink, { creatorId: this.creatorId || undefined });
return this.iotHubApiService.getPublishedVersions(query, { ignoreLoading: true });
}

2
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts

@ -473,7 +473,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
const buildQuery = (type: ItemType, size: number): MpItemVersionQuery => {
const pageLink = new PageLink(size, 0, null, sortOrder);
return new MpItemVersionQuery(pageLink, type);
return new MpItemVersionQuery(pageLink, { type });
};
forkJoin({

86
ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts

@ -112,64 +112,66 @@ export interface MpItemVersionView {
resources: MpItemVersionResource[];
}
export interface MpItemVersionQueryOptions {
type?: string;
peOnly?: boolean;
creatorId?: string;
categories?: string[];
useCases?: string[];
cfTypes?: string[];
widgetTypes?: string[];
ruleChainTypes?: string[];
tbVersion?: number;
hardwareTypes?: string[];
connectivity?: string[];
vendors?: string[];
scadaFirst?: boolean;
}
export class MpItemVersionQuery {
constructor(
public pageLink: PageLink,
public type?: string,
public peOnly?: boolean,
public creatorId?: string,
public categories?: string[],
public useCases?: string[],
public cfTypes?: string[],
public widgetTypes?: string[],
public ruleChainTypes?: string[],
public tbVersion?: number,
public hardwareTypes?: string[],
public connectivity?: string[],
public vendors?: string[],
public scadaFirst?: boolean
) {}
constructor(public pageLink: PageLink, public options: MpItemVersionQueryOptions = {}) {}
public toQuery(): string {
let query = this.pageLink.toQuery();
if (this.type) {
query += `&type=${this.type}`;
const o = this.options;
if (o.type) {
query += `&type=${o.type}`;
}
if (this.peOnly != null) {
query += `&peOnly=${this.peOnly}`;
if (o.peOnly != null) {
query += `&peOnly=${o.peOnly}`;
}
if (this.creatorId) {
query += `&creatorId=${this.creatorId}`;
if (o.creatorId) {
query += `&creatorId=${o.creatorId}`;
}
if (this.categories?.length) {
query += this.categories.map(c => `&categories=${encodeURIComponent(c)}`).join('');
if (o.categories?.length) {
query += o.categories.map(c => `&categories=${encodeURIComponent(c)}`).join('');
}
if (this.useCases?.length) {
query += this.useCases.map(u => `&useCases=${encodeURIComponent(u)}`).join('');
if (o.useCases?.length) {
query += o.useCases.map(u => `&useCases=${encodeURIComponent(u)}`).join('');
}
if (this.cfTypes?.length) {
query += this.cfTypes.map(t => `&cfTypes=${encodeURIComponent(t)}`).join('');
if (o.cfTypes?.length) {
query += o.cfTypes.map(t => `&cfTypes=${encodeURIComponent(t)}`).join('');
}
if (this.widgetTypes?.length) {
query += this.widgetTypes.map(t => `&widgetTypes=${encodeURIComponent(t)}`).join('');
if (o.widgetTypes?.length) {
query += o.widgetTypes.map(t => `&widgetTypes=${encodeURIComponent(t)}`).join('');
}
if (this.ruleChainTypes?.length) {
query += this.ruleChainTypes.map(t => `&ruleChainTypes=${encodeURIComponent(t)}`).join('');
if (o.ruleChainTypes?.length) {
query += o.ruleChainTypes.map(t => `&ruleChainTypes=${encodeURIComponent(t)}`).join('');
}
if (this.tbVersion != null) {
query += `&tbVersion=${this.tbVersion}`;
if (o.tbVersion != null) {
query += `&tbVersion=${o.tbVersion}`;
}
if (this.hardwareTypes?.length) {
query += this.hardwareTypes.map(ht => `&hardwareTypes=${encodeURIComponent(ht)}`).join('');
if (o.hardwareTypes?.length) {
query += o.hardwareTypes.map(ht => `&hardwareTypes=${encodeURIComponent(ht)}`).join('');
}
if (this.connectivity?.length) {
query += this.connectivity.map(c => `&connectivity=${encodeURIComponent(c)}`).join('');
if (o.connectivity?.length) {
query += o.connectivity.map(c => `&connectivity=${encodeURIComponent(c)}`).join('');
}
if (this.vendors?.length) {
query += this.vendors.map(v => `&vendors=${encodeURIComponent(v)}`).join('');
if (o.vendors?.length) {
query += o.vendors.map(v => `&vendors=${encodeURIComponent(v)}`).join('');
}
if (this.scadaFirst != null) {
query += `&scadaFirst=${this.scadaFirst}`;
if (o.scadaFirst != null) {
query += `&scadaFirst=${o.scadaFirst}`;
}
return query;
}

5
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3712,6 +3712,7 @@
"try-adjusting-search": "Try adjusting your search",
"add-widget-from-iot-hub": "Add widget from IoT Hub",
"all-widgets": "All widgets",
"all-iot-hub-widgets": "All IoT Hub widgets",
"installed-from-iot-hub": "Installed from IoT Hub",
"include-deprecated": "Include deprecated",
"widget-category-value": "{{ category }} widgets",
@ -3781,6 +3782,10 @@
"retry": "Retry",
"no-items-found": "No items found",
"no-items-found-text": "Try adjusting your search or filters.",
"no-widgets-found": "No widgets found",
"browse-tip-prefix": "Can't find what you need?",
"browse-tip-link": "Browse IoT Hub",
"browse-tip-suffix": "for more widgets.",
"creator": "Creator",
"manage": "Manage",
"details": "Details",

BIN
ui-ngx/src/assets/widget/all-iot-hub-widgets.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
ui-ngx/src/assets/widget/all-widgets.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

7
ui-ngx/src/assets/widget/all-widgets.svg

@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 96" fill="none">
<rect width="120" height="96" rx="4" fill="#F5F7FA"/>
<rect x="12" y="12" width="44" height="32" rx="3" fill="#E3F2FD" stroke="#90CAF9"/>
<rect x="64" y="12" width="44" height="32" rx="3" fill="#E8F5E9" stroke="#A5D6A7"/>
<rect x="12" y="52" width="44" height="32" rx="3" fill="#FFF3E0" stroke="#FFCC80"/>
<rect x="64" y="52" width="44" height="32" rx="3" fill="#F3E5F5" stroke="#CE93D8"/>
</svg>

Before

Width:  |  Height:  |  Size: 481 B

BIN
ui-ngx/src/assets/widget/installed-from-iot-hub.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

9
ui-ngx/src/assets/widget/installed-from-iot-hub.svg

@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 96" fill="none">
<rect width="120" height="96" rx="4" fill="#E8F5E9"/>
<rect x="18" y="18" width="40" height="28" rx="3" fill="#FFFFFF" stroke="#A5D6A7"/>
<rect x="62" y="18" width="40" height="28" rx="3" fill="#FFFFFF" stroke="#A5D6A7"/>
<rect x="18" y="50" width="40" height="28" rx="3" fill="#FFFFFF" stroke="#A5D6A7"/>
<rect x="62" y="50" width="40" height="28" rx="3" fill="#FFFFFF" stroke="#A5D6A7"/>
<circle cx="98" cy="74" r="13" fill="#198038"/>
<path d="M98 67v12M93 74l5 5 5-5" stroke="#FFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 647 B

Loading…
Cancel
Save