Pull request #113: Virtual scroll

Merge in RED/ui from virtual-scroll to master

* commit '2c203eeb9131d5099b138bb766bcd3856b297bef':
  dummy patch virtual scroll
  Virtual scroll
  Fixed team members
This commit is contained in:
Timo Bejan 2021-02-11 09:22:15 +01:00
commit 9bb0d5c7d2
22 changed files with 293 additions and 247 deletions

View File

@ -110,6 +110,7 @@ import { DefaultColorsScreenComponent } from './screens/admin/default-colors-scr
import { EditColorDialogComponent } from './screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component';
import { DownloadsListScreenComponent } from './screens/downloads-list-screen/downloads-list-screen.component';
import { DigitalSignatureScreenComponent } from './screens/admin/digital-signature-screen/digital-signature-screen.component';
import { ScrollingModule } from '@angular/cdk/scrolling';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -397,7 +398,8 @@ const matImports = [
FileUploadDownloadModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
ColorPickerModule,
AceEditorModule
AceEditorModule,
ScrollingModule
],
providers: [
{

View File

@ -8,6 +8,7 @@ import { PermissionsService } from '../../common/service/permissions.service';
})
export class TeamMembersComponent implements OnInit {
@Input() public memberIds: string[];
@Input() public perLine: number;
@Input() public canAdd = true;
@Input() public largeSpacing = false;
@Input() public canRemove = false;
@ -23,10 +24,7 @@ export class TeamMembersComponent implements OnInit {
ngOnInit(): void {}
public get maxTeamMembersBeforeExpand(): number {
const width = this.container.nativeElement.offsetWidth;
// 32px element width + margin right (2px or 12px)
const elementWidth = this.largeSpacing ? 46 : 34;
return Math.floor(width / elementWidth) - (this.canAdd ? 1 : 0);
return this.perLine - (this.canAdd ? 1 : 0);
}
public get displayedMembers(): string[] {

View File

@ -21,6 +21,7 @@
[largeSpacing]="true"
[canRemove]="true"
(remove)="toggleSelected($event)"
[perLine]="13"
></redaction-team-members>
<pre *ngIf="selectedUserList.length === 0" class="info" [innerHTML]="'assign-' + data.type + '-owner.dialog.no-members' | translate"></pre>

View File

@ -34,9 +34,9 @@
<div class="scrollbar-placeholder"></div>
</div>
<div class="grid-container" redactionHasScrollbar>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div class="table-item" *ngFor="let color of colors | sortBy: sortingOption.order:sortingOption.column">
<div class="table-item" *cdkVirtualFor="let color of colors | sortBy: sortingOption.order:sortingOption.column">
<div>
<div class="table-item-title heading" [translate]="'default-colors-screen.types.' + color.key"></div>
</div>
@ -59,7 +59,7 @@
</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</cdk-virtual-scroll-viewport>
</div>
</div>
</section>

View File

@ -1,26 +1,30 @@
.left-container {
width: 100vw;
.grid-container {
grid-template-columns: 2fr 1fr 2fr 11px;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 2fr 11px;
&.has-scrollbar:hover {
grid-template-columns: 2fr 1fr 2fr;
.table-item {
> div:not(.scrollbar-placeholder) {
padding-left: 24px;
}
.color-wrapper {
align-items: center;
.color-square {
width: 16px;
height: 16px;
min-width: 16px;
}
}
}
}
.table-item {
> div:not(.scrollbar-placeholder) {
padding-left: 24px;
}
.color-wrapper {
align-items: center;
.color-square {
width: 16px;
height: 16px;
min-width: 16px;
}
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 2fr;
}
}
}

View File

@ -93,11 +93,10 @@
<div class="scrollbar-placeholder"></div>
</div>
<div class="grid-container" redactionHasScrollbar>
<!-- Table lines -->
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div
class="table-item pointer"
*ngFor="let dict of displayedDictionaries | sortBy: sortingOption.order:sortingOption.column"
*cdkVirtualFor="let dict of displayedDictionaries | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[dict.type]"
>
<div class="pr-0" (click)="toggleDictSelected($event, dict)">
@ -155,7 +154,7 @@
</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</cdk-virtual-scroll-viewport>
</ng-container>
</div>

View File

@ -24,44 +24,48 @@ redaction-table-col-name::ng-deep {
.left-container {
width: calc(100vw - 353px);
.grid-container {
grid-template-columns: auto 2fr 1fr 1fr 1fr 11px;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 2fr 1fr 1fr 1fr 11px;
&.has-scrollbar:hover {
grid-template-columns: auto 2fr 1fr 1fr 1fr;
}
.table-item {
> div:not(.scrollbar-placeholder) {
display: flex;
flex-direction: row;
padding-left: 10px;
align-items: center;
justify-content: flex-start;
.table-item {
> div:not(.scrollbar-placeholder) {
display: flex;
flex-direction: row;
padding-left: 10px;
align-items: center;
justify-content: flex-start;
&.analyzed,
&.rank {
justify-content: center;
}
&.analyzed,
&.rank {
justify-content: center;
}
.color-square {
width: 16px;
height: 16px;
min-width: 16px;
margin-right: 16px;
}
.color-square {
width: 16px;
height: 16px;
min-width: 16px;
margin-right: 16px;
}
.dict-name {
z-index: 1;
max-width: 100%;
}
.dict-name {
z-index: 1;
max-width: 100%;
}
.stats-subtitle {
margin-top: 4px;
.stats-subtitle {
margin-top: 4px;
}
}
}
}
}
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 2fr 1fr 1fr 1fr;
}
}
}
.right-container {

View File

@ -7,6 +7,7 @@
}
.grid-container {
display: grid;
grid-template-columns: 1fr 2fr;
margin: 20px;

View File

@ -87,11 +87,10 @@
<div class="scrollbar-placeholder"></div>
</div>
<div class="grid-container" redactionHasScrollbar>
<!-- Table lines -->
<cdk-virtual-scroll-viewport [itemSize]="100" redactionHasScrollbar>
<div
class="table-item pointer"
*ngFor="let ruleSet of displayedRuleSets | sortBy: sortingOption.order:sortingOption.column"
*cdkVirtualFor="let ruleSet of displayedRuleSets | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[ruleSet.ruleSetId, 'dictionaries']"
>
<div class="pr-0" (click)="toggleTemplateSelected($event, ruleSet)">
@ -135,7 +134,7 @@
<div class="scrollbar-placeholder"></div>
</div>
</div>
</cdk-virtual-scroll-viewport>
</div>
</div>
</section>

View File

@ -14,40 +14,44 @@ redaction-table-col-name::ng-deep {
.left-container {
width: 100vw;
.grid-container {
grid-template-columns: auto 1fr 1fr 1fr 1fr 11px;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 1fr 1fr 1fr 1fr 11px;
&.has-scrollbar:hover {
grid-template-columns: auto 1fr 1fr 1fr 1fr;
.table-item {
> div:not(.scrollbar-placeholder) {
display: flex;
flex-direction: row;
padding-left: 10px;
align-items: center;
justify-content: flex-start;
&.template-name {
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.stats-subtitle {
margin-top: 4px;
}
.table-item-title {
max-width: 100%;
}
&.created-by,
&.created-on,
&.modified-on {
display: flex;
}
}
}
}
.table-item {
> div:not(.scrollbar-placeholder) {
display: flex;
flex-direction: row;
padding-left: 10px;
align-items: center;
justify-content: flex-start;
&.template-name {
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.stats-subtitle {
margin-top: 4px;
}
.table-item-title {
max-width: 100%;
}
&.created-by,
&.created-on,
&.modified-on {
display: flex;
}
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 1fr 1fr 1fr 1fr;
}
}
}

View File

@ -37,16 +37,16 @@
<div class="scrollbar-placeholder"></div>
</div>
<div class="grid-container" redactionHasScrollbar>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div class="table-item" *ngFor="let user of displayedUsers">
<div class="table-item" *cdkVirtualFor="let user of displayedUsers">
<div>
<redaction-initials-avatar [userId]="user.userId" [withName]="true" size="large"></redaction-initials-avatar>
</div>
<div>{{ user.email || '-' }}</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</cdk-virtual-scroll-viewport>
</div>
<div class="right-container"></div>

View File

@ -1,16 +1,20 @@
.left-container {
width: calc(100vw - 353px);
.grid-container {
grid-template-columns: 1fr 1fr 11px;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 1fr 1fr 11px;
&.has-scrollbar:hover {
grid-template-columns: 1fr 1fr;
.table-item {
> div {
padding: 0 24px;
}
}
}
.table-item {
> div {
padding: 0 24px;
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 1fr 1fr;
}
}
}

View File

@ -23,9 +23,9 @@
<div *ngIf="noData" class="no-data heading-l" translate="downloads-list.no-data"></div>
<div class="grid-container" redactionHasScrollbar>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div *ngFor="let download of fileDownloadService.downloads" class="table-item">
<div *cdkVirtualFor="let download of fileDownloadService.downloads" class="table-item">
<div>
<div class="table-item-title heading">{{ download.filename }}</div>
</div>
@ -58,7 +58,7 @@
</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</cdk-virtual-scroll-viewport>
</div>
</div>
</section>

View File

@ -1,16 +1,20 @@
.left-container {
width: 100vw;
.grid-container {
grid-template-columns: 2fr 1fr 1fr auto 11px;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr auto 11px;
&.has-scrollbar:hover {
grid-template-columns: 2fr 1fr 1fr auto;
.table-item {
> div:not(.scrollbar-placeholder) {
padding-left: 24px;
}
}
}
.table-item {
> div:not(.scrollbar-placeholder) {
padding-left: 24px;
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr auto;
}
}
}

View File

@ -76,9 +76,9 @@
<div *ngIf="noData" class="no-data heading-l" translate="project-listing.no-projects-match"></div>
<div class="grid-container" redactionHasScrollbar>
<cdk-virtual-scroll-viewport [itemSize]="85" redactionHasScrollbar>
<div
*ngFor="let pw of displayedProjects | sortBy: sortingOption.order:sortingOption.column"
*cdkVirtualFor="let pw of displayedProjects | sortBy: sortingOption.order:sortingOption.column"
[class.pointer]="canOpenProject(pw)"
[routerLink]="[canOpenProject(pw) ? '/ui/projects/' + pw.project.projectId : []]"
class="table-item"
@ -87,6 +87,12 @@
<div class="table-item-title heading">
{{ pw.project.projectName }}
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:template"></mat-icon>
{{ getRuleSet(pw).name }}
</div>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:document"></mat-icon>
@ -108,10 +114,6 @@
<mat-icon svgIcon="red:lightning"></mat-icon>
{{ pw.project.dueDate | date: 'mediumDate' }}
</div>
<div>
<mat-icon svgIcon="red:template"></mat-icon>
{{ getRuleSet(pw).name }}
</div>
</div>
</div>
<div>
@ -125,7 +127,7 @@
</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</cdk-virtual-scroll-viewport>
</div>
<div class="right-container" redactionHasScrollbar>

View File

@ -2,27 +2,31 @@
@import '../../../assets/styles/red-variables';
.left-container {
.grid-container {
grid-template-columns: 2fr 1fr 1fr auto 11px;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr auto 11px;
.table-item {
> div {
height: 85px;
padding: 0 24px;
}
.status-container {
width: 160px;
padding-right: 13px;
}
}
//.stats-subtitle {
// margin-top: 6px;
//}
}
&.has-scrollbar:hover {
grid-template-columns: 2fr 1fr 1fr auto;
}
.table-item {
> div {
height: 100px;
padding: 0 24px;
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr auto;
}
.status-container {
width: 160px;
padding-right: 13px;
}
}
.stats-subtitle {
margin-top: 6px;
}
}

View File

@ -1,5 +1,5 @@
<ng-container *ngIf="appStateService.activeProject"
><div class="collapsed-wrapper mt-8">
<ng-container *ngIf="appStateService.activeProject">
<div class="collapsed-wrapper mt-8">
<redaction-circle-button
(action)="toggleCollapse.emit()"
icon="red:expand"
@ -33,7 +33,11 @@
<div class="mt-16">
<div class="all-caps-label" translate="project-details.members"></div>
<redaction-team-members [memberIds]="memberIds" (openAssignProjectMembersDialog)="openAssignProjectMembersDialog.emit()"></redaction-team-members>
<redaction-team-members
[memberIds]="memberIds"
(openAssignProjectMembersDialog)="openAssignProjectMembersDialog.emit()"
[perLine]="9"
></redaction-team-members>
</div>
<div *ngIf="hasFiles" class="mt-24">

View File

@ -169,9 +169,9 @@
<div *ngIf="noData" class="no-data heading-l" translate="project-overview.no-files-match"></div>
<div redactionHasScrollbar class="grid-container">
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div
*ngFor="let fileStatus of displayedFiles | sortBy: sortingOption.order:sortingOption.column; trackBy: fileId"
*cdkVirtualFor="let fileStatus of displayedFiles | sortBy: sortingOption.order:sortingOption.column; trackBy: fileId"
[class.pointer]="permissionsService.canOpenFile(fileStatus)"
[routerLink]="fileLink(fileStatus)"
class="table-item"
@ -242,7 +242,7 @@
</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</cdk-virtual-scroll-viewport>
</ng-container>
</div>

View File

@ -15,55 +15,59 @@ redaction-table-col-name::ng-deep {
}
}
.grid-container {
grid-template-columns: auto 3fr 2fr 1fr 2fr 1fr auto 11px;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 3fr 2fr 1fr 2fr 1fr auto 11px;
&.has-scrollbar:hover {
grid-template-columns: auto 3fr 2fr 1fr 2fr 1fr auto;
}
.table-item {
> div {
padding-left: 10px;
}
.table-item {
> div {
padding-left: 10px;
}
.disabled {
color: $grey-7;
}
.disabled {
color: $grey-7;
}
.error {
color: $red-1;
}
.error {
color: $red-1;
}
.extend-cols {
grid-column-end: span 3;
align-items: flex-end;
}
.extend-cols {
grid-column-end: span 3;
align-items: flex-end;
}
.table-item-title {
max-width: 25vw;
}
.table-item-title {
max-width: 25vw;
}
.pages {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
opacity: 0.7;
color: $grey-1;
font-size: 11px;
letter-spacing: 0;
line-height: 14px;
.pages {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
opacity: 0.7;
color: $grey-1;
font-size: 11px;
letter-spacing: 0;
line-height: 14px;
.mat-icon {
width: 10px;
height: 10px;
margin-right: 4px;
}
}
.mat-icon {
width: 10px;
height: 10px;
margin-right: 4px;
.status-container {
align-items: flex-end;
}
}
}
.status-container {
align-items: flex-end;
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 3fr 2fr 1fr 2fr 1fr auto;
}
}
}

View File

@ -10,7 +10,14 @@ export class HasScrollbarDirective implements AfterContentChecked {
@HostBinding('class') class = '';
ngAfterContentChecked() {
this.class = this.hasScrollbar ? 'has-scrollbar' : '';
this._process();
}
_process() {
const newClass = this.hasScrollbar ? 'has-scrollbar' : '';
if (this.class !== newClass) {
this.class = newClass;
}
}
public get hasScrollbar() {

View File

@ -1,20 +1,16 @@
import { AfterViewChecked, Directive, ElementRef, HostListener, Input } from '@angular/core';
import { AfterViewChecked, AfterViewInit, Directive, ElementRef, HostListener, Input } from '@angular/core';
import { debounce } from './debounce';
@Directive({
selector: '[redactionSyncWidth]',
exportAs: 'redactionSyncWidth'
})
export class SyncWidthDirective implements AfterViewChecked {
export class SyncWidthDirective implements AfterViewInit {
@Input()
redactionSyncWidth: string;
constructor(private el: ElementRef) {}
ngAfterViewChecked() {
this.matchWidth();
}
private get _sampleRow(): { tableRow: Element; length: number } {
const tableRows = document.getElementsByClassName(this.redactionSyncWidth);
let length = 0;
@ -31,12 +27,14 @@ export class SyncWidthDirective implements AfterViewChecked {
return { tableRow, length };
}
@debounce(0)
@debounce(10)
matchWidth() {
const headerItems = this.el.nativeElement.children;
const tableRows = document.getElementsByClassName(this.redactionSyncWidth);
if (!tableRows || !tableRows.length) return;
if (!tableRows || !tableRows.length) {
return;
}
const { tableRow, length } = this._sampleRow;
@ -56,4 +54,8 @@ export class SyncWidthDirective implements AfterViewChecked {
onResize() {
this.matchWidth();
}
ngAfterViewInit(): void {
this.matchWidth();
}
}

View File

@ -21,76 +21,79 @@
}
}
.grid-container {
display: grid;
max-height: calc(100vh - 50px - 30px - 111px);
overflow-y: hidden;
cdk-virtual-scroll-viewport {
height: calc(100vh - 50px - 31px - 111px);
overflow-y: hidden !important;
.table-item {
display: contents;
.cdk-virtual-scroll-content-wrapper {
display: grid;
> div {
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
box-sizing: border-box;
height: 80px;
border-bottom: 1px solid $separator;
padding: 0 13px;
.table-item {
display: contents;
&:not(.scrollbar-placeholder):not(.pr-0) {
min-width: 110px;
}
}
.table-item-title {
font-weight: 600;
@include line-clamp(1);
}
.action-buttons {
position: absolute;
display: none;
right: -11px;
top: 0;
height: 100%;
width: fit-content;
flex-direction: row;
align-items: center;
padding-left: 100px;
padding-right: 24px;
z-index: 1;
background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, #f4f5f7 35%);
mat-icon {
width: 14px;
}
&.active {
display: flex;
// compensate for scroll
padding-right: 23px;
}
}
&:hover {
> div {
background-color: #f9fafb;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
box-sizing: border-box;
height: 80px;
border-bottom: 1px solid $separator;
padding: 0 13px;
&:not(.scrollbar-placeholder):not(.pr-0) {
min-width: 110px;
}
}
.select-oval {
opacity: 1;
.table-item-title {
font-weight: 600;
@include line-clamp(1);
}
.action-buttons {
display: flex;
position: absolute;
display: none;
right: -11px;
top: 0;
height: 100%;
width: fit-content;
flex-direction: row;
align-items: center;
padding-left: 100px;
padding-right: 24px;
z-index: 1;
background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, #f4f5f7 35%);
mat-icon {
width: 14px;
}
&.active {
display: flex;
// compensate for scroll
padding-right: 23px;
}
}
&:hover {
> div {
background-color: #f9fafb;
}
.select-oval {
opacity: 1;
}
.action-buttons {
display: flex;
}
}
}
}
&:hover {
overflow-y: auto;
overflow-y: auto !important;
@include scroll-bar;
&.has-scrollbar {