More mobile friendly lists

More mobile friendly lists using menus
This commit is contained in:
Sean Engelhardt 2019-09-13 13:37:59 +02:00
parent 1da7ba51fd
commit bd83a74830
17 changed files with 281 additions and 111 deletions

View File

@ -33,7 +33,11 @@
<!-- Projector column -->
<div *pblNgridCellDef="'projector'; row as viewModel" class="fill ngrid-lg">
<os-projector-button class="projector-button" [object]="getProjectable(viewModel)" (changeEvent)="viewUpdateEvent()"></os-projector-button>
<os-projector-button
class="projector-button"
[object]="getProjectable(viewModel)"
(changeEvent)="viewUpdateEvent()"
></os-projector-button>
</div>
<!-- No Results -->
@ -42,6 +46,16 @@
</div>
<!-- Slot transclusion for the individual cells -->
<ng-content class="ngrid-lg" select=".cell-slot"></ng-content>
<div #contentWrapper>
<ng-content class="ngrid-lg" select=".cell-slot"></ng-content>
</div>
<!-- Speaker -->
<div *pblNgridCellDef="'speaker'; row as viewModel; rowContext as rowContext" class="fill">
<os-speaker-button
[object]="viewModel.contentObjectData ? viewModel.contentObjectData : viewModel"
[disabled]="multiSelect"
></os-speaker-button>
</div>
</pbl-ngrid>
</mat-drawer-container>

View File

