diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts index 4a9418eec8..083b858c70 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts @@ -30,7 +30,7 @@ import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-it import { IotHubApiService } from '@core/http/iot-hub-api.service'; import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from './iot-hub-item-detail-dialog.component'; import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from './iot-hub-install-dialog.component'; import { TbIotHubUpdateDialogComponent, IotHubUpdateDialogData } from './iot-hub-update-dialog.component'; @@ -101,7 +101,8 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { private iotHubApiService: IotHubApiService, private dialog: MatDialog, private translate: TranslateService, - private router: Router + private router: Router, + private route: ActivatedRoute ) {} ngOnInit(): void { @@ -112,6 +113,15 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.pageIndex = 0; this.loadItems(); }); + if (!this.embedded) { + const params = this.route.snapshot.queryParams; + if (params['type'] && Object.values(ItemType).includes(params['type'])) { + this.activeType = params['type'] as ItemType; + } + if (params['search']) { + this.textSearch = params['search']; + } + } this.updateCategories(); this.loadItems(); } diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html new file mode 100644 index 0000000000..8794fe53b1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html @@ -0,0 +1,200 @@ + +
+ +
+ +
+ + + @for (heroType of heroTypes; track heroType.type) { + @if (heroType.icons.length) { +
+ @for (iconUrl of heroType.icons; track iconUrl; let i = $index) { + + } +
+ } + } + +
+

{{ 'iot-hub.home-title' | translate }}

