Refine the tabs in ellipsis code

Signed-off-by: AllForNothing <sshijun@vmware.com>
Signed-off-by: Yogi_Wang <yawang@vmware.com>
This commit is contained in:
Yogi_Wang 2019-12-27 16:27:15 +08:00 committed by sshijun
parent 6571135cbf
commit ad67e45932
8 changed files with 107 additions and 31 deletions

View File

@ -105,4 +105,4 @@
<button type="button" class="btn btn-primary" [disabled]="!isValid" (click)="onSave()">{{'BUTTON.SAVE' | translate}}</button> <button type="button" class="btn btn-primary" [disabled]="!isValid" (click)="onSave()">{{'BUTTON.SAVE' | translate}}</button>
</div> </div>
</clr-modal> </clr-modal>

View File

@ -6,7 +6,7 @@
<clr-tabs id="project-tabs" class="tabs" [class.in-overflow]="isTabLinkInOverFlow()"> <clr-tabs id="project-tabs" class="tabs" [class.in-overflow]="isTabLinkInOverFlow()">
<ng-container *ngFor="let tab of tabLinkNavList;let i=index"> <ng-container *ngFor="let tab of tabLinkNavList;let i=index">
<ng-container *ngIf="tab.permissions()"> <ng-container *ngIf="tab.permissions()">
<clr-tab > <clr-tab>
<button [class.clear-default-active]="isDefaultTab(tab, i)" [clrTabLinkInOverflow]="tab.tabLinkInOverflow" id="{{'project-'+tab.linkName}}" clrTabLink <button [class.clear-default-active]="isDefaultTab(tab, i)" [clrTabLinkInOverflow]="tab.tabLinkInOverflow" id="{{'project-'+tab.linkName}}" clrTabLink
routerLink="{{tab.linkName}}" routerLinkActive="active" type="button"> routerLink="{{tab.linkName}}" routerLinkActive="active" type="button">
<a class="nav-link">{{tab.showTabName | translate}}</a></button> <a class="nav-link">{{tab.showTabName | translate}}</a></button>

View File

@ -42,16 +42,3 @@ button {
padding: 0; padding: 0;
} }
} }
.in-overflow {
::ng-deep {
.tabs-overflow {
> .nav-item {
> button {
box-shadow: 0 -3px 0 #0077b8 inset;
color: 0077b8;
}
}
}
}
}

View File

@ -11,21 +11,24 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, HostListener, AfterViewInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Project } from '../project'; import { Project } from '../project';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { AppConfigService } from "../../app-config.service"; import { AppConfigService } from "../../app-config.service";
import { forkJoin } from "rxjs"; import { forkJoin, Subject, Subscription } from "rxjs";
import { ProjectService, UserPermissionService, USERSTATICPERMISSION } from "../../../lib/services"; import { UserPermissionService, USERSTATICPERMISSION } from "../../../lib/services";
import { ErrorHandler } from "../../../lib/utils/error-handler"; import { ErrorHandler } from "../../../lib/utils/error-handler";
import { debounceTime } from 'rxjs/operators';
import { DOWN, SHOW_ELLIPSIS_WIDTH, UP } from './project-detail.const';
@Component({ @Component({
selector: 'project-detail', selector: 'project-detail',
templateUrl: 'project-detail.component.html', templateUrl: 'project-detail.component.html',
styleUrls: ['project-detail.component.scss'] styleUrls: ['project-detail.component.scss']
}) })
export class ProjectDetailComponent implements OnInit { export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy {
hasSignedIn: boolean; hasSignedIn: boolean;
currentProject: Project; currentProject: Project;
@ -89,29 +92,32 @@ export class ProjectDetailComponent implements OnInit {
}, },
{ {
linkName: "tag-strategy", linkName: "tag-strategy",
tabLinkInOverflow: true, tabLinkInOverflow: false,
showTabName: "PROJECT_DETAIL.TAG_STRATEGY", showTabName: "PROJECT_DETAIL.TAG_STRATEGY",
permissions: () => this.hasTagRetentionPermission permissions: () => this.hasTagRetentionPermission
}, },
{ {
linkName: "robot-account", linkName: "robot-account",
tabLinkInOverflow: true, tabLinkInOverflow: false,
showTabName: "PROJECT_DETAIL.ROBOT_ACCOUNTS", showTabName: "PROJECT_DETAIL.ROBOT_ACCOUNTS",
permissions: () => this.hasRobotListPermission permissions: () => this.hasRobotListPermission
}, },
{ {
linkName: "webhook", linkName: "webhook",
tabLinkInOverflow: true, tabLinkInOverflow: false,
showTabName: "PROJECT_DETAIL.WEBHOOKS", showTabName: "PROJECT_DETAIL.WEBHOOKS",
permissions: () => this.hasWebhookListPermission permissions: () => this.hasWebhookListPermission
}, },
{ {
linkName: "logs", linkName: "logs",
tabLinkInOverflow: true, tabLinkInOverflow: false,
showTabName: "PROJECT_DETAIL.LOGS", showTabName: "PROJECT_DETAIL.LOGS",
permissions: () => this.hasLogListPermission permissions: () => this.hasLogListPermission
} }
]; ];
previousWindowWidth: number;
private _subject = new Subject<string>();
private _subscription: Subscription;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
@ -119,8 +125,7 @@ export class ProjectDetailComponent implements OnInit {
private appConfigService: AppConfigService, private appConfigService: AppConfigService,
private userPermissionService: UserPermissionService, private userPermissionService: UserPermissionService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private projectService: ProjectService) { private cdf: ChangeDetectorRef) {
this.hasSignedIn = this.sessionService.getCurrentUser() !== null; this.hasSignedIn = this.sessionService.getCurrentUser() !== null;
this.route.data.subscribe(data => { this.route.data.subscribe(data => {
this.currentProject = <Project>data['projectResolver']; this.currentProject = <Project>data['projectResolver'];
@ -131,6 +136,32 @@ export class ProjectDetailComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.projectId = this.route.snapshot.params['id']; this.projectId = this.route.snapshot.params['id'];
this.getPermissionsList(this.projectId); this.getPermissionsList(this.projectId);
if (!this._subscription) {
this._subscription = this._subject.pipe(
debounceTime(100)
).subscribe(
type => {
if (type === DOWN) {
this.resetTabsForDownSize();
} else {
this.resetTabsForUpSize();
}
}
);
}
}
ngAfterViewInit() {
this.previousWindowWidth = window.innerWidth;
setTimeout(() => {
this.resetTabsForDownSize();
}, 0);
}
ngOnDestroy() {
if (this._subscription) {
this._subscription.unsubscribe();
this._subscription = null;
}
} }
getPermissionsList(projectId: number): void { getPermissionsList(projectId: number): void {
let permissionsList = []; let permissionsList = [];
@ -153,17 +184,17 @@ export class ProjectDetailComponent implements OnInit {
permissionsList.push(this.userPermissionService.getPermission(projectId, permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.CREATE)); USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.CREATE));
permissionsList.push(this.userPermissionService.getPermission(projectId, permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.TAG_RETENTION.KEY, USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ)); USERSTATICPERMISSION.TAG_RETENTION.KEY, USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ));
permissionsList.push(this.userPermissionService.getPermission(projectId, permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.WEBHOOK.KEY, USERSTATICPERMISSION.WEBHOOK.VALUE.LIST)); USERSTATICPERMISSION.WEBHOOK.KEY, USERSTATICPERMISSION.WEBHOOK.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId, permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.SCANNER.KEY, USERSTATICPERMISSION.SCANNER.VALUE.READ)); USERSTATICPERMISSION.SCANNER.KEY, USERSTATICPERMISSION.SCANNER.VALUE.READ));
forkJoin(...permissionsList).subscribe(Rules => { forkJoin(...permissionsList).subscribe(Rules => {
[this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission [this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission
, this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission , this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission
, this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission, , this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission,
this.hasScannerReadPermission] = Rules; this.hasScannerReadPermission] = Rules;
}, error => this.errorHandler.error(error)); }, error => this.errorHandler.error(error));
} }
@ -188,10 +219,46 @@ export class ProjectDetailComponent implements OnInit {
isDefaultTab(tab, index) { isDefaultTab(tab, index) {
return this.route.snapshot.children[0].routeConfig.path !== tab.linkName && index === 0; return this.route.snapshot.children[0].routeConfig.path !== tab.linkName && index === 0;
} }
isTabLinkInOverFlow() { isTabLinkInOverFlow() {
return this.tabLinkNavList.some(tab => { return this.tabLinkNavList.some(tab => {
return tab.tabLinkInOverflow && this.route.snapshot.children[0].routeConfig.path === tab.linkName; return tab.tabLinkInOverflow && this.route.snapshot.children[0].routeConfig.path === tab.linkName;
}); });
} }
@HostListener('window:resize', ['$event'])
onResize(event) {
if (this.previousWindowWidth) {
// down size
if (this.previousWindowWidth > event.target.innerWidth) {
this._subject.next(DOWN);
} else { // up size
this._subject.next(UP);
}
}
this.previousWindowWidth = event.target.innerWidth;
}
resetTabsForDownSize(): void {
this.tabLinkNavList.filter(item => !item.tabLinkInOverflow).forEach((item, index) => {
const tabEle: HTMLElement = document.getElementById('project-' + item.linkName);
// strengthen code
if (tabEle && tabEle.getBoundingClientRect) {
const right: number = window.innerWidth - document.getElementById('project-' + item.linkName).getBoundingClientRect().right;
if (right < SHOW_ELLIPSIS_WIDTH) {
this.tabLinkNavList[index].tabLinkInOverflow = true;
}
}
});
}
resetTabsForUpSize() {
// 1.Set tabLinkInOverflow to false for all tabs(show all tabs)
for ( let i = 0; i < this.tabLinkNavList.length; i++) {
this.tabLinkNavList[i].tabLinkInOverflow = false;
}
// 2.Manually detect changes to rerender dom
this.cdf.detectChanges();
// 3. Hide overflowed tabs
this.resetTabsForDownSize();
}
} }

View File

@ -0,0 +1,3 @@
export const SHOW_ELLIPSIS_WIDTH = 80;
export const DOWN: string = "down";
export const UP: string = "up";

View File

@ -0,0 +1,11 @@
// styles for dark and light theme should be defined here.
.in-overflow {
.tabs-overflow {
> .nav-item {
> button {
box-shadow: 0 -3px 0 $dark-active-tab-color inset;
color: 0077b8;
}
}
}
}

View File

@ -1,7 +1,9 @@
// Variables for dark theme should be defined here.
@import "../../node_modules/@clr/ui/clr-ui-dark.min.css"; @import "../../node_modules/@clr/ui/clr-ui-dark.min.css";
$dark-background-color: rgb(27, 42, 50) !important; $dark-background-color: rgb(27, 42, 50) !important;
$dark-font-color1: #acbac3 !important; $dark-font-color1: #acbac3 !important;
$dark-font-color-title1: #eaedf0 !important; $dark-font-color-title1: #eaedf0 !important;
$dark-active-tab-color: #4aaed9;
.label-form { .label-form {
background-color: #212129 !important; background-color: #212129 !important;
@ -110,4 +112,6 @@ clr-dg-action-overflow {
color: #1b2a32; color: #1b2a32;
} }
} }
} }
@import "./common.scss";

View File

@ -1 +1,5 @@
@import "../../node_modules/@clr/ui/clr-ui.min.css"; // Variables for dark theme should be defined here.
@import "../../node_modules/@clr/ui/clr-ui.min.css";
$dark-active-tab-color: #0077b8;
@import "./common.scss";