UI improvements

- better select field for category and block in motion detail
- improved motion block views
- chips for submitters
- motion detail template
This commit is contained in:
Emanuel Schütze 2019-01-17 14:07:54 +01:00
parent f2b9ba0e52
commit 8536f46d01
16 changed files with 232 additions and 171 deletions

View File

@ -1,4 +1,9 @@
<mat-toolbar color="primary" [ngClass]="multiSelectMode ? 'multi-select' : ''" *ngIf="!vp.isMobile"></mat-toolbar>
<mat-toolbar color="primary" [ngClass]="multiSelectMode ? 'multi-select' : ''" *ngIf="!vp.isMobile">
<!-- Nav menu -->
<button mat-icon-button class="on-transition-fade" *ngIf="!multiSelectMode" (click)="clickHamburgerMenu()">
<mat-icon>menu</mat-icon>
</button>
</mat-toolbar>
<mat-toolbar color="primary" [ngClass]="multiSelectMode ? 'multi-select' : ''" class="sticky-toolbar">
<div class="toolbar-left">
<!-- Nav menu -->
@ -63,7 +68,6 @@
<mat-icon>edit</mat-icon>
</button>
<!-- Save button -->
<button mat-button *ngIf="editMode" (click)="save()"><strong translate class="upper">Save</strong></button>

View File

@ -1,9 +1,6 @@
.list {
width: 100%;
border: solid 1px #ccc;
display: block;
background: white; // TODO theme
border-radius: 4px;
overflow: hidden;
}
@ -11,7 +8,6 @@
width: 100%;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
background: white; // TODO theme
font-size: 14px;
}

View File

