Browse Source

UI: Implement quick links widget

feature/home-page
Igor Kulikov 3 years ago
parent
commit
263b804dca
  1. 1
      ui-ngx/angular.json
  2. 104
      ui-ngx/src/app/core/services/menu.service.ts
  3. 33
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.html
  4. 29
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.scss
  5. 57
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.ts
  6. 10
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html
  7. 2
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.ts
  8. 13
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.ts
  9. 63
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-doc-links-dialog.component.html
  10. 114
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-doc-links-dialog.component.ts
  11. 95
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.html
  12. 4
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.scss
  13. 122
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.ts
  14. 16
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts
  15. 33
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page.scss
  16. 10
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/link.component.scss
  17. 91
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.html
  18. 318
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.ts
  19. 36
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.ts
  20. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

1
ui-ngx/angular.json

@ -85,6 +85,7 @@
"node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css",
"node_modules/leaflet/dist/leaflet.css",
"src/app/modules/home/components/widget/lib/maps/markers.scss",
"src/app/modules/home/components/widget/lib/home-page/home-page.scss",
"node_modules/leaflet.markercluster/dist/MarkerCluster.css",
"node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css",
"node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css",

104
ui-ngx/src/app/core/services/menu.service.ts

@ -107,7 +107,7 @@ export class MenuService {
icon: 'supervisor_account'
},
{
id: 'tenantProfiles',
id: 'tenant_profiles',
name: 'tenant-profile.tenant-profiles',
type: 'link',
path: '/tenantProfiles',
@ -122,14 +122,14 @@ export class MenuService {
icon: 'folder',
pages: [
{
id: 'resourcesWidgetsBundles',
id: 'widget_library',
name: 'widget.widget-library',
type: 'link',
path: '/resources/widgets-bundles',
icon: 'now_widgets'
},
{
id: 'resourcesResourcesLibrary',
id: 'resources_library',
name: 'resource.resources-library',
type: 'link',
path: '/resources/resources-library',
@ -139,7 +139,7 @@ export class MenuService {
]
},
{
id: 'notification',
id: 'notifications_center',
name: 'notification.notification-center',
type: 'link',
path: '/notification',
@ -147,28 +147,28 @@ export class MenuService {
isMdiIcon: true,
pages: [
{
id: 'notificationInbox',
id: 'notification_inbox',
name: 'notification.inbox',
type: 'link',
path: '/notification/inbox',
icon: 'inbox'
},
{
id: 'notificationSent',
id: 'notification_sent',
name: 'notification.sent',
type: 'link',
path: '/notification/sent',
icon: 'outbox'
},
{
id: 'notificationRecipients',
id: 'notification_recipients',
name: 'notification.recipients',
type: 'link',
path: '/notification/recipients',
icon: 'contacts'
},
{
id: 'notificationTemplates',
id: 'notification_templates',
name: 'notification.templates',
type: 'link',
path: '/notification/templates',
@ -176,7 +176,7 @@ export class MenuService {
isMdiIcon: true
},
{
id: 'notificationRules',
id: 'notification_rules',
name: 'notification.rules',
type: 'link',
path: '/notification/rules',
@ -193,21 +193,21 @@ export class MenuService {
icon: 'settings',
pages: [
{
id: 'settingsGeneral',
id: 'general',
name: 'admin.general',
type: 'link',
path: '/settings/general',
icon: 'settings_applications'
},
{
id: 'settingsOutgoingMail',
id: 'mail_server',
name: 'admin.outgoing-mail',
type: 'link',
path: '/settings/outgoing-mail',
icon: 'mail'
},
{
id: 'settingsNotifications',
id: 'notification_settings',
name: 'admin.notifications',
type: 'link',
path: '/settings/notifications',
@ -215,7 +215,7 @@ export class MenuService {
isMdiIcon: true
},
{
id: 'settingsQueues',
id: 'queues',
name: 'admin.queues',
type: 'link',
path: '/settings/queues',
@ -224,21 +224,21 @@ export class MenuService {
]
},
{
id: 'securitySettings',
id: 'security_settings',
name: 'security.security',
type: 'toggle',
path: '/security-settings',
icon: 'security',
pages: [
{
id: 'securitySettingsGeneral',
id: 'security_settings_general',
name: 'admin.general',
type: 'link',
path: '/security-settings/general',
icon: 'settings_applications'
},
{
id: 'securitySettings2fa',
id: '2fa',
name: 'admin.2fa.2fa',
type: 'link',
path: '/security-settings/2fa',
@ -246,7 +246,7 @@ export class MenuService {
isMdiIcon: true
},
{
id: 'securitySettingsOauth2',
id: 'oauth2',
name: 'admin.oauth2.oauth2',
type: 'link',
path: '/security-settings/oauth2',
@ -370,21 +370,21 @@ export class MenuService {
icon: 'category',
pages: [
{
id: 'entitiesDevices',
id: 'devices',
name: 'device.devices',
type: 'link',
path: '/entities/devices',
icon: 'devices_other'
},
{
id: 'entitiesAssets',
id: 'assets',
name: 'asset.assets',
type: 'link',
path: '/entities/assets',
icon: 'domain'
},
{
id: 'entitiesEntityViews',
id: 'entity_views',
name: 'entity-view.entity-views',
type: 'link',
path: '/entities/entityViews',
@ -400,7 +400,7 @@ export class MenuService {
icon: 'badge',
pages: [
{
id: 'profilesDeviceProfiles',
id: 'device_profiles',
name: 'device-profile.device-profiles',
type: 'link',
path: '/profiles/deviceProfiles',
@ -408,7 +408,7 @@ export class MenuService {
isMdiIcon: true
},
{
id: 'profilesAssetProfiles',
id: 'asset_profiles',
name: 'asset-profile.asset-profiles',
type: 'link',
path: '/profiles/assetProfiles',
@ -425,7 +425,7 @@ export class MenuService {
icon: 'supervisor_account'
},
{
id: 'ruleChains',
id: 'rule_chains',
name: 'rulechain.rulechains',
type: 'link',
path: '/ruleChains',
@ -435,21 +435,21 @@ export class MenuService {
if (authState.edgesSupportEnabled) {
sections.push(
{
id: 'edgeManagement',
id: 'edge_management',
name: 'edge.management',
type: 'toggle',
path: '/edgeManagement',
icon: 'settings_input_antenna',
pages: [
{
id: 'edgeManagementInstances',
id: 'edges',
name: 'edge.instances',
type: 'link',
path: '/edgeManagement/instances',
icon: 'router'
},
{
id: 'edgeManagementRuleChains',
id: 'rulechain_templates',
name: 'edge.rulechain-templates',
type: 'link',
path: '/edgeManagement/ruleChains',
@ -468,14 +468,14 @@ export class MenuService {
icon: 'construction',
pages: [
{
id: 'featuresOtaUpdates',
id: 'otaUpdates',
name: 'ota-update.ota-updates',
type: 'link',
path: '/features/otaUpdates',
icon: 'memory'
},
{
id: 'featuresVc',
id: 'version_control',
name: 'version-control.version-control',
type: 'link',
path: '/features/vc',
@ -491,14 +491,14 @@ export class MenuService {
icon: 'folder',
pages: [
{
id: 'resourcesWidgetsBundles',
id: 'widget_library',
name: 'widget.widget-library',
type: 'link',
path: '/resources/widgets-bundles',
icon: 'now_widgets'
},
{
id: 'resourcesResourcesLibrary',
id: 'resources_library',
name: 'resource.resources-library',
type: 'link',
path: '/resources/resources-library',
@ -508,7 +508,7 @@ export class MenuService {
]
},
{
id: 'notification',
id: 'notifications_center',
name: 'notification.notification-center',
type: 'link',
path: '/notification',
@ -516,28 +516,28 @@ export class MenuService {
isMdiIcon: true,
pages: [
{
id: 'notificationInbox',
id: 'notification_inbox',
name: 'notification.inbox',
type: 'link',
path: '/notification/inbox',
icon: 'inbox'
},
{
id: 'notificationSent',
id: 'notification_sent',
name: 'notification.sent',
type: 'link',
path: '/notification/sent',
icon: 'outbox'
},
{
id: 'notificationRecipients',
id: 'notification_recipients',
name: 'notification.recipients',
type: 'link',
path: '/notification/recipients',
icon: 'contacts'
},
{
id: 'notificationTemplates',
id: 'notification_templates',
name: 'notification.templates',
type: 'link',
path: '/notification/templates',
@ -545,7 +545,7 @@ export class MenuService {
isMdiIcon: true
},
{
id: 'notificationRules',
id: 'notification_rules',
name: 'notification.rules',
type: 'link',
path: '/notification/rules',
@ -555,7 +555,7 @@ export class MenuService {
]
},
{
id: 'usage',
id: 'api_usage',
name: 'api-usage.api-usage',
type: 'link',
path: '/usage',
@ -569,14 +569,14 @@ export class MenuService {
icon: 'settings',
pages: [
{
id: 'settingsHome',
id: 'home_settings',
name: 'admin.home',
type: 'link',
path: '/settings/home',
icon: 'settings_applications'
},
{
id: 'settingsNotifications',
id: 'notification_settings',
name: 'admin.notifications',
type: 'link',
path: '/settings/notifications',
@ -584,14 +584,14 @@ export class MenuService {
isMdiIcon: true
},
{
id: 'settingsRepository',
id: 'repository_settings',
name: 'admin.repository',
type: 'link',
path: '/settings/repository',
icon: 'manage_history'
},
{
id: 'settingsAutoCommit',
id: 'auto_commit_settings',
name: 'admin.auto-commit',
type: 'link',
path: '/settings/auto-commit',
@ -600,14 +600,14 @@ export class MenuService {
]
},
{
id: 'securitySettings',
id: 'security_settings',
name: 'security.security',
type: 'toggle',
path: '/security-settings',
icon: 'security',
pages: [
{
id: 'securitySettingsAuditLogs',
id: 'audit_log',
name: 'audit-log.audit-logs',
type: 'link',
path: '/security-settings/auditLogs',
@ -811,21 +811,21 @@ export class MenuService {
icon: 'category',
pages: [
{
id: 'entitiesDevices',
id: 'devices',
name: 'device.devices',
type: 'link',
path: '/entities/devices',
icon: 'devices_other'
},
{
id: 'entitiesAssets',
id: 'assets',
name: 'asset.assets',
type: 'link',
path: '/entities/assets',
icon: 'domain'
},
{
id: 'entitiesEntityViews',
id: 'entity_views',
name: 'entity-view.entity-views',
type: 'link',
path: '/entities/entityViews',
@ -837,7 +837,7 @@ export class MenuService {
if (authState.edgesSupportEnabled) {
sections.push(
{
id: 'edgeManagementInstances',
id: 'edges',
name: 'edge.edge-instances',
type: 'link',
path: '/edgeManagement/instances',
@ -847,7 +847,7 @@ export class MenuService {
}
sections.push(
{
id: 'notification',
id: 'notifications_center',
name: 'notification.notification-center',
type: 'link',
path: '/notification',
@ -855,7 +855,7 @@ export class MenuService {
isMdiIcon: true,
pages: [
{
id: 'notificationInbox',
id: 'notification_inbox',
name: 'notification.inbox',
type: 'link',
path: '/notification/inbox',
@ -963,7 +963,11 @@ export class MenuService {
public menuLinksByIds(ids: string[]): Observable<Array<MenuSection>> {
return this.availableMenuLinks$.pipe(
map((links) => links.filter(link => ids.includes(link.id)))
map((links) => links.filter(link => ids.includes(link.id)).sort((a, b) => {
const i1 = ids.indexOf(a.id);
const i2 = ids.indexOf(b.id);
return i1 - i2;
}))
);
}

33
ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.html

@ -0,0 +1,33 @@
<!--
Copyright © 2016-2023 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.
-->
<form class="tb-add-quick-link-dialog" [formGroup]="addQuickLinkFormGroup">
<mat-toolbar style="background: transparent;">
<h2 translate>widgets.quick-links.add-link-title</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content>
<tb-quick-link formControlName="link" [addOnly]="true"
(quickLinkAdded)="add($event)">
</tb-quick-link>
</div>
</form>

29
ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.scss

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2023 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.
*/
:host {
.tb-add-quick-link-dialog {
width: 480px;
.mat-toolbar-single-row {
padding: 0 24px;
}
h2 {
color: rgba(0, 0, 0, 0.76);
}
.mat-icon {
color: rgba(0, 0, 0, 0.54);
}
}
}

57
ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.ts

@ -0,0 +1,57 @@
///
/// Copyright © 2016-2023 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, SkipSelf } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
@Component({
selector: 'tb-add-quick-link-dialog',
templateUrl: './add-quick-link-dialog.component.html',
styleUrls: ['./add-quick-link-dialog.component.scss']
})
export class AddQuickLinkDialogComponent extends
DialogComponent<AddQuickLinkDialogComponent, string> implements OnInit {
addQuickLinkFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<AddQuickLinkDialogComponent, string>,
public fb: UntypedFormBuilder) {
super(store, router, dialogRef);
}
ngOnInit(): void {
this.addQuickLinkFormGroup = this.fb.group({
link: [null, [Validators.required]]
});
}
cancel(): void {
this.dialogRef.close(null);
}
add(link: string): void {
this.dialogRef.close(link);
}
}

10
ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html

@ -16,7 +16,7 @@
-->
<div fxLayout="row" *ngIf="addMode || editMode; else docLinkTemplate">
<div fxFlex class="tb-edit-doc-link" [ngClass]="{'edit-mode': editMode}" [formGroup]="editDocLinkFormGroup" fxLayout="column">
<div fxFlex class="tb-edit-link" [ngClass]="{'edit-mode': editMode}" [formGroup]="editDocLinkFormGroup" fxLayout="column">
<tb-material-icon-select required formControlName="icon"></tb-material-icon-select>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.documentation.name</mat-label>
@ -44,12 +44,12 @@
</div>
<ng-template #docLinkTemplate>
<div fxLayout="row">
<div fxFlex class="tb-doc-link">
<div class="tb-doc-container">
<div class="tb-doc-icon-container">
<div fxFlex class="tb-link">
<div class="tb-link-container">
<div class="tb-link-icon-container">
<mat-icon color="primary">{{ docLink.icon }}</mat-icon>
</div>
<div class="tb-doc-text">{{ docLink.name }}</div>
<div class="tb-link-text">{{ docLink.name }}</div>
</div>
</div>
<div fxLayout="row" fxLayoutAlign="start center" class="tb-edit-buttons">

2
ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.ts

@ -31,7 +31,7 @@ import { ErrorStateMatcher } from '@angular/material/core';
@Component({
selector: 'tb-doc-link',
templateUrl: './doc-link.component.html',
styleUrls: ['./doc-link.component.scss'],
styleUrls: ['./link.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

13
ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.ts

@ -27,9 +27,9 @@ import { WidgetContext } from '@home/models/widget-component.models';
import { MatDialog } from '@angular/material/dialog';
import { AddDocLinkDialogComponent } from '@home/components/widget/lib/home-page/add-doc-link-dialog.component';
import {
EditDocLinksDialogComponent,
EditDocLinksDialogData
} from '@home/components/widget/lib/home-page/edit-doc-links-dialog.component';
EditLinksDialogComponent,
EditLinksDialogData
} from '@home/components/widget/lib/home-page/edit-links-dialog.component';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
@ -168,12 +168,13 @@ export class DocLinksWidgetComponent extends PageComponent implements OnInit, On
}
edit() {
this.dialog.open<EditDocLinksDialogComponent, EditDocLinksDialogData,
boolean>(EditDocLinksDialogComponent, {
this.dialog.open<EditLinksDialogComponent, EditLinksDialogData,
boolean>(EditLinksDialogComponent, {
disableClose: true,
autoFocus: false,
data: {
docLinks: this.documentationLinks
mode: 'docs',
links: this.documentationLinks
},
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed().subscribe(

63
ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-doc-links-dialog.component.html

@ -1,63 +0,0 @@
<!--
Copyright © 2016-2023 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.
-->
<form class="tb-edit-doc-links-dialog" [formGroup]="editDocLinksFormGroup">
<mat-toolbar style="background: transparent;">
<h2 translate>widgets.documentation.title</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="close()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content>
<div class="tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="docLinkDrop($event)" [cdkDropListDisabled]="editMode || addMode">
<div cdkDrag class="tb-draggable" *ngFor="let docLinkControl of docLinksFormArray().controls; trackBy: trackByDocLink;
let $index = index; last as isLast;"
fxLayout="row" fxLayoutAlign="start center" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-doc-link fxFlex #docLinkComponent [formControl]="docLinkControl"
[disableEdit]="editMode || addMode"
(editModeChanged)="editMode = $event"
(docLinkUpdated)="update()"
(docLinkDeleted)="deleteLink($index)">
</tb-doc-link>
<div *ngIf="!docLinkComponent.isEditing()"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above"
class="tb-drag-handle">
<mat-icon>drag_indicator</mat-icon>
</div>
</div>
</div>
</div>
<div mat-dialog-actions>
<div *ngIf="!editMode && !addMode" fxFlex class="tb-add-doc-button"
matTooltip="{{ 'widgets.documentation.add-link' | translate }}"
matTooltipPosition="above"
(click)="addLink()">
<mat-icon class="tb-add-icon">add</mat-icon>
</div>
<tb-doc-link *ngIf="addMode" fxFlex [(ngModel)]="addingDocLink"
[ngModelOptions]="{standalone: true}"
(docLinkAdded)="linkAdded($event)"
(docLinkAddCanceled)="addMode = false">
</tb-doc-link>
</div>
</form>

114
ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-doc-links-dialog.component.ts

@ -1,114 +0,0 @@
///
/// Copyright © 2016-2023 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, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { DocumentationLink, DocumentationLinks } from '@shared/models/user-settings.models';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { UserSettingsService } from '@core/http/user-settings.service';
export interface EditDocLinksDialogData {
docLinks: DocumentationLinks;
}
@Component({
selector: 'tb-edit-doc-links-dialog',
templateUrl: './edit-doc-links-dialog.component.html',
styleUrls: ['./edit-doc-links-dialog.component.scss']
})
export class EditDocLinksDialogComponent extends
DialogComponent<EditDocLinksDialogComponent, boolean> implements OnInit {
updated = false;
addMode = false;
editMode = false;
docLinks = this.data.docLinks;
addingDocLink: Partial<DocumentationLink>;
editDocLinksFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: EditDocLinksDialogData,
public dialogRef: MatDialogRef<EditDocLinksDialogComponent, boolean>,
public fb: UntypedFormBuilder,
private userSettingsService: UserSettingsService) {
super(store, router, dialogRef);
}
ngOnInit(): void {
const docLinksControls: Array<AbstractControl> = [];
for (const docLink of this.docLinks.links) {
docLinksControls.push(this.fb.control(docLink, [Validators.required]));
}
this.editDocLinksFormGroup = this.fb.group({
links: this.fb.array(docLinksControls)
});
}
docLinksFormArray(): UntypedFormArray {
return this.editDocLinksFormGroup.get('links') as UntypedFormArray;
}
trackByDocLink(index: number, docLinkControl: AbstractControl): any {
return docLinkControl;
}
docLinkDrop(event: CdkDragDrop<string[]>) {
const docLinksArray = this.editDocLinksFormGroup.get('links') as UntypedFormArray;
const docLink = docLinksArray.at(event.previousIndex);
docLinksArray.removeAt(event.previousIndex);
docLinksArray.insert(event.currentIndex, docLink);
this.update();
}
addLink() {
this.addingDocLink = { icon: 'notifications' };
this.addMode = true;
}
linkAdded(docLink: DocumentationLink) {
this.addMode = false;
const docLinksArray = this.editDocLinksFormGroup.get('links') as UntypedFormArray;
const docLinkControl = this.fb.control(docLink, [Validators.required]);
docLinksArray.push(docLinkControl);
this.update();
}
deleteLink(index: number) {
(this.editDocLinksFormGroup.get('links') as UntypedFormArray).removeAt(index);
this.update();
}
update() {
if (this.editDocLinksFormGroup.valid) {
const docLinks: DocumentationLinks = this.editDocLinksFormGroup.value;
this.userSettingsService.updateDocumentationLinks(docLinks).subscribe(() => {
this.updated = true;
});
}
}
close(): void {
this.dialogRef.close(this.updated);
}
}

95
ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.html

@ -0,0 +1,95 @@
<!--
Copyright © 2016-2023 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.
-->
<form class="tb-edit-links-dialog" [formGroup]="editLinksFormGroup">
<mat-toolbar style="background: transparent;">
<h2>{{ (mode === 'docs' ? 'widgets.documentation.title' : 'widgets.quick-links.title') | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="close()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content>
<div class="tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="linkDrop($event)" [cdkDropListDisabled]="editMode || addMode">
<div cdkDrag class="tb-draggable" *ngFor="let linkControl of linksFormArray().controls; trackBy: trackByLink;
let $index = index; last as isLast;"
fxLayout="row" fxLayoutAlign="start center" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<ng-container [ngSwitch]="mode">
<ng-template [ngSwitchCase]="'docs'">
<tb-doc-link fxFlex #docLink [formControl]="linkControl"
[disableEdit]="editMode || addMode"
(editModeChanged)="editMode = $event"
(docLinkUpdated)="update()"
(docLinkDeleted)="deleteLink($index)">
</tb-doc-link>
<ng-container *ngIf="!docLink.isEditing()">
<ng-container *ngTemplateOutlet="dragHandle"></ng-container>
</ng-container>
</ng-template>
<ng-template [ngSwitchCase]="'quickLinks'">
<tb-quick-link fxFlex #quickLink [formControl]="linkControl"
[disableEdit]="editMode || addMode"
(editModeChanged)="editMode = $event"
(quickLinkUpdated)="update()"
(quickLinkDeleted)="deleteLink($index)">
</tb-quick-link>
<ng-container *ngIf="!quickLink.isEditing()">
<ng-container *ngTemplateOutlet="dragHandle"></ng-container>
</ng-container>
</ng-template>
</ng-container>
<ng-template #dragHandle>
<div cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above"
class="tb-drag-handle">
<mat-icon>drag_indicator</mat-icon>
</div>
</ng-template>
</div>
</div>
</div>
<div mat-dialog-actions>
<div *ngIf="!editMode && !addMode" fxFlex class="tb-add-link-button"
matTooltip="{{ (mode === 'docs' ? 'widgets.documentation.add-link' : 'widgets.quick-links.add-link') | translate }}"
matTooltipPosition="above"
(click)="addLink()">
<mat-icon class="tb-add-icon">add</mat-icon>
</div>
<ng-container *ngIf="addMode">
<ng-container [ngSwitch]="mode">
<ng-template [ngSwitchCase]="'docs'">
<tb-doc-link fxFlex [(ngModel)]="addingLink"
[ngModelOptions]="{standalone: true}"
(docLinkAdded)="linkAdded($event)"
(docLinkAddCanceled)="addMode = false">
</tb-doc-link>
</ng-template>
<ng-template [ngSwitchCase]="'quickLinks'">
<tb-quick-link fxFlex [(ngModel)]="addingLink"
[ngModelOptions]="{standalone: true}"
(quickLinkAdded)="linkAdded($event)"
(quickLinkAddCanceled)="addMode = false">
</tb-quick-link>
</ng-template>
</ng-container>
</ng-container>
</div>
</form>

4
ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-doc-links-dialog.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.scss

@ -14,7 +14,7 @@
* limitations under the License.
*/
:host {
.tb-edit-doc-links-dialog {
.tb-edit-links-dialog {
width: 480px;
.mat-mdc-dialog-content {
max-height: 50vh;
@ -42,7 +42,7 @@
color: rgba(0, 0, 0, 0.38);
}
}
.tb-add-doc-button {
.tb-add-link-button {
height: 55px;
cursor: pointer;
display: flex;

122
ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.ts

@ -0,0 +1,122 @@
///
/// Copyright © 2016-2023 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, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { DocumentationLink, DocumentationLinks, QuickLinks } from '@shared/models/user-settings.models';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { UserSettingsService } from '@core/http/user-settings.service';
import { Observable } from 'rxjs';
export interface EditLinksDialogData {
mode: 'docs' | 'quickLinks';
links: DocumentationLinks | QuickLinks;
}
@Component({
selector: 'tb-edit-links-dialog',
templateUrl: './edit-links-dialog.component.html',
styleUrls: ['./edit-links-dialog.component.scss']
})
export class EditLinksDialogComponent extends
DialogComponent<EditLinksDialogComponent, boolean> implements OnInit {
updated = false;
addMode = false;
editMode = false;
links = this.data.links;
mode = this.data.mode;
addingLink: Partial<DocumentationLink> | string;
editLinksFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: EditLinksDialogData,
public dialogRef: MatDialogRef<EditLinksDialogComponent, boolean>,
public fb: UntypedFormBuilder,
private userSettingsService: UserSettingsService) {
super(store, router, dialogRef);
}
ngOnInit(): void {
const linksControls: Array<AbstractControl> = [];
for (const link of this.links.links) {
linksControls.push(this.fb.control(link, [Validators.required]));
}
this.editLinksFormGroup = this.fb.group({
links: this.fb.array(linksControls)
});
}
linksFormArray(): UntypedFormArray {
return this.editLinksFormGroup.get('links') as UntypedFormArray;
}
trackByLink(index: number, linkControl: AbstractControl): any {
return linkControl;
}
linkDrop(event: CdkDragDrop<string[]>) {
const linksArray = this.editLinksFormGroup.get('links') as UntypedFormArray;
const link = linksArray.at(event.previousIndex);
linksArray.removeAt(event.previousIndex);
linksArray.insert(event.currentIndex, link);
this.update();
}
addLink() {
this.addingLink = this.mode === 'docs' ? { icon: 'notifications' } : null;
this.addMode = true;
}
linkAdded(link: DocumentationLink | string) {
this.addMode = false;
const linksArray = this.editLinksFormGroup.get('links') as UntypedFormArray;
const linkControl = this.fb.control(link, [Validators.required]);
linksArray.push(linkControl);
this.update();
}
deleteLink(index: number) {
(this.editLinksFormGroup.get('links') as UntypedFormArray).removeAt(index);
this.update();
}
update() {
if (this.editLinksFormGroup.valid) {
let updateObservable: Observable<void>;
if (this.mode === 'docs') {
updateObservable = this.userSettingsService.updateDocumentationLinks(this.editLinksFormGroup.value);
} else {
updateObservable = this.userSettingsService.updateQuickLinks(this.editLinksFormGroup.value);
}
updateObservable.subscribe(() => {
this.updated = true;
});
}
}
close(): void {
this.dialogRef.close(this.updated);
}
}

16
ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts

@ -23,7 +23,7 @@ import { VersionInfoComponent } from '@home/components/widget/lib/home-page/vers
import { DocLinksWidgetComponent } from '@home/components/widget/lib/home-page/doc-links-widget.component';
import { DocLinkComponent } from '@home/components/widget/lib/home-page/doc-link.component';
import { AddDocLinkDialogComponent } from '@home/components/widget/lib/home-page/add-doc-link-dialog.component';
import { EditDocLinksDialogComponent } from '@home/components/widget/lib/home-page/edit-doc-links-dialog.component';
import { EditLinksDialogComponent } from '@home/components/widget/lib/home-page/edit-links-dialog.component';
import { GettingStartedWidgetComponent } from '@home/components/widget/lib/home-page/getting-started-widget.component';
import {
GettingStartedCompletedDialogComponent
@ -31,6 +31,8 @@ import {
import { ToggleHeaderComponent } from '@home/components/widget/lib/home-page/toggle-header.component';
import { UsageInfoWidgetComponent } from '@home/components/widget/lib/home-page/usage-info-widget.component';
import { QuickLinksWidgetComponent } from '@home/components/widget/lib/home-page/quick-links-widget.component';
import { QuickLinkComponent } from '@home/components/widget/lib/home-page/quick-link.component';
import { AddQuickLinkDialogComponent } from '@home/components/widget/lib/home-page/add-quick-link-dialog.component';
@NgModule({
declarations:
@ -41,12 +43,14 @@ import { QuickLinksWidgetComponent } from '@home/components/widget/lib/home-page
DocLinksWidgetComponent,
DocLinkComponent,
AddDocLinkDialogComponent,
EditDocLinksDialogComponent,
EditLinksDialogComponent,
GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent,
ToggleHeaderComponent,
UsageInfoWidgetComponent,
QuickLinksWidgetComponent
QuickLinksWidgetComponent,
QuickLinkComponent,
AddQuickLinkDialogComponent
],
imports: [
CommonModule,
@ -59,12 +63,14 @@ import { QuickLinksWidgetComponent } from '@home/components/widget/lib/home-page
DocLinksWidgetComponent,
DocLinkComponent,
AddDocLinkDialogComponent,
EditDocLinksDialogComponent,
EditLinksDialogComponent,
GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent,
ToggleHeaderComponent,
UsageInfoWidgetComponent,
QuickLinksWidgetComponent
QuickLinksWidgetComponent,
QuickLinkComponent,
AddQuickLinkDialogComponent
]
})
export class HomePageWidgetsModule { }

33
ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page.scss

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2023 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.
*/
.tb-autocomplete.tb-quick-links {
.mat-mdc-option {
border-bottom: none;
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.76);
.mat-icon {
margin-right: 10px;
color: rgba(0, 0, 0, 0.54);
font-size: 20px;
width: 20px;
height: 20px;
}
}
}

10
ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/home-page/link.component.scss

@ -14,7 +14,7 @@
* limitations under the License.
*/
:host {
.tb-edit-doc-link {
.tb-edit-link {
padding: 16px;
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.04);
@ -23,7 +23,7 @@
border: 1px solid rgba(48, 86, 128, 0.32);
}
}
.tb-doc-link {
.tb-link {
height: 55px;
display: flex;
flex-direction: column;
@ -33,18 +33,18 @@
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.04);
border-radius: 10px;
.tb-doc-container {
.tb-link-container {
display: flex;
flex-direction: row;
align-items: center;
.tb-doc-icon-container {
.tb-link-icon-container {
height: 40px;
padding: 8px;
background: #F3F6FA;
border-radius: 6px;
margin-right: 8px;
}
.tb-doc-text {
.tb-link-text {
font-weight: 400;
font-size: 14px;
line-height: 20px;

91
ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.html

@ -0,0 +1,91 @@
<!--
Copyright © 2016-2023 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.
-->
<div fxLayout="row" *ngIf="addMode || editMode; else quickLinkTemplate">
<div fxFlex class="tb-edit-link" [ngClass]="{'edit-mode': editMode}" [formGroup]="editQuickLinkFormGroup" fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.quick-links.quick-link</mat-label>
<input matInput type="text"
#linkInput
formControlName="link"
(focusin)="onFocus()"
required
[matAutocomplete]="linkAutocomplete">
<mat-icon matPrefix *ngIf="quickLink && !quickLink.isMdiIcon" color="primary">{{ quickLink.icon }}</mat-icon>
<mat-icon matPrefix *ngIf="quickLink && quickLink.isMdiIcon" color="primary" [svgIcon]="quickLink.icon"></mat-icon>
<button *ngIf="editQuickLinkFormGroup.get('link').value && !disabled"
type="button"
matSuffix mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete
class="tb-autocomplete tb-quick-links"
#linkAutocomplete="matAutocomplete"
[displayWith]="displayLinkFn">
<mat-option *ngFor="let link of filteredLinks | async" [value]="link">
<mat-icon *ngIf="!link.isMdiIcon">{{ link.icon }}</mat-icon>
<mat-icon *ngIf="link.isMdiIcon" [svgIcon]="link.icon"></mat-icon>
<span [innerHTML]="link.name | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredLinks | async)?.length" [value]="null">
<span>
{{ translate.get('widgets.quick-links.no-links-matching', {name: searchText}) | async }}
</span>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="editQuickLinkFormGroup.get('link').hasError('required')">
{{ 'widgets.quick-links.quick-link-required' | translate }}
</mat-error>
</mat-form-field>
<div *ngIf="addMode" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="8px">
<button *ngIf="!addOnly" mat-button color="primary" (click)="cancelAdd()">{{ 'action.cancel' | translate }}</button>
<button mat-raised-button color="primary" (click)="add()">{{ 'action.add' | translate }}</button>
</div>
</div>
<div *ngIf="editMode" fxLayout="row" class="tb-edit-buttons">
<button mat-icon-button (click)="apply()"><mat-icon>check</mat-icon></button>
<button mat-icon-button (click)="cancelEdit()"><mat-icon>close</mat-icon></button>
</div>
</div>
<ng-template #quickLinkTemplate>
<div fxLayout="row">
<div fxFlex class="tb-link">
<div class="tb-link-container">
<div class="tb-link-icon-container">
<mat-icon *ngIf="!quickLink?.isMdiIcon" color="primary">{{ quickLink?.icon }}</mat-icon>
<mat-icon *ngIf="quickLink?.isMdiIcon" color="primary" [svgIcon]="quickLink?.icon"></mat-icon>
</div>
<div class="tb-link-text">{{ displayLinkFn(quickLink) }}</div>
</div>
</div>
<div fxLayout="row" fxLayoutAlign="start center" class="tb-edit-buttons">
<button mat-icon-button
matTooltip="{{ 'action.edit' | translate }}"
matTooltipPosition="above"
(click)="switchToEditMode()">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above"
(click)="delete()">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</ng-template>

318
ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.ts

@ -0,0 +1,318 @@
///
/// Copyright © 2016-2023 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,
ElementRef,
EventEmitter,
forwardRef,
Input,
OnInit,
Output,
SkipSelf,
ViewChild
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormGroupDirective,
NG_VALUE_ACCESSOR,
NgForm,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ErrorStateMatcher } from '@angular/material/core';
import { MenuService } from '@core/services/menu.service';
import { Observable, of } from 'rxjs';
import { MenuSection } from '@core/services/menu.models';
import { TranslateService } from '@ngx-translate/core';
import { catchError, distinctUntilChanged, map, publishReplay, refCount, share, switchMap, tap } from 'rxjs/operators';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { deepClone } from '@core/utils';
@Component({
selector: 'tb-quick-link',
templateUrl: './quick-link.component.html',
styleUrls: ['./link.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => QuickLinkComponent),
multi: true
},
{provide: ErrorStateMatcher, useExisting: QuickLinkComponent}
]
})
export class QuickLinkComponent extends PageComponent implements OnInit, ControlValueAccessor, ErrorStateMatcher {
@Input()
disabled: boolean;
@Input()
addOnly = false;
@Input()
disableEdit = false;
@Output()
quickLinkAdded = new EventEmitter<string>();
@Output()
quickLinkAddCanceled = new EventEmitter<void>();
@Output()
quickLinkUpdated = new EventEmitter<string>();
@Output()
quickLinkDeleted = new EventEmitter<void>();
@Output()
editModeChanged = new EventEmitter<boolean>();
@ViewChild('linkInput', {static: false}) linkInput: ElementRef;
filteredLinks: Observable<Array<MenuSection>>;
private allLinksObservable$: Observable<Array<MenuSection>> = null;
searchText = '';
editMode = false;
addMode = false;
quickLink: MenuSection;
private propagateChange = null;
public editQuickLinkFormGroup: UntypedFormGroup;
private submitted = false;
private dirty = false;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder,
private menuService: MenuService,
public translate: TranslateService,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher) {
super(store);
}
ngOnInit(): void {
this.addMode = this.addOnly;
this.editQuickLinkFormGroup = this.fb.group({
link: [null, [this.requiredLinkValidator]]
});
this.filteredLinks = this.editQuickLinkFormGroup.get('link').valueChanges
.pipe(
tap(value => {
let modelValue;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
modelValue = value;
}
this.updateView(modelValue);
}),
map(value => value ? (typeof value === 'string' ? value :
((value as any).translated ? value.name : this.translate.instant(value.name))) : ''),
distinctUntilChanged(),
switchMap(name => this.fetchLinks(name) ),
share()
);
}
requiredLinkValidator(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value || typeof value === 'string') {
return {required: true};
}
return null;
}
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
return originalErrorState || customErrorState;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.editQuickLinkFormGroup.disable({emitEvent: false});
} else {
this.editQuickLinkFormGroup.enable({emitEvent: false});
}
}
writeValue(value: string): void {
if (value) {
this.menuService.menuLinkById(value).subscribe(
(link) => {
this.quickLink = link;
this.editQuickLinkFormGroup.get('link').patchValue(
link, {emitEvent: false}
);
}
);
} else {
this.quickLink = null;
this.editQuickLinkFormGroup.get('link').patchValue(
value, {emitEvent: false}
);
if (!this.editQuickLinkFormGroup.valid) {
this.addMode = true;
this.editModeChanged.emit(true);
}
}
this.dirty = true;
}
updateView(value: MenuSection | null) {
if (this.quickLink !== value) {
this.quickLink = value;
}
}
displayLinkFn = (link?: MenuSection): string | undefined =>
link ? ((link as any).translated ? link.name : this.translate.instant(link.name)) : undefined;
fetchLinks(searchText?: string): Observable<Array<MenuSection>> {
this.searchText = searchText;
const pageLink = new PageLink(100, 0, searchText, {
property: 'name',
direction: Direction.ASC
});
return this.getLinks(pageLink).pipe(
catchError(() => of(emptyPageData<MenuSection>())),
map(pageData => pageData.data)
);
}
getLinks(pageLink: PageLink): Observable<PageData<MenuSection>> {
return this.allLinks().pipe(
map((links) => pageLink.filterData(links))
);
}
allLinks(): Observable<Array<MenuSection>> {
if (this.allLinksObservable$ === null) {
this.allLinksObservable$ = this.menuService.availableMenuLinks().pipe(
map((links) => {
const result = deepClone(links);
for (const link of result) {
link.name = this.translate.instant(link.name);
(link as any).translated = true;
}
return result;
}),
publishReplay(1),
refCount()
);
}
return this.allLinksObservable$;
}
onFocus() {
if (this.dirty) {
this.editQuickLinkFormGroup.get('link').updateValueAndValidity({onlySelf: true});
this.dirty = false;
}
}
clear() {
this.editQuickLinkFormGroup.get('link').patchValue('');
setTimeout(() => {
this.linkInput.nativeElement.blur();
this.linkInput.nativeElement.focus();
}, 0);
}
switchToEditMode() {
if (!this.disableEdit && !this.editMode) {
this.submitted = false;
this.editQuickLinkFormGroup.get('link').patchValue(
this.quickLink, {emitEvent: false}
);
this.editMode = true;
this.editModeChanged.emit(true);
}
}
apply() {
this.submitted = true;
this.updateModel();
if (this.quickLink) {
this.editMode = false;
this.editModeChanged.emit(false);
this.quickLinkUpdated.next(this.quickLink.id);
}
}
cancelEdit() {
this.submitted = false;
this.editMode = false;
this.editModeChanged.emit(false);
}
add() {
this.submitted = true;
this.updateModel();
if (this.quickLink) {
if (!this.addOnly) {
this.addMode = false;
this.editModeChanged.emit(false);
}
this.quickLinkAdded.next(this.quickLink.id);
}
}
cancelAdd() {
this.editModeChanged.emit(false);
this.quickLinkAddCanceled.emit();
}
delete() {
this.quickLinkDeleted.emit();
}
isEditing() {
return this.editMode || this.addMode;
}
private updateModel() {
if (this.quickLink) {
this.propagateChange(this.quickLink.id);
} else {
this.propagateChange(null);
}
}
}

36
ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.ts

@ -29,17 +29,22 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { MenuService } from '@core/services/menu.service';
import { MenuSection } from '@core/services/menu.models';
import { AddQuickLinkDialogComponent } from '@home/components/widget/lib/home-page/add-quick-link-dialog.component';
import {
EditLinksDialogComponent,
EditLinksDialogData
} from '@home/components/widget/lib/home-page/edit-links-dialog.component';
const defaultQuickLinksMap = new Map<Authority, QuickLinks>(
[
[Authority.SYS_ADMIN, {
links: ['tenants', 'tenantProfiles']
links: ['tenants', 'tenant_profiles']
}],
[Authority.TENANT_ADMIN, {
links: ['alarms', 'dashboards', 'entitiesDevices']
links: ['alarms', 'dashboards', 'devices']
}],
[Authority.CUSTOMER_USER, {
links: ['alarms', 'dashboards', 'entitiesDevices']
links: ['alarms', 'dashboards', 'devices']
}]
]
);
@ -128,35 +133,36 @@ export class QuickLinksWidgetComponent extends PageComponent implements OnInit,
}
edit() {
/* this.dialog.open<EditDocLinksDialogComponent, EditDocLinksDialogData,
boolean>(EditDocLinksDialogComponent, {
this.dialog.open<EditLinksDialogComponent, EditLinksDialogData,
boolean>(EditLinksDialogComponent, {
disableClose: true,
autoFocus: false,
data: {
docLinks: this.documentationLinks
mode: 'quickLinks',
links: this.quickLinks
},
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed().subscribe(
(result) => {
if (result) {
this.loadDocLinks();
this.loadQuickLinks();
}
}); */
});
}
addLink() {
/* this.dialog.open<AddDocLinkDialogComponent, any,
DocumentationLink>(AddDocLinkDialogComponent, {
this.dialog.open<AddQuickLinkDialogComponent, any,
string>(AddQuickLinkDialogComponent, {
disableClose: true,
autoFocus: false,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed().subscribe(
(docLink) => {
if (docLink) {
this.documentationLinks.links.push(docLink);
(link) => {
if (link) {
this.quickLinks.links.push(link);
this.cd.markForCheck();
this.userSettingsService.updateDocumentationLinks(this.documentationLinks).subscribe();
this.userSettingsService.updateQuickLinks(this.quickLinks).subscribe();
}
}); */
});
}
}

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

@ -5146,6 +5146,10 @@
"quick-links": {
"title": "Quick links",
"add-link": "Add link",
"add-link-title": "Add quick link",
"quick-link": "Quick link",
"quick-link-required": "Quick link is required.",
"no-links-matching": "No links matching '{{name}}' were found.",
"columns": "Columns"
},
"configured-features": {

Loading…
Cancel
Save