parent
2ae17f10fb
commit
764ffe28b5
@ -0,0 +1 @@ |
||||
export const rangeSeparator = '-'; |
@ -1,6 +1,6 @@ |
||||
<op-single-date-picker |
||||
<!--op-single-date-picker |
||||
[required]="to.required" |
||||
[disabled]="to.disabled" |
||||
[formControl]="formControl" |
||||
[formlyAttributes]="field" |
||||
></op-single-date-picker> |
||||
></op-single-date-picker--> |
||||
|
@ -1,127 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2022 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { |
||||
AfterViewInit, |
||||
ChangeDetectorRef, |
||||
Directive, |
||||
ElementRef, |
||||
EventEmitter, |
||||
Injector, |
||||
Input, |
||||
NgZone, |
||||
OnDestroy, |
||||
Output, |
||||
ViewChild, |
||||
} from '@angular/core'; |
||||
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; |
||||
import { TimezoneService } from 'core-app/core/datetime/timezone.service'; |
||||
import { DatePicker } from 'core-app/shared/components/op-date-picker/datepicker'; |
||||
import { ConfigurationService } from 'core-app/core/config/configuration.service'; |
||||
|
||||
@Directive() |
||||
export abstract class AbstractDatePickerDirective extends UntilDestroyedMixin implements OnDestroy, AfterViewInit { |
||||
@Output() public canceled = new EventEmitter<string>(); |
||||
|
||||
@Input() public appendTo?:HTMLElement; |
||||
|
||||
@Input() public classes = ''; |
||||
|
||||
@Input() public id = ''; |
||||
|
||||
@Input() public name = ''; |
||||
|
||||
@Input() public required = false; |
||||
|
||||
@Input() public size = 20; |
||||
|
||||
@Input() public disabled = false; |
||||
|
||||
@ViewChild('dateInput') dateInput:ElementRef; |
||||
|
||||
protected datePickerInstance:DatePicker; |
||||
|
||||
public constructor( |
||||
readonly injector:Injector, |
||||
protected timezoneService:TimezoneService, |
||||
protected configurationService:ConfigurationService, |
||||
protected ngZone:NgZone, |
||||
protected changeDetectorRef:ChangeDetectorRef, |
||||
) { |
||||
super(); |
||||
|
||||
if (!this.id) { |
||||
this.id = `datepicker-input-${Math.floor(Math.random() * 1000).toString(3)}`; |
||||
} |
||||
} |
||||
|
||||
ngAfterViewInit():void { |
||||
this.initializeDatepicker(); |
||||
} |
||||
|
||||
ngOnDestroy():void { |
||||
if (this.datePickerInstance) { |
||||
this.datePickerInstance.destroy(); |
||||
} |
||||
} |
||||
|
||||
openOnClick():void { |
||||
if (!this.disabled) { |
||||
this.datePickerInstance.show(); |
||||
} |
||||
} |
||||
|
||||
closeOnOutsideClick(event:MouseEvent):void { |
||||
if (this.isOutsideClick(event)) { |
||||
this.close(); |
||||
} |
||||
} |
||||
|
||||
isOutsideClick(event:MouseEvent):boolean { |
||||
return (!(event.relatedTarget |
||||
&& this.datePickerInstance.datepickerInstance.calendarContainer.contains(event.relatedTarget as HTMLElement))); |
||||
} |
||||
|
||||
close():void { |
||||
this.datePickerInstance.hide(); |
||||
} |
||||
|
||||
protected isEmpty():boolean { |
||||
return this.currentValue.trim() === ''; |
||||
} |
||||
|
||||
protected get currentValue():string { |
||||
return this.inputElement?.value || ''; |
||||
} |
||||
|
||||
protected get inputElement():HTMLInputElement { |
||||
return this.dateInput.nativeElement as HTMLInputElement; |
||||
} |
||||
|
||||
protected abstract initializeDatepicker():void; |
||||
} |
@ -1,21 +0,0 @@ |
||||
import { NgModule } from '@angular/core'; |
||||
import { CommonModule } from '@angular/common'; |
||||
import { OpSpotModule } from 'core-app/spot/spot.module'; |
||||
import { OpRangeDatePickerComponent } from 'core-app/shared/components/op-date-picker/op-range-date-picker/op-range-date-picker.component'; |
||||
import { OpSingleDatePickerComponent } from 'core-app/shared/components/op-date-picker/op-single-date-picker/op-single-date-picker.component'; |
||||
|
||||
@NgModule({ |
||||
imports: [ |
||||
CommonModule, |
||||
OpSpotModule, |
||||
], |
||||
declarations: [ |
||||
OpSingleDatePickerComponent, |
||||
OpRangeDatePickerComponent, |
||||
], |
||||
exports: [ |
||||
OpSingleDatePickerComponent, |
||||
OpRangeDatePickerComponent, |
||||
], |
||||
}) |
||||
export class DatePickerModule { } |
@ -1,15 +0,0 @@ |
||||
<input |
||||
#dateInput |
||||
[id]="id" |
||||
[name]="name" |
||||
[value]="initialValue" |
||||
[ngClass]="classes + ' op-input'" |
||||
[size]="size" |
||||
[required]="required" |
||||
[disabled]="disabled" |
||||
(click)="openOnClick()" |
||||
(keydown.escape)="close()" |
||||
(keydown)="onKeyDown()" |
||||
(blur)="closeOnOutsideClick($event)" |
||||
type="text" |
||||
> |
@ -1,84 +0,0 @@ |
||||
import { |
||||
ChangeDetectionStrategy, |
||||
Component, |
||||
Input, |
||||
Output, |
||||
} from '@angular/core'; |
||||
import { Instance } from 'flatpickr/dist/types/instance'; |
||||
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum'; |
||||
import { DatePicker } from 'core-app/shared/components/op-date-picker/datepicker'; |
||||
import { AbstractDatePickerDirective } from 'core-app/shared/components/op-date-picker/date-picker.directive'; |
||||
import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; |
||||
import { componentDestroyed } from '@w11k/ngx-componentdestroyed'; |
||||
|
||||
export const rangeSeparator = '-'; |
||||
|
||||
@Component({ |
||||
selector: 'op-range-date-picker', |
||||
templateUrl: './op-range-date-picker.component.html', |
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
}) |
||||
export class OpRangeDatePickerComponent extends AbstractDatePickerDirective { |
||||
@Output() public changed = new DebouncedEventEmitter<string[]>(componentDestroyed(this)); |
||||
|
||||
@Input() public initialDates:string[] = []; |
||||
|
||||
initialValue = ''; |
||||
|
||||
protected initializeDatepicker():void { |
||||
this.initialDates = this.initialDates || []; |
||||
this.initialValue = this.resolveDateArrayToString(this.initialDates); |
||||
|
||||
const options = { |
||||
allowInput: true, |
||||
appendTo: this.appendTo, |
||||
mode: 'range' as const, |
||||
onChange: (selectedDates:Date[], dateStr:string) => { |
||||
if (this.isEmpty()) { |
||||
return; |
||||
} |
||||
|
||||
this.inputElement.value = dateStr; |
||||
if (selectedDates.length === 2) { |
||||
this.changed.emit(this.resolveDateStringToArray(dateStr)); |
||||
} |
||||
}, |
||||
onKeyDown: (selectedDates:Date[], dateStr:string, instance:Instance, data:KeyboardEvent) => { |
||||
if (data.which === KeyCodes.ESCAPE) { |
||||
this.canceled.emit(); |
||||
} |
||||
}, |
||||
}; |
||||
|
||||
let initialValue; |
||||
if (this.isEmpty() && this.initialDates.length > 0) { |
||||
initialValue = this.initialDates.map((date) => this.timezoneService.parseISODate(date).toDate()); |
||||
} else { |
||||
initialValue = this.resolveDateStringToArray(this.currentValue); |
||||
} |
||||
|
||||
this.datePickerInstance = new DatePicker( |
||||
this.injector, |
||||
`#${this.id}`, |
||||
initialValue, |
||||
options, |
||||
null, |
||||
); |
||||
} |
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onKeyDown():boolean { |
||||
// Disable any manual user input as it most likely return in a wrong format
|
||||
return false; |
||||
} |
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
private resolveDateStringToArray(dates:string):string[] { |
||||
return dates.split(` ${rangeSeparator} `).map((date) => date.trim()); |
||||
} |
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
private resolveDateArrayToString(dates:string[]):string { |
||||
return dates.join(` ${rangeSeparator} `); |
||||
} |
||||
} |
@ -1,36 +0,0 @@ |
||||
<spot-drop-modal |
||||
[open]="isOpen" |
||||
(closed)="close()" |
||||
> |
||||
<!--input |
||||
#dateInput |
||||
|
||||
class="spot-input" |
||||
|
||||
[id]="id" |
||||
[name]="name" |
||||
[value]="initialDate || ''" |
||||
[size]="size" |
||||
[required]="required" |
||||
[disabled]="disabled" |
||||
|
||||
(click)="open()" |
||||
(focus)="open()" |
||||
(keydown.enter)="enterPressed.emit(dateValue)" |
||||
(keydown.escape)="close()" |
||||
(input)="onInputChange()" |
||||
|
||||
type="text" |
||||
slot="trigger" |
||||
/--> |
||||
<button |
||||
slot="trigger" |
||||
type="button" |
||||
[disabled]="disabled" |
||||
(click)="open()" |
||||
>{{ value || 'Select a date' }}</button> |
||||
|
||||
<div |
||||
slot="body" |
||||
>body</div> |
||||
</spot-drop-modal> |
@ -1,124 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2022 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { |
||||
Component, |
||||
EventEmitter, |
||||
Input, |
||||
Output, |
||||
OnDestroy, |
||||
forwardRef, |
||||
} from '@angular/core'; |
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
import { TimezoneService } from 'core-app/core/datetime/timezone.service'; |
||||
|
||||
/* eslint-disable-next-line change-detection-strategy/on-push */ |
||||
@Component({ |
||||
selector: 'op-single-date-picker', |
||||
templateUrl: './op-single-date-picker.component.html', |
||||
providers: [ |
||||
{ |
||||
provide: NG_VALUE_ACCESSOR, |
||||
useExisting: forwardRef(() => OpSingleDatePickerComponent), |
||||
multi: true, |
||||
}, |
||||
], |
||||
}) |
||||
export class OpSingleDatePickerComponent implements ControlValueAccessor, OnDestroy { |
||||
@Output() public changed = new EventEmitter(); |
||||
|
||||
@Output() public blurred = new EventEmitter<string>(); |
||||
|
||||
@Output() public enterPressed = new EventEmitter<string>(); |
||||
|
||||
@Input() public value = ''; |
||||
|
||||
@Input() public id = ''; |
||||
|
||||
@Input() public name = ''; |
||||
|
||||
@Input() public required = false; |
||||
|
||||
@Input() public size = 20; |
||||
|
||||
@Input() public disabled = false; |
||||
|
||||
currentValue = ''; |
||||
|
||||
isOpen = false; |
||||
|
||||
constructor( |
||||
protected timezoneService:TimezoneService, |
||||
) {} |
||||
|
||||
open() { |
||||
this.isOpen = true; |
||||
} |
||||
|
||||
close() { |
||||
this.isOpen = false; |
||||
} |
||||
|
||||
protected inputIsValidDate():boolean { |
||||
return (/\d{4}-\d{2}-\d{2}/.exec(this.currentValue)) !== null; |
||||
} |
||||
|
||||
public formatter(data:string):string { |
||||
if (moment(data, 'YYYY-MM-DD', true).isValid()) { |
||||
const d = this.timezoneService.parseDate(data); |
||||
|
||||
return this.timezoneService.formattedISODate(d); |
||||
} |
||||
return data; |
||||
} |
||||
|
||||
ngOnDestroy():void { |
||||
} |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
onControlChange:(_?:unknown) => void = () => { }; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
onControlTouch:(_?:unknown) => void = () => { }; |
||||
|
||||
writeValue(date:string):void { |
||||
this.value = date; //this.formatter(date);
|
||||
} |
||||
|
||||
registerOnChange(fn:(_:unknown) => void):void { |
||||
this.onControlChange = fn; |
||||
} |
||||
|
||||
registerOnTouched(fn:(_:unknown) => void):void { |
||||
this.onControlTouch = fn; |
||||
} |
||||
|
||||
setDisabledState(disabled:boolean):void { |
||||
this.disabled = disabled; |
||||
} |
||||
} |
Loading…
Reference in new issue