@ -51,8 +51,6 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
* @param vp determine the viewport
* @param durationService Converts numbers to readable duration strings
* @param csvExport Handles the exporting into csv
* @param repo the agenda repository
* @param promptService
* @param filterService: service for filtering data
*/
public constructor(

View File

@ -42,7 +42,7 @@
</mat-card-actions>
</mat-card>
<mat-accordion class="os-card">
<mat-accordion class="os-form-card">
<mat-expansion-panel *ngFor="let category of categories" (opened)="openId = category.id" (closed)="panelClosed(category)"
[expanded]="openId === category.id" multiple="false">

View File

@ -6,41 +6,40 @@
</h4>
<div *ngIf="!isEditMode">
<mat-chip-list *ngFor="let submitter of motion.submitters">
<mat-chip-list *ngFor="let submitter of motion.submitters" class="user">
<mat-chip>{{ submitter.full_name }}</mat-chip>
</mat-chip-list>
</div>
<div *ngIf="isEditMode">
<mat-card>
<form *ngIf="users && users.value.length > 0" [formGroup]="addSubmitterForm">
<os-search-value-selector
class="search-users"
ngDefaultControl
[form]="addSubmitterForm"
[formControl]="addSubmitterForm.get('userId')"
[multiple]="false"
listname="{{ 'Select or search new submitter ...' | translate }}"
[InputListValues]="users"
></os-search-value-selector>
</form>
<os-sorting-list
class="testclass"
[input]="editSubmitterObservable"
[live]="true"
[count]="true"
(sortEvent)="onSortingChange($event)"
>
<!-- implicit user references into the component using ng-template slot -->
<ng-template let-user>
<button type="button" mat-icon-button matTooltip="{{ 'Remove' | translate }}" (click)="onRemove(user)">
<mat-icon>close</mat-icon>
</button>
</ng-template>
</os-sorting-list>
<mat-card-actions>
<button type="button" mat-button (click)="onSave()"><span translate>Save</span></button>
<button type="button" mat-button (click)="onCancel()"><span translate>Cancel</span></button>
</mat-card-actions>
</mat-card>
<div *ngIf="isEditMode">
<os-sorting-list
[input]="editSubmitterObservable"
[live]="true"
[count]="false"
(sortEvent)="onSortingChange($event)"
>
<!-- implicit user references into the component using ng-template slot -->
<ng-template let-user>
<button type="button" mat-icon-button matTooltip="{{ 'Remove' | translate }}" (click)="onRemove(user)">
<mat-icon>close</mat-icon>
</button>
</ng-template>
</os-sorting-list>
<form *ngIf="users && users.value.length > 0" [formGroup]="addSubmitterForm">
<os-search-value-selector
class="search-users"
ngDefaultControl
[form]="addSubmitterForm"
[formControl]="addSubmitterForm.get('userId')"
[multiple]="false"
listname="{{ 'Select or search new submitter ...' | translate }}"
[InputListValues]="users"
></os-search-value-selector>
</form>
<p>
<button type="button" mat-button (click)="onSave()"><span translate>Save</span></button>
<button type="button" mat-button (click)="onCancel()"><span translate>Cancel</span></button>
</p>
</div>

View File

@ -1,34 +1,4 @@
<ng-container *ngIf="vp.isMobile ; then mobileView; else desktopView"></ng-container>
<ng-template #title>
<ng-content select=".meta-text-block-title"></ng-content>
</ng-template>
<ng-template #content>
<ng-content select=".meta-text-block-content"></ng-content>
</ng-template>
<ng-template #actionRow>
<ng-content select=".meta-text-block-action-row"></ng-content>
</ng-template>
<ng-template #mobileView>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon>{{ icon }}</mat-icon>
<ng-container *ngTemplateOutlet="title"></ng-container>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-container *ngTemplateOutlet="content"></ng-container>
<mat-action-row *ngIf="showActionRow">
<ng-container *ngTemplateOutlet="actionRow"></ng-container>
</mat-action-row>
</mat-expansion-panel>
</ng-template>
<ng-template #desktopView>
<div>
<mat-card class="meta-text-block">
<mat-card-header>
<mat-card-title>
@ -47,4 +17,14 @@
<ng-container *ngTemplateOutlet="content"></ng-container>
</mat-card-content>
</mat-card>
</div>
<ng-template #title>
<ng-content select=".meta-text-block-title"></ng-content>
</ng-template>
<ng-template #content>
<ng-content select=".meta-text-block-content"></ng-content>
</ng-template>
<ng-template #actionRow>
<ng-content select=".meta-text-block-action-row"></ng-content>
</ng-template>

View File

@ -1,6 +1,6 @@
.meta-text-block {
padding: 0px;
margin: 20px 10px 20px 0;
margin: 20px 0;
min-width: 200px;
mat-card-header {

View File

@ -1,14 +1,10 @@
<os-head-bar
mainButtonIcon="edit"
[nav]="false"
[mainButton]="true"
[editMode]="editBlock"
(mainEvent)="toggleEditMode()"
(saveEvent)="saveBlock()"
>
<os-head-bar>
<!-- Title -->
<div class="title-slot">
<h2 *ngIf="block && !editBlock">{{ 'Motion block' | translate }} {{ block.id }}</h2>
<h2 *ngIf="block && !editBlock">{{ block.title }}</h2>
<form [formGroup]="blockEditForm" (ngSubmit)="saveBlock()" (keydown)="onKeyDown($event)" *ngIf="editBlock">
<mat-form-field>
@ -24,21 +20,23 @@
</form>
</div>
<!-- Menu -->
<div class="menu-slot">
<button type="button" mat-icon-button [matMenuTriggerFor]="motionBlockMenu">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<!-- Save button -->
<div *ngIf="editBlock" class="extra-controls-slot on-transition-fade">
<button mat-button (click)="saveBlock()"><strong translate class="upper">Save</strong></button>
</div>
</os-head-bar>
<!-- Title -->
<div *ngIf="block" class="block-title on-transition-fade">
<h2 *ngIf="!editBlock">{{ block.title }}</h2>
<h2 *ngIf="editBlock">{{ blockEditForm.get('title').value }}</h2>
</div>
<mat-card class="block-card">
<mat-card>
<button mat-raised-button color="primary" (click)="onFollowRecButton()" [disabled]="isFollowingProhibited()">
<mat-icon>done_all</mat-icon>
<span translate>Follow recommendations for all motions</span>
@ -115,8 +113,14 @@
<mat-icon>videocam</mat-icon>
<span translate>Project</span>
</button>
<mat-divider></mat-divider>
<button mat-menu-item (click)="toggleEditMode()">
<mat-icon>edit</mat-icon>
<span translate>Edit title</span>
</button>
<button mat-menu-item class="red-warning-text" (click)="onDeleteBlockButton()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>

View File

@ -5,7 +5,7 @@
<!-- Creating a new motion block -->
<mat-card class="os-card" *ngIf="blockToCreate">
<mat-card-title translate>Create new motion block</mat-card-title>
<mat-card-title translate>New motion block</mat-card-title>
<mat-card-content>
<form [formGroup]="createBlockForm"
(keydown)="onKeyDown($event)">
@ -60,19 +60,39 @@
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource" matSort>
<!-- title column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header> <span translate>Name</span> </mat-header-cell>
<mat-cell *matCellDef="let block"> {{ block.title }} </mat-cell>
<mat-header-cell *matHeaderCellDef mat-sort-header> <span translate>Title</span> </mat-header-cell>
<mat-cell *matCellDef="let block" (click)="openItem(block)"> {{ block.title }} </mat-cell>
</ng-container>
<!-- amount column -->
<ng-container matColumnDef="amount">
<mat-header-cell *matHeaderCellDef> <span translate>Motions</span> </mat-header-cell>
<mat-cell *matCellDef="let block">
<mat-cell *matCellDef="let block" (click)="openItem(block)">
<span class="os-amount-chip">{{ getMotionAmount(block.motionBlock) }}</span>
</mat-cell>
</ng-container>
<!-- menu -->
<ng-container matColumnDef="menu">
<mat-header-cell *matHeaderCellDef>Menu</mat-header-cell>
<mat-cell *matCellDef="let block">
<button mat-icon-button [matMenuTriggerFor]="singleItemMenu" [matMenuTriggerData]="{ item: block }">
<mat-icon>more_vert</mat-icon>
</button>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
<mat-row (click)="onSelectRow(row)" *matRowDef="let row; columns: getColumnDefinition()"> </mat-row>
<mat-row *matRowDef="let row; columns: getColumnDefinition()"> </mat-row>
</table>
</mat-card>
<mat-menu #singleItemMenu="matMenu">
<ng-template matMenuContent let-item="item">
<!-- Delete Button -->
<button mat-menu-item class="red-warning-text" (click)="onDelete(item)">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</ng-template>
</mat-menu>

View File

@ -1,11 +1,20 @@
.os-headed-listview-table {
// Title
.mat-column-title {
flex: 3 0 0;
flex: 9 0 0;
}
// Amount
.mat-column-amount {
flex: 1 0 0;
flex: 1 0 60px;
}
// Menu
.mat-column-menu {
flex: 0 0 40px;
}
}
::ng-deep .mat-form-field {
width: 50%;
}

View File

@ -14,6 +14,7 @@ import { DataStoreService } from 'app/core/services/data-store.service';
import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service';
import { ViewMotionBlock } from '../../models/view-motion-block';
import { AgendaRepositoryService } from 'app/site/agenda/services/agenda-repository.service';
import { PromptService } from '../../../../core/services/prompt.service';
/**
* Table for the motion blocks
@ -53,7 +54,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
* Constructor for the motion block list view
*
* @param titleService sets the title
* @param translate translations
* @param translate translpations
* @param matSnackBar display errors in the snack bar
* @param router routing to children
* @param route determine the local route
@ -61,6 +62,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
* @param agendaRepo the agenda repository service
* @param DS the dataStore
* @param formBuilder creates forms
* @param promptService the delete prompt
*/
public constructor(
titleService: Title,
@ -71,7 +73,8 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
private repo: MotionBlockRepositoryService,
private agendaRepo: AgendaRepositoryService,
private DS: DataStoreService,
private formBuilder: FormBuilder
private formBuilder: FormBuilder,
private promptService: PromptService
) {
super(titleService, translate, matSnackBar);
@ -98,6 +101,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
});
this.repo.getViewModelListObservable().subscribe(newMotionblocks => {
newMotionblocks.sort((a, b) => (a > b ? 1 : -1));
this.dataSource.data = newMotionblocks;
});
@ -110,7 +114,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
* @returns an array of strings building the column definition
*/
public getColumnDefinition(): string[] {
return ['title', 'amount'];
return ['title', 'amount', 'menu'];
}
/**
@ -118,7 +122,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
*
* @param block the given motion block
*/
public onSelectRow(block: ViewMotionBlock): void {
public openItem(block: ViewMotionBlock): void {
this.router.navigate([`${block.id}`], { relativeTo: this.route });
}
@ -132,6 +136,18 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
return this.repo.getMotionAmountByBlock(motionBlock);
}
/**
* Click handler to delete motion blocks
*
* @param motionBlock the block to delete
*/
public async onDelete(motionBlock: ViewMotionBlock): Promise<void> {
const content = this.translate.instant('Are you sure you want to delete this motion block?');
if (await this.promptService.open(motionBlock.title, content)) {
await this.repo.delete(motionBlock);
}
}
/**
* Helper function reset the form and set the default values
*/

View File

@ -60,23 +60,23 @@
<mat-menu #motionExtraMenu="matMenu">
<div *ngIf="motion">
<!-- PDF -->
<button mat-menu-item
(click)="onDownloadPdf()">
<mat-icon>picture_as_pdf</mat-icon>
<span translate>PDF</span>
</button>
<!-- List of speakers -->
<button mat-menu-item [routerLink]="getSpeakerLink()">
<mat-icon>mic</mat-icon>
<span translate>List of speakers</span>
</button>
<!-- Project -->
<button mat-menu-item>
<!-- possible icons: screen_share, cast, videocam -->
<mat-icon>videocam</mat-icon>
<span translate>Project</span>
</button>
<!-- New amendment -->
<button
mat-menu-item
(click)="createAmendment()"
@ -85,7 +85,7 @@
<mat-icon>add</mat-icon>
<span translate>New amendment</span>
</button>
<!-- Show entire motion text -->
<button
mat-menu-item
(click)="showAmendmentContext = !showAmendmentContext"
@ -97,6 +97,7 @@
<mat-divider></mat-divider>
<!-- Delete -->
<button mat-menu-item class="red-warning-text" (click)="deleteMotionButton()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
@ -105,59 +106,50 @@
</mat-menu>
</os-head-bar>
<!-- Title -->
<div class="title-left on-transition-fade" *ngIf="motion && !editMotion">
<div class="title-line">
<h1>
{{ motion.title }}
</h1>
<button mat-icon-button color="primary" (click)="toggleFavorite()">
<mat-icon>{{ motion.star ? 'star' : 'star_border' }}</mat-icon>
</button>
<div class="content-container">
<!-- Title -->
<div class="title on-transition-fade" *ngIf="motion && !editMotion">
<div class="title-line">
<h1>
{{ motion.title }}
</h1>
<button mat-icon-button color="primary" (click)="toggleFavorite()">
<mat-icon>{{ motion.star ? 'star' : 'star_border' }}</mat-icon>
</button>
</div>
<span class="main-nav-color title-font"><span translate>Sequential number</span>&nbsp;{{ motion.id }}</span>
</div>
<span class="main-nav-color title-font"><span translate>Sequential number</span>&nbsp;{{ motion.id }}</span>
<ng-container *ngIf="vp.isMobile; then mobileView; else desktopView"></ng-container>
</div>
<ng-container *ngIf="vp.isMobile; then mobileView; else desktopView"></ng-container>
<ng-template #mobileView>
<mat-accordion multi="true" class="on-transition-fade">
<!-- MetaInfo Panel-->
<mat-expansion-panel #metaInfoPanel [expanded]="true" class="meta-info-block meta-info-panel">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon>info</mat-icon>
<span translate>Meta information</span>
</mat-panel-title>
</mat-expansion-panel-header>
<!-- Meta info -->
<div class="expansion-panel-custom-body">
<ng-container *ngTemplateOutlet="metaInfoTemplate"></ng-container>
</div>
</mat-expansion-panel>
<!-- Meta info -->
<div class="hspacing">
<ng-container *ngTemplateOutlet="metaInfoTemplate"></ng-container>
</div>
<!-- Content -->
<mat-expansion-panel #contentPanel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon>format_align_left</mat-icon>
<span translate>Content</span>
</mat-panel-title>
</mat-expansion-panel-header>
<mat-divider class="spacer-top-10 spacer-bottom-20"></mat-divider>
<div class="expansion-panel-custom-body">
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>
</mat-expansion-panel>
<!-- Content -->
<div class="hspacing">
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>
<os-motion-comments *ngIf="!editMotion" [motion]="motion"></os-motion-comments>
<os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note>
<button mat-button *ngIf="canShowLog" (click)="motionLogExpanded =!motionLogExpanded">
<span translate>Show motion log</span>
</button>
<os-motion-log *ngIf="motionLogExpanded" [motion]="motion"></os-motion-log>
</mat-accordion>
<mat-divider class="spacer-top-10 spacer-bottom-20"></mat-divider>
<!-- Comments -->
<os-motion-comments *ngIf="!editMotion" [motion]="motion"></os-motion-comments>
<!-- Personal note -->
<os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note>
<!-- Motoin log -->
<button mat-button *ngIf="canShowLog" (click)="motionLogExpanded =!motionLogExpanded">
<span translate>Show motion log</span>
</button>
<os-motion-log *ngIf="motionLogExpanded" [motion]="motion"></os-motion-log>
</ng-template>
<ng-template #desktopView>
@ -287,7 +279,7 @@
<!-- Category -->
<!-- Disabled during "new motion" since changing has no effect -->
<div *ngIf="!editMotion">
<div *ngIf="!editMotion && categoryObserver.value.length > 0">
<h4 translate>Category</h4>
<mat-menu #categoryMenu="matMenu">
<button
@ -295,27 +287,26 @@
*ngFor="let category of categoryObserver.value"
(click)="setCategory(category.id)"
>
<mat-icon *ngIf="motion.category_id === category.id">check</mat-icon>
{{ category }}
</button>
<button mat-menu-item (click)="setCategory(null)">---</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]="categoryMenu" class="grey">
{{ motion.category ? motion.category : ('not set' | translate) }}
{{ motion.category ? motion.category : '' }}
</mat-basic-chip>
</div>
<!-- Block -->
<div *ngIf="!editMotion">
<div *ngIf="!editMotion && blockObserver.value.length > 0">
<h4 translate>Motion block</h4>
<mat-menu #blockMenu="matMenu">
<button mat-menu-item *ngFor="let block of blockObserver.value" (click)="setBlock(block.id)">
<mat-icon *ngIf="motion.motion_block_id === block.id">check</mat-icon>
{{ block }}
</button>
<button mat-menu-item (click)="setBlock(null)">---</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]="blockMenu" class="grey">
{{ motion.motion_block ? motion.motion_block : ('not set' | translate) }}
{{ motion.motion_block ? motion.motion_block : '' }}
</mat-basic-chip>
</div>
@ -326,7 +317,7 @@
</div>
<!-- motion polls -->
<div *ngIf="!editMotion">
<div *ngIf="!editMotion" class="spacer-top-20 spacer-bottom-20">
<os-motion-poll *ngFor="let poll of motion.motion.polls; let i = index" [rawPoll]="poll" [pollIndex]="i">
</os-motion-poll>
<div class="create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">

