Merge pull request #4950 from GabrielInTheWorld/config-datetimepicker
Adds a picker for date and time in config
This commit is contained in:
commit
53dbcca85a
@ -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",
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -66,16 +66,36 @@
|
||||
|
||||
<!-- datetimepicker -->
|
||||
<div *ngIf="configItem.inputType === 'datetimepicker'">
|
||||
<div class="datetimepicker-container">
|
||||
<mat-form-field>
|
||||
<mat-label>{{ configItem.label | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="dateValue"
|
||||
formControlName="date"
|
||||
[matDatepicker]="datepicker"
|
||||
(click)="datepicker.open()"
|
||||
/>
|
||||
|
||||
<mat-label>{{ configItem.label | translate }}</mat-label>
|
||||
<span matSuffix>
|
||||
<mat-icon pull="right" class="text-success" *ngIf="updateSuccessIcon">check_circle</mat-icon>
|
||||
</span>
|
||||
<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 -->
|
||||
|
@ -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;
|
||||
|
@ -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,6 +66,11 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
|
||||
this.configItem = value;
|
||||
|
||||
if (this.form) {
|
||||
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
|
||||
@ -79,6 +80,7 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The form for this configItem.
|
||||
@ -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?
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user