+
+ {{ 'iot-hub.home-subtitle-prefix' | translate }} + @for (ht of heroTypes; track ht.type; let last = $last) { + {{ ht.labelKey | translate }}@if (!last) {,} + } +
+ + + + +
+
+ + +
+ @for (card of categoryCards; track card.type) { +
+ {{ card.titleKey | translate }} + +
+ } +
+ + @if (!isLoading) { + + @if (popularWidgets.length) { +
+ + {{ 'iot-hub.popular-widgets' | translate }} + chevron_right + +
+ @for (item of popularWidgets; track item.id) { + + + } +
+
+ } + + + @if (popularDashboards.length) { +
+ + {{ 'iot-hub.popular-dashboards' | translate }} + chevron_right + +
+ @for (item of popularDashboards; track item.id) { + + + } +
+
+ } + + + @if (popularSolutionTemplates.length) { +
+ + {{ 'iot-hub.popular-solution-templates' | translate }} + chevron_right + +
+ @for (item of popularSolutionTemplates; track item.id) { + + + } +
+
+ } + + + @if (popularCalcFields.length) { +
+ + {{ 'iot-hub.popular-calculated-fields' | translate }} + chevron_right + +
+ @for (item of popularCalcFields; track item.id) { + + + } +
+
+ } + + + @if (popularRuleChains.length) { +
+ + {{ 'iot-hub.popular-rule-chains' | translate }} + chevron_right + +
+ @for (item of popularRuleChains; track item.id) { + + + } +
+
+ } + + +
+
+

{{ 'iot-hub.become-a-creator' | translate }}

+

{{ 'iot-hub.become-creator-text' | translate }}

+ +
+ } + + @if (isLoading) { +
+ +
+ } +
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss new file mode 100644 index 0000000000..401d9ec234 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss @@ -0,0 +1,419 @@ +/** + * Copyright © 2016-2026 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Outer page — 16px padding around the white container +:host { + display: block; + padding: 16px; +} + +// Page container — white card with 8px radius, matches Figma "page-container" +.tb-iot-hub-home { + position: relative; + background: white; + border-radius: 8px; + overflow: hidden; + + // Dot grid pattern — covers top 800px, fades out via gradient mask + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 800px; + background-image: radial-gradient(circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px); + background-size: 20px 20px; + mask-image: linear-gradient(to bottom, black 0%, transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 100%); + pointer-events: none; + z-index: 0; + } + + > * { + position: relative; + z-index: 1; + } +} + +// Hero section +.tb-iot-hub-hero { + text-align: center; + padding: 140px 40px 80px; + position: relative; + overflow: hidden; +} + +// Animated background gradient +.tb-iot-hub-hero-gradient { + position: absolute; + inset: 0; + transition: background 0.6s ease; + pointer-events: none; +} + +// Floating icons container per type +.tb-iot-hub-hero-icons { + position: absolute; + inset: 0; + pointer-events: none; + + // Icons start near center (translate toward center, scaled down, invisible) + .tb-iot-hub-hero-float-icon { + opacity: 0; + transform: var(--icon-rotation) translate(var(--start-x), var(--start-y)) scale(0.9); + transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); + } + + // Active: icons move outward to final position, scale to 1, fully visible + &.active .tb-iot-hub-hero-float-icon { + opacity: 1; + transform: var(--icon-rotation) translate(0, 0) scale(1); + } +} + +// Individual floating icon — final (resting) positions match Figma +.tb-iot-hub-hero-float-icon { + position: absolute; + width: 64px; + height: 64px; + + // Top-left: moves from center-right and center-down to final pos + &.icon-pos-1 { + top: 40px; + left: 23%; + --icon-rotation: rotate(-15deg); + --start-x: 40px; + --start-y: 12px; + } + + // Top-right: moves from center-left and center-down + &.icon-pos-2 { + top: 40px; + right: 23%; + --icon-rotation: rotate(15deg); + --start-x: -40px; + --start-y: 12px; + } + + // Bottom-left: moves from center-right and center-up + &.icon-pos-3 { + bottom: 100px; + left: 18%; + --icon-rotation: rotate(-30deg); + --start-x: 50px; + --start-y: -8px; + } + + // Bottom-right: moves from center-left and center-up + &.icon-pos-4 { + bottom: 100px; + right: 18%; + --icon-rotation: rotate(30deg); + --start-x: -45px; + --start-y: -4px; + } +} + +.tb-iot-hub-hero-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 40px; + position: relative; + z-index: 1; +} + +.tb-iot-hub-hero-title { + font-size: 56px; + font-weight: 500; + line-height: 1.2; + letter-spacing: 0.14px; + color: rgba(0, 0, 0, 0.87); + margin: 0; +} + +.tb-iot-hub-hero-subtitle { + font-size: 20px; + line-height: 24px; + letter-spacing: 0.1px; + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 6px; +} + +.tb-iot-hub-hero-prefix { + font-weight: 400; + color: rgba(0, 0, 0, 0.76); +} + +.tb-iot-hub-hero-keyword { + font-weight: 600; + color: rgba(0, 0, 0, 0.54); + opacity: 0.48; + cursor: pointer; + transition: color 0.3s ease, opacity 0.3s ease; + + &.active { + color: var(--keyword-color); + opacity: 1; + } + + &:hover { + opacity: 0.8; + } +} + +.tb-iot-hub-hero-search { + width: 600px; + max-width: 100%; + + ::ng-deep .mat-mdc-text-field-wrapper { + background: white; + border-radius: 8px; + box-shadow: + 0 7px 16px 0 rgba(0, 0, 0, 0.04), + 0 29px 29px 0 rgba(0, 0, 0, 0.03), + 0 64px 38px 0 rgba(0, 0, 0, 0.02); + } + + .mat-mdc-form-field-subscript-wrapper { + display: none; + } +} + +// Category cards — Figma: centered 1200px container, flex-col gap=20, pb=48, 3-col rows +.tb-iot-hub-categories { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + max-width: 1200px; + margin: 0 auto; + padding-bottom: 48px; +} + +// Card — Figma: flex-col, gap=24, pt=24, rounded-8, border, overflow-clip, h=220 +.tb-iot-hub-category-card { + height: 220px; + border-radius: 8px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; + padding-top: 24px; + transition: transform 0.2s, box-shadow 0.2s; + border: 1px solid rgba(0, 0, 0, 0.12); + overflow: hidden; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + } +} + +// Title — Figma: 18px Medium, centered, tracking=0.15, flex child +.tb-iot-hub-category-title { + font-size: 18px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.87); + text-align: center; + width: 100%; + flex-shrink: 0; +} + +// Card image — Figma: h=148, w=full, overflow-clip, fills remaining space +.tb-iot-hub-category-img { + width: 100%; + height: 148px; + object-fit: cover; + flex-shrink: 0; +} + +// Exact Figma gradients (104.75deg angle) +.category-widgets { + background: linear-gradient(104.75deg, rgb(240, 255, 245) 0%, rgb(147, 246, 182) 100%); +} + +.category-dashboards { + background: linear-gradient(104.75deg, rgb(245, 246, 255) 0%, rgb(189, 197, 255) 100%); +} + +.category-solutions { + background: linear-gradient(104.75deg, rgb(245, 250, 255) 0%, rgb(149, 200, 255) 100%); +} + +.category-calc-fields { + background: linear-gradient(104.75deg, rgb(245, 252, 255) 0%, rgb(149, 222, 248) 100%); +} + +.category-rule-chains { + background: linear-gradient(104.75deg, rgb(255, 252, 245) 0%, rgb(255, 238, 194) 100%); +} + +.category-devices { + background: linear-gradient(104.75deg, rgb(245, 255, 252) 0%, rgb(147, 240, 213) 100%); +} + +// Sections — matches Figma "items-section" (px=40, gap between sections ~48px) +.tb-iot-hub-section { + padding: 0 40px; + margin-top: 48px; +} + +// Section header — matches Figma "button" (height=40, font-size=20, font-weight=500) +.tb-iot-hub-section-header { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 20px; + font-weight: 500; + line-height: 40px; + color: rgba(0, 0, 0, 0.87); + cursor: pointer; + text-decoration: none; + + mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + } + + &:hover { + color: #00695c; + } +} + +// Big card row — matches Figma "cards-row" (5 cards, gap=20, mt=12 from header) +.tb-iot-hub-big-cards-row { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 20px; + margin-top: 12px; +} + +// Compact card grid — matches Figma "cards" (3 columns, row gap=20, col gap=20) +.tb-iot-hub-compact-cards-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + margin-top: 12px; +} + +// Divider — matches Figma "divider" (1px, rgba(0,0,0,0.12)) +.tb-iot-hub-divider { + height: 1px; + background: rgba(0, 0, 0, 0.12); + margin: 48px 0 0; +} + +// Become a Creator — matches Figma (pt=64, text centered, button at y=156) +.tb-iot-hub-become-creator { + text-align: center; + padding: 64px 40px; + + h2 { + font-size: 24px; + font-weight: 500; + line-height: 32px; + color: rgba(0, 0, 0, 0.87); + margin: 0 0 8px; + } + + p { + font-size: 14px; + line-height: 20px; + color: rgba(0, 0, 0, 0.54); + margin: 0 0 32px; + } +} + +// Loading +.tb-iot-hub-loading { + display: flex; + justify-content: center; + padding: 80px 0; +} + +// Page load animation — Figma: Default→appear-first, Smart Animate 1.02s, Gentle easing +// Hero elements fade in + slide DOWN 20px; categories fade in + slide UP 20px +@keyframes iot-hub-fade-slide-down { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes iot-hub-fade-slide-up { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.tb-iot-hub-hero-title, +.tb-iot-hub-hero-subtitle, +.tb-iot-hub-hero-search { + animation: iot-hub-fade-slide-down 1s cubic-bezier(0.19, 1, 0.22, 1) 0.2s both; +} + +.tb-iot-hub-categories { + animation: iot-hub-fade-slide-up 1s cubic-bezier(0.19, 1, 0.22, 1) 0.2s both; +} + +// Responsive +@media (max-width: 1200px) { + .tb-iot-hub-big-cards-row { + grid-template-columns: repeat(3, 1fr); + } + + .tb-iot-hub-compact-cards-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 900px) { + .tb-iot-hub-categories { + grid-template-columns: repeat(2, 1fr); + + .tb-iot-hub-category-card { + height: 180px; + } + } + + .tb-iot-hub-big-cards-row { + grid-template-columns: repeat(2, 1fr); + } + + .tb-iot-hub-hero-title { + font-size: 36px; + } + + .tb-iot-hub-hero { + padding: 80px 24px 40px; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts new file mode 100644 index 0000000000..e0633aa243 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts @@ -0,0 +1,222 @@ +/// +/// Copyright © 2016-2026 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Router } from '@angular/router'; +import { MatDialog } from '@angular/material/dialog'; +import { forkJoin } from 'rxjs'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MpItemVersionQuery, MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models'; +import { ItemType, itemTypeTranslations } from '@shared/models/iot-hub/iot-hub-item.models'; +import { IotHubApiService } from '@core/http/iot-hub-api.service'; +import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from './iot-hub-item-detail-dialog.component'; +import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from './iot-hub-install-dialog.component'; + +interface CategoryCard { + type: ItemType; + titleKey: string; + icon: string; + cssClass: string; + image: string; +} + +interface HeroTypeConfig { + type: ItemType; + labelKey: string; + color: string; + gradientColor: string; + icons: string[]; +} + +@Component({ + selector: 'tb-iot-hub-home', + standalone: false, + templateUrl: './iot-hub-home.component.html', + styleUrls: ['./iot-hub-home.component.scss'] +}) +export class TbIotHubHomeComponent implements OnInit, OnDestroy { + + readonly ItemType = ItemType; + + searchText = ''; + + heroTypes: HeroTypeConfig[] = [ + { + type: ItemType.WIDGET, labelKey: 'item.type-widget-plural', color: '#2c9755', + gradientColor: 'rgba(44, 151, 85, 0.1)', + icons: ['assets/iot-hub/hero-widget-icon-1.svg', 'assets/iot-hub/hero-widget-icon-2.svg', 'assets/iot-hub/hero-widget-icon-3.svg', 'assets/iot-hub/hero-widget-icon-4.svg'] + }, + { + type: ItemType.DASHBOARD, labelKey: 'item.type-dashboard-plural', color: '#4d5fd0', + gradientColor: 'rgba(77, 95, 208, 0.1)', + icons: ['assets/iot-hub/hero-dashboard-icon-1.svg', 'assets/iot-hub/hero-dashboard-icon-2.svg', 'assets/iot-hub/hero-dashboard-icon-3.svg', 'assets/iot-hub/hero-dashboard-icon-4.svg'] + }, + { + type: ItemType.SOLUTION_TEMPLATE, labelKey: 'item.type-solution-template-plural', color: '#2666a9', + gradientColor: 'rgba(38, 102, 169, 0.1)', icons: [] + }, + { + type: ItemType.CALCULATED_FIELD, labelKey: 'item.type-calculated-field-plural', color: '#006d92', + gradientColor: 'rgba(0, 109, 146, 0.1)', icons: [] + }, + { + type: ItemType.RULE_CHAIN, labelKey: 'item.type-rule-chain-plural', color: '#95694b', + gradientColor: 'rgba(149, 105, 75, 0.1)', icons: [] + }, + { + type: ItemType.DEVICE, labelKey: 'iot-hub.and-devices', color: '#4b8a79', + gradientColor: 'rgba(75, 138, 121, 0.1)', icons: [] + } + ]; + + activeHeroType: HeroTypeConfig = this.heroTypes[0]; + heroIconsReady = false; + private heroInterval: any; + + categoryCards: CategoryCard[] = [ + { type: ItemType.WIDGET, titleKey: 'item.type-widget-plural', icon: 'widgets', cssClass: 'category-widgets', image: 'assets/iot-hub/category-widgets-img.svg' }, + { type: ItemType.DASHBOARD, titleKey: 'item.type-dashboard-plural', icon: 'dashboard', cssClass: 'category-dashboards', image: 'assets/iot-hub/category-dashboards-img.svg' }, + { type: ItemType.SOLUTION_TEMPLATE, titleKey: 'item.type-solution-template-plural', icon: 'integration_instructions', cssClass: 'category-solutions', image: 'assets/iot-hub/category-solution-templates-img.png' }, + { type: ItemType.CALCULATED_FIELD, titleKey: 'item.type-calculated-field-plural', icon: 'functions', cssClass: 'category-calc-fields', image: 'assets/iot-hub/category-calculated-fields-img.svg' }, + { type: ItemType.RULE_CHAIN, titleKey: 'item.type-rule-chain-plural', icon: 'account_tree', cssClass: 'category-rule-chains', image: 'assets/iot-hub/category-rule-chains-img.svg' }, + { type: ItemType.DEVICE, titleKey: 'iot-hub.device-library', icon: 'memory', cssClass: 'category-devices', image: 'assets/iot-hub/category-device-library-img.svg' } + ]; + + popularWidgets: MpItemVersionView[] = []; + popularDashboards: MpItemVersionView[] = []; + popularSolutionTemplates: MpItemVersionView[] = []; + popularCalcFields: MpItemVersionView[] = []; + popularRuleChains: MpItemVersionView[] = []; + + isLoading = true; + + constructor( + private router: Router, + private dialog: MatDialog, + private iotHubApiService: IotHubApiService + ) {} + + ngOnInit(): void { + this.loadPopularItems(); + // One-tick delay so Angular renders icons in hidden state first, then triggers transition + requestAnimationFrame(() => { + this.heroIconsReady = true; + this.startHeroCycle(); + }); + } + + ngOnDestroy(): void { + this.stopHeroCycle(); + } + + onHeroTypeHover(config: HeroTypeConfig): void { + this.stopHeroCycle(); + this.activeHeroType = config; + } + + onHeroTypeLeave(): void { + this.startHeroCycle(); + } + + private startHeroCycle(): void { + this.stopHeroCycle(); + this.heroInterval = setInterval(() => { + const idx = this.heroTypes.indexOf(this.activeHeroType); + this.activeHeroType = this.heroTypes[(idx + 1) % this.heroTypes.length]; + }, 3000); + } + + private stopHeroCycle(): void { + if (this.heroInterval) { + clearInterval(this.heroInterval); + this.heroInterval = null; + } + } + + onSearch(): void { + if (this.searchText?.trim()) { + this.router.navigate(['/iot-hub/browse'], { queryParams: { search: this.searchText.trim() } }); + } + } + + navigateToBrowse(type: ItemType): void { + this.router.navigate(['/iot-hub/browse'], { queryParams: { type } }); + } + + navigateToInstalledItems(): void { + this.router.navigate(['/iot-hub/installed']); + } + + openItemDetail(item: MpItemVersionView): void { + this.dialog.open(TbIotHubItemDetailDialogComponent, { + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + autoFocus: false, + data: { + item, + iotHubApiService: this.iotHubApiService + } as IotHubItemDetailDialogData + }); + } + + installItem(item: MpItemVersionView): void { + this.dialog.open(TbIotHubInstallDialogComponent, { + panelClass: ['tb-dialog'], + data: { + item, + iotHubApiService: this.iotHubApiService + } as IotHubInstallDialogData + }); + } + + navigateToCreator(creatorId: string): void { + this.router.navigate(['/iot-hub/creator', creatorId]); + } + + openSignup(): void { + window.open('https://iothub.thingsboard.io/signup', '_blank'); + } + + private loadPopularItems(): void { + const sortOrder: SortOrder = { property: 'totalInstallCount', direction: Direction.DESC }; + const config = { ignoreLoading: true }; + + const buildQuery = (type: ItemType, size: number): MpItemVersionQuery => { + const pageLink = new PageLink(size, 0, null, sortOrder); + return new MpItemVersionQuery(pageLink, type); + }; + + forkJoin({ + widgets: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.WIDGET, 5), config), + dashboards: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.DASHBOARD, 5), config), + solutionTemplates: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.SOLUTION_TEMPLATE, 5), config), + calcFields: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.CALCULATED_FIELD, 6), config), + ruleChains: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.RULE_CHAIN, 6), config) + }).subscribe({ + next: (results) => { + this.popularWidgets = results.widgets.data; + this.popularDashboards = results.dashboards.data; + this.popularSolutionTemplates = results.solutionTemplates.data; + this.popularCalcFields = results.calcFields.data; + this.popularRuleChains = results.ruleChains.data; + this.isLoading = false; + }, + error: () => { + this.isLoading = false; + } + }); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts index 33580a26bd..040bf2ef5c 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts @@ -18,6 +18,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; +import { TbIotHubHomeComponent } from './iot-hub-home.component'; import { TbIotHubBrowseComponent } from './iot-hub-browse.component'; import { TbIotHubCreatorProfileComponent } from './iot-hub-creator-profile.component'; import { TbIotHubInstalledItemsComponent } from './iot-hub-installed-items.component'; @@ -35,10 +36,22 @@ const routes: Routes = [ children: [ { path: '', + component: TbIotHubHomeComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'iot-hub.iot-hub' + } + }, + { + path: 'browse', component: TbIotHubBrowseComponent, data: { auth: [Authority.TENANT_ADMIN], - title: 'iot-hub.browse' + title: 'iot-hub.browse', + breadcrumb: { + label: 'iot-hub.browse', + icon: 'search' + } } }, { diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts index 94110fb092..092dbc401c 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts @@ -18,6 +18,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { IotHubRoutingModule } from './iot-hub-routing.module'; +import { TbIotHubHomeComponent } from './iot-hub-home.component'; import { TbIotHubBrowseComponent } from './iot-hub-browse.component'; import { TbIotHubCreatorProfileComponent } from './iot-hub-creator-profile.component'; import { TbIotHubItemDetailDialogComponent } from './iot-hub-item-detail-dialog.component'; @@ -28,6 +29,7 @@ import { TbIotHubUpdateDialogComponent } from './iot-hub-update-dialog.component @NgModule({ declarations: [ + TbIotHubHomeComponent, TbIotHubBrowseComponent, TbIotHubCreatorProfileComponent, TbIotHubItemDetailDialogComponent, diff --git a/ui-ngx/src/assets/iot-hub/category-calculated-fields-img.svg b/ui-ngx/src/assets/iot-hub/category-calculated-fields-img.svg new file mode 100644 index 0000000000..e74a12f489 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/category-calculated-fields-img.svg @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/category-dashboards-img.svg b/ui-ngx/src/assets/iot-hub/category-dashboards-img.svg new file mode 100644 index 0000000000..2db1d0fafa --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/category-dashboards-img.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/category-device-library-img.svg b/ui-ngx/src/assets/iot-hub/category-device-library-img.svg new file mode 100644 index 0000000000..57b90cde55 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/category-device-library-img.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/category-rule-chains-img.svg b/ui-ngx/src/assets/iot-hub/category-rule-chains-img.svg new file mode 100644 index 0000000000..09bd6882da --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/category-rule-chains-img.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/category-solution-templates-img.png b/ui-ngx/src/assets/iot-hub/category-solution-templates-img.png new file mode 100644 index 0000000000..efcd683abd Binary files /dev/null and b/ui-ngx/src/assets/iot-hub/category-solution-templates-img.png differ diff --git a/ui-ngx/src/assets/iot-hub/category-widgets-img.svg b/ui-ngx/src/assets/iot-hub/category-widgets-img.svg new file mode 100644 index 0000000000..607a9c4911 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/category-widgets-img.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg new file mode 100644 index 0000000000..471177f37b --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg new file mode 100644 index 0000000000..3e453e4b17 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-3.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-3.svg new file mode 100644 index 0000000000..fc733a6dc8 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-3.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-4.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-4.svg new file mode 100644 index 0000000000..949f7c267d --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-4.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-1.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-1.svg new file mode 100644 index 0000000000..9740f9c55a --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-1.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-2.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-2.svg new file mode 100644 index 0000000000..1d02f2d107 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-2.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-3.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-3.svg new file mode 100644 index 0000000000..0bd4d29731 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-3.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-4.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-4.svg new file mode 100644 index 0000000000..37d0c227b0 --- /dev/null +++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-4.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4ebba5fd38..7b82928c68 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3684,14 +3684,32 @@ "item": { "selected": "Selected", "type-widget": "Widget", + "type-widget-plural": "Widgets", "type-dashboard": "Dashboard", + "type-dashboard-plural": "Dashboards", "type-solution-template": "Solution Template", + "type-solution-template-plural": "Solution Templates", "type-calculated-field": "Calculated Field", + "type-calculated-field-plural": "Calculated Fields", "type-rule-chain": "Rule Chain", - "type-device": "Device" + "type-rule-chain-plural": "Rule Chains", + "type-device": "Device", + "type-device-plural": "Devices" }, "iot-hub": { "iot-hub": "IoT Hub", + "home-title": "ThingsBoard IoT Hub", + "home-subtitle-prefix": "Discover ready-to-use", + "and-devices": "& Devices", + "search-placeholder": "Search in IoT Hub...", + "device-library": "Device Library", + "popular-widgets": "Popular Widgets", + "popular-dashboards": "Popular Dashboards", + "popular-solution-templates": "Popular Solution Templates", + "popular-calculated-fields": "Popular Calculated Fields", + "popular-rule-chains": "Popular Rule Chains", + "become-creator-text": "Submit your templates to the ThingsBoard IoT Hub to get featured and showcase your solutions to our global community.", + "submit-template": "Submit Template", "browse": "Browse IoT Hub", "creator-profile": "Creator Profile", "title-widgets": "Widgets",