Merge pull request #4216 from MaximilianKrambach/duration

Estimated duration, and datetimepicker in config
This commit is contained in:
Emanuel Schütze 2019-02-05 14:49:58 +01:00 committed by GitHub
commit 4395b86b9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 146 additions and 31 deletions

View File

@ -46,6 +46,7 @@
"css-element-queries": "^1.1.1",
"file-saver": "^2.0.0",
"material-design-icons": "^3.0.1",
"ng-pick-datetime": "^7.0.0",
"ngx-file-drop": "^5.0.2",
"ngx-mat-select-search": "^1.5.2",
"ngx-papaparse": "^3.0.2",

View File

@ -266,4 +266,31 @@ export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> {
})
);
}
/**
* Calculates the estimated end time based on the configured start and the
* sum of durations of all agenda items
*
* @returns a Date object
*/
public calculateEndTime(): Date {
const startTime = this.config.instant<number>('agenda_start_event_date_time'); // a timestamp
const durationTime = this.calculateDuration() * 60 * 1000; // minutes to miliseconds
return new Date(startTime + durationTime);
}
/**
* get the sum of durations of all agenda items
*
* @returns a numerical value representing item durations (currently minutes)
*/
public calculateDuration(): number {
let duration = 0;
this.getViewModelList().forEach(item => {
if (item.duration) {
duration += item.duration;
}
});
return duration;
}
}

View File

@ -2,6 +2,7 @@
<div class="filter-count" *ngIf="filterService">
<span>{{ displayedCount }}&nbsp;</span><span translate>of</span>
<span>&nbsp;{{ filterService.totalCount }}</span>
<span *ngIf="extraItemInfo">&nbsp;·&nbsp;{{ extraItemInfo }}</span>
</div>
<div class="current-filters" *ngIf="filterService && filterService.activeFilterCount">
<div><span translate>Active filters</span>:&nbsp;</div>

View File