View File

@ -107,7 +107,6 @@ span {
.desktop-left {
width: 30%;
float: left;
padding-left: 65px;
padding-right: 25px;
}

View File

@ -923,7 +923,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
* @param id Motion category id
*/
public setCategory(id: number): void {
this.repo.setCatetory(this.motion, id);
if (id === this.motion.category_id) {
this.repo.setCatetory(this.motion, null);
} else {
this.repo.setCatetory(this.motion, id);
}
}
/**
@ -932,7 +936,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
* @param id Motion block id
*/
public setBlock(id: number): void {
this.repo.setBlock(this.motion, id);
if (id === this.motion.motion_block_id) {
this.repo.setBlock(this.motion, null);
} else {
this.repo.setBlock(this.motion, id);
}
}
/**

View File

@ -114,12 +114,10 @@ export class SiteComponent extends BaseComponent implements OnInit {
}
/**
* Closes the sidenav in mobile view
* Closes the sidenav
*/
public toggleSideNav(): void {
if (this.vp.isMobile) {
this.sideNav.toggle();
}
this.sideNav.toggle();
}
/**

View File

@ -318,6 +318,13 @@ mat-panel-title mat-icon {
margin: 8px 8px 8px 0;
}
.mat-chip-list.user .mat-chip {
border-radius: 16px !important;
padding: 15px !important;
border: solid 1px lightgray;
background: #fff;
}
// to display quantities. Use in span or div
.os-amount-chip {
border-radius: 50%;
@ -338,6 +345,10 @@ button.mat-menu-item.selected {
font-weight: bold !important;
}
.mat-menu-item .mat-icon {
margin-right: 8px !important;
}
.meta-text-block .mat-icon-button {
margin-top: -12px !important;
}
@ -347,13 +358,16 @@ button.mat-menu-item.selected {
/** helper classes for margin/padding */
.spacer-top-10 {
margin-top: 10px;
margin-top: 10px !important;
}
.spacer-top-20 {
margin-top: 20px;
margin-top: 20px !important;
}
.spacer-bottom-10 {
margin-bottom: 10px !important;
}
.spacer-bottom-20 {
margin-bottom: 20px;
margin-bottom: 20px !important;
}
.button24 {
background-color: white;
@ -378,8 +392,7 @@ button.mat-menu-item.selected {
line-height: 24px;
}
}
.title-left {
padding-left: 65px;
.title {
padding-top: 20px;
padding-bottom: 20px;
}
@ -387,6 +400,32 @@ button.mat-menu-item.selected {
padding-right: 20px;
}
.content-container {
margin: 0 65px;
}
/** media queries */
/* medium */
@media only screen and (min-width: 500px) and (max-width: 960px) {
.content-container {
margin: 0 25px;
}
.content-container h1 {
font-size: 30px
}
}
/* small */
@media only screen and (max-width: 500px) {
.content-container {
margin: 0 15px;
}
.content-container h1 {
font-size: 30px
}
}
/** more helper classes **/
.center {
text-align: center;