TSK-1475: Hide mechanism workbasket list (#1369)

* TSK-1475: implement toggle button that resizes the width of workbasket list

* TSK-1475: update pagination component view when workbasket list is collapsed

* TSK-1475: optimize workbasket list toolbar when workbasket list is collapsed

* TSK-1475: fix broken jest tests
This commit is contained in:
Chi Nguyen 2020-12-14 09:25:48 +01:00 committed by GitHub
parent 5ac12037bc
commit 3c067c55e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 185 additions and 63 deletions

View File

@ -10,7 +10,7 @@
</button>
<!-- IMPORT EXPORT -->
<taskana-administration-import-export [currentSelection]="selectionToImport" [parentComponent]="'workbaskets'"></taskana-administration-import-export>
<taskana-administration-import-export *ngIf="workbasketListExpanded" [currentSelection]="selectionToImport" [parentComponent]="'workbaskets'"></taskana-administration-import-export>
<span class="workbasket-details__spacer" style="flex: 1 1 auto"> </span>
@ -29,9 +29,10 @@
</button>
<div class="filter__filter-component-wrapper">
<taskana-shared-filter [isExpanded]="isExpanded" (performFilter)="filtering($event)"></taskana-shared-filter>
<taskana-shared-filter [isExpanded]="isExpanded"
(performFilter)="filtering($event)"
component="workbasket-list"
(inputComponent)="setComponent($event)"></taskana-shared-filter>
</div>
</div>
<taskana-shared-filter *ngIf="showFilter" (performFilter)="filtering($event)" component="workbasket-list" (inputComponent)="setComponent($event)"></taskana-shared-filter>
</div>

View File

@ -4,6 +4,7 @@
padding: 16px 16px 20px;
flex-wrap: wrap;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.workbasket-list-toolbar__action-toolbar {

View File

@ -19,6 +19,7 @@ import { WorkbasketService } from '../../../shared/services/workbasket/workbaske
styleUrls: ['./workbasket-list-toolbar.component.scss']
})
export class WorkbasketListToolbarComponent implements OnInit {
@Input() workbasketListExpanded: boolean = true;
@Input() workbaskets: Array<WorkbasketSummary>;
@Input() workbasketDefaultSortBy: string;
@Output() performSorting = new EventEmitter<Sorting>();

View File

@ -2,7 +2,7 @@
<!-- TOOLBAR -->
<section #wbToolbar class="workbasket-list__toolbar">
<taskana-administration-workbasket-list-toolbar [workbaskets]="workbasketsSummary$ | async" (performFilter)="performFilter($event)"
(performSorting)="performSorting($event)" [workbasketDefaultSortBy]="workbasketDefaultSortBy">
(performSorting)="performSorting($event)" [workbasketDefaultSortBy]="workbasketDefaultSortBy" [workbasketListExpanded]="expanded">
</taskana-administration-workbasket-list-toolbar>
</section>
@ -16,7 +16,7 @@
[value]="workbasket.workbasketId">
<div class="workbaskets-item__wrapper">
<div class="workbaskets-item__icon">
<div class="workbaskets-item__icon" *ngIf="expanded">
<taskana-administration-icon-type [type]="workbasket.type" size="large" tooltip="true" [selected]="workbasket.workbasketId === selectedId"></taskana-administration-icon-type>
</div>
@ -24,7 +24,7 @@
<p>
<b>{{workbasket.name}}</b>, <i>{{workbasket.key}} </i>
</p>
<p>{{workbasket.description}} &nbsp;</p>
<p style="max-height: 20px; overflow: hidden; text-overflow: ellipsis">{{workbasket.description}} &nbsp;</p>
<p>{{workbasket.owner}} &nbsp;</p>
</div>
@ -53,6 +53,7 @@
<taskana-shared-pagination
[page]="(workbasketsSummaryRepresentation$ | async) ? (workbasketsSummaryRepresentation$ | async)?.page : (workbasketsSummaryRepresentation$ | async)"
[type]="type"
[expanded]="expanded"
[numberOfItems]="(workbasketsSummary$ | async)?.length"
(changePage)="changePage($event)">
</taskana-shared-pagination>

View File

@ -12,6 +12,7 @@
}
.mat-list-item {
height: 90px !important;
overflow: hidden;
}
.mat-list-single-selected-option {
background-color: $green !important;
@ -25,6 +26,7 @@
}
.workbaskets-item__info {
display: block;
overflow: hidden;
padding: 8px 0;
}
p {

View File

@ -73,6 +73,7 @@ const requestInProgressServiceSpy = jest.fn().mockImplementation(
class WorkbasketListToolbarStub {
@Input() workbaskets: Array<WorkbasketSummary>;
@Input() workbasketDefaultSortBy: string;
@Input() workbasketListExpanded: boolean;
@Output() performSorting = new EventEmitter<Sorting>();
@Output() performFilter = new EventEmitter<Filter>();
}
@ -87,6 +88,7 @@ class IconTypeStub {
class PaginationStub {
@Input() page: Page;
@Input() type: String;
@Input() expanded: boolean;
@Output() changePage = new EventEmitter<number>();
@Input() numberOfItems: number;
}

View File

@ -1,4 +1,4 @@
import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation';
@ -40,6 +40,7 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
filterBy: Filter = new Filter({ name: '', owner: '', type: '', description: '', key: '' });
requestInProgress: boolean;
requestInProgressLocal = false;
@Input() expanded: boolean;
@ViewChild('wbToolbar', { static: true })
private toolbarElement: ElementRef;

View File

@ -1,11 +1,22 @@
<div class="workbasket-overview">
<taskana-administration-workbasket-list></taskana-administration-workbasket-list>
<div class="vertical-right-divider"></div>
<taskana-administration-workbasket-details
*ngIf="showDetail; else showEmptyPage"></taskana-administration-workbasket-details>
<div class="workbasket-overview__list" #workbasketList>
<taskana-administration-workbasket-list [expanded]="expanded"></taskana-administration-workbasket-list>
</div>
<div class="vertical-right-divider">
<span class="workbasket-overview__toggle-view-button" (click)="toggleWidth()" #toggleButton>
<mat-icon class="md-36" *ngIf="expanded">chevron_left</mat-icon>
<mat-icon class="md-36" *ngIf="!expanded">chevron_right</mat-icon>
</span>
</div>
<div class="workbasket-overview__details" *ngIf="showDetail; else showEmptyPage">
<taskana-administration-workbasket-details></taskana-administration-workbasket-details>
</div>
<ng-template #showEmptyPage>
<div class="hidden-xs hidden-sm col-md-8 container-no-items">
<div class="workbasket-overview__empty-page">
<div class="center-block no-detail">
<h3 class="grey">Select a workbasket</h3>
<svg-icon class="img-responsive empty-icon" src="./assets/icons/wb-empty.svg"></svg-icon>

View File

@ -4,10 +4,33 @@
height: 100%;
overflow: hidden;
align-items: stretch;
}
taskana-administration-workbasket-list {
min-width: 500px;
}
taskana-administration-workbasket-details {
flex-grow: 1;
position: relative;
&__toggle-view-button {
width: 40px;
height: 40px;
border-radius: 999px;
border: 1px solid #cecece;
background-color: white;
position: absolute;
cursor: pointer;
top: 50%;
left: 480px;
z-index: 10;
transition: 0.2s ease-out;
}
&__list {
min-width: 500px;
width: 500px;
transition: 0.2s ease-out;
}
&__details {
flex-grow: 1;
}
&__empty-page {
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
}
}

View File

@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkbasketOverviewComponent } from './workbasket-overview.component';
import { Component, DebugElement } from '@angular/core';
import { Component, DebugElement, Input } from '@angular/core';
import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store';
import { Observable, of } from 'rxjs';
import { WorkbasketState } from '../../../shared/store/workbasket-store/workbasket.state';
@ -17,6 +17,7 @@ import { StartupService } from '../../../shared/services/startup/startup.service
import { TaskanaEngineService } from '../../../shared/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from '../../../shared/services/window/window.service';
import { workbasketReadStateMock } from '../../../shared/store/mock-data/mock-store';
import { MatIconModule } from '@angular/material/icon';
const showDialogFn = jest.fn().mockReturnValue(true);
const NotificationServiceSpy = jest.fn().mockImplementation(
@ -47,7 +48,9 @@ const mockActivatedRouteNoParams = {
};
@Component({ selector: 'taskana-administration-workbasket-list', template: '' })
class WorkbasketListStub {}
class WorkbasketListStub {
@Input() expanded: boolean;
}
@Component({ selector: 'taskana-administration-workbasket-details', template: '' })
class WorkbasketDetailsStub {}
@ -64,7 +67,12 @@ describe('WorkbasketOverviewComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([]), NgxsModule.forRoot([WorkbasketState])],
imports: [
MatIconModule,
HttpClientTestingModule,
RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([WorkbasketState])
],
declarations: [WorkbasketOverviewComponent, WorkbasketListStub, WorkbasketDetailsStub, SvgIconStub],
providers: [
WorkbasketService,
@ -124,7 +132,12 @@ describe('WorkbasketOverviewComponent Alternative Params ID', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([]), NgxsModule.forRoot([WorkbasketState])],
imports: [
MatIconModule,
HttpClientTestingModule,
RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([WorkbasketState])
],
declarations: [WorkbasketOverviewComponent, WorkbasketListStub, WorkbasketDetailsStub, SvgIconStub],
providers: [
WorkbasketService,
@ -162,7 +175,12 @@ describe('WorkbasketOverviewComponent No Params', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([]), NgxsModule.forRoot([WorkbasketState])],
imports: [
MatIconModule,
HttpClientTestingModule,
RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([WorkbasketState])
],
declarations: [WorkbasketOverviewComponent, WorkbasketListStub, WorkbasketDetailsStub, SvgIconStub],
providers: [
WorkbasketService,

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
@ -19,6 +19,10 @@ export class WorkbasketOverviewComponent implements OnInit {
@Select(WorkbasketSelectors.selectedWorkbasket) selectedWorkbasket$: Observable<Workbasket>;
destroy$ = new Subject<void>();
routerParams: any;
expanded = true;
@ViewChild('workbasketList') workbasketList: ElementRef;
@ViewChild('toggleButton') toggleButton: ElementRef;
constructor(private route: ActivatedRoute, private store: Store) {}
@ -52,6 +56,20 @@ export class WorkbasketOverviewComponent implements OnInit {
});
}
toggleWidth() {
if (this.workbasketList.nativeElement.offsetWidth === 250) {
this.expanded = true;
this.workbasketList.nativeElement.style.width = '500px';
this.workbasketList.nativeElement.style.minWidth = '500px';
this.toggleButton.nativeElement.style.left = '480px';
} else {
this.expanded = false;
this.workbasketList.nativeElement.style.width = '250px';
this.workbasketList.nativeElement.style.minWidth = '250px';
this.toggleButton.nativeElement.style.left = '230px';
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();

View File

@ -1,14 +1,18 @@
<div class="pagination">
<mat-paginator [length]="page?.totalElements" [pageIndex]="pageSelected - 1 " hidePageSize="true" [pageSize]="page?.size" (page)="changeToPage($event)" showFirstLastButtons="true"></mat-paginator>
<div class="pagination__go-to">
<div class="pagination__go-to-label">Page: </div>
<div class="pagination" #pagination>
<mat-paginator class="pagination__mat-paginator" [length]="page?.totalElements" [pageIndex]="pageSelected - 1 "
hidePageSize="true" [pageSize]="page?.size" (page)="changeToPage($event)" [showFirstLastButtons]="true"
[ngClass]="
{'pagination__mat-paginator--expanded': expanded,
'pagination__mat-paginator--collapsed': !expanded }"></mat-paginator>
<div class="pagination__go-to" *ngIf="expanded">
<div class="pagination__go-to-label">Page:</div>
<mat-form-field>
<input #inputTypeAhead matInput type="text" [matAutocomplete]="auto" [(ngModel)]="value" name="accessId"
(ngModelChange)="filter(value)"/>
(ngModelChange)="filter(value)"/>
<mat-autocomplete #autoComplete autoActiveFirstOption (optionSelected)="goToPage($event.option.value)"
#auto="matAutocomplete">
#auto="matAutocomplete">
<mat-option *ngFor="let pageNumber of filteredPages" [value]="pageNumber">{{ pageNumber }}</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
</div>
</div>

View File

@ -2,26 +2,34 @@
display: flex;
max-width: 100%;
background-color: #fafafa;
}
mat-paginator {
background-color: #fafafa;
width: 70%;
font-feature-settings: 'tnum';
font-variant-numeric: tabular-nums;
}
//Original
.pagination__go-to {
margin-right: 12px;
display: flex;
align-items: baseline;
mat-form-field {
width: 56px;
margin: 6px 4px 0 4px;
font-size: 12px;
&__mat-paginator {
background-color: #fafafa;
font-feature-settings: 'tnum';
font-variant-numeric: tabular-nums;
&--expanded {
width: 70%;
}
&--collapsed {
width: 100%;
text-align: center;
}
}
.pagination__go-to-label {
margin: 0 4px;
font-size: 12px;
color: rgba(0, 0, 0, 0.54);
&__go-to {
margin-right: 12px;
display: flex;
align-items: baseline;
mat-form-field {
width: 56px;
margin: 6px 4px 0 4px;
font-size: 12px;
}
.pagination__go-to-label {
margin: 0 4px;
font-size: 12px;
color: rgba(0, 0, 0, 0.54);
}
}
}
.hidden {
display: none;
}

View File

@ -1,4 +1,14 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild
} from '@angular/core';
import { Page } from 'app/shared/models/page';
import { MatPaginator } from '@angular/material/paginator';
@ -17,12 +27,17 @@ export class PaginationComponent implements OnInit, OnChanges {
@Input()
numberOfItems: number;
@Input()
expanded: boolean = true;
@Output()
changePage = new EventEmitter<number>();
@ViewChild(MatPaginator, { static: true })
paginator: MatPaginator;
@ViewChild('pagination') paginationWrapper: ElementRef;
hasItems = true;
pageSelected = 1;
pageNumbers: number[];
@ -30,6 +45,32 @@ export class PaginationComponent implements OnInit, OnChanges {
value: number;
ngOnInit() {
this.changeLabel();
this.value = 1;
}
ngOnChanges(changes: SimpleChanges): void {
const rangeLabel = this.paginationWrapper?.nativeElement?.querySelector('.mat-paginator-range-label');
const container = this.paginationWrapper?.nativeElement?.querySelector('.mat-paginator-container');
if (rangeLabel && container) {
if (!this.expanded) {
container.style.justifyContent = 'center';
rangeLabel.style.display = 'none';
} else {
container.style.justifyContent = 'flex-end';
rangeLabel.style.display = 'block';
}
}
if (changes.page && changes.page.currentValue) {
this.pageSelected = changes.page.currentValue.number;
}
this.hasItems = this.numberOfItems > 0;
if (changes.page) {
this.updateGoto();
}
}
changeLabel() {
// Custom label: EG. "1-7 of 21 workbaskets"
// return `${start} - ${end} of ${length} workbaskets`;
@ -44,16 +85,6 @@ export class PaginationComponent implements OnInit, OnChanges {
return `${start} - ${end} of ${length}`;
}
};
this.value = 1;
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.page && changes.page.currentValue) {
this.pageSelected = changes.page.currentValue.number;
}
this.hasItems = this.numberOfItems > 0;
if (changes.page) {
this.updateGoto();
}
}
changeToPage(event) {