@ -12,7 +12,7 @@ import {
} from '@angular/core';
import { columnFactory, createDS, PblDataSource, PblNgridComponent } from '@pebula/ngrid';
import { PblColumnDefinition, PblNgridColumnSet } from '@pebula/ngrid/lib/table';
import { PblColumnDefinition, PblColumnFactory, PblNgridColumnSet } from '@pebula/ngrid/lib/table';
import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
import { Observable } from 'rxjs';
@ -112,7 +112,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
* Current state of the multi select mode.
*/
@Input()
private multiSelect = false;
public multiSelect = false;
/**
* If a Projector column should be shown (at all)
@ -151,6 +151,9 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
@Input()
public columns: PblColumnDefinition[] = [];
/**
* Properties to filter for
*/
@Input()
public filterProps: string[];
@ -166,6 +169,18 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
@Input()
public showFilterBar = true;
/**
* If the menu should always be shown
*/
@Input()
public alwaysShowMenu = false;
/**
* If the speaker tab should appear
*/
@Input()
public showListOfSpeakers = true;
/**
* Inform about changes in the dataSource
*/
@ -187,10 +202,15 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
*/
public columnSet: PblNgridColumnSet;
/**
* To dynamically recreate the columns
*/
public columnFactory: PblColumnFactory;
/**
* Check if mobile and required semaphore for change detection
*/
private isMobile: boolean;
public isMobile: boolean;
/**
* Search input value
@ -206,22 +226,38 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
/**
* Most, of not all list views require these
*/
private get defaultColumns(): PblColumnDefinition[] {
private get defaultStartColumns(): PblColumnDefinition[] {
const columns = [
{
prop: 'selection',
label: '',
width: this.columnMinWidth
}
];
if (this.allowProjector && this.operator.hasPerms('core.can_manage_projector')) {
columns.push({
width: '40px'
},
{
prop: 'projector',
label: '',
width: this.columnMinWidth
});
}
}
];
return columns;
}
/**
* End columns
*/
private get defaultEndColumns(): PblColumnDefinition[] {
const columns = [
{
prop: 'speaker',
label: '',
width: '40px'
},
{
prop: 'menu',
label: '',
width: '40px'
}
];
return columns;
}
@ -256,22 +292,42 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
public get hiddenColumns(): string[] {
let hidden: string[] = [];
if (this.multiSelect) {
hidden.push('projector');
} else {
if (!this.multiSelect) {
hidden.push('selection');
}
if (this.isMobile && this.hiddenInMobile && this.hiddenInMobile.length) {
hidden = hidden.concat(this.hiddenInMobile);
if (!this.alwaysShowMenu && !this.isMobile) {
hidden.push('menu');
}
// hide the projector columns
if (
this.multiSelect ||
this.isMobile ||
!this.allowProjector ||
!this.operator.hasPerms('core.can_manage_projector')
) {
hidden.push('projector');
}
// hide the speakers in mobile
if (this.isMobile || !this.operator.hasPerms('agenda.can_see_list_of_speakers') || !this.showListOfSpeakers) {
hidden.push('speaker');
}
// hide all columns with restrictions
if (this.restricted && this.restricted.length) {
const restrictedColumns = this.restricted
.filter(restriction => !this.operator.hasPerms(restriction.permission))
.map(restriction => restriction.columnName);
hidden = hidden.concat(restrictedColumns);
}
// define columns that are hidden in mobile
if (this.isMobile && this.hiddenInMobile && this.hiddenInMobile.length) {
hidden = hidden.concat(this.hiddenInMobile);
}
return hidden;
}
@ -390,7 +446,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
// the constructor of this class
this.columnSet = columnFactory()
.default({ width: this.columnMinWidth })
.table(...this.defaultColumns, ...this.columns)
.table(...this.defaultStartColumns, ...this.columns, ...this.defaultEndColumns)
.build();
// restore scroll position

View File

@ -20,6 +20,7 @@
[multiSelect]="isMultiSelect"
[restricted]="restrictedColumns"
[hiddenInMobile]="['info']"
[alwaysShowMenu]="true"
[filterProps]="filterProps"
listStorageKey="agenda"
[(selectedRows)]="selectedRows"
@ -58,23 +59,11 @@
</div>
</div>
<!-- Speaker -->
<div *pblNgridCellDef="'speaker'; row as item; rowContext as rowContext" class="cell-slot fill">
<os-speaker-button [disabled]="isMultiSelect"></os-speaker-button>
<os-speaker-button
[object]="item.contentObjectData"
[disabled]="isMultiSelect"
(click)="saveScrollIndex('agenda', rowContext.identity)"
></os-speaker-button>
</div>
<!-- Menu -->
<div *pblNgridCellDef="'menu'; row as item" class="cell-slot fill">
<button
mat-icon-button
[disabled]="isMultiSelect"
*osPerms="'agenda.can_manage'"
[matMenuTriggerFor]="singleItemMenu"
(click)="$event.stopPropagation()"
[matMenuTriggerData]="{ item: item }"
@ -187,36 +176,45 @@
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-item="item">
<!-- Done check -->
<button mat-menu-item (click)="onDoneSingleButton(item)">
<mat-icon color="accent"> {{ item.closed ? 'check_box' : 'check_box_outline_blank' }} </mat-icon>
<span translate>Done</span>
</button>
<!-- Mobile entries -->
<div *ngIf="vp.isMobile">
<os-projector-button [object]="item.contentObject" [menuItem]="true"></os-projector-button>
<os-speaker-button [object]="item.contentObjectData" [menuItem]="true"></os-speaker-button>
</div>
<!-- Edit button -->
<button mat-menu-item (click)="openEditInfo(item, $event)">
<mat-icon>edit</mat-icon>
<span translate>Edit details</span>
</button>
<!-- Agenda entries -->
<div *osPerms="'agenda.can_manage'">
<!-- Done check -->
<button mat-menu-item (click)="onDoneSingleButton(item)">
<mat-icon color="accent"> {{ item.closed ? 'check_box' : 'check_box_outline_blank' }} </mat-icon>
<span translate>Done</span>
</button>
<!-- Delete Button -->
<button
mat-menu-item
(click)="removeFromAgenda(item)"
*ngIf="item.contentObjectData.collection !== 'topics/topic'"
>
<mat-icon>remove</mat-icon>
<span translate>Remove from agenda</span>
</button>
<!-- Edit button -->
<button mat-menu-item (click)="openEditInfo(item, $event)">
<mat-icon>edit</mat-icon>
<span translate>Edit details</span>
</button>
<button
mat-menu-item
class="red-warning-text"
(click)="deleteTopic(item)"
*ngIf="item.contentObjectData.collection === 'topics/topic'"
>
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
<!-- Delete Button -->
<button
mat-menu-item
(click)="removeFromAgenda(item)"
*ngIf="item.contentObjectData.collection !== 'topics/topic'"
>
<mat-icon>remove</mat-icon>
<span translate>Remove from agenda</span>
</button>
<button
mat-menu-item
class="red-warning-text"
(click)="deleteTopic(item)"
*ngIf="item.contentObjectData.collection === 'topics/topic'"
>
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</div>
</ng-template>
</mat-menu>

View File

@ -80,14 +80,6 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
{
prop: 'info',
width: '15%'
},
{
prop: 'speaker',
width: this.badgeButtonWidth
},
{
prop: 'menu',
width: this.singleButtonWidth
}
];
@ -95,10 +87,6 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
{
columnName: 'menu',
permission: 'agenda.can_manage'
},
{
columnName: 'speaker',
permission: 'agenda.can_see_list_of_speakers'
}
];

