Merge pull request #4950 from GabrielInTheWorld/config-datetimepicker

Adds a picker for date and time in config
This commit is contained in:
Sean 2019-08-29 11:43:50 +02:00 committed by GitHub
commit 53dbcca85a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 71 deletions

View File

@ -37,6 +37,7 @@
"@angular/core": "^8.0.3",
"@angular/forms": "^8.0.3",
"@angular/material": "^8.0.1",
"@angular/material-moment-adapter": "^8.1.2",
"@angular/platform-browser": "^8.0.3",
"@angular/platform-browser-dynamic": "^8.0.3",
"@angular/pwa": "^0.800.6",
@ -57,9 +58,11 @@
"hammerjs": "^2.0.8",
"lz4js": "^0.2.0",
"material-icon-font": "git+https://github.com/petergng/materialIconFont.git",
"moment": "^2.24.0",
"ng2-pdf-viewer": "^5.2.3",
"ngx-file-drop": "^8.0.3",
"ngx-mat-select-search": "^1.7.2",
"ngx-material-timepicker": "^4.0.2",
"ngx-papaparse": "^3.0.2",
"pdfmake": "^0.1.53",
"po2json": "^1.0.0-alpha",

View File

@ -100,6 +100,7 @@ export class AppComponent {
const browserLang = translate.getBrowserLang();
// try to use the browser language if it is available. If not, uses english.
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
// change default JS functions
this.overloadArrayToString();
this.overloadFlatMap();

View File

@ -1,29 +1,30 @@
import { Injectable } from '@angular/core';
import { NativeDateAdapter } from '@angular/material/core';
import { Inject, Injectable, Optional } from '@angular/core';
import { MAT_DATE_LOCALE } from '@angular/material';
import {
MAT_MOMENT_DATE_ADAPTER_OPTIONS,
MatMomentDateAdapterOptions,
MomentDateAdapter
} from '@angular/material-moment-adapter';
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
/**
* A custom DateAdapter for the datetimepicker in the config. This is still not fully working and needs to be done later.
* See comments in PR #3895.
* A custom DateAdapter for the datetimepicker in the config. Uses MomentDateAdapter for localisation.
* Is needed to subscribe to language changes
*/
@Injectable()
export class OpenSlidesDateAdapter extends NativeDateAdapter {
public format(date: Date, displayFormat: Object): string {
if (displayFormat === 'input') {
return this.toFullIso8601(date);
} else {
return date.toDateString();
}
}
private to2digit(n: number): string {
return ('00' + n).slice(-2);
}
public toFullIso8601(date: Date): string {
return (
[date.getUTCFullYear(), this.to2digit(date.getUTCMonth() + 1), this.to2digit(date.getUTCDate())].join('-') +
'T' +
[this.to2digit(date.getUTCHours()), this.to2digit(date.getUTCMinutes())].join(':')
);
export class OpenSlidesDateAdapter extends MomentDateAdapter {
public constructor(
translate: TranslateService,
@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string,
@Optional() @Inject(MAT_MOMENT_DATE_ADAPTER_OPTIONS) _options?: MatMomentDateAdapterOptions
) {
super(dateLocale, _options);
// subscribe to language changes to change localisation of dates accordingly
// DateAdapter seems not to be a singleton so we do that in this subclass instead of app.component
this.setLocale(translate.currentLang);
translate.onLangChange.subscribe((e: LangChangeEvent) => {
this.setLocale(e.lang);
});
}
}

View File

@ -10,7 +10,8 @@ import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatNativeDateModule, DateAdapter } from '@angular/material/core';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MatMomentDateModule, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
@ -42,7 +43,7 @@ import { CdkTreeModule } from '@angular/cdk/tree';
import { ScrollingModule } from '@angular/cdk/scrolling';
// ngx-translate
import { TranslateModule } from '@ngx-translate/core';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
// ngx-file-drop
import { NgxFileDropModule } from 'ngx-file-drop';
@ -61,6 +62,9 @@ import { PblNgridModule } from '@pebula/ngrid';
import { PblNgridMaterialModule } from '@pebula/ngrid-material';
import { PblNgridTargetEventsModule } from '@pebula/ngrid/target-events';
// time picker because angular still doesnt offer one!!
import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker';
// components
import { HeadBarComponent } from './components/head-bar/head-bar.component';
import { LegalNoticeContentComponent } from './components/legal-notice-content/legal-notice-content.component';
@ -125,7 +129,7 @@ import { HeightResizingDirective } from './directives/height-resizing.directive'
MatCheckboxModule,
MatToolbarModule,
MatDatepickerModule,
MatNativeDateModule,
MatMomentDateModule,
MatCardModule,
MatInputModule,
MatTableModule,
@ -164,7 +168,8 @@ import { HeightResizingDirective } from './directives/height-resizing.directive'
PblNgridModule,
PblNgridMaterialModule,
PblNgridTargetEventsModule,
PdfViewerModule
PdfViewerModule,
NgxMaterialTimepickerModule
],
exports: [
FormsModule,
@ -248,7 +253,8 @@ import { HeightResizingDirective } from './directives/height-resizing.directive'
RoundedInputComponent,
GlobalSpinnerComponent,
OverlayComponent,
PreviewComponent
PreviewComponent,
NgxMaterialTimepickerModule
],
declarations: [
PermsDirective,
@ -296,7 +302,11 @@ import { HeightResizingDirective } from './directives/height-resizing.directive'
HeightResizingDirective
],
providers: [
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
{
provide: DateAdapter,
useClass: OpenSlidesDateAdapter,
deps: [TranslateService, MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS]
}, // see remarks in OpenSlidesDateAdapter
SearchValueSelectorComponent,
SortingListComponent,
SortingTreeComponent,

View File

@ -66,16 +66,36 @@
<!-- datetimepicker -->
<div *ngIf="configItem.inputType === 'datetimepicker'">
<input
matInput
[value]="dateValue"
/>
<mat-label>{{ configItem.label | translate }}</mat-label>
<span matSuffix>
<mat-icon pull="right" class="text-success" *ngIf="updateSuccessIcon">check_circle</mat-icon>
</span>
<mat-error *ngIf="error"> {{ error }} </mat-error>
<div class="datetimepicker-container">
<mat-form-field>
<mat-label>{{ configItem.label | translate }}</mat-label>
<input
matInput
formControlName="date"
[matDatepicker]="datepicker"
(click)="datepicker.open()"
/>
<mat-hint *ngIf="configItem.helpText">{{ configItem.helpText | translate }}</mat-hint>
<div class="suffix-wrapper" matSuffix>
<mat-icon class="text-success" *ngIf="updateSuccessIcon">check_circle</mat-icon>
<mat-datepicker-toggle
[for]="datepicker"
(click)="$event.preventDefault()"
></mat-datepicker-toggle>
</div>
<mat-error *ngIf="error"> {{ error }} </mat-error>
<mat-datepicker #datepicker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<input matInput [format]="24" formControlName="time" [ngxTimepicker]="timepicker" />
<div class="suffix-wrapper" matSuffix>
<mat-icon class="text-success" *ngIf="updateSuccessIcon">check_circle</mat-icon>
<ngx-material-timepicker-toggle [for]="timepicker"></ngx-material-timepicker-toggle>
</div>
<mat-error *ngIf="error"> {{ error }} </mat-error>
<ngx-material-timepicker #timepicker></ngx-material-timepicker>
</mat-form-field>
</div>
</div>
<!-- The editor -->

View File

@ -3,6 +3,35 @@
width: 100%;
}
.datetimepicker-container {
margin-left: -20px;
.mat-form-field {
width: 50%;
box-sizing: border-box;
padding-left: 20px;
.suffix-wrapper {
display: flex;
align-items: center;
.mat-datepicker-toggle {
padding-left: 4px;
padding-right: 4px;
.mat-datepicker-toggle-default-icon {
width: 20px;
margin-bottom: 10px;
}
}
.ngx-material-timepicker-toggle {
width: 28px;
}
}
}
}
/* limit the color bar of color inputs */
input[type='color'] {
max-width: 100px;

View File

@ -1,8 +1,10 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment';
import { Moment } from 'moment';
import { distinctUntilChanged } from 'rxjs/operators';
import { BaseComponent } from 'app/base.component';
@ -12,7 +14,7 @@ import { ViewConfig } from '../../models/view-config';
/**
* Component for a config field, used by the {@link ConfigListComponent}. Handles
* all inpu types defined by the server, as well as updating the configs
* all input types defined by the server, as well as updating the configs
*
* @example
* ```ts
@ -23,16 +25,12 @@ import { ViewConfig } from '../../models/view-config';
selector: 'os-config-field',
templateUrl: './config-field.component.html',
styleUrls: ['./config-field.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None // to style the date and time pickers
})
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.
*/
@ -58,8 +56,6 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
*/
public translatedValue: object;
public rawDate: Date;
/**
* The config item for this component. Just accepts components with already
* populated constants-info.
@ -70,12 +66,18 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
this.configItem = value;
if (this.form) {
this.form.patchValue(
{
value: this.configItem.value
},
{ emitEvent: false }
);
if (this.configItem.inputType === 'datetimepicker') {
// datetime has to be converted
const datetimeObj = this.unixToDateAndTime(this.configItem.value as number);
this.form.patchValue(datetimeObj, { emitEvent: false });
} else {
this.form.patchValue(
{
value: this.configItem.value
},
{ emitEvent: false }
);
}
}
}
}
@ -99,7 +101,6 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
* @param formBuilder FormBuilder
* @param cd ChangeDetectorRef
* @param repo ConfigRepositoryService
* @param dateTimeAdapter DateTimeAdapter
*/
public constructor(
protected titleService: Title,
@ -116,7 +117,9 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
*/
public ngOnInit(): void {
this.form = this.formBuilder.group({
value: ['']
value: [''],
date: [''],
time: ['']
});
this.translatedValue = this.configItem.value;
if (
@ -128,8 +131,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);
if (this.configItem.inputType === 'datetimepicker' && this.configItem.value) {
const datetimeObj = this.unixToDateAndTime(this.configItem.value as number);
this.form.patchValue(datetimeObj);
}
this.form.patchValue({
value: this.translatedValue
@ -143,6 +147,41 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
});
}
/**
* Helper function to split a unix timestamp into a date as a moment object and a time string in the form of HH:SS
*
* @param unix the timestamp
*
* @return an object with a date and a time field
*/
private unixToDateAndTime(unix: number): { date: Moment; time: string } {
const date = moment.unix(unix);
const time = date.hours() + ':' + date.minutes();
return { date: date, time: time };
}
/**
* Helper function to fuse a moment object as the date part and a time string (HH:SS) as the time part.
*
* @param date the moment date object
* @param time the time string
*
* @return a unix timestamp
*/
private dateAndTimeToUnix(date: Moment, time: string): number {
if (date) {
if (time) {
const timeSplit = time.split(':');
// + is faster than parseint and number(). ~~ would be fastest but prevented by linter...
date.hour(+timeSplit[0]);
date.minute(+timeSplit[1]);
}
return date.unix();
} else {
return null;
}
}
/**
* Trigger an update of the data
*/
@ -152,7 +191,10 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
return;
}
if (this.configItem.inputType === 'datetimepicker') {
this.dateValue = new Date(value as number);
// datetime has to be converted
const date = this.form.get('date').value;
const time = this.form.get('time').value;
value = this.dateAndTimeToUnix(date, time);
}
if (this.debounceTimeout !== null) {
clearTimeout(<any>this.debounceTimeout);
@ -214,6 +256,8 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
/**
* Sets the error on this field.
*
* @param error The error as string.
*/
private setError(error: string): void {
this.error = error;
@ -226,6 +270,7 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
* input, textarea, choice or date
*
* @param type: the type of a config item
*
* @returns the template type
*/
public formType(type: string): string {
@ -243,6 +288,7 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
* Checks of the config.type can be part of the form
*
* @param type the config.type of a setting
*
* @returns wheather it should be excluded or not
*/
public isExcludedType(type: string): boolean {
@ -250,17 +296,6 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
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?

View File

@ -36,6 +36,7 @@ def get_config_variables():
yield ConfigVariable(
name="general_event_date",
default_value="",
input_type="datetimepicker",
label="Event date",
weight=120,
group="General",