@ -15,10 +15,13 @@ span.right-with-margin {
}
.filter-count {
text-align: right;
font-style: italic;
margin-right: 10px;
margin-left: 10px;
min-width: 50px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.current-filters {

View File

@ -49,6 +49,12 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
@Input()
public filterService: any; // TODO a FilterListService extending FilterListService
/**
* optional additional string to show after the item count. This string will not be translated here
*/
@Input()
public extraItemInfo: string;
@Output()
public searchFieldChange = new EventEmitter<string>();
/**

View File

@ -33,7 +33,7 @@ export class Item extends BaseModel<Item> {
public closed: boolean;
public type: number;
public is_hidden: boolean;
public duration: number;
public duration: number; // minutes
public speakers: Speaker[];
public speaker_list_closed: boolean;
public content_object: ContentObject;

View File

@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime';
// MaterialUI modules
import {
@ -130,6 +131,8 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont
MatStepperModule,
MatTabsModule,
MatSliderModule,
OwlDateTimeModule,
OwlNativeDateTimeModule,
DragDropModule,
OpenSlidesTranslateModule.forChild(),
RouterModule,
@ -196,7 +199,9 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont
ResizedDirective,
MetaTextBlockComponent,
ProjectorComponent,
SlideContainerComponent
SlideContainerComponent,
OwlDateTimeModule,
OwlNativeDateTimeModule
],
declarations: [
PermsDirective,

View File

@ -13,7 +13,12 @@
</div>
</os-head-bar>
<mat-drawer-container class="on-transition-fade">
<os-sort-filter-bar [filterCount]="filteredCount" [filterService]="filterService" (searchFieldChange)="searchFilter($event)"></os-sort-filter-bar>
<os-sort-filter-bar
[filterCount]="filteredCount"
[extraItemInfo]="getDurationEndString()"
[filterService]="filterService"
(searchFieldChange)="searchFilter($event)"
></os-sort-filter-bar>
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- selector column -->
<ng-container matColumnDef="selector">
@ -28,7 +33,7 @@
<mat-header-cell *matHeaderCellDef mat-sort-header>Topic</mat-header-cell>
<!-- <mat-cell (click)="onTitleColumn(item)" *matCellDef="let item"> -->
<mat-cell (click)="selectItem(item, $event)" *matCellDef="let item">
<div [ngStyle]="{'margin-left': item.agendaListLevel * 25 + 'px' }">
<div [ngStyle]="{ 'margin-left': item.agendaListLevel * 25 + 'px' }">
<span *ngIf="item.closed"> <mat-icon class="done-check">check</mat-icon> </span>
<span class="table-view-list-title">{{ item.getListTitle() }}</span>
</div>

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { MatSnackBar, MatDialog } from '@angular/material';
import { Router, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { MatSnackBar, MatDialog } from '@angular/material';
import { TranslateService } from '@ngx-translate/core';
import { AgendaFilterListService } from '../../services/agenda-filter-list.service';
@ -125,6 +125,8 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
if (result) {
if (result.durationText) {
result.duration = this.durationService.stringToDuration(result.durationText);
} else {
result.duration = 0;
}
this.repo.update(result, item);
}
@ -242,4 +244,22 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
const filename = this.translate.instant('Agenda');
this.pdfService.download(this.agendaPdfService.agendaListToDocDef(this.dataSource.filteredData), filename);
}
/**
* Get the calculated end date and time
*
* @returns a readable string with end date and time in the current languages' convention
*/
public getDurationEndString(): string {
const duration = this.repo.calculateDuration();
if (!duration) {
return '';
}
const durationString = this.durationService.durationToString(duration);
const endTimeString = this.repo
.calculateEndTime()
.toLocaleTimeString(this.translate.currentLang, { hour: 'numeric', minute: 'numeric' });
return `${this.translate.instant('Duration')}: ${durationString} (${this.translate.instant('Estimated end')}:
${endTimeString} h)`;
}
}

View File

@ -4,9 +4,6 @@
<mat-form-field *ngIf="!isExcludedType(configItem.inputType)">
<!-- Decides which input-type to take (i.e) date, select, input) -->
<ng-container [ngSwitch]="configItem.inputType">
<ng-container *ngSwitchCase="'datetimepicker'">
<ng-container *ngTemplateOutlet="date"></ng-container>
</ng-container>
<ng-container *ngSwitchCase="'choice'">
<ng-container *ngTemplateOutlet="select"></ng-container>
</ng-container>
@ -24,11 +21,6 @@
<mat-error *ngIf="error"> {{ error }} </mat-error>
<!-- templates for exchangeable inputs. Add more here if necessary -->
<ng-template #date ngProjectAs="[matInput]">
<input matInput formControlName="value" [matDatepicker]="picker" [errorStateMatcher]="matcher" />
<mat-datepicker-toggle matPrefix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</ng-template>
<ng-template #select ngProjectAs="mat-select">
<mat-select formControlName="value" [errorStateMatcher]="matcher">
@ -64,6 +56,23 @@
</mat-form-field>
</div>
<!-- datetimepicker -->
<div *ngIf="configItem.inputType === 'datetimepicker'">
<input
[owlDateTime]="dt1"
[owlDateTimeTrigger]="dt1"
(dateTimeChange)="updateTime($event)"
[value]="dateValue"
/>
<owl-date-time #dt1></owl-date-time>
<mat-label>{{ configItem.label | translate }}</mat-label>
<mat-hint *ngIf="configItem.helpText">{{ configItem.helpText | translate }}</mat-hint>
<span matSuffix>
<mat-icon pull="right" class="text-success" *ngIf="updateSuccessIcon">check_circle</mat-icon>
</span>
<mat-error *ngIf="error"> {{ error }} </mat-error>
</div>
<!-- The editor -->
<div *ngIf="configItem.inputType === 'markupText'">
<h4>{{ configItem.label | translate }}</h4>
@ -78,8 +87,7 @@
</form>
</div>
<div class="reset-button">
<button mat-icon-button *ngIf="configItem.defaultValue !== undefined" matTooltip="{{ 'Reset' | translate }}"
(click)="onResetButton()">
<button mat-icon-button *ngIf="hasDefault()" matTooltip="{{ 'Reset' | translate }}" (click)="onResetButton()">
<mat-icon>replay</mat-icon>
</button>
</div>

View File

