Commit e1814597 authored by Deng, Qian's avatar Deng, Qian
Browse files

Change the admin rename UX

Change the rename operation from double click icon to click button
parent bf6192f3
......@@ -43,4 +43,4 @@
"web-animations-js": "^2.2.1",
"zone.js": "^0.8.4"
}
}
}
\ No newline at end of file
......@@ -7,14 +7,17 @@
<div class="form-group form-group-override">
<label for="account_settings_username" aria-haspopup="true" class="form-group-label-override">{{'PROFILE.USER_NAME' | translate}}</label>
<input type="text" name="account_settings_username" [(ngModel)]="account.username" disabled id="account_settings_username" size="33">
<clr-tooltip *ngIf="canRename">
<button (dblclick)="openRenameAlert()" class="btn btn-link">
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
<div *ngIf="canRename" class="rename-tool">
<button [disabled]="RenameOnGoing" (click)="onRename()" class="btn btn-outline btn-sm">
{{'PROFILE.ADMIN_RENAME_BUTTON' | translate}}
</button>
<clr-tooltip-content clrPosition="bottom-left" clrSize="md" *clrIfOpen>
<span (click)="openRenameAlert()"> {{'PROFILE.ADMIN_RENAME_TIP' | translate}}</span>
</clr-tooltip-content>
</clr-tooltip>
<clr-tooltip>
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
<clr-tooltip-content clrPosition="bottom-left" clrSize="md" *clrIfOpen>
<span> {{'PROFILE.ADMIN_RENAME_TIP' | translate}}</span>
</clr-tooltip-content>
</clr-tooltip>
</div>
</div>
<div class="form-group form-group-override">
<label for="account_settings_email" class="required form-group-label-override">{{'PROFILE.EMAIL' | translate}}</label>
......
clr-modal {
::ng-deep div.modal-dialog {
width: unset;
}
.rename-tool {
.btn {
margin-right: 6px;
padding-left: 3px;
padding-right: 3px;
}
position: relative;
bottom: 9px;
}
}
\ No newline at end of file
import { ChangeDetectorRef } from '@angular/core';
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
......@@ -23,206 +24,238 @@ import { SearchTriggerService } from "../../base/global-search/search-trigger.se
import { CommonRoutes } from "../../shared/shared.const";
@Component({
selector: "account-settings-modal",
templateUrl: "account-settings-modal.component.html",
styleUrls: ["./account-settings-modal.component.scss", "../../common.scss"]
selector: "account-settings-modal",
templateUrl: "account-settings-modal.component.html",
styleUrls: ["./account-settings-modal.component.scss", "../../common.scss"]
})
export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
opened = false;
staticBackdrop = true;
account: SessionUser;
error: any = null;
originalStaticData: SessionUser;
emailTooltip = "TOOLTIP.EMAIL";
mailAlreadyChecked = {};
isOnCalling = false;
formValueChanged = false;
checkOnGoing = false;
RenameOnGoing = false;
accountFormRef: NgForm;
@ViewChild("accountSettingsFrom") accountForm: NgForm;
@ViewChild(InlineAlertComponent)
inlineAlert: InlineAlertComponent;
constructor(
private session: SessionService,
private msgHandler: MessageHandlerService,
private router: Router,
private searchTrigger: SearchTriggerService
) { }
private validationStateMap: any = {
"account_settings_email": true,
"account_settings_full_name": true
};
ngOnInit(): void {
// Value copy
this.account = Object.assign({}, this.session.getCurrentUser());
}
getValidationState(key: string): boolean {
return this.validationStateMap[key];
opened = false;
staticBackdrop = true;
originalStaticData: SessionUser;
account: SessionUser;
error: any = null;
emailTooltip = "TOOLTIP.EMAIL";
mailAlreadyChecked = {};
isOnCalling = false;
formValueChanged = false;
checkOnGoing = false;
RenameOnGoing = false;
originAdminName = "admin";
newAdminName = "admin@harbor.local";
renameConfirmation = false;
// confirmRename = false;
accountFormRef: NgForm;
@ViewChild("accountSettingsFrom") accountForm: NgForm;
@ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
constructor(
private session: SessionService,
private msgHandler: MessageHandlerService,
private router: Router,
private searchTrigger: SearchTriggerService,
private ref: ChangeDetectorRef
) {}
private validationStateMap: any = {
account_settings_email: true,
account_settings_full_name: true
};
ngOnInit(): void {
// Value copy
this.account = Object.assign({}, this.session.getCurrentUser());
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
}
ngAfterViewChecked(): void {
if (this.accountFormRef !== this.accountForm) {
this.accountFormRef = this.accountForm;
if (this.accountFormRef) {
this.accountFormRef.valueChanges.subscribe(data => {
if (this.error) {
this.error = null;
}
this.formValueChanged = true;
if (this.account.username === this.originAdminName) {
this.inlineAlert.close();
}
});
}
}
handleValidation(key: string, flag: boolean): void {
if (flag) {
// Checking
let cont = this.accountForm.controls[key];
if (cont) {
this.validationStateMap[key] = cont.valid;
// Check email existing from backend
if (cont.valid && key === "account_settings_email") {
if (this.formValueChanged && this.account.email !== this.originalStaticData.email) {
if (this.mailAlreadyChecked[this.account.email]) {
this.validationStateMap[key] = !this.mailAlreadyChecked[this.account.email].result;
if (!this.validationStateMap[key]) {
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
}
return;
}
// Mail changed
this.checkOnGoing = true;
this.session.checkUserExisting("email", this.account.email)
.then((res: boolean) => {
this.checkOnGoing = false;
this.validationStateMap[key] = !res;
if (res) {
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
}
this.mailAlreadyChecked[this.account.email] = {
result: res
}; // Tag it checked
})
.catch(error => {
this.checkOnGoing = false;
this.validationStateMap[key] = false; // Not valid @ backend
});
}
}
}
getValidationState(key: string): boolean {
return this.validationStateMap[key];
}
handleValidation(key: string, flag: boolean): void {
if (flag) {
// Checking
let cont = this.accountForm.controls[key];
if (cont) {
this.validationStateMap[key] = cont.valid;
// Check email existing from backend
if (cont.valid && key === "account_settings_email") {
if (
this.formValueChanged &&
this.account.email !== this.originalStaticData.email
) {
if (this.mailAlreadyChecked[this.account.email]) {
this.validationStateMap[key] = !this.mailAlreadyChecked[
this.account.email
].result;
if (!this.validationStateMap[key]) {
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
}
return;
}
} else {
// Reset
this.validationStateMap[key] = true;
this.emailTooltip = "TOOLTIP.EMAIL";
}
}
isUserDataChange(): boolean {
if (!this.originalStaticData || !this.account) {
return false;
}
for (let prop in this.originalStaticData) {
if (this.originalStaticData[prop] !== this.account[prop]) {
return true;
}
// Mail changed
this.checkOnGoing = true;
this.session
.checkUserExisting("email", this.account.email)
.then((res: boolean) => {
this.checkOnGoing = false;
this.validationStateMap[key] = !res;
if (res) {
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
}
this.mailAlreadyChecked[this.account.email] = {
result: res
}; // Tag it checked
})
.catch(error => {
this.checkOnGoing = false;
this.validationStateMap[key] = false; // Not valid @ backend
});
}
}
return false;
}
} else {
// Reset
this.validationStateMap[key] = true;
this.emailTooltip = "TOOLTIP.EMAIL";
}
}
public get isValid(): boolean {
return this.accountForm &&
this.accountForm.valid &&
this.error === null &&
this.validationStateMap["account_settings_email"]; // backend check is valid as well
isUserDataChange(): boolean {
if (!this.originalStaticData || !this.account) {
return false;
}
public get showProgress(): boolean {
return this.isOnCalling;
for (let prop in this.originalStaticData) {
if (this.originalStaticData[prop] !== this.account[prop]) {
return true;
}
}
public get checkProgress(): boolean {
return this.checkOnGoing;
}
public get canRename(): boolean {
return this.account && this.account.has_admin_role && this.account.username === "admin" && this.account.user_id === 1;
}
openRenameAlert(): void {
this.RenameOnGoing = true;
this.inlineAlert.showInlineConfirmation({
message: "PROFILE.RENAME_CONFIRM_INFO"
return false;
}
public get isValid(): boolean {
return (
this.accountForm &&
this.accountForm.valid &&
this.error === null &&
this.validationStateMap["account_settings_email"]
); // backend check is valid as well
}
public get showProgress(): boolean {
return this.isOnCalling;
}
public get checkProgress(): boolean {
return this.checkOnGoing;
}
public get canRename(): boolean {
return (
this.account &&
this.account.has_admin_role &&
this.originalStaticData.username === "admin" &&
this.account.user_id === 1
);
}
onRename(): void {
this.account.username = this.newAdminName;
this.RenameOnGoing = true;
}
confirmRename(): void {
if (this.canRename) {
this.session
.updateAccountSettings(this.account)
.then(() => {
this.session.renameAdmin(this.account)
.then(() => {
this.msgHandler.showSuccess("PROFILE.RENAME_SUCCESS");
this.opened = false;
this.logOut();
})
.catch(error => {
this.msgHandler.handleError(error);
});
}
confirmRename(): void {
if (this.canRename) {
this.session.renameAdmin(this.account)
.then(() => {
this.msgHandler.showSuccess("PROFILE.RENAME_SUCCESS");
})
.catch(error => {
this.msgHandler.handleError(error);
});
}
}
ngAfterViewChecked(): void {
if (this.accountFormRef !== this.accountForm) {
this.accountFormRef = this.accountForm;
if (this.accountFormRef) {
this.accountFormRef.valueChanges.subscribe(data => {
if (this.error) {
this.error = null;
}
this.formValueChanged = true;
this.inlineAlert.close();
});
}
})
.catch(error => {
this.isOnCalling = false;
this.error = error;
if (this.msgHandler.isAppLevel(error)) {
this.opened = false;
this.msgHandler.handleError(error);
} else {
this.inlineAlert.showInlineError(error);
}
});
}
}
// Log out system
logOut(): void {
// Naviagte to the sign in route
// Appending 'signout' means destroy session cache
let navigatorExtra: NavigationExtras = {
queryParams: { signout: true }
};
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
// Confirm search result panel is close
this.searchTrigger.closeSearch(true);
}
open() {
// Keep the initial data for future diff
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
this.account = Object.assign({}, this.session.getCurrentUser());
this.formValueChanged = false;
// Confirm inline alert is closed
this.inlineAlert.close();
// Clear check history
this.mailAlreadyChecked = {};
// Reset validation status
this.validationStateMap = {
account_settings_email: true,
account_settings_full_name: true
};
// Log out system
logOut(): void {
// Naviagte to the sign in route
// Appending 'signout' means destroy session cache
let navigatorExtra: NavigationExtras = {
queryParams: { "signout": true }
};
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
// Confirm search result panel is close
this.searchTrigger.closeSearch(true);
}
open() {
// Keep the initial data for future diff
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
this.account = Object.assign({}, this.session.getCurrentUser());
this.formValueChanged = false;
// Confirm inline alert is closed
this.inlineAlert.close();
// Clear check history
this.mailAlreadyChecked = {};
// Reset validation status
this.validationStateMap = {
"account_settings_email": true,
"account_settings_full_name": true
};
this.opened = true;
}
this.opened = true;
}
close() {
if (this.RenameOnGoing) {
this.RenameOnGoing = false;
}
if (this.formValueChanged) {
if (!this.isUserDataChange()) {
this.opened = false;
} else {
// Need user confirmation
this.inlineAlert.showInlineConfirmation({
message: "ALERT.FORM_CHANGE_CONFIRMATION"
});
if (this.RenameOnGoing) {
this.RenameOnGoing = false;
this.opened = false;
} else {
// Need user confirmation
this.inlineAlert.showInlineConfirmation({
message: "ALERT.FORM_CHANGE_CONFIRMATION"
});
}
}
} else {
this.opened = false;
......@@ -240,38 +273,55 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
return;
}
if (this.RenameOnGoing && !this.renameConfirmation) {
this.renameConfirmation = true;
this.inlineAlert.showInlineWarning({
message: "PROFILE.RENAME_CONFIRM_INFO"
});
return;
}
this.isOnCalling = true;
this.session.updateAccountSettings(this.account)
.then(() => {
this.isOnCalling = false;
this.opened = false;
this.msgHandler.showSuccess("PROFILE.SAVE_SUCCESS");
})
.catch(error => {
this.isOnCalling = false;
this.error = error;
if (this.msgHandler.isAppLevel(error)) {
if (this.RenameOnGoing && this.renameConfirmation) {
this.confirmRename();
} else {
this.session
.updateAccountSettings(this.account)
.then(() => {
this.isOnCalling = false;
this.opened = false;
this.msgHandler.handleError(error);
} else {
this.inlineAlert.showInlineError(error);
}
});
this.msgHandler.showSuccess("PROFILE.SAVE_SUCCESS");
})
.catch(error => {
this.isOnCalling = false;
this.error = error;
if (this.msgHandler.isAppLevel(error)) {
this.opened = false;
this.msgHandler.handleError(error);
} else {
this.inlineAlert.showInlineError(error);
}
});
}
}
confirmNo($event: any): void {
if (this.RenameOnGoing) {
this.RenameOnGoing = false;
}
confirmNo($event: any): void {
if (this.RenameOnGoing) {
this.RenameOnGoing = false;
}
confirmYes($event: any): void {
if (this.RenameOnGoing) {
this.confirmRename();
this.RenameOnGoing = false;
this.logOut();
}
this.inlineAlert.close();
this.opened = false;
if (this.renameConfirmation) {
this.renameConfirmation = false;
}
}
confirmYes($event: any): void {
if (this.RenameOnGoing) {
this.RenameOnGoing = false;
}
if (this.renameConfirmation) {
this.renameConfirmation = false;
}
this.inlineAlert.close();
this.opened = false;
}
}
......@@ -69,6 +69,19 @@ export class InlineAlertComponent {
this.useAppLevelStyle = false;
}
// Show warning
public showInlineWarning(warning: any): void {
this.displayedText = "";
if (warning && warning.message) {
this.translate.get(warning.message).subscribe((res: string) => this.displayedText = res);
}
this.inlineAlertType = 'alert-warning';
this.showCancelAction = false;
this.inlineAlertClosable = true;
this.alertClose = false;