25 changed files with 756 additions and 155 deletions
@ -0,0 +1,34 @@ |
|||
<!-- |
|||
|
|||
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 class="tb-getting-started-completed-dialog-container" fxLayout="column"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" style="height: 64px; padding: 8px;"> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button (click)="close()" type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutAlign="center center" style="height: 70px;"> |
|||
<div class="tb-done-container" fxLayout="row" fxLayoutAlign="center center"> |
|||
<mat-icon class="tb-done-icon" color="primary">done</mat-icon> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="column" fxLayoutAlign="start center" style="padding-top: 30px;"> |
|||
<div class="tb-done-welcome-title" translate>widgets.getting-started.done-welcome-title</div> |
|||
<div class="tb-done-welcome-text" translate>widgets.getting-started.done-welcome-text</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* 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-getting-started-completed-dialog { |
|||
.mat-mdc-dialog-container .mdc-dialog__surface { |
|||
border-radius: 8px; |
|||
background-image: url(/assets/home/greetings_bg.svg); |
|||
background-repeat: no-repeat; |
|||
} |
|||
|
|||
.tb-getting-started-completed-dialog-container { |
|||
width: 500px; |
|||
height: 290px; |
|||
} |
|||
|
|||
.tb-done-container { |
|||
width: 70px; |
|||
height: 70px; |
|||
border-radius: 50%; |
|||
background: rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.tb-done-icon { |
|||
width: 62px; |
|||
height: 62px; |
|||
line-height: 56px; |
|||
font-size: 36px; |
|||
text-align: center; |
|||
border-radius: 50%; |
|||
border: 2px solid #305680; |
|||
background: #FFFFFF; |
|||
} |
|||
|
|||
.tb-done-welcome-title { |
|||
font-weight: 600; |
|||
font-size: 20px; |
|||
line-height: 24px; |
|||
letter-spacing: 0.1px; |
|||
color: rgba(0, 0, 0, 0.87); |
|||
padding-bottom: 8px; |
|||
} |
|||
|
|||
.tb-done-welcome-text { |
|||
font-weight: 400; |
|||
font-size: 16px; |
|||
line-height: 24px; |
|||
letter-spacing: 0.15px; |
|||
color: rgba(0, 0, 0, 0.54); |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
///
|
|||
/// 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, ViewEncapsulation } from '@angular/core'; |
|||
import { MatDialogRef } from '@angular/material/dialog'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { UntypedFormGroup } from '@angular/forms'; |
|||
import { DialogComponent } from '@shared/components/dialog.component'; |
|||
import { Router } from '@angular/router'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-getting-started-completed-dialog', |
|||
templateUrl: './getting-started-completed-dialog.component.html', |
|||
styleUrls: ['./getting-started-completed-dialog.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class GettingStartedCompletedDialogComponent extends |
|||
DialogComponent<GettingStartedCompletedDialogComponent, void> { |
|||
|
|||
addDocLinkFormGroup: UntypedFormGroup; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
public dialogRef: MatDialogRef<GettingStartedCompletedDialogComponent, void>) { |
|||
super(store, router, dialogRef); |
|||
dialogRef.addPanelClass('tb-getting-started-completed-dialog'); |
|||
} |
|||
|
|||
close(): void { |
|||
this.dialogRef.close(); |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
<!-- |
|||
|
|||
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 class="tb-title" translate>widgets.getting-started.get-started</div> |
|||
<div class="tb-get-started-container"> |
|||
<mat-stepper #matStepper class="tb-get-started" orientation="vertical" [linear]="false" |
|||
[selectedIndex]="gettingStarted.lastSelectedIndex" |
|||
(selectionChange)="updateSelectedIndex($event)"> |
|||
<ng-template matStepperIcon="edit" let-index="index"> |
|||
{{ index + 1 }} |
|||
</ng-template> |
|||
<ng-container [ngSwitch]="authUser.authority"> |
|||
<ng-template [ngSwitchCase]="authority.SYS_ADMIN"> |
|||
<mat-step [completed]="gettingStarted.maxSelectedIndex > -1"> |
|||
<ng-template matStepLabel> |
|||
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div translate>widgets.getting-started.sys-admin.step1.title</div> |
|||
<a *ngIf="matStepper.selectedIndex === 0" mat-button color="primary" routerLink="/tenants">{{ 'tenant.tenants' | translate }}</a> |
|||
</div> |
|||
</ng-template> |
|||
<div [innerHTML]="'widgets.getting-started.sys-admin.step1.content' | translate | safe: 'html'"></div> |
|||
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/ui/tenants/#adding-a-tenant" target="_blank"> |
|||
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step1.how-to-create-tenant' | translate }}</a> |
|||
</mat-step> |
|||
<mat-step [completed]="gettingStarted.maxSelectedIndex > 0"> |
|||
<ng-template matStepLabel> |
|||
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div translate>widgets.getting-started.sys-admin.step2.title</div> |
|||
<a *ngIf="matStepper.selectedIndex === 1" mat-button color="primary" routerLink="/settings/outgoing-mail">{{ 'admin.settings' | translate }}</a> |
|||
</div> |
|||
</ng-template> |
|||
<div [innerHTML]="'widgets.getting-started.sys-admin.step2.content' | translate | safe: 'html'"></div> |
|||
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/ui/mail-settings/" target="_blank"> |
|||
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step2.how-to-configure-mail-server' | translate }}</a> |
|||
</mat-step> |
|||
<mat-step [completed]="gettingStarted.maxSelectedIndex > 1"> |
|||
<ng-template matStepLabel> |
|||
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div translate>widgets.getting-started.sys-admin.step3.title</div> |
|||
<a *ngIf="matStepper.selectedIndex === 2" mat-button color="primary" routerLink="/settings/notifications">{{ 'admin.settings' | translate }}</a> |
|||
</div> |
|||
</ng-template> |
|||
<div [innerHTML]="'widgets.getting-started.sys-admin.step3.content' | translate | safe: 'html'"></div> |
|||
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/ui/sms-provider-settings/" target="_blank"> |
|||
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step3.how-to-configure-sms-provider' | translate }}</a> |
|||
</mat-step> |
|||
<mat-step [completed]="gettingStarted.maxSelectedIndex > 2"> |
|||
<ng-template matStepLabel> |
|||
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div translate>widgets.getting-started.sys-admin.step4.title</div> |
|||
<a *ngIf="matStepper.selectedIndex === 3" mat-button color="primary" routerLink="/security-settings/2fa">{{ 'admin.settings' | translate }}</a> |
|||
</div> |
|||
</ng-template> |
|||
<div [innerHTML]="'widgets.getting-started.sys-admin.step4.content' | translate | safe: 'html'"></div> |
|||
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/two-factor-authentication/" target="_blank"> |
|||
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step4.how-to-configure-2fa' | translate }}</a> |
|||
</mat-step> |
|||
<mat-step [completed]="gettingStarted.maxSelectedIndex > 3"> |
|||
<ng-template matStepLabel> |
|||
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div translate>widgets.getting-started.sys-admin.step5.title</div> |
|||
<a *ngIf="matStepper.selectedIndex === 4" mat-button color="primary" routerLink="/security-settings/oauth2">{{ 'admin.settings' | translate }}</a> |
|||
</div> |
|||
</ng-template> |
|||
<div [innerHTML]="'widgets.getting-started.sys-admin.step5.content' | translate | safe: 'html'"></div> |
|||
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/oauth-2-support/" target="_blank"> |
|||
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step5.how-to-configure-oauth2' | translate }}</a> |
|||
</mat-step> |
|||
<mat-step [completed]="gettingStarted.maxSelectedIndex > 4"> |
|||
<ng-template matStepLabel> |
|||
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div translate>widgets.getting-started.sys-admin.step6.title</div> |
|||
<a *ngIf="matStepper.selectedIndex === 5" mat-button color="primary" routerLink="/notification/rules">{{ 'admin.settings' | translate }}</a> |
|||
</div> |
|||
</ng-template> |
|||
<div [innerHTML]="'widgets.getting-started.sys-admin.step6.content' | translate | safe: 'html'"></div> |
|||
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/to-do/" target="_blank"> |
|||
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step6.how-to-configure-notifications' | translate }}</a> |
|||
</mat-step> |
|||
</ng-template> |
|||
</ng-container> |
|||
</mat-stepper> |
|||
<div *ngIf="allCompleted" fxLayout="column" fxLayoutAlign="center center" style="padding-top: 24px;"> |
|||
<button (click)="gettingStartedCompleted()" mat-flat-button color="primary"> |
|||
<mat-icon>rocket_launch</mat-icon>{{ 'widgets.getting-started.finish' | translate }}</button> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,118 @@ |
|||
/** |
|||
* 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 { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
|
|||
.tb-title { |
|||
font-weight: 600; |
|||
font-size: 20px; |
|||
line-height: 24px; |
|||
letter-spacing: 0.1px; |
|||
color: rgba(0, 0, 0, 0.76); |
|||
padding-bottom: 24px; |
|||
@media screen and (min-width: 960px) and (max-width: 1819px) { |
|||
font-weight: 500; |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
letter-spacing: 0.25px; |
|||
padding-bottom: 20px; |
|||
} |
|||
} |
|||
.tb-get-started-container { |
|||
flex: 1; |
|||
overflow: auto; |
|||
} |
|||
} |
|||
|
|||
:host ::ng-deep { |
|||
|
|||
.tb-get-started .mat-vertical-stepper-header { |
|||
border-radius: 12px; |
|||
padding: 12px; |
|||
height: auto; |
|||
} |
|||
|
|||
.tb-get-started .mat-vertical-stepper-header[aria-selected="true"] { |
|||
background: rgba(0, 0, 0, 0.03); |
|||
border-bottom-right-radius: 0; |
|||
border-bottom-left-radius: 0; |
|||
} |
|||
|
|||
.tb-get-started .mat-vertical-content-container { |
|||
margin-left: 0; |
|||
border-bottom-left-radius: 12px; |
|||
border-bottom-right-radius: 12px; |
|||
background: rgba(0, 0, 0, 0.03); |
|||
} |
|||
|
|||
.tb-get-started .mat-stepper-vertical-line::before { |
|||
content: none; |
|||
} |
|||
|
|||
.tb-get-started .mat-step-label { |
|||
width: 100%; |
|||
white-space: inherit; |
|||
font-weight: 400; |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
color: rgba(0, 0, 0, 0.76); |
|||
} |
|||
|
|||
.tb-get-started .mat-step-label.mat-step-label-selected { |
|||
font-weight: 500; |
|||
font-size: 16px; |
|||
line-height: 24px; |
|||
color: rgba(0, 0, 0, 0.87); |
|||
} |
|||
|
|||
.tb-get-started .mat-vertical-content p { |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
letter-spacing: 0.2px; |
|||
color: rgba(0, 0, 0, 0.54); |
|||
} |
|||
|
|||
@media screen and (min-width: 960px) and (max-width: 1819px) { |
|||
.tb-get-started .mat-step-label { |
|||
font-size: 12px; |
|||
line-height: 16px; |
|||
} |
|||
.tb-get-started .mat-step-label.mat-step-label-selected { |
|||
font-size: 13px; |
|||
line-height: 16px; |
|||
} |
|||
|
|||
.tb-get-started .mdc-button { |
|||
font-size: 12px; |
|||
line-height: 16px; |
|||
} |
|||
|
|||
.tb-get-started .mat-vertical-content p { |
|||
font-size: 12px; |
|||
line-height: 16px; |
|||
letter-spacing: 0.25px; |
|||
} |
|||
|
|||
.tb-get-started .mat-vertical-content { |
|||
padding: 0 16px 16px 16px; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
///
|
|||
/// 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 { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { UserSettingsService } from '@core/http/user-settings.service'; |
|||
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
|||
import { WidgetContext } from '@home/models/widget-component.models'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { |
|||
GettingStartedCompletedDialogComponent |
|||
} from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component'; |
|||
import { GettingStarted } from '@shared/models/user-settings.models'; |
|||
import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper'; |
|||
import { isUndefined } from '@core/utils'; |
|||
import { MatStepper } from '@angular/material/stepper'; |
|||
import { first } from 'rxjs/operators'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-getting-started-widget', |
|||
templateUrl: './getting-started-widget.component.html', |
|||
styleUrls: ['./getting-started-widget.component.scss'] |
|||
}) |
|||
export class GettingStartedWidgetComponent extends PageComponent implements OnInit, OnDestroy { |
|||
|
|||
@ViewChild('matStepper') |
|||
matStepper: MatStepper; |
|||
|
|||
@Input() |
|||
ctx: WidgetContext; |
|||
|
|||
authority = Authority; |
|||
|
|||
authUser = getCurrentAuthUser(this.store); |
|||
gettingStarted: GettingStarted = { |
|||
lastSelectedIndex: 0, |
|||
maxSelectedIndex: 0 |
|||
}; |
|||
allCompleted = false; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private cd: ChangeDetectorRef, |
|||
private userSettingsService: UserSettingsService, |
|||
private dialog: MatDialog) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.userSettingsService.getGettingStarted().subscribe( |
|||
(gettingStarted) => { |
|||
if (gettingStarted) { |
|||
this.gettingStarted = gettingStarted; |
|||
if (isUndefined(this.gettingStarted.lastSelectedIndex)) { |
|||
this.gettingStarted.lastSelectedIndex = 0; |
|||
} |
|||
if (isUndefined(this.gettingStarted.maxSelectedIndex)) { |
|||
this.gettingStarted.maxSelectedIndex = 0; |
|||
} |
|||
if (this.gettingStarted.lastSelectedIndex > 0 && this.gettingStarted.lastSelectedIndex < this.matStepper.steps.length) { |
|||
const animationDuration = this.matStepper.animationDuration; |
|||
this.matStepper.animationDuration = '0'; |
|||
this.matStepper.selectedIndex = this.gettingStarted.lastSelectedIndex; |
|||
this.matStepper.animationDone.pipe(first()).subscribe(() => { |
|||
setTimeout(() => {this.matStepper.animationDuration = animationDuration;}, 0); |
|||
}); |
|||
} |
|||
if (this.matStepper.steps.length) { |
|||
this.updateCompletedSteps(); |
|||
this.cd.markForCheck(); |
|||
} else { |
|||
this.matStepper.steps.changes.subscribe( |
|||
() => { |
|||
this.updateCompletedSteps(); |
|||
this.cd.markForCheck(); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
ngOnDestroy() { |
|||
super.ngOnDestroy(); |
|||
} |
|||
|
|||
isSelected(step: CdkStep) { |
|||
return this.matStepper?.selected === step; |
|||
} |
|||
|
|||
gettingStartedCompleted() { |
|||
this.dialog.open<GettingStartedCompletedDialogComponent, any, |
|||
void>(GettingStartedCompletedDialogComponent, { |
|||
disableClose: true, |
|||
autoFocus: false, |
|||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] |
|||
}).afterClosed().subscribe(); |
|||
} |
|||
|
|||
updateSelectedIndex(event: StepperSelectionEvent) { |
|||
if (this.gettingStarted.lastSelectedIndex !== event.selectedIndex) { |
|||
this.gettingStarted.lastSelectedIndex = event.selectedIndex; |
|||
if (event.selectedIndex > this.gettingStarted.maxSelectedIndex) { |
|||
this.gettingStarted.maxSelectedIndex = event.selectedIndex; |
|||
this.updateCompletedSteps(); |
|||
} |
|||
this.userSettingsService |
|||
.updateGettingStarted(this.gettingStarted, {ignoreLoading: true}).subscribe(); |
|||
} |
|||
} |
|||
|
|||
updateCompletedSteps() { |
|||
if (this.gettingStarted.maxSelectedIndex >= this.matStepper.steps.length-1) { |
|||
this.allCompleted = true; |
|||
} |
|||
} |
|||
|
|||
} |
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue