mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-18 14:47:38 +01:00
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:
parent
6571135cbf
commit
ad67e45932
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export const SHOW_ELLIPSIS_WIDTH = 80;
|
||||||
|
export const DOWN: string = "down";
|
||||||
|
export const UP: string = "up";
|
11
src/portal/src/css/common.scss
Normal file
11
src/portal/src/css/common.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
@ -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";
|
Loading…
Reference in New Issue
Block a user