Browse Source

Feature/horizontal table scrolling (#389)

* Sync scrolling

* Scroll directive.

* Finalized I hope.

* Fix horizontal scrolling.

* Fixed a bug with borders.

* Sync directive fix.
pull/397/head
Sebastian Stehle 7 years ago
committed by GitHub
parent
commit
0c4fc7c078
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  2. 13
      src/Squidex/app/features/content/pages/contents/contents-page.component.scss
  3. 6
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  4. 1
      src/Squidex/app/features/content/shared/assets-editor.component.html
  5. 84
      src/Squidex/app/features/content/shared/content-item.component.html
  6. 20
      src/Squidex/app/features/content/shared/contents-selector.component.html
  7. 13
      src/Squidex/app/features/content/shared/contents-selector.component.scss
  8. 5
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  9. 30
      src/Squidex/app/framework/angular/sync-scrolling.directive.ts
  10. 1
      src/Squidex/app/framework/declarations.ts
  11. 3
      src/Squidex/app/framework/module.ts
  12. 2
      src/Squidex/app/shared/state/filter.state.ts
  13. 10
      src/Squidex/app/theme/_lists.scss
  14. 1
      src/Squidex/app/theme/_mixins.scss

22
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -39,13 +39,19 @@
<ng-container content>
<div class="grid-header">
<table class="table table-items table-fixed">
<table class="table table-items table-fixed" [style.minWidth]="minWidth" #header>
<thead>
<tr>
<th class="cell-select">
<input type="checkbox" class="form-check" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" />
</th>
<th class="cell-auto" *ngFor="let field of schema.listFields">
<th class="cell-actions cell-actions-left">
Actions
</th>
<th class="cell-user">
<sqx-table-header text="By"></sqx-table-header>
</th>
<th class="cell-auto cell-content" *ngFor="let field of schema.listFields">
<sqx-table-header [text]="field.displayName"
[sortable]="field.properties.isSortable"
[sorting]="filter.sortMode(field) | async"
@ -59,12 +65,6 @@
(sortingChange)="sort('lastModified', $event)">
</sqx-table-header>
</th>
<th class="cell-user">
<sqx-table-header text="By"></sqx-table-header>
</th>
<th class="cell-actions">
Actions
</th>
</tr>
</thead>
</table>
@ -85,9 +85,9 @@
</button>
</div>
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed">
<div class="grid-content" [sqxSyncScrolling]="header">
<div class="table-container" sqxIgnoreScrollbar>
<table class="table table-items table-fixed" [style.minWidth]="minWidth">
<tbody *ngFor="let content of contentsState.contents | async; trackBy: trackByContent">
<tr [sqxContent]="content"
(delete)="delete(content)"

13
src/Squidex/app/features/content/pages/contents/contents-page.component.scss

@ -11,6 +11,19 @@
cursor: pointer;
}
.table-container {
display: inline-block;
padding: 0;
padding-right: 1.5rem;
min-width: 100%;
}
.grid-content {
overflow-y: auto;
overflow-x: auto;
padding-right: 0;
}
.icon-plus {
font-size: .8rem;
}

6
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -52,6 +52,8 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
public isAllSelected = false;
public minWidth: string;
@ViewChild('dueTimeSelector', { static: false })
public dueTimeSelector: DueTimeSelectorComponent;
@ -77,6 +79,8 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
this.schema = schema!;
this.schemaQueries = new Queries(this.uiState, `schemas.${this.schema.name}`);
this.minWidth = `${300 + (200 * this.schema.listFields.length)}px`;
this.contentsState.load();
}));
@ -154,6 +158,8 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
public selectLanguage(language: AppLanguageDto) {
this.language = language;
this.filter.setLanguage(language);
}
public isItemSelected(content: ContentDto): boolean {

1
src/Squidex/app/features/content/shared/assets-editor.component.html

@ -1,4 +1,3 @@
<div class="assets-container" [class.disabled]="snapshot.isDisabled" (sqxDropFile)="addFiles($event)" tabindex="1000">
<div class="header list">
<div class="row no-gutters">

84
src/Squidex/app/features/content/shared/content-item.component.html

@ -11,53 +11,14 @@
</ng-template>
</td>
<td class="cell-auto" *ngFor="let field of schemaFields; let i = index; trackBy: trackByField.bind(this)" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)">
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate">
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor>
</ng-container>
<ng-template #displayTemplate>
<sqx-content-value [value]="values[i]"></sqx-content-value>
</ng-template>
</td>
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small>
</td>
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td>
<ng-container *ngIf="isDirty">
<td class="cell-user" >
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick>
<i class="icon-checkmark"></i>
</button>
</td>
<td class="cell-actions" >
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick>
<i class="icon-close"></i>
</button>
</td>
</ng-container>
<td class="cell-actions" *ngIf="!isReadOnly && !isDirty" sqxStopClick>
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick>
<div class="dropdown dropdown-options" *ngIf="content">
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade>
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)">
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}}
</a>
@ -77,6 +38,47 @@
</ng-container>
</div>
</td>
<ng-container *ngIf="isDirty">
<td class="cell-actions" >
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick>
<i class="icon-close"></i>
</button>
</td>
<td class="cell-user" >
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick>
<i class="icon-checkmark"></i>
</button>
</td>
</ng-container>
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td>
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByField.bind(this)" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)">
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate">
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor>
</ng-container>
<ng-template #displayTemplate>
<sqx-content-value [value]="values[i]"></sqx-content-value>
</ng-template>
</td>
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small>
</td>
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty">
<div class="reference-edit">
<button type="button" class="btn btn-text-secondary">