@ -1,14 +1,15 @@
import { Component, OnInit, Input, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Title } from '@angular/platform-browser';
import { distinctUntilChanged } from 'rxjs/operators';
import { DateTimeAdapter } from 'ng-pick-datetime';
import { TranslateService } from '@ngx-translate/core';
import { ViewConfig } from '../../models/view-config';
import { BaseComponent } from '../../../../base.component';
import { FormGroup, FormBuilder } from '@angular/forms';
import { ConfigRepositoryService } from '../../services/config-repository.service';
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
import { ViewConfig } from '../../models/view-config';
/**
* List view for the categories.
@ -24,6 +25,11 @@ import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
export class ConfigFieldComponent extends BaseComponent implements OnInit {
public configItem: ViewConfig;
/**
* Date representation od the config value, used by the datetimepicker
*/
public dateValue: Date;
/**
* Option to show a green check-icon.
*/
@ -49,6 +55,8 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
*/
public translatedValue: object;
public rawDate: Date;
/**
* The config item for this component. Just accept components with already populated constants-info.
*/
@ -79,18 +87,26 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
public matcher = new ParentErrorStateMatcher();
/**
* The usual component constructor
* @param titleService
* @param translate
* The usual component constructor. datetime pickers will set their locale
* to the current language chosen
*
* @param titleService Title
* @param translate TranslateService
* @param formBuilder FormBuilder
* @param cdRef ChangeDetectorRef
* @param repo ConfigRepositoryService
* @param dateTimeAdapter DateTimeAdapter
*/
public constructor(
protected titleService: Title,
protected translate: TranslateService,
private formBuilder: FormBuilder,
private cdRef: ChangeDetectorRef,
public repo: ConfigRepositoryService
public repo: ConfigRepositoryService,
dateTimeAdapter: DateTimeAdapter<any>
) {
super(titleService, translate);
dateTimeAdapter.setLocale(this.translate.currentLang);
}
/**
@ -100,7 +116,6 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
this.form = this.formBuilder.group({
value: ['']
});
this.translatedValue = this.configItem.value;
if (
this.configItem.inputType === 'string' ||
@ -111,7 +126,9 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
this.translatedValue = this.translate.instant(this.configItem.value);
}
}
if (this.configItem.inputType === 'datetimepicker') {
this.dateValue = new Date(this.configItem.value as number);
}
this.form.patchValue({
value: this.translatedValue
});
@ -128,6 +145,9 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
* Trigger an update of the data
*/
private onChange(value: any): void {
if (this.configItem.inputType === 'datetimepicker') {
this.dateValue = new Date(value as number);
}
if (this.debounceTimeout !== null) {
clearTimeout(<any>this.debounceTimeout);
}
@ -147,15 +167,10 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
}
/**
* Updates the config field.
* Sends an update request for the config item to the server.
* @param value The new value to set.
*/
private update(value: any): void {
// TODO: Fix the Datetimepicker parser and formatter.
if (this.configItem.inputType === 'datetimepicker') {
value = Date.parse(value);
}
this.debounceTimeout = null;
this.repo.update({ value: value }, this.configItem).then(() => {
this.error = null;
@ -212,7 +227,28 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
* @returns wheather it should be excluded or not
*/
public isExcludedType(type: string): boolean {
const excluded = ['boolean', 'markupText', 'text', 'translations'];
const excluded = ['boolean', 'markupText', 'text', 'translations', 'datetimepicker'];
return excluded.includes(type);
}
/**
* custom handler for datetime picker updates. Sets the form's value
* to the timestamp of the Date being the event's value
*
* @param event an event-like object with a Date as value property
*/
public updateTime(event: { value: Date }): void {
this.dateValue = event.value;
this.onChange(event.value.valueOf());
}
/**
* Determines if a reset buton should be offered.
* TODO: is 'null' a valid default in some cases?
*
* @returns true if any default exists
*/
public hasDefault(): boolean {
return this.configItem.defaultValue !== undefined && this.configItem.defaultValue !== null;
}
}

View File

@ -23,6 +23,9 @@
@import '~angular-tree-component/dist/angular-tree-component.css';
/** date-time-picker */
@import "~ng-pick-datetime/assets/style/picker.min.css";
/** Define the classes to switch between themes */
.openslides-theme {
@include angular-material-theme($openslides-theme);