View File

@ -58,8 +58,29 @@
</mat-chip>
</mat-chip-list>
</div>
<!-- Menu -->
<div *pblNgridCellDef="'menu'; row as assignment" class="cell-slot fill">
<button
mat-icon-button
[disabled]="isMultiSelect"
[matMenuTriggerFor]="singleItemMenu"
(click)="$event.stopPropagation()"
[matMenuTriggerData]="{ assignment: assignment }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</os-list-view-table>
<!-- Menu for mobile entries -->
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-assignment="assignment">
<os-projector-button [object]="assignment" [menuItem]="true"></os-projector-button>
<os-speaker-button [object]="assignment" [menuItem]="true"></os-speaker-button>
</ng-template>
</mat-menu>
<mat-menu #assignmentMenu="matMenu">
<div *ngIf="!isMultiSelect">
<button mat-menu-item *osPerms="'assignment.can_manage'" (click)="toggleMultiSelect()">

View File

@ -88,12 +88,28 @@
</div>
</div>
<!-- List of Speakers -->
<div *pblNgridCellDef="'speakers'; row as motion" class="cell-slot fill">
<os-speaker-button [object]="motion"></os-speaker-button>
<!-- Menu -->
<div *pblNgridCellDef="'menu'; row as motion" class="cell-slot fill">
<button
mat-icon-button
[disabled]="isMultiSelect"
[matMenuTriggerFor]="singleItemMenu"
(click)="$event.stopPropagation()"
[matMenuTriggerData]="{ motion: motion }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</os-list-view-table>
<!-- Menu for mobile entries -->
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-motion="motion">
<os-projector-button [object]="motion" [menuItem]="true"></os-projector-button>
<os-speaker-button [object]="motion" [menuItem]="true"></os-speaker-button>
</ng-template>
</mat-menu>
<mat-menu #amendmentListMenu="matMenu">
<div *ngIf="!isMultiSelect">
<div *osPerms="'motions.can_manage'">

View File

@ -15,9 +15,11 @@
<os-list-view-table
[repo]="repo"
[allowProjector]="false"
[showListOfSpeakers]="false"
[columns]="tableColumnDefinition"
[filterProps]="filterProps"
listStorageKey="category"
[hiddenInMobile]="['menu']"
(dataSourceChange)="onDataSourceChange($event)"
>
<!-- Title -->

View File

@ -35,6 +35,7 @@
[columns]="tableColumnDefinition"
[restricted]="restrictedColumns"
[filterProps]="filterProps"
[hiddenInMobile]="['remove']"
(dataSourceChange)="onDataSourceChange($event)"
>
<!-- Title column -->
@ -71,8 +72,33 @@
<mat-icon>close</mat-icon>
</button>
</div>
<!-- Menu -->
<div *pblNgridCellDef="'menu'; row as motion" class="cell-slot fill">
<button
mat-icon-button
[disabled]="isMultiSelect"
[matMenuTriggerFor]="singleItemMenu"
(click)="$event.stopPropagation()"
[matMenuTriggerData]="{ motion: motion }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</os-list-view-table>
<!-- Menu for mobile entries -->
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-motion="motion">
<os-projector-button [object]="motion" [menuItem]="true"></os-projector-button>
<os-speaker-button [object]="motion" [menuItem]="true"></os-speaker-button>
<button mat-menu-item class="red-warning-text" (click)="onRemoveMotionButton(motion)">
<mat-icon>close</mat-icon>
<span translate>Remove from motion block</span>
</button>
</ng-template>
</mat-menu>
<!-- The menu content -->
<mat-menu #motionBlockMenu="matMenu">
<div *ngIf="vp.isMobile">

View File

@ -34,16 +34,28 @@
<span class="os-amount-chip" matTooltip="{{ 'Motions' | translate }}">{{ block.motions.length }}</span>
</div>
<!-- Speaker column-->
<div *pblNgridCellDef="'speaker'; row as block; rowContext as rowContext" class="fill">
<os-speaker-button
[object]="block"
<!-- Menu -->
<div *pblNgridCellDef="'menu'; row as block" class="cell-slot fill">
<button
mat-icon-button
[disabled]="isMultiSelect"
(click)="saveScrollIndex('motionBlock', rowContext.identity)"
></os-speaker-button>
[matMenuTriggerFor]="singleItemMenu"
(click)="$event.stopPropagation()"
[matMenuTriggerData]="{ block: block }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</os-list-view-table>
<!-- Menu for mobile entries -->
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-block="block">
<os-projector-button [object]="block" [menuItem]="true"></os-projector-button>
<os-speaker-button [object]="block" [menuItem]="true"></os-speaker-button>
</ng-template>
</mat-menu>
<!-- Template for new motion block dialog -->
<ng-template #newMotionBlockDialog>
<h1 mat-dialog-title>

View File

@ -72,10 +72,6 @@ export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBl
{
prop: 'amount',
label: this.translate.instant('Motions')
},
{
prop: 'speaker',
width: this.badgeButtonWidth
}
];

View File

@ -47,9 +47,8 @@
[sortService]="sortService"
[columns]="tableColumnDefinition"
[multiSelect]="isMultiSelect"
[restricted]="restrictedColumns"
[filterProps]="filterProps"
[hiddenInMobile]="['state']"
[hiddenInMobile]="['identifier', 'state']"
listStorageKey="motion"
[(selectedRows)]="selectedRows"
(dataSourceChange)="onDataSourceChange($event)"
@ -84,6 +83,10 @@
<!-- The title -->
<span class="motion-list-title">
<span *ngIf="vp.isMobile && motion.identifier">
{{ motion.identifier }}
<span>&middot;</span>
</span>
{{ motion.title }}
</span>
</div>
@ -156,17 +159,29 @@
</div>
</div>
<!-- Speaker column-->
<div *pblNgridCellDef="'speaker'; row as motion; rowContext as rowContext" class="fill">
<os-speaker-button
[object]="motion"
<!-- Menu -->
<div *pblNgridCellDef="'menu'; row as motion" class="cell-slot fill">
<button
mat-icon-button
[disabled]="isMultiSelect"
(click)="saveScrollIndex('motion', rowContext.identity)"
></os-speaker-button>
[matMenuTriggerFor]="singleItemMenu"
(click)="$event.stopPropagation()"
[matMenuTriggerData]="{ motion: motion }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</os-list-view-table>
</ng-template>
<!-- Menu for mobile entries -->
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-motion="motion">
<os-projector-button [object]="motion" [menuItem]="true"></os-projector-button>
<os-speaker-button [object]="motion" [menuItem]="true"></os-speaker-button>
</ng-template>
</mat-menu>
<ng-template #tiles>
<os-grid-layout>
<os-block-tile

View File