20
src/Squidex/app/features/content/shared/contents-selector.component.html

@ -12,10 +12,10 @@
</div>
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Search for content"
expandable="true"
[fieldExample]="'data/[MY_FIELD]/iv'"
[filter]="filter"
(querySubmit)="search()"
expandable="true">
(querySubmit)="search()">
</sqx-search-form>
</div>
@ -27,13 +27,16 @@
<ng-container content>
<div class="grid-header">
<table class="table table-items table-fixed">
<table class="table table-items table-fixed" [style.minWidth]="minWidth" #header>
<thead>
<tr>
<th class="cell-select">
<input type="checkbox" class="form-check" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" />
</th>
<th class="cell-auto" *ngFor="let field of schema.referenceFields">
<th class="cell-user">
<sqx-table-header text="By"></sqx-table-header>
</th>
<th class="cell-content" *ngFor="let field of schema.referenceFields">
<sqx-table-header [text]="field.displayName"
[sortable]="field.properties.isSortable"
[sorting]="filter.sortMode(field) | async"
@ -47,17 +50,14 @@
(sortingChange)="sort('lastModified', $event)">
</sqx-table-header>
</th>
<th class="cell-user">
<sqx-table-header text="By"></sqx-table-header>
</th>
</tr>
</thead>
</table>
</div>
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed" *ngIf="contentsState.contents | async; let contents">
<div class="grid-content" [sqxSyncScrolling]="header">
<div class="table-container" sqxIgnoreScrollbar>
<table class="table table-items table-fixed" [style.minWidth]="minWidth" *ngIf="contentsState.contents | async; let contents">
<tbody *ngFor="let content of contents; trackBy: trackByContent">
<tr [sqxContent]="content"
[selectable]="!isItemAlreadySelected(content)"

13
src/Squidex/app/features/content/shared/contents-selector.component.scss

@ -9,4 +9,17 @@
.modal-tabs {
background: $color-dark-foreground;
}
}
.table-container {
display: inline-block;
padding: 0;
padding-right: 1.5rem;
min-width: 100%;
}
.grid-content {
overflow-y: auto;
overflow-x: auto;
padding-right: 0;
}

5
src/Squidex/app/features/content/shared/contents-selector.component.ts

@ -51,14 +51,17 @@ export class ContentsSelectorComponent implements OnInit {
public isAllSelected = false;
public minWidth: string;
constructor(
public readonly contentsState: ManualContentsState
) {
}
public ngOnInit() {
this.contentsState.schema = this.schema;
this.minWidth = `${200 + (200 * this.schema.referenceFields.length)}px`;
this.contentsState.schema = this.schema;
this.contentsState.load();
}

30
src/Squidex/app/framework/angular/sync-scrolling.directive.ts

@ -0,0 +1,30 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Directive, HostListener, Input, Renderer2 } from '@angular/core';
@Directive({
selector: '[sqxSyncScrolling]'
})
export class SyncScollingDirective {
@Input('sqxSyncScrolling')
public target: HTMLElement;
constructor(
private readonly renderer: Renderer2
) {
}
@HostListener('scroll', ['$event'])
public onScroll(event: Event) {
if (this.target) {
const scroll = (<HTMLElement>event.target).scrollLeft;
this.renderer.setStyle(this.target, 'transform', `translate(-${scroll - this.target.scrollLeft}px, 0px)`);
}
}
}

1
src/Squidex/app/framework/declarations.ts

@ -68,6 +68,7 @@ export * from './angular/scroll-active.directive';
export * from './angular/shortcut.component';
export * from './angular/sorted.directive';
export * from './angular/stop-click.directive';
export * from './angular/sync-scrolling.directive';
export * from './angular/template-wrapper.directive';
export * from './angular/title.component';

3
src/Squidex/app/framework/module.ts

@ -83,6 +83,7 @@ import {
SortedDirective,
StarsComponent,
StopClickDirective,
SyncScollingDirective,
TagEditorComponent,
TemplateWrapperDirective,
TitleComponent,
@ -159,6 +160,7 @@ import {
SortedDirective,
StarsComponent,
StopClickDirective,
SyncScollingDirective,
TagEditorComponent,
TemplateWrapperDirective,
TitleComponent,
@ -229,6 +231,7 @@ import {
SortedDirective,
StarsComponent,
StopClickDirective,
SyncScollingDirective,
TagEditorComponent,
TemplateWrapperDirective,
TitleComponent,

2
src/Squidex/app/shared/state/filter.state.ts

@ -136,7 +136,7 @@ function getFieldPath(snapshot: Snapshot, field?: Field) {
if (Types.isString(field)) {
path = field;
} else if (field.isLocalizable && snapshot.language) {
path = `data/${escapeField(field.name)}/${snapshot.language.iso2Code}`;
path = `data/${escapeField(field.name)}/${snapshot.language.iso2Code.replace('-', '_')}`;
} else {
path = `data/${escapeField(field.name)}/iv`;
}

10
src/Squidex/app/theme/_lists.scss

@ -208,7 +208,7 @@
}
&-time {
@include force-width(160px);
@include force-width(180px);
}
&-actions {
@ -219,6 +219,10 @@
@include force-width(150px);
}
&-content {
min-width: 200px;
}
&-separator,
&-select {
text-align: center;
@ -229,6 +233,10 @@
&-actions-lg {
text-align: right;
}
&-actions-left {
text-align: left;
}
}
//

1
src/Squidex/app/theme/_mixins.scss

@ -50,6 +50,7 @@
background: $foreground-color;
}
&::-webkit-scrollbar-corner,
&::-webkit-scrollbar-track {
background: $background-color;
}

Loading…
Cancel
Save