@ -16,7 +16,7 @@ import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.
import { OsFilterOptionCondition } from 'app/core/ui-services/base-filter-list.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { OverlayService } from 'app/core/ui-services/overlay.service';
import { ColumnRestriction } from 'app/shared/components/list-view-table/list-view-table.component';
import { ViewportService } from 'app/core/ui-services/viewport.service';
import { infoDialogSettings, largeDialogSettings } from 'app/shared/utils/dialog-settings';
import { BaseListViewComponent } from 'app/site/base/base-list-view';
import { ViewCategory } from 'app/site/motions/models/view-category';
@ -114,10 +114,6 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
prop: 'state',
width: '20%',
minWidth: 160
},
{
prop: 'speaker',
width: this.badgeButtonWidth
}
];
@ -139,16 +135,6 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
public categories: ViewCategory[] = [];
public motionBlocks: ViewMotionBlock[] = [];
/**
* Columns that demand certain permissions
*/
public restrictedColumns: ColumnRestriction[] = [
{
columnName: 'speaker',
permission: 'agenda.can_see_list_of_speakers'
}
];
/**
* Define extra filter properties
*
@ -214,7 +200,8 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
public multiselectService: MotionMultiselectService,
public perms: LocalPermissionsService,
private motionExport: MotionExportService,
private overlayService: OverlayService
private overlayService: OverlayService,
public vp: ViewportService
) {
super(titleService, translate, matSnackBar, storage);
this.canMultiSelect = true;

View File

@ -7,6 +7,10 @@
[repo]="workflowRepo"
[columns]="tableColumnDefinition"
listStorageKey="workflow"
[allowProjector]="false"
[hiddenInMobile]="['menu']"
[showListOfSpeakers]="false"
[filterProps]="filterProps"
(dataSourceChange)="onDataSourceChange($event)"
>
<!-- Name column -->

View File

@ -41,6 +41,11 @@ export class WorkflowListComponent extends BaseListViewComponent<ViewWorkflow> i
}
];
/**
* Define extra filter properties
*/
public filterProps = ['name', 'states'];
/**
* Constructor
*

View File

@ -6,6 +6,8 @@
[repo]="repo"
[columns]="tableColumnDefinition"
[allowProjector]="false"
[showListOfSpeakers]="false"
[hiddenInMobile]="['menu']"
[(selectedRows)]="selectedRows"
(dataSourceChange)="onDataSourceChange($event)"
[filterProps]="['name']"

View File

@ -20,8 +20,9 @@
[sortService]="sortService"
[columns]="tableColumnDefinition"
[filterProps]="filterProps"
[showListOfSpeakers]="false"
[multiSelect]="isMultiSelect"
[hiddenInMobile]="['group']"
[hiddenInMobile]="['group', 'presence']"
listStorageKey="user"
[(selectedRows)]="selectedRows"
(dataSourceChange)="onDataSourceChange($event)"
@ -101,8 +102,34 @@
<span translate>Present</span>
</mat-checkbox>
</div>
<!-- Menu -->
<div *pblNgridCellDef="'menu'; row as user" class="cell-slot fill">
<button
mat-icon-button
[disabled]="isMultiSelect"
[matMenuTriggerFor]="singleItemMenu"
(click)="$event.stopPropagation()"
[matMenuTriggerData]="{ user: user }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</os-list-view-table>
<!-- Menu for mobile entries -->
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-user="user">
<os-projector-button [object]="user" [menuItem]="true"></os-projector-button>
<!-- Presence -->
<button mat-menu-item (click)="setPresent(user)">
<mat-icon color="accent"> {{ user.is_present ? 'check_box' : 'check_box_outline_blank' }} </mat-icon>
<span translate>Present</span>
</button>
</ng-template>
</mat-menu>
<mat-menu #userMenu="matMenu">
<div *ngIf="!isMultiSelect">
<button mat-menu-item *osPerms="'users.can_manage'" (click)="toggleMultiSelect()">

View File

@ -120,7 +120,8 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
width: '15%'
},
{
prop: 'infos'
prop: 'infos',
width: this.singleButtonWidth
},
{
prop: 'presence',