aboutsummaryrefslogtreecommitdiffstats
path: root/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker
diff options
context:
space:
mode:
Diffstat (limited to 'sdc-workflow-designer-ui/src/app/paletx/plx-datepicker')
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/numberedFixLen.pipe.ts27
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.html134
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.less434
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts1712
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.module.ts27
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.html14
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.ts162
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover-config.ts13
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover.ts175
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/time.ts51
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker-config.ts19
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.less163
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.ts558
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/popup.ts58
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/positioning.ts153
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/triggers.ts62
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/util.ts39
17 files changed, 3801 insertions, 0 deletions
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/numberedFixLen.pipe.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/numberedFixLen.pipe.ts
new file mode 100644
index 00000000..9d26b16f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/numberedFixLen.pipe.ts
@@ -0,0 +1,27 @@
+/**
+ * numberFixedLen.pipe
+ */
+
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'numberFixedLen'
+})
+export class NumberFixedLenPipe implements PipeTransform {
+ transform(num: number, len: number): any {
+ let numberInt = Math.floor(num);
+ let length = Math.floor(len);
+
+ if (num === null || isNaN(numberInt) || isNaN(length)) {
+ return num;
+ }
+
+ let numString = numberInt.toString();
+
+ while (numString.length < length) {
+ numString = '0' + numString;
+ }
+
+ return numString;
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.html b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.html
new file mode 100644
index 00000000..8e4102c2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.html
@@ -0,0 +1,134 @@
+<div
+ class="owl-dateTime owl-widget"
+ [ngClass]="{'owl-dateTime-inline': inline}"
+ [ngStyle]="style"
+ #container>
+ <div *ngIf="!inline && customTemp.children.length == 0" class="owl-dateTime-inputWrapper" (mouseout)="Mouseout($event)" (mouseover)="Mouseover($event)">
+ <input *ngIf="!supportKeyboardInput" type="text" [class]="inputStyleClass"
+ [ngClass]="{
+ 'owl-dateTime-input owl-inputtext owl-state-default': true
+ }"
+ [ngStyle]="inputStyle"
+ [attr.placeholder]="placeHolder"
+ [attr.tabindex]="tabIndex" [attr.id]="inputId"
+ [attr.required]="required"
+ [disabled]="disabled"
+ [value]="formattedValue"
+ (focus)="onInputFocus($event)" (blur)="onInputBlur($event)" (click)="onInputClick($event)" readonly>
+ <input *ngIf="supportKeyboardInput" type="text" [class]="inputStyleClass"
+ [ngClass]="{
+ 'owl-dateTime-input owl-inputtext owl-state-default': true
+ }"
+ [ngStyle]="inputStyle"
+ [attr.placeholder]="placeHolder"
+ [attr.tabindex]="tabIndex" [attr.id]="inputId"
+ (change)="onInputChange($event)"
+ [attr.required]="required"
+ [value]="formattedValue"
+ (focus)="onInputFocus($event)" (blur)="onInputBlur($event)" (click)="onInputClick($event)">
+ <i class="ict ict-close owl-icon" style="margin-right: 5px;" [hidden]="(!value)||(disabled)||(!mouseIn)||(!canClear)" (click)="clearValue($event)"></i>
+ <i class="fa fa-calendar owl-icon" style="margin-right: 5px;" [hidden]="value&&(!disabled)" (click)="onInputClick($event)" ></i>
+ </div>
+ <!-- Workaround of ng-content default content (angular issue #12530) -->
+ <div [ngClass]="{'owl-dateTime-customTemp': customTemp.children.length !== 0}" #customTemp (click)="onInputClick($event)">
+ <ng-content></ng-content>
+ </div>
+ <div class="owl-dateTime-dialog owl-state-default owl-corner-all"
+ [ngStyle]="{'display': inline ? 'inline-block' : null}"
+ [@fadeInOut]="dialogVisible? 'visible' : (!inline? 'hidden': null)"
+ (click)="onDialogClick($event)" #dialog>
+ <div *ngIf="showHeader" class="owl-dateTime-dialogHeader owl-corner-top">
+ <span *ngIf="value; else elseBlock">{{formattedValue}}</span>
+ <ng-template #elseBlock><span>{{placeHolder}}</span></ng-template>
+ </div>
+ <div *ngIf="type ==='both' || type === 'calendar'" class="owl-calendar-wrapper owl-corner-all owl-padding">
+ <div class="owl-calendar-control owl-cal-header">
+ <div class="owl-calendar-controlNav">
+ <span class="fa fa-angle-left" style="padding: 8px;margin-left:12px; font-size: 20px;"
+ (click)="prevNav($event)"></span>
+ </div>
+ <div class="owl-calendar-controlContent">
+ <span class="month-control" (click)="changeDialogType(2)">{{pickerMonth}}</span>
+ <span class="year-control" (click)="changeDialogType(3)">{{pickerYear}}</span>
+ </div>
+ <div class="owl-calendar-controlNav">
+ <span class="fa fa-angle-right" style="padding: 8px; font-size: 20px;" (click)="nextNav($event)"></span>
+ </div>
+ </div>
+ <div class="owl-calendar" [hidden]="dialogType !== 1">
+ <table class="owl-calendar-day">
+ <thead class="owl-cal-header">
+ <tr class="owl-weekdays" style="height:40px">
+ <th *ngFor="let weekDay of calendarWeekdays" class="owl-weekday" scope="col">
+ <span>{{weekDay}}</span>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="owl-days" *ngFor="let week of calendarDays">
+ <td *ngFor="let d of week" class="owl-day" style="padding: 5px;"
+ [ngClass]="{
+ 'owl-calendar-invalid': !isValidDay(d.date),
+ 'owl-calendar-outFocus': d.otherMonth,
+ 'owl-calendar-hidden': d.hide,
+ 'owl-day-today': d.today
+ }" (click)="selectDate($event, d.date)">
+ <div style="height:32px;" class="owl-day day" [ngClass]=" {'owl-calendar-selected': isSelectedDay(d.date), 'owl-calendar-invalid': !isValidDay(d.date)}">
+ <a style="line-height:32px;">{{d.num}}</a>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div class="owl-calendar" [hidden]="dialogType !== 2">
+ <table class="owl-calendar-month">
+ <tbody>
+ <tr class="owl-months" *ngFor="let months of calendarMonths; let i = index">
+ <td *ngFor="let month of months; let j = index" class="owl-month"
+ (click)="selectMonth(i*3 + j)">
+ <div class="owl-month" [ngClass]="{'owl-calendar-div-selected': isCurrentMonth(i*3 + j),'owl-calendar-month-part':true,'owl-calendar-month-selected': isCurrentMonth(i*3 + j)}">
+ <a>{{month}}</a>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div class="owl-calendar" [hidden]="dialogType !== 3">
+ <table class="owl-calendar-year">
+ <tbody>
+ <tr class="owl-years" *ngFor="let years of calendarYears">
+ <td class="owl-year" *ngFor="let year of years"
+ (click)="selectYear(+year)">
+ <div [ngClass]="{'owl-calendar-year-part':true,'owl-calendar-year-selected': isCurrentYear(year)}">
+ <a>{{year}}</a>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <!--
+ <div class="owl-calendar-yearArrow left" style="left: 12px;
+ font-size: 20px;
+ margin-top: -116px;" (click)="generateYearList('prev')">
+ <i style="padding:8px" class="fa fa-angle-left"></i>
+ </div>
+ <div class="owl-calendar-yearArrow right" style="left: 261px;
+ font-size: 20px;
+ margin-top: -116px;" (click)="generateYearList('next')">
+ <i style="padding:8px" class="fa fa-angle-right"></i>
+ </div>
+ -->
+ </div>
+ </div>
+ <div *ngIf="type ==='both' || type === 'timer'" [hidden]="dialogType !== 1" >
+ <div style="height: 35px; padding: 8px;margin-bottom: 20px;">
+ <oes-timepickerr #timepicker [max]="_max" [min]="_min" class="time-picker" (TimerChange)="TimerChange($event)" [showSecondsTimer]="showSeconds" [(ngModel)]="mtime" [size]="'small'" [seconds]="seconds"></oes-timepickerr>
+ <div class="confirm-btn-div" style=" float: right; margin-top: -35px;">
+ <button class="plx-btn plx-btn-primary plx-btn-xs" (click)="confirm()" type="button"> {{locale.confirm}} </button>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.less b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.less
new file mode 100644
index 00000000..8e50660b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.less
@@ -0,0 +1,434 @@
+@import "../../assets/components/themes/default/theme.less";
+@import "../../assets/components/themes/common/plx-input.less";
+@import "../../assets/components/themes/common/plx-button.less";
+
+.owl-dateTime {
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ font-family: @font-family;
+ font-size: @font-size;
+ background: @component-bg;
+ color: @text-color;
+}
+
+.owl-dateTime input {
+ .plx-input;
+}
+
+.owl-dateTime input:-ms-input-placeholder {
+ color: @unselected-text-color !important;
+}
+.owl-dateTime input::-webkit-input-placeholder {
+ color: @unselected-text-color !important;
+}
+
+.owl-dateTime-input {
+ width: 100%;
+ padding-right: 1.5em; }
+
+.owl-dateTime-cancel {
+ position: absolute;
+ top: 50%;
+ right: .1em;
+ border-radius: 50%;
+ transform: translateY(-50%);
+ cursor: pointer;
+ color: inherit; }
+
+.owl-dateTime-inputWrapper {
+ position: relative; }
+
+.owl-dateTime-customTemp {
+ display: inline-block;
+ position: relative; }
+
+.owl-dateTime-dialog {
+ padding: 0px;
+ position: absolute; }
+
+.owl-dateTime-dialogHeader {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%; }
+
+.owl-calendar-wrapper,
+.owl-timer-wrapper {
+ position: relative;
+ width: 100%;
+ padding: .2em .5em; }
+
+.owl-calendar-control {
+ display: flex;
+ justify-content: space-around;
+ width: 100%;
+ height: 2em; }
+ .owl-calendar-control .owl-calendar-controlNav {
+ position: relative;
+ cursor: pointer;
+ width: 12.5%; }
+ .owl-calendar-control .owl-calendar-controlContent {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 75%;
+ height: 100%; }
+
+.owl-calendar {
+ position: relative;
+ min-height: 13.7em; }
+ .owl-calendar table {
+ width: 100%;
+ border-collapse: collapse; }
+ .owl-calendar tbody td {
+ position: relative;
+ text-align: center; }
+ .owl-calendar tbody td a {
+ display: block;
+ width: 100%;
+ height: 100%;
+ text-decoration: none;
+ color: inherit;
+ font-size:12px;
+ }
+ .owl-calendar .owl-calendar-yearArrow {
+ position: absolute;
+ top: 50%;
+ width: 1.5em;
+ height: 1.5em;
+ transform: translateY(-50%);
+ cursor: pointer; }
+ .owl-calendar .owl-calendar-yearArrow.left {
+ left: -.5em; }
+ .owl-calendar .owl-calendar-yearArrow.right {
+ right: -.5em; }
+
+.owl-timer-wrapper {
+ position: relative;
+ display: flex;
+ justify-content: center; }
+ .owl-timer-wrapper .owl-timer {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 25%;
+ height: 100%; }
+ .owl-timer-wrapper .owl-timer-control {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 30%;
+ width: 100%;
+ cursor: pointer; }
+ .owl-timer-wrapper .owl-timer-control .icon:before {
+ margin: 0; }
+ .owl-timer-wrapper .owl-timer-input {
+ width: 60%;
+ height: 100%; }
+
+/*# sourceMappingURL=picker.component.css.map */
+.font-face {
+ font-weight: normal;
+ font-style: normal; }
+
+[class^="paletx-datepicker-icon-"]:before, [class*="paletx-datepicker-icon-"]:before {
+ font-family: "fontello";
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+ display: inline-block;
+ text-decoration: inherit;
+ width: 1em;
+ margin-right: .2em;
+ text-align: center;
+ /* opacity: .8; */
+ /* For safety - reset parent styles, that can break glyph codes*/
+ font-variant: normal;
+ text-transform: none;
+ /* fix buttons height, for twitter bootstrap */
+ line-height: 1em;
+ /* Animation center compensation - margins should be symmetric */
+ /* remove if not needed */
+ margin-left: .2em;
+ /* you can be more comfortable with increased icons size */
+ /* font-size: 120%; */
+ /* Font smoothing. That was taken from TWBS */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ /* Uncomment for 3D effect */
+ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ }
+
+.paletx-datepicker-icon-cancel:before {
+ content: '\e802'; }
+
+/* '' */
+.paletx-datepicker-icon-up-open:before {
+ content: '\e805'; }
+
+/* '' */
+.paletx-datepicker-icon-down-open:before {
+ content: '\e80b'; }
+
+/* '' */
+.paletx-datepicker-icon-left-open:before {
+ content: '\e817'; }
+
+/* '' */
+.paletx-datepicker-icon-right-open:before {
+ content: '\e818'; }
+
+/* '' */
+.owl-widget,
+.owl-widget * {
+ box-sizing: border-box; }
+
+.owl-widget {
+ font-size: 1em; }
+.owl-padding{
+ padding: 0px;
+}
+.owl-corner-all {
+ border-radius: 3px; }
+
+.owl-corner-top {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px; }
+
+.owl-state-default {
+ border: 1px solid @border-color-base;
+ background: @component-bg;
+ color: @text-color; }
+
+.owl-inputtext {
+ margin: 0;
+ outline: medium none;
+ transition: .2s; }
+
+
+ .owl-dateTime.owl-dateTime-inline {
+ width: auto; }
+ .owl-dateTime.owl-dateTime-inline .owl-dateTime-dialog {
+ position: relative;
+ z-index: auto; }
+
+.owl-dateTime-dialog {
+ width: 300px;
+ user-select: none;
+ z-index: 99999; }
+
+.owl-dateTime-dialogHeader {
+ height: 2.5em;
+ padding: .25em;
+ background-color: @component-bg;
+ overflow-y: auto; }
+
+.owl-calendar-control .owl-calendar-controlNav .nav-prev,
+.owl-calendar-control .owl-calendar-controlNav .nav-next {
+ position: absolute;
+ top: 50%;
+ right: auto;
+ bottom: auto;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.owl-cal-header{
+ background: @selected-bg-color;
+ //color: @form-label;
+ height: 35px;
+ //width: 105%;
+ //margin-left: -7px;
+}
+ .owl-calendar-control .owl-calendar-controlNav .nav-prev:before,
+ .owl-calendar-control .owl-calendar-controlNav .nav-next:before {
+ //content: "";
+ border-top: .5em solid transparent;
+ border-bottom: .5em solid transparent;
+ border-right: 0.75em solid #000000;
+ width: 0;
+ height: 0;
+ display: block;
+ margin: 0 auto; }
+.owl-calendar-control .owl-calendar-controlNav .nav-next:before {
+ border-right: 0;
+ border-left: 0.75em solid #000000; }
+.owl-calendar-control .owl-calendar-controlContent .month-control,
+.owl-calendar-control .owl-calendar-controlContent .year-control {
+ color: @unselected-text-color;
+ display: inline-block;
+ cursor: pointer;
+ transition: transform 200ms ease; }
+ .owl-calendar-control .owl-calendar-controlContent .month-control:hover,
+ .owl-calendar-control .owl-calendar-controlContent .year-control:hover {
+ // transform: scale(1.2); }
+ color: @guide-color; }
+.owl-calendar-control .owl-calendar-controlContent .month-control {
+ font-size: @font-size-title-group;
+ margin-right: .8rem;
+}
+.owl-calendar-control .owl-calendar-controlContent .year-control {
+ font-size: @font-size-title-group;
+}
+
+.owl-calendar tbody td.owl-calendar-selected {
+ background-color: @guide-color;
+ color: @component-bg }
+.owl-calendar tbody td.owl-calendar-invalid {
+ color: @disabled-text-color }
+.owl-calendar tbody td.owl-calendar-outFocus {
+ color: @unselected-text-color; }
+.owl-calendar tbody td.owl-calendar-hidden {
+ visibility: hidden; }
+ /**
+.owl-calendar tbody td:not(.owl-calendar-selected):not(.owl-calendar-invalid):hover {
+ background-color: @hover-bg-color;
+ color: @shadow-color }
+**/
+.owl-years td.owl-year,
+.owl-years td.owl-month,
+.owl-months td.owl-year,
+.owl-months td.owl-month {
+ font-size: 1.2em;
+ height: 2.5em;
+ width: 33.33%;
+ line-height: 2.5em;
+ border-radius: 60px;
+ }
+
+.owl-weekdays th.owl-weekday {
+ height: 1em;
+ line-height: 2em;
+ text-align: center;
+ font-weight: normal;
+ font-size: @font-size;
+ /**color: @unselected-text-color; **/
+ }
+
+.owl-days td.owl-day {
+ border-radius: 30px;
+ height: 2em;
+ width: calc(100% / 7);
+ line-height: 2em; }
+ .owl-days td.owl-day.owl-day-today:before {
+ content: '';
+ display: block;
+ position: absolute;
+ right: 2px;
+ top: 2px;
+ color: @primary-color;
+ border-top: 0.5em solid @primary-color-hover;
+ border-left: .5em solid transparent;
+ }
+
+.owl-timer-wrapper {
+ height: 5.4em;
+ background-color: @shadow-color; }
+ .owl-timer-wrapper .owl-timer-text {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 40%;
+ font-size: 1.5em; }
+ .owl-timer-wrapper .owl-meridian-btn {
+ font-size: .8em;
+ color: @guide-color;
+ background-image: none;
+ background-color: transparent;
+ border-color: @guide-color; }
+ .owl-timer-wrapper .owl-meridian-btn:hover {
+ color: @scene-textcolor;
+ background-color: @guide-color;
+ border-color: @guide-color; }
+
+.owl-timer-divider {
+ display: inline-block;
+ align-self: flex-end;
+ position: absolute;
+ width: .6em;
+ height: 100%;
+ left: -.3em; }
+ .owl-timer-divider .owl-timer-dot {
+ display: block;
+ width: .3em;
+ height: .3em;
+ position: absolute;
+ left: 50%;
+ border-radius: 50%;
+ transform: translateX(-50%); }
+ .owl-timer-divider .owl-timer-dot.dot-top {
+ top: 38%; }
+ .owl-timer-divider .owl-timer-dot.dot-bottom {
+ bottom: 38%; }
+.owl-icon{
+ position: absolute;
+ top: 50%;
+ right: .1em;
+ border-radius: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ cursor: pointer;
+ color: @fonticon-color;
+}
+
+.oes-time-control{
+ color: @text-color !important;
+}
+.owl-calendar-selected {
+ background-color: @guide-color;
+ color: #fff;
+ border-radius: 50%;
+}
+.owl-calendar tbody td div.day:not(.owl-calendar-selected):not(.owl-calendar-invalid):hover {
+ background-color: @hover-bg-color;
+ color:#000;
+ border-radius: 50%; }
+.oes-time-control{
+ font-size: @font-size;
+}
+.owl-calendar-year-part{
+ width: 42px;
+ margin-left: 30px;
+ text-align: center;
+}
+.owl-calendar-year-part:hover{
+ background-color: @hover-bg-color;
+ color:#000;
+ border-radius: 50%;
+}
+.owl-calendar-year-selected{
+ background-color: @guide-color;
+ color: #fff;
+ border-radius: 50%;
+}
+.owl-calendar-year-selected:hover{
+ background-color: @guide-color;
+ color: #fff;
+ border-radius: 50%;
+}
+.owl-calendar-month-part{
+ width: 42px;
+ margin-left: 30px;
+ text-align: center;
+}
+.owl-calendar-month-part:hover{
+ background-color: @hover-bg-color;
+ color:#000;
+ border-radius: 50%;
+}
+.owl-calendar-month-selected{
+ background-color: @guide-color;
+ color: #fff;
+ border-radius: 50%;
+}
+.owl-calendar-month-selected:hover{
+ background-color: @guide-color;
+ color: #fff;
+ border-radius: 50%;
+}
+
+/*# sourceMappingURL=picker.css.map */
+
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts
new file mode 100644
index 00000000..493e0cb2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts
@@ -0,0 +1,1712 @@
+/**
+ * picker.component
+ */
+
+import {
+ AfterViewInit,
+ Component, ElementRef, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output, Renderer2,
+ ViewChild
+} from '@angular/core';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import {
+ parse,
+ isValid,
+ startOfMonth,
+ getDate,
+ getDay,
+ addDays,
+ addMonths,
+ isSameDay,
+ isSameMonth,
+ isToday,
+ getMonth,
+ setMonth,
+ getYear,
+ addYears,
+ differenceInCalendarDays,
+ setYear,
+ getHours,
+ setHours,
+ getMinutes,
+ setMinutes,
+ getSeconds,
+ setSeconds,
+ isBefore,
+ isAfter,
+ compareAsc,
+ startOfDay,
+ format,
+ endOfDay,
+} from 'date-fns';
+import { NumberFixedLenPipe } from './numberedFixLen.pipe';
+
+export interface LocaleSettings {
+ firstDayOfWeek?: number;
+ dayNames: string[];
+ dayNamesShort: string[];
+ monthNames: string[];
+ monthNamesShort: string[];
+ dateFns: any;
+ confirm: string;
+}
+
+export enum DialogType {
+ Time,
+ Date,
+ Month,
+ Year,
+}
+
+export const DATETIMEPICKER_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => DateTimePickerComponent),
+ multi: true
+};
+
+@Component({
+ selector: 'plx-datepicker',
+ templateUrl: './picker.component.html',
+ styleUrls: ['./picker.component.less'],
+ providers: [NumberFixedLenPipe, DATETIMEPICKER_VALUE_ACCESSOR],
+ animations: [
+ trigger('fadeInOut', [
+ state('hidden', style({
+ opacity: 0,
+ display: 'none'
+ })),
+ state('visible', style({
+ opacity: 1,
+ display: 'block'
+ })),
+ transition('visible => hidden', animate('200ms ease-in')),
+ transition('hidden => visible', animate('400ms ease-out'))
+ ])
+ ],
+})
+
+export class DateTimePickerComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor {
+
+ @ViewChild('timepicker') public timepicker;
+
+ /**
+ * Type of the value to write back to ngModel
+ * @default 'date' -- Javascript Date type
+ * @type {'string' | 'date'}
+ * */
+ @Input() dataType: 'string' | 'date' = 'date';
+
+ /**
+ * Format of the date
+ * @default 'y/MM/dd'
+ * @type {String}
+ * */
+ @Input() dateFormat: string = 'YYYY-MM-DD HH:mm';
+ /**
+ * When present, it specifies that the component should be disabled
+ * @default false
+ * @type {Boolean}
+ * */
+ @Input() disabled: boolean;
+ /**
+ * @default false
+ * @type {Boolean}
+ * */
+ @Input() supportKeyboardInput: boolean = false;
+ /**
+ * Array with dates that should be disabled (not selectable)
+ * @default null
+ * @type {Date[]}
+ * */
+ @Input() disabledDates: Date[] = [];
+
+ /**
+ * Array with weekday numbers that should be disabled (not selectable)
+ * @default null
+ * @type {number[]}
+ * */
+ @Input() disabledDays: number[];
+
+ /**
+ * When enabled, displays the calendar as inline
+ * @default false -- popup mode
+ * @type {boolean}
+ * */
+ @Input() inline: boolean;
+
+ /**
+ * Identifier of the focus input to match a label defined for the component
+ * @default null
+ * @type {String}
+ * */
+ @Input() inputId: string;
+
+ /**
+ * Inline style of the picker text input
+ * @default null
+ * @type {any}
+ * */
+ @Input() inputStyle: any;
+
+ /**
+ * Style class of the picker text input
+ * @default null
+ * @type {String}
+ * */
+ @Input() inputStyleClass: string;
+
+ /**
+ * Maximum number of selectable dates in multiple mode
+ * @default null
+ * @type {number}
+ * */
+ @Input() maxDateCount: number;
+
+ /**
+ * The minimum selectable date time
+ * @default null
+ * @type {Date | string}
+ * */
+ private _max: Date;
+ @Input()
+ get max() {
+ return this._max;
+ }
+
+ set max(val: Date | string) {
+ this._max = this.parseToDate(val);
+ }
+
+ @Input()
+ get maxDate() {
+ return this._max;
+ }
+
+ set maxDate(val: Date | string) {
+ this._max = this.parseToDate(val);
+ }
+
+ /**
+ * The maximum selectable date time
+ * @default null
+ * @type {Date | string }
+ * */
+ private _min: Date;
+ @Input()
+ get min() {
+ return this._min;
+ }
+
+ set min(val: Date | string) {
+ this._min = this.parseToDate(val);
+ }
+ @Input()
+ get minDate() {
+ return this._min;
+ }
+
+ set minDate(val: Date | string) {
+ this._min = this.parseToDate(val);
+ }
+
+ @Input()
+ get dateValue() {
+ return this.value;
+ }
+
+ set dateValue(val: Date | string) {
+ let newvalue = this.parseToDate(val);
+ if(newvalue!==undefined)
+ {
+ this.updateModel(newvalue);
+ this.updateCalendar(newvalue);
+ this.updateTimer(newvalue);
+ this.updateFormattedValue();
+ }
+ }
+
+
+ /**
+ * Picker input placeholder value
+ * @default
+ * @type {String}
+ * */
+ @Input() placeHolder: string = 'yyyy-mm-dd hh:mm';
+
+ /**
+ * When present, it specifies that an input field must be filled out before submitting the form
+ * @default false
+ * @type {Boolean}
+ * */
+ @Input() required: boolean;
+
+ /**
+ * Defines the quantity of the selection
+ * 'single' -- allow only a date value to be selected
+ * 'multiple' -- allow multiple date value to be selected
+ * 'range' -- allow to select an range ot date values
+ * @default 'single'
+ * @type {string}
+ * */
+ @Input() selectionMode: 'single' | 'multiple' | 'range' = 'single';
+
+ /**
+ * Whether to show the picker dialog header
+ * @default false
+ * @type {Boolean}
+ * */
+ @Input() showHeader: boolean;
+
+ @Input() canClear: boolean = true;
+
+ /**
+ * Whether to show the second's timer
+ * @default false
+ * @type {Boolean}
+ * */
+ @Input() showSeconds: boolean;
+
+ /**
+ * Inline style of the element
+ * @default null
+ * @type {any}
+ * */
+ @Input() style: any;
+
+ /**
+ * Style class of the element
+ * @default null
+ * @type {String}
+ * */
+ @Input() styleClass: string;
+
+ /**
+ * Index of the element in tabbing order
+ * @default null
+ * @type {Number}
+ * */
+ @Input() tabIndex: number;
+
+ /**
+ * Set the type of the dateTime picker
+ * 'both' -- show both calendar and timer
+ * 'calendar' -- show only calendar
+ * 'timer' -- show only timer
+ * @default 'both'
+ * @type {'both' | 'calendar' | 'timer'}
+ * */
+ @Input() type: 'both' | 'calendar' | 'timer' = 'calendar';
+
+ //附加方法
+ @Input()
+ set timeOnly(value: boolean) {
+ if (value) {
+ this.type = 'timer';
+ }
+ else {
+ this.type = "both";
+ }
+ }
+
+ @Input()
+ set showTime(value: boolean) {
+ if (value) {
+ this.type = 'both';
+ }
+ else {
+ this.type = "calendar";
+ }
+ }
+
+ /**
+ * An object having regional configuration properties for the dateTimePicker
+ * */
+ @Input()
+ get locale(): any {
+ return this._locale;
+ }
+ set locale(val: any) {
+ if (val !== undefined) {
+ this._locale = val;
+ this._userlocale = true;
+ }
+ }
+
+ @Input() localePrefab: 'Zh' | 'En' = 'En';
+ /**
+ * Determine the hour format (12 or 24)
+ * @default '24'
+ * @type {'24'| '12'}
+ * */
+ @Input() hourFormat: '12' | '24' = '24';
+
+
+ /**
+ * When it is set to false, only show current month's days in calendar
+ * @default true
+ * @type {boolean}
+ * */
+ @Input() showOtherMonths: boolean = true;
+
+ /**
+ * Callback to invoke when dropdown gets focus.
+ * */
+ @Output() onFocus: any = new EventEmitter<any>();
+
+ /**
+ * Callback to invoke when dropdown gets focus.
+ * */
+ @Output() onConfirm: any = new EventEmitter<any>();
+
+ /**
+ * Callback to invoke when dropdown loses focus.
+ * */
+ @Output() onBlur: any = new EventEmitter<any>();
+
+ /**
+ * Callback to invoke when a invalid date is selected.
+ * */
+ @Output() onInvalid: any = new EventEmitter<any>();
+
+
+
+ @ViewChild('container') containerElm: ElementRef;
+ @ViewChild('textInput') textInputElm: ElementRef;
+ @ViewChild('dialog') dialogElm: ElementRef;
+
+ public calendarDays: Array<any[]>;
+ public calendarWeekdays: string[];
+ public calendarMonths: Array<string[]>;
+ public calendarYears: Array<string[]> = [];
+ public dialogType: DialogType = DialogType.Date;
+ public dialogVisible: boolean;
+ public focus: boolean;
+ public formattedValue: string = '';
+ public value: any;
+ public pickerMoment: Date;
+ public pickerMonth: string;
+ public pickerYear: string;
+
+ public hourValue: number;
+ public minValue: number;
+ public secValue: number;
+ public meridianValue: string = 'AM';
+ private _userlocale: boolean = false;
+ private _locale: LocaleSettings = {
+ firstDayOfWeek: 0,
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ dayNamesShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
+ //dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ dateFns: null,
+ confirm: 'OK'
+ };
+ private _localeEn: LocaleSettings = {
+ firstDayOfWeek: 0,
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ dayNamesShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
+ monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ dateFns: null,
+ confirm: 'OK'
+ };
+ private _localeZh: LocaleSettings = {
+ firstDayOfWeek: 0,
+ dayNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
+ dayNamesShort: ['日', '一', '二', '三', '四', '五', '六'],
+ monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
+ monthNamesShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
+ dateFns: null,
+ confirm: '确定'
+ };
+ private dialogClick: boolean;
+ private documentClickListener: Function;
+ private valueIndex: number = 0;
+ private onModelChange: Function = () => {
+ //
+ }
+ private onModelTouched: Function = () => {
+ //
+ }
+
+ constructor(private renderer: Renderer2,
+ private numFixedLenPipe: NumberFixedLenPipe) {
+ }
+ private updateDate(newvalue : Date)
+ {
+ if(newvalue!==undefined&&newvalue!==null)
+ {
+ if(this.min)
+ {
+ newvalue = this._min.getTime()<=newvalue.getTime()?newvalue:new Date(this._min);
+ }
+ if(this.max)
+ {
+ newvalue = this._max.getTime()>=newvalue.getTime()?newvalue:new Date(this._max);
+ }
+ this.updateModel(newvalue);
+ this.updateCalendar(newvalue);
+ this.updateTimer(newvalue);
+ this.updateFormattedValue();
+ return;
+ }
+ }
+ public onInputChange(event:any): void {
+ let newvalue = this.parseToDate(event.target.value);
+ if(newvalue!==undefined&&newvalue!==null)
+ {
+ if(this.min)
+ {
+ newvalue = this._min.getTime()<=newvalue.getTime()?newvalue:new Date(this._min);
+ }
+ if(this.max)
+ {
+ newvalue = this._max.getTime()>=newvalue.getTime()?newvalue:new Date(this._max);
+ }
+ this.updateModel(newvalue);
+ this.updateCalendar(newvalue);
+ this.updateTimer(newvalue);
+ this.updateFormattedValue();
+ return;
+ }
+ this.updateModel(null);
+ this.updateCalendar(null);
+ this.updateTimer(null);
+ this.updateFormattedValue();
+ }
+ public ngOnInit(): void {
+
+ if ((!this._userlocale) || this.locale === null && this.locale === undefined) {
+ switch (this.localePrefab) {
+
+ case 'Zh': {
+ this._locale = this._localeZh;
+ break;
+ }
+ case 'En': {
+ this._locale = this._localeEn;
+ break;
+ }
+ default:
+ {
+ this._locale = this._localeEn;
+ break;
+ }
+ }
+ }
+ this.pickerMoment = new Date();
+
+ if (this.type === 'both' || this.type === 'calendar') {
+ this.generateWeekDays();
+ this.generateMonthList();
+ }
+ this.updateTimer(this.value);
+ }
+
+ public ngAfterViewInit(): void {
+ this.updateCalendar(this.value);
+ this.updateTimer(this.value);
+ }
+
+ public ngOnDestroy(): void {
+ this.unbindDocumentClickListener();
+ }
+
+ public writeValue(obj: any): void {
+
+ if (obj instanceof Array) {
+ this.value = [];
+ for (let o of obj) {
+ let v = this.parseToDate(o);
+ this.value.push(v);
+ }
+ this.updateCalendar(this.value[0]);
+ this.updateTimer(this.value[0]);
+ } else {
+ this.value = this.parseToDate(obj);
+ this.updateCalendar(this.value);
+ this.updateTimer(this.value);
+ }
+ this.updateFormattedValue();
+ }
+
+ public registerOnChange(fn: any): void {
+ this.onModelChange = fn;
+ }
+
+ public registerOnTouched(fn: any): void {
+ this.onModelTouched = fn;
+ }
+
+ public setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ }
+
+
+ private initflag = true;
+ /**
+ * Handle click event on the text input
+ * @param {any} event
+ * @return {void}
+ * */
+ public onInputClick(event: any): void {
+
+ if (this.timepicker !== undefined && this.initflag) {
+ this.initflag = false;
+ if (this.value !== undefined && this.value !== null) {
+ this.timepicker.updateHour(this.value.getHours());
+ this.timepicker.updateMinute(this.value.getMinutes());
+ this.timepicker.updateSecond(this.value.getSeconds());
+ }
+ else {
+ this.timepicker.updateHour(0);
+ this.timepicker.updateMinute(0);
+ this.timepicker.updateSecond(0);
+ this.updateModel(null);
+ this.updateFormattedValue();
+ }
+ }
+ if (this.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ this.dialogClick = true;
+ if (!this.dialogVisible) {
+ this.show();
+ }
+ event.preventDefault();
+ return;
+ }
+
+ /**
+ * Set the element on focus
+ * @param {any} event
+ * @return {void}
+ * */
+ public onInputFocus(event: any): void {
+ this.focus = true;
+ this.onFocus.emit(event);
+ event.preventDefault();
+ return;
+ }
+
+ /**
+ * Set the element on blur
+ * @param {any} event
+ * @return {void}
+ * */
+ public onInputBlur(event: any): void {
+ this.focus = false;
+ this.onModelTouched();
+ this.onBlur.emit(event);
+ event.preventDefault();
+ return;
+ }
+
+ /**
+ * Handle click event on the dialog
+ * @param {any} event
+ * @return {void}
+ * */
+ public onDialogClick(event: any): void {
+ this.dialogClick = true;
+ }
+
+ /**
+ * Go to previous month
+ * @param {any} event
+ * @return {void}
+ * */
+ public prevMonth(event: any): void {
+
+ if (this.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ this.pickerMoment = addMonths(this.pickerMoment, -1);
+ this.generateCalendar();
+ if(this.value!==undefined&&this.value!==null)
+ {
+ let nowvalue = new Date(this.value);
+ nowvalue.setMonth(this.pickerMoment.getMonth());
+ this.updateDate(nowvalue);
+ }
+ event.preventDefault();
+ return;
+ }
+
+ /**
+ * Go to next month
+ * @param {any} event
+ * @return {void}
+ * */
+ public nextMonth(event: any): void {
+
+ if (this.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ this.pickerMoment = addMonths(this.pickerMoment, 1);
+ this.generateCalendar();
+ if(this.value!==undefined&&this.value!==null)
+ {
+ let nowvalue = new Date(this.value);
+ nowvalue.setMonth(this.pickerMoment.getMonth());
+ this.updateDate(nowvalue);
+ }
+ event.preventDefault();
+ return;
+ }
+
+ /**
+ * Select a date
+ * @param {any} event
+ * @param {Date} date
+ * @return {void}
+ * */
+ public selectDate(event: any, date: Date): void {
+
+ if (this.disabled || !date) {
+ event.preventDefault();
+ return;
+ }
+
+ let temp: Date;
+ // check if the selected date is valid
+ if (this.isValidValue(date)) {
+ temp = date;
+ } else {
+ if (isSameDay(date, this._min)) {
+ temp = new Date(this._min);
+ } else if (isSameDay(date, this._max)) {
+ temp = new Date(this._max);
+ } else {
+ this.onInvalid.emit({ originalEvent: event, value: date });
+ return;
+ }
+ }
+ if (this.minValue !== undefined) {
+ temp.setMinutes(this.minValue);
+ }
+ if (this.secValue !== undefined) {
+ temp.setSeconds(this.secValue);
+ }
+ if (this.hourValue !== undefined) {
+ temp.setHours(this.hourValue);
+ }
+ let selected;
+ if (this.isSingleSelection()) {
+ if (!isSameDay(this.value, temp)) {
+ selected = temp;
+ }
+ } else if (this.isRangeSelection()) {
+ if (this.value && this.value.length) {
+ let startDate = this.value[0];
+ let endDate = this.value[1];
+
+ if (!endDate && temp.getTime() > startDate.getTime()) {
+ endDate = temp;
+ this.valueIndex = 1;
+ } else {
+ startDate = temp;
+ endDate = null;
+ this.valueIndex = 0;
+ }
+ selected = [startDate, endDate];
+ } else {
+ selected = [temp, null];
+ this.valueIndex = 0;
+ }
+ } else if (this.isMultiSelection()) {
+
+ // check if it exceeds the maxDateCount limit
+ if (this.maxDateCount && this.value &&
+ this.value.length && this.value.length >= this.maxDateCount) {
+ this.onInvalid.emit({ originalEvent: event, value: 'Exceed max date count.' });
+ return;
+ }
+
+ if (this.isSelectedDay(temp)) {
+ selected = this.value.filter((d: Date) => {
+ return !isSameDay(d, temp);
+ });
+ } else {
+ selected = this.value ? [...this.value, temp] : [temp];
+ this.valueIndex = selected.length - 1;
+ }
+ }
+ if (selected) {
+ this.updateModel(selected);
+ if (this.value instanceof Array) {
+ this.updateCalendar(this.value[this.valueIndex]);
+ this.updateTimer(this.value[this.valueIndex]);
+ } else {
+ this.updateCalendar(this.value);
+ this.updateTimer(this.value);
+ }
+ this.updateFormattedValue();
+ }
+ }
+
+ /**
+ * Set a pickerMoment's month
+ * @param {Number} monthNum
+ * @return {void}
+ * */
+ public selectMonth(monthNum: number): void {
+ this.pickerMoment = setMonth(this.pickerMoment, monthNum);
+ this.generateCalendar();
+ if(this.value!==undefined&&this.value!==null)
+ {
+ let nowvalue = new Date(this.value);
+ nowvalue.setMonth(monthNum);
+ this.updateDate(nowvalue);
+ }
+ this.changeDialogType(DialogType.Month);
+ }
+
+ /**
+ * Set a pickerMoment's year
+ * @param {Number} yearNum
+ * @return {void}
+ * */
+ public selectYear(yearNum: number): void {
+ this.pickerMoment = setYear(this.pickerMoment, yearNum);
+ this.generateCalendar();
+ if(this.value!==undefined&&this.value!==null)
+ {
+ let nowvalue = new Date(this.value);
+ nowvalue.setFullYear(yearNum);
+ this.updateDate(nowvalue);
+ }
+ this.changeDialogType(DialogType.Year);
+ }
+
+ /**
+ * Set the selected moment's meridian
+ * @param {any} event
+ * @return {void}
+ * */
+ public toggleMeridian(event: any): void {
+
+ let value = this.value ? (this.value.length ? this.value[this.valueIndex] : this.value) : null;
+
+ if (this.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if (!value) {
+ this.meridianValue = this.meridianValue === 'AM' ? 'PM' : 'AM';
+ return;
+ }
+
+ let hours = getHours(value);
+ if (this.meridianValue === 'AM') {
+ hours += 12;
+ } else if (this.meridianValue === 'PM') {
+ hours -= 12;
+ }
+
+ let selectedTime = setHours(value, hours);
+ this.setSelectedTime(selectedTime);
+ event.preventDefault();
+ return;
+ }
+
+ /**
+ * Set the selected moment's hour
+ * @param {any} event
+ * @param {'increase' | 'decrease' | number} val
+ * 'increase' -- increase hour value by 1
+ * 'decrease' -- decrease hour value by 1
+ * number -- set hour value to val
+ * @param {HTMLInputElement} input -- optional
+ * @return {boolean}
+ * */
+ public setHours(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean {
+
+ let value;
+ if (this.value) {
+ if (this.value.length) {
+ value = this.value[this.valueIndex];
+ } else {
+ value = this.value;
+ }
+ } else {
+ if (this.type === 'timer') {
+ value = new Date();
+ } else {
+ value = null;
+ }
+ }
+
+ if (this.disabled || !value) {
+ event.preventDefault();
+ return false;
+ }
+
+ let hours = getHours(value);
+ if (val === 'increase') {
+ hours += 1;
+ } else if (val === 'decrease') {
+ hours -= 1;
+ } else {
+ hours = val;
+ }
+
+ if (hours > 23) {
+ hours = 0;
+ } else if (hours < 0) {
+ hours = 23;
+ }
+
+ let selectedTime = setHours(value, hours);
+ let done = this.setSelectedTime(selectedTime);
+
+ // Focus the input and select its value when model updated
+ if (input) {
+ setTimeout(() => {
+ input.focus();
+ }, 0);
+ }
+
+ event.preventDefault();
+ return done;
+ }
+
+ /**
+ * Set the selected moment's minute
+ * @param {any} event
+ * @param {'increase' | 'decrease' | number} val
+ * 'increase' -- increase minute value by 1
+ * 'decrease' -- decrease minute value by 1
+ * number -- set minute value to val
+ * @param {HTMLInputElement} input -- optional
+ * @return {boolean}
+ * */
+ public setMinutes(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean {
+
+ let value;
+ if (this.value) {
+ if (this.value.length) {
+ value = this.value[this.valueIndex];
+ } else {
+ value = this.value;
+ }
+ } else {
+ if (this.type === 'timer') {
+ value = new Date();
+ } else {
+ value = null;
+ }
+ }
+
+ if (this.disabled || !value) {
+ event.preventDefault();
+ return false;
+ }
+
+ let minutes = getMinutes(value);
+ if (val === 'increase') {
+ minutes += 1;
+ } else if (val === 'decrease') {
+ minutes -= 1;
+ } else {
+ minutes = val;
+ }
+
+ if (minutes > 59) {
+ minutes = 0;
+ } else if (minutes < 0) {
+ minutes = 59;
+ }
+
+ let selectedTime = setMinutes(value, minutes);
+ let done = this.setSelectedTime(selectedTime);
+
+ // Focus the input and select its value when model updated
+ if (input) {
+ setTimeout(() => {
+ input.focus();
+ }, 0);
+ }
+
+ event.preventDefault();
+ return done;
+ }
+
+ /**
+ * Set the selected moment's second
+ * @param {any} event
+ * @param {'increase' | 'decrease' | number} val
+ * 'increase' -- increase second value by 1
+ * 'decrease' -- decrease second value by 1
+ * number -- set second value to val
+ * @param {HTMLInputElement} input -- optional
+ * @return {boolean}
+ * */
+ public setSeconds(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean {
+
+ let value;
+ if (this.value) {
+ if (this.value.length) {
+ value = this.value[this.valueIndex];
+ } else {
+ value = this.value;
+ }
+ } else {
+ if (this.type === 'timer') {
+ value = new Date();
+ } else {
+ value = null;
+ }
+ }
+
+ if (this.disabled || !value) {
+ event.preventDefault();
+ return false;
+ }
+
+ let seconds = getSeconds(value);
+ if (val === 'increase') {
+ seconds = this.secValue + 1;
+ } else if (val === 'decrease') {
+ seconds = this.secValue - 1;
+ } else {
+ seconds = val;
+ }
+
+ if (seconds > 59) {
+ seconds = 0;
+ } else if (seconds < 0) {
+ seconds = 59;
+ }
+
+ let selectedTime = setSeconds(value, seconds);
+ let done = this.setSelectedTime(selectedTime);
+
+ // Focus the input and select its value when model updated
+ if (input) {
+ setTimeout(() => {
+ input.focus();
+ }, 0);
+ }
+
+ event.preventDefault();
+ return done;
+ }
+
+ /**
+ * Check if the date is selected
+ * @param {Date} date
+ * @return {Boolean}
+ * */
+ public isSelectedDay(date: Date): boolean {
+ if (this.isSingleSelection()) {
+ return this.value && isSameDay(this.value, date);
+ } else if (this.isRangeSelection() && this.value && this.value.length) {
+ if (this.value[1]) {
+ return (isSameDay(this.value[0], date) || isSameDay(this.value[1], date) ||
+ this.isDayBetween(this.value[0], this.value[1], date)) && this.isValidDay(date);
+ } else {
+ return isSameDay(this.value[0], date);
+ }
+ } else if (this.isMultiSelection() && this.value && this.value.length) {
+ let selected;
+ for (let d of this.value) {
+ selected = isSameDay(d, date);
+ if (selected) {
+ break;
+ }
+ }
+ return selected;
+ }
+ return false;
+ }
+
+ /**
+ * Check if a day is between two specific days
+ * @param {Date} start
+ * @param {Date} end
+ * @param {Date} day
+ * @return {boolean}
+ * */
+ public isDayBetween(start: Date, end: Date, day: Date): boolean {
+ if (start && end) {
+ return isAfter(day, start) && isBefore(day, end);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Check if the calendar day is a valid day
+ * @param {Date} date
+ * @return {Boolean}
+ * */
+ public isValidDay(date: Date): boolean {
+ let isValid = true;
+ if (this.disabledDates && this.disabledDates.length) {
+ for (let disabledDate of this.disabledDates) {
+ if (isSameDay(disabledDate, date)) {
+ return false;
+ }
+ }
+ }
+
+ if (isValid && this.disabledDays && this.disabledDays.length) {
+ let weekdayNum = getDay(date);
+ isValid = this.disabledDays.indexOf(weekdayNum) === -1;
+ }
+
+ if (isValid && this.min) {
+ isValid = isValid && !isBefore(date, startOfDay(this.min));
+ }
+
+ if (isValid && this.max) {
+ isValid = isValid && !isAfter(date, endOfDay(this.max));
+ }
+ return isValid;
+ }
+
+ /**
+ * Check if the month is current pickerMoment's month
+ * @param {Number} monthNum
+ * @return {Boolean}
+ * */
+ public isCurrentMonth(monthNum: number): boolean {
+ return getMonth(this.pickerMoment) === monthNum;
+ }
+
+ /**
+ * Check if the year is current pickerMoment's year
+ * @param {Number} yearNum
+ * @return {Boolean}
+ * */
+ public isCurrentYear(yearNum: any): boolean {
+ return getYear(this.pickerMoment) === yearNum||(getYear(this.pickerMoment)+"") === yearNum;
+ }
+
+ /**
+ * Change the dialog type
+ * @param {DialogType} type
+ * @return {void}
+ * */
+ public changeDialogType(type: DialogType): void {
+ if (this.dialogType === type) {
+ this.dialogType = DialogType.Date;
+ return;
+ } else {
+ this.dialogType = type;
+ }
+
+ if (this.dialogType === DialogType.Year) {
+ this.generateYearList();
+ }
+ }
+
+ /**
+ * Handle blur event on timer input
+ * @param {any} event
+ * @param {HTMLInputElement} input
+ * @param {string} type
+ * @param {number} modelValue
+ * @return {void}
+ * */
+ public onTimerInputBlur(event: any, input: HTMLInputElement, type: string, modelValue: number): void {
+ let val = +input.value;
+
+ if (this.disabled || val === modelValue) {
+ event.preventDefault();
+ return;
+ }
+
+ let done;
+ if (!isNaN(val)) {
+ switch (type) {
+ case 'hours':
+ if (this.hourFormat === '24' &&
+ val >= 0 && val <= 23) {
+ done = this.setHours(event, val);
+ } else if (this.hourFormat === '12'
+ && val >= 1 && val <= 12) {
+ if (this.meridianValue === 'AM' && val === 12) {
+ val = 0;
+ } else if (this.meridianValue === 'PM' && val < 12) {
+ val = val + 12;
+ }
+ done = this.setHours(event, val);
+ }
+ break;
+ case 'minutes':
+ if (val >= 0 && val <= 59) {
+ done = this.setMinutes(event, val);
+ }
+ break;
+ case 'seconds':
+ if (val >= 0 && val <= 59) {
+ done = this.setSeconds(event, val);
+ }
+ break;
+ }
+ }
+
+ if (!done) {
+ input.value = this.numFixedLenPipe.transform(modelValue, 2);
+ input.focus();
+ return;
+ }
+ event.preventDefault();
+ return;
+ }
+
+ /**
+ * Set value to null
+ * @param {any} event
+ * @return {void}
+ * */
+ public clearValue(event: any): void {
+ this.dialogClick = true;
+ this.updateModel(null);
+ this.updateTimer(this.value);
+ if (this.timepicker!==undefined) {
+ this.timepicker.settime(undefined);
+ }
+ this.updateFormattedValue();
+ if(this.value!==null)
+ {
+ event.date=new Date(this.value);
+ }
+ this.onConfirm.emit(event);
+ event.preventDefault();
+ }
+
+ /**
+ * Show the dialog
+ * @return {void}
+ * */
+ private show(): void {
+ this.alignDialog();
+ this.dialogVisible = true;
+ this.dialogType = DialogType.Date;
+ this.bindDocumentClickListener();
+ return;
+ }
+ private nextNav(event : any):void {
+ if( this.dialogType===DialogType.Date|| this.dialogType===DialogType.Month)
+ {
+ this.nextMonth(event);
+ }
+ else if(this.dialogType===DialogType.Year){
+ this.generateYearList('next');
+ }
+ }
+ private prevNav(event : any):void {
+ if( this.dialogType===DialogType.Date|| this.dialogType===DialogType.Month)
+ {
+ this.prevMonth(event);
+ }
+ else if(this.dialogType===DialogType.Year){
+ this.generateYearList('prev');
+ }
+ }
+ /**
+ * Hide the dialog
+ * @return {void}
+ * */
+ private hide(): void {
+ this.dialogVisible = false;
+ this.timepicker ? this.timepicker.closeProp() : 0;
+ this.unbindDocumentClickListener();
+ if(this.value!==null)
+ {
+ event["date"]=new Date(this.value);
+ }
+ this.onConfirm.emit(event);
+ return;
+ }
+
+ /**
+ * Set the dialog position
+ * @return {void}
+ * */
+ private alignDialog(): void {
+ let element = this.dialogElm.nativeElement;
+ let target = this.containerElm.nativeElement;
+ let elementDimensions = element.offsetParent ? {
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ } : this.getHiddenElementDimensions(element);
+ let targetHeight = target.offsetHeight;
+ let targetWidth = target.offsetWidth;
+ let targetOffset = target.getBoundingClientRect();
+ let viewport = this.getViewport();
+ let top, left;
+
+ if ((targetOffset.top + targetHeight + elementDimensions.height) > viewport.height) {
+ top = -1 * (elementDimensions.height);
+ if (targetOffset.top + top < 0) {
+ top = 0;
+ }
+ } else {
+ top = targetHeight;
+ }
+
+
+ if ((targetOffset.left + elementDimensions.width) > viewport.width) {
+ left = targetWidth - elementDimensions.width;
+ } else {
+ left = 0;
+ }
+
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ }
+
+ /**
+ * Bind click event on document
+ * @return {void}
+ * */
+ private bindDocumentClickListener(): void {
+ let firstClick = true;
+ if (!this.documentClickListener) {
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if (!firstClick && !this.dialogClick) {
+ this.hide();
+ }
+
+ firstClick = false;
+ this.dialogClick = false;
+ });
+ }
+ return;
+ }
+
+ /**
+ * Unbind click event on document
+ * @return {void}
+ * */
+ private unbindDocumentClickListener(): void {
+ if (this.documentClickListener) {
+ this.documentClickListener();
+ this.documentClickListener = null;
+ }
+ return;
+ }
+
+ /**
+ * Parse a object to Date object
+ * @param {any} val
+ * @return {Date}
+ * */
+ private parseToDate(val: any): Date {
+ if (!val) {
+ return;
+ }
+
+ let parsedVal;
+ if (typeof val === 'string') {
+ parsedVal = parse(val);
+ } else {
+ parsedVal = val;
+ }
+
+ return isValid(parsedVal) ? parsedVal : null;
+ }
+
+ /**
+ * Generate the calendar days array
+ * @return {void}
+ * */
+ private generateCalendar(): void {
+
+ if (!this.pickerMoment) {
+ return;
+ }
+
+ this.calendarDays = [];
+ let startDateOfMonth = startOfMonth(this.pickerMoment);
+ let startWeekdayOfMonth = getDay(startDateOfMonth);
+
+ let dayDiff = 0 - (startWeekdayOfMonth + (7 - this.locale.firstDayOfWeek)) % 7;
+
+ for (let i = 1; i < 7; i++) {
+ let week = [];
+ for (let j = 0; j < 7; j++) {
+ let date = addDays(startDateOfMonth, dayDiff);
+ let inOtherMonth = !isSameMonth(date, this.pickerMoment);
+ week.push({
+ date,
+ num: getDate(date),
+ today: isToday(date),
+ otherMonth: inOtherMonth,
+ hide: !this.showOtherMonths && inOtherMonth,
+ });
+ dayDiff += 1;
+ }
+ this.calendarDays.push(week);
+ }
+
+ this.pickerMonth = this.locale.monthNames[getMonth(this.pickerMoment)];
+ this.pickerYear = getYear(this.pickerMoment).toString();
+ }
+
+ /**
+ * Generate the calendar weekdays array
+ * */
+ private generateWeekDays(): void {
+ this.calendarWeekdays = [];
+ let dayIndex = this.locale.firstDayOfWeek;
+ for (let i = 0; i < 7; i++) {
+ this.calendarWeekdays.push(this.locale.dayNamesShort[dayIndex]);
+ dayIndex = (dayIndex === 6) ? 0 : ++dayIndex;
+ }
+ }
+
+ /**
+ * Generate the calendar month array
+ * @return {void}
+ * */
+ private generateMonthList(): void {
+ this.calendarMonths = [];
+ let monthIndex = 0;
+ for (let i = 0; i < 4; i++) {
+ let months = [];
+ for (let j = 0; j < 3; j++) {
+ months.push(this.locale.monthNamesShort[monthIndex]);
+ monthIndex += 1;
+ }
+ this.calendarMonths.push(months);
+ }
+ }
+
+ /**
+ * Generate the calendar year array
+ * @return {void}
+ * */
+ public generateYearList(dir?: string): void {
+
+ if (!this.pickerMoment) {
+ return;
+ }
+ let start;
+
+ if (dir === 'prev') {
+ start = +this.calendarYears[0][0] - 12;
+ if(start<0)
+ {
+ start=0;
+ }
+ } else if (dir === 'next') {
+ start = +this.calendarYears[3][2] + 1;
+ } else {
+ start = getYear(addYears(this.pickerMoment, -4));
+ }
+
+ for (let i = 0; i < 4; i++) {
+ let years = [];
+ for (let j = 0; j < 3; j++) {
+ let year = (start + i * 3 + j).toString();
+ years.push(year);
+ }
+ this.calendarYears[i] = years;
+ }
+ return;
+ }
+
+ /**
+ * Update the calendar
+ * @param {Date} value
+ * @return {void}
+ * */
+ private updateCalendar(value: Date): void {
+
+ // if the dateTime picker is only the timer,
+ // no need to update the update Calendar.
+ if (this.type === 'timer') {
+ return;
+ }
+
+ if (value && (!this.calendarDays || !isSameMonth(value, this.pickerMoment))) {
+ this.pickerMoment = setMonth(this.pickerMoment, getMonth(value));
+ this.pickerMoment = setYear(this.pickerMoment, getYear(value));
+ this.generateCalendar();
+ } else if (!value && !this.calendarDays) {
+ this.generateCalendar();
+ }
+ return;
+ }
+
+ /**
+ * Update the timer
+ * @param {Date} value
+ * @return {boolean}
+ * */
+ private updateTimer(value: Date): boolean {
+
+ // if the dateTime picker is only the calendar,
+ // no need to update the timer
+ if (this.type === 'calendar') {
+ return false;
+ }
+
+ if (!value) {
+ this.hourValue = null;
+ this.minValue = null;
+ this.secValue = null;
+ this.mtime.hour = 0;
+ this.mtime.minute = 0;
+ this.mtime.second = 0;
+ return true;
+ }
+ this.mtime.hour = value.getHours();
+ this.mtime.minute = value.getMinutes();
+ this.mtime.second = value.getSeconds();;
+
+ let time = value;
+ let hours = getHours(time);
+ if (this.hourFormat === '12') {
+ if (hours < 12 && hours > 0) {
+ this.hourValue = hours;
+ this.meridianValue = 'AM';
+ } else if (hours > 12) {
+ this.hourValue = hours - 12;
+ this.meridianValue = 'PM';
+ } else if (hours === 12) {
+ this.hourValue = 12;
+ this.meridianValue = 'PM';
+ } else if (hours === 0) {
+ this.hourValue = 12;
+ this.meridianValue = 'AM';
+ }
+ } else if (this.hourFormat === '24') {
+ this.hourValue = hours;
+ }
+
+ this.minValue = getMinutes(time);
+ this.secValue = getSeconds(time);
+ if(this.value!==undefined&&this.timepicker!==undefined)
+ {
+ this.timepicker.settime(new Date(this.value));
+ }
+ return true;
+ }
+
+ /**
+ * Update ngModel
+ * @param {Date} value
+ * @return {Boolean}
+ * */
+ private updateModel(value: Date | Date[]): boolean {
+ this.value = value;
+ if (this.dataType === 'date') {
+ this.onModelChange(this.value);
+ } else if (this.dataType === 'string') {
+ if (this.value && this.value.length) {
+ let formatted = [];
+ for (let v of this.value) {
+ if (v) {
+ formatted.push(format(v, this.dateFormat, { locale: this.locale.dateFns }));
+ } else {
+ formatted.push(null);
+ }
+ }
+ this.onModelChange(formatted);
+ } else {
+ this.onModelChange(format(this.value, this.dateFormat, { locale: this.locale.dateFns }));
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Update variable formattedValue
+ * @return {void}
+ * */
+ private updateFormattedValue(): void {
+ let formattedValue = '';
+
+ if (this.value) {
+ if (this.isSingleSelection()) {
+ formattedValue = format(this.value, this.dateFormat, { locale: this.locale.dateFns });
+ } else if (this.isRangeSelection()) {
+ let startDate = this.value[0];
+ let endDate = this.value[1];
+
+ formattedValue = format(startDate, this.dateFormat, { locale: this.locale.dateFns });
+
+ if (endDate) {
+ formattedValue += ' - ' + format(endDate, this.dateFormat, { locale: this.locale.dateFns });
+ } else {
+ formattedValue += ' - ' + this.dateFormat;
+ }
+ } else if (this.isMultiSelection()) {
+ for (let i = 0; i < this.value.length; i++) {
+ let dateAsString = format(this.value[i], this.dateFormat, { locale: this.locale.dateFns });
+ formattedValue += dateAsString;
+ if (i !== (this.value.length - 1)) {
+ formattedValue += ', ';
+ }
+ }
+ }
+ }
+
+ this.formattedValue = formattedValue;
+
+ return;
+ }
+
+ /**
+ * Set the time
+ * @param {Date} val
+ * @return {boolean}
+ * */
+ public setSelectedTime(val: Date): boolean {
+ let done;
+ if (this.isValidValue(val)) {
+ if (this.value instanceof Array) {
+ this.value[this.valueIndex] = val;
+ done = this.updateModel(this.value);
+ done = done && this.updateTimer(this.value[this.valueIndex]);
+ } else {
+ done = this.updateModel(val);
+ done = done && this.updateTimer(this.value);
+ }
+ this.updateFormattedValue();
+ } else {
+ this.onInvalid.emit({ originalEvent: event, value: val });
+ done = false;
+ }
+ return done;
+ }
+
+ private isValidValue(value: Date): boolean {
+ let isValid = true;
+
+ if (this.disabledDates && this.disabledDates.length) {
+ for (let disabledDate of this.disabledDates) {
+ if (isSameDay(disabledDate, value)) {
+ return false;
+ }
+ }
+ }
+
+ if (isValid && this.disabledDays && this.disabledDays.length) {
+ let weekdayNum = getDay(value);
+ isValid = this.disabledDays.indexOf(weekdayNum) === -1;
+ }
+
+ if (isValid && this.min) {
+ isValid = isValid && !isBefore(value, this.min);
+ }
+
+ if (isValid && this.max) {
+ isValid = isValid && !isAfter(value, this.max);
+ }
+
+ return isValid;
+ }
+
+ /**
+ * Check if the selection mode is 'single'
+ * @return {boolean}
+ * */
+ private isSingleSelection(): boolean {
+ return this.selectionMode === 'single';
+ }
+
+ /**
+ * Check if the selection mode is 'range'
+ * @return {boolean}
+ * */
+ private isRangeSelection(): boolean {
+ return this.selectionMode === 'range';
+ }
+
+ /**
+ * Check if the selection mode is 'multiple'
+ * @return {boolean}
+ * */
+ private isMultiSelection(): boolean {
+ return this.selectionMode === 'multiple';
+ }
+
+ private getHiddenElementDimensions(element: any): any {
+ let dimensions: any = {};
+ element.style.visibility = 'hidden';
+ element.style.display = 'block';
+ dimensions.width = element.offsetWidth;
+ dimensions.height = element.offsetHeight;
+ element.style.display = 'none';
+ element.style.visibility = 'visible';
+
+ return dimensions;
+ }
+
+ private getViewport(): any {
+ let win = window,
+ d = document,
+ e = d.documentElement,
+ g = d.getElementsByTagName('body')[0],
+ w = win.innerWidth || e.clientWidth || g.clientWidth,
+ h = win.innerHeight || e.clientHeight || g.clientHeight;
+
+ return { width: w, height: h };
+ }
+ public confirm() {
+ this.hide();
+ }
+ public seconds = false;
+ public mtime: any = { hour: 0, minute: 0, second: 0 };
+ public TimerChange(time: any) {
+ let value;
+ if (this.value) {
+ if (this.value.length) {
+ value = this.value[this.valueIndex];
+ } else {
+ value = this.value;
+ }
+ } else {
+ if (this.type === 'timer') {
+ value = new Date();
+ } else {
+ value = new Date();
+ }
+ }
+
+ if (this.disabled || !value) {
+ event.preventDefault();
+ return false;
+ }
+
+ let minute = time.minute;
+ let hour = time.hour;
+ let second = time.second;
+ this.minValue = minute;
+ this.hourValue = hour;
+ this.secValue = second;
+ let selectedTime = setMinutes(value, minute);
+ selectedTime = setHours(selectedTime, hour);
+ selectedTime = setSeconds(selectedTime, second);
+ let done = this.setSelectedTime(selectedTime);
+
+ // Focus the input and select its value when model updated
+
+ event.preventDefault();
+ return done;
+ }
+ private mouseIn :boolean = false;
+ private Mouseout(event:any)
+ {
+ this.mouseIn = false;
+ }
+ private Mouseover(event:any)
+ {
+ this.mouseIn = true;
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.module.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.module.ts
new file mode 100644
index 00000000..0511ad71
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.module.ts
@@ -0,0 +1,27 @@
+/**
+ * picker.module
+ */
+
+import { NgModule } from '@angular/core';
+
+import { DateTimePickerComponent } from './picker.component';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { NumberFixedLenPipe } from './numberedFixLen.pipe';
+import { NgbTimepickerr } from './timepicker';
+import { OesDaterangePopover, OesDaterangePopoverWindow } from './popover';
+import { OesDaterangePopoverConfig } from './popover-config';
+import { NgbTimepickerConfig } from './timepicker-config';
+import { PlxDateRangePickerComponent } from './pickerrange.component'
+export {DateTimePickerComponent} from './picker.component';
+
+@NgModule({
+ imports: [CommonModule, FormsModule],
+ exports: [DateTimePickerComponent, NgbTimepickerr, OesDaterangePopover,PlxDateRangePickerComponent],
+ declarations: [DateTimePickerComponent, NumberFixedLenPipe, NgbTimepickerr, OesDaterangePopoverWindow, OesDaterangePopover,PlxDateRangePickerComponent],
+ providers: [OesDaterangePopoverConfig, NgbTimepickerConfig, OesDaterangePopoverConfig],
+ entryComponents: [DateTimePickerComponent, OesDaterangePopoverWindow]
+})
+export class PlxDatePickerModule {
+}
+
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.html b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.html
new file mode 100644
index 00000000..2b1986fe
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.html
@@ -0,0 +1,14 @@
+<div style="width:100%;">
+<div class="datepickboxleft" >
+ <plx-datepicker [canClear]="canClear" [supportKeyboardInput]="supportKeyboardInput" [disabled]="disabled" [(ngModel)]="startDate" [showTime]="showTime" [showSeconds]="showSeconds" [timeOnly]="timeOnly" [dateFormat]="dateFormat" [locale]="locale" [minDate]="startMinDate" [maxDate]="_startMaxDate" (onConfirm)="EvonStartDateClosed($event)"
+ placeHolder="{{placeHolderStartDate}}"></plx-datepicker>
+</div>
+<div class="datepickboxto" >
+{{locale.to}}
+</div>
+<div class="datepickboxright" >
+ <plx-datepicker [canClear]="canClear" [supportKeyboardInput]="supportKeyboardInput" [disabled]="disabled" [(ngModel)]="endDate" [showTime]="showTime" [showSeconds]="showSeconds" [timeOnly]="timeOnly" [dateFormat]="dateFormat" [locale]="locale" [minDate]="_endMinDate" [maxDate]="endMaxDate" (onConfirm)="EvonEndDateClosed($event)"
+ placeHolder="{{placeHolderEndDate}}"></plx-datepicker>
+</div>
+<br/>
+</div> \ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.ts
new file mode 100644
index 00000000..a84e0987
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/pickerrange.component.ts
@@ -0,0 +1,162 @@
+/**
+ * picker.component
+ */
+
+import {
+ AfterViewInit,
+ Component, ElementRef, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output, Renderer2,
+ ViewChild
+} from '@angular/core';
+import {animate, state, style, transition, trigger} from '@angular/animations';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
+
+export interface LocaleSettings {
+ firstDayOfWeek?: number;
+ dayNames: string[];
+ dayNamesShort: string[];
+ monthNames: string[];
+ monthNamesShort: string[];
+ dateFns: any;
+}
+
+export enum DialogType {
+ Time,
+ Date,
+ Month,
+ Year,
+}
+
+@Component({
+ selector: 'plx-daterange-picker',
+ templateUrl: './pickerrange.component.html',
+ styleUrls: ['./pickerrange.component.css'],
+ providers: [],
+})
+
+export class PlxDateRangePickerComponent {
+ /*
+disabled boolean false 设置为true时input框不能输入
+minDate Date null 最小可选日期
+maxDate Date null 最大可选日期
+showTime boolean false 设置为true时显示时间选择器
+showSeconds boolean false 时间选择器显示秒
+timeOnly boolean false 设置为true时只显示时间选择器
+dateFormat string YYYY-MM-DD HH:mm 设置时间选择模式
+locale Object null 设置国际化对象,请参考国际化例子。
+改变组件时间*/
+
+ @Input() disabled : boolean = false;
+ @Input() showTime : boolean = false;
+ @Input() showSeconds : boolean = false;
+ @Input() timeOnly : boolean = false;
+ @Input() dateFormat : string = "YYYY-MM-DD HH:mm";
+ @Input() placeHolderStartDate : string = "";
+ @Input() placeHolderEndDate : string = "";
+ @Input() locale : any ={
+ firstDayOfWeek: 0,
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ dateFns: null,
+ confirm:'OK',
+ to:"to"
+ };
+ @Input() startDate : Date;
+ @Input() endDate : Date;
+ @Input() canClear: boolean = true;
+ @Input() startMinDate:Date;
+ @Input() endMaxDate:Date;
+ /**
+ * @default false
+ * @type {Boolean}
+ * */
+ @Input() supportKeyboardInput: boolean = false;
+ _startSetMaxDate:Date;
+ _startMaxDate:Date;
+ @Input()
+ set startMaxDate( date:Date)
+ {
+ this._startSetMaxDate=date;
+ this.BuildstartMaxDate();
+ }
+ _endSetMinDate:Date;
+ _endMinDate:Date;
+ @Input()
+ set endMinDate( date:Date)
+ {
+ this._endSetMinDate=date;
+ this.BuildendMinDate();
+ }
+ BuildstartMaxDate()
+ {
+ if(this._startSetMaxDate===undefined)
+ {
+ this._startMaxDate=this.endDate
+ return;
+ }
+ if(this.endDate!==undefined)
+ {
+ this._startMaxDate= this.endDate<this._startSetMaxDate?this.endDate:this._startSetMaxDate;
+ return;
+ }
+ this._startMaxDate=this._startSetMaxDate;
+ }
+ BuildendMinDate()
+ {
+ if(this._endSetMinDate===undefined)
+ {
+ this._endMinDate=this.startDate
+ return;
+ }
+ if(this.startDate!==undefined)
+ {
+ this._endMinDate= this.startDate>this._endSetMinDate?this.startDate:this._endSetMinDate;
+ return;
+ }
+ this._endMinDate=this._endSetMinDate;
+ }
+
+ @Output()
+ onStartDateClosed: EventEmitter<any> = new EventEmitter<any>();
+ @Output()
+ onEndDateClosed: EventEmitter<any> = new EventEmitter<any>();
+
+ EvonStartDateClosed(event : any)
+ {
+ this.BuildendMinDate();
+ if(this.startDate!==null)
+ {
+ event.date=new Date(this.startDate);
+ }
+ this.onStartDateClosed.emit(event);
+ event.preventDefault();
+ let dd= this;
+ return;
+ }
+
+
+ EvonEndDateClosed (event : any)
+ {
+
+ this.BuildstartMaxDate()
+ if(this.endDate!==null)
+ {
+ event.date=new Date(this.endDate);
+ }
+ this.onEndDateClosed.emit(event);
+ event.preventDefault();
+ let dd= this;
+ return;
+ }
+
+
+ public navigateTo (startDate: Date, endDate: Date)
+ {
+ this.startDate=startDate;
+ this.endDate = endDate;
+ }
+
+
+
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover-config.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover-config.ts
new file mode 100644
index 00000000..5ac773c5
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover-config.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+
+/**
+ * Configuration service for the OesDaterangePopover directive.
+ * You can inject this service, typically in your root component, and customize the values of its properties in
+ * order to provide default values for all the popovers used in the application.
+ */
+@Injectable()
+export class OesDaterangePopoverConfig {
+ public placement: 'top' | 'bottom' | 'left' | 'right' = 'top';
+ public triggers = 'click';
+ public container: string;
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover.ts
new file mode 100644
index 00000000..3d054120
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/popover.ts
@@ -0,0 +1,175 @@
+import {
+ Component,
+ Directive,
+ Input,
+ Output,
+ EventEmitter,
+ ChangeDetectionStrategy,
+ OnInit,
+ OnDestroy,
+ Injector,
+ Renderer,
+ ComponentRef,
+ ElementRef,
+ TemplateRef,
+ ViewContainerRef,
+ ComponentFactoryResolver,
+ NgZone
+} from '@angular/core';
+
+import { listenToTriggers } from './util/triggers';
+import { positionElements } from './util/positioning';
+import { PopupService } from './util/popup';
+import { OesDaterangePopoverConfig } from './popover-config';
+
+let nextId = 0;
+
+@Component({
+ selector: 'ngb-popover-window',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: { '[class]': '"popover show popover-" + placement', 'role': 'tooltip', '[id]': 'id' },
+ styles: [`
+
+ .popover-title,.popover-content{
+ background-color: #fff;
+ }
+ .popover-custom{
+ padding:9px 5px !important;
+ }
+
+
+ `],
+ template: `
+ <h3 class="popover-title">{{title}}</h3><div class="popover-content popover-custom"><ng-content></ng-content></div>
+ `
+})
+export class OesDaterangePopoverWindow {
+ @Input() public placement: 'top' | 'bottom' | 'left' | 'right' = 'top';
+ @Input() public title: string;
+ @Input() public id: string;
+}
+
+/**
+ * A lightweight, extensible directive for fancy oes-popover creation.
+ */
+@Directive({ selector: '[oesDaterangePopover]', exportAs: 'oesDaterangePopover' })
+export class OesDaterangePopover implements OnInit, OnDestroy {
+ /**
+ * Content to be displayed as oes-popover.
+ */
+ @Input() public oesDaterangePopover: string | TemplateRef<any>;
+ /**
+ * Title of a oes-popover.
+ */
+ @Input() public popoverTitle: string;
+ /**
+ * Placement of a oes-popover. Accepts: "top", "bottom", "left", "right"
+ */
+ @Input() public placement: 'top' | 'bottom' | 'left' | 'right';
+ /**
+ * Specifies events that should trigger. Supports a space separated list of event names.
+ */
+ @Input() public triggers: string;
+ /**
+ * A selector specifying the element the oes-popover should be appended to.
+ * Currently only supports "body".
+ */
+ @Input() public container: string;
+ /**
+ * Emits an event when the oes-popover is shown
+ */
+ @Output() public shown = new EventEmitter();
+ /**
+ * Emits an event when the oes-popover is hidden
+ */
+ @Output() public hidden = new EventEmitter();
+
+ private _OesDaterangePopoverWindowId = `ngb-popover-${nextId++}`;
+ private _popupService: PopupService<OesDaterangePopoverWindow>;
+ private _windowRef: ComponentRef<OesDaterangePopoverWindow>;
+ private _unregisterListenersFn;
+ private _zoneSubscription: any;
+
+ constructor(
+ private _elementRef: ElementRef, private _renderer: Renderer, injector: Injector,
+ componentFactoryResolver: ComponentFactoryResolver, viewContainerRef: ViewContainerRef, config: OesDaterangePopoverConfig,
+ ngZone: NgZone) {
+ this.placement = config.placement;
+ this.triggers = config.triggers;
+ this.container = config.container;
+ this._popupService = new PopupService<OesDaterangePopoverWindow>(
+ OesDaterangePopoverWindow, injector, viewContainerRef, _renderer, componentFactoryResolver);
+
+ this._zoneSubscription = ngZone.onStable.subscribe(() => {
+ if (this._windowRef) {
+ positionElements(
+ this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement,
+ this.container === 'body');
+ }
+ });
+ }
+
+ /**
+ * Opens an element’s oes-popover. This is considered a “manual” triggering of the oes-popover.
+ * The context is an optional value to be injected into the oes-popover template when it is created.
+ */
+ public open(context?: any) {
+ if (!this._windowRef) {
+ this._windowRef = this._popupService.open(this.oesDaterangePopover, context);
+ this._windowRef.instance.placement = this.placement;
+ this._windowRef.instance.title = this.popoverTitle;
+ this._windowRef.instance.id = this._OesDaterangePopoverWindowId;
+
+ this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-describedby', this._OesDaterangePopoverWindowId);
+
+ if (this.container === 'body') {
+ window.document.querySelector(this.container).appendChild(this._windowRef.location.nativeElement);
+ }
+
+ // we need to manually invoke change detection since events registered via
+ // Renderer::listen() are not picked up by change detection with the OnPush strategy
+ this._windowRef.changeDetectorRef.markForCheck();
+ this.shown.emit();
+ }
+ }
+
+ /**
+ * Closes an element’s oes-popover. This is considered a “manual” triggering of the oes-popover.
+ */
+ public close(): void {
+ if (this._windowRef) {
+ this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-describedby', null);
+ this._popupService.close();
+ this._windowRef = null;
+ this.hidden.emit();
+ }
+ }
+
+ /**
+ * Toggles an element’s oes-popover. This is considered a “manual” triggering of the oes-popover.
+ */
+ public toggle(): void {
+ if (this._windowRef) {
+ this.close();
+ } else {
+ this.open();
+ }
+ }
+
+ /**
+ * Returns whether or not the oes-popover is currently being shown
+ */
+ public isOpen(): boolean { return this._windowRef !== null; }
+
+ public ngOnInit() {
+ this._unregisterListenersFn = listenToTriggers(
+ this._renderer, this._elementRef.nativeElement, this.triggers, this.open.bind(this), this.close.bind(this),
+ this.toggle.bind(this));
+ }
+
+ public ngOnDestroy() {
+ this.close();
+ this._unregisterListenersFn();
+ this._zoneSubscription.unsubscribe();
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/time.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/time.ts
new file mode 100644
index 00000000..ab31a498
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/time.ts
@@ -0,0 +1,51 @@
+import { isNumber, toInteger } from './util/util';
+
+export class NgbTime {
+ public hour: number;
+ public minute: number;
+ public second: number;
+
+ constructor(hour?: number, minute?: number, second?: number) {
+ this.hour = toInteger(hour);
+ this.minute = toInteger(minute);
+ this.second = toInteger(second);
+ }
+
+ public changeHour(step = 1) { this.updateHour((isNaN(this.hour) ? 0 : this.hour) + step); }
+
+ public updateHour(hour: number) {
+ if (isNumber(hour)) {
+ this.hour = (hour < 0 ? 24 + hour : hour) % 24;
+ } else {
+ this.hour = NaN;
+ }
+ }
+
+ public changeMinute(step = 1) { this.updateMinute((isNaN(this.minute) ? 0 : this.minute) + step); }
+
+ public updateMinute(minute: number) {
+ if (isNumber(minute)) {
+ this.minute = minute % 60 < 0 ? 60 + minute % 60 : minute % 60;
+ this.changeHour(Math.floor(minute / 60));
+ } else {
+ this.minute = NaN;
+ }
+ }
+
+ public changeSecond(step = 1) { this.updateSecond((isNaN(this.second) ? 0 : this.second) + step); }
+
+ public updateSecond(second: number) {
+ if (isNumber(second)) {
+ this.second = second < 0 ? 60 + second % 60 : second % 60;
+ this.changeMinute(Math.floor(second / 60));
+ } else {
+ this.second = NaN;
+ }
+ }
+
+ public isValid(checkSecs = true) {
+ return isNumber(this.hour) && isNumber(this.minute) && (checkSecs ? isNumber(this.second) : true);
+ }
+
+ public toString() { return `${this.hour || 0}:${this.minute || 0}:${this.second || 0}`; }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker-config.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker-config.ts
new file mode 100644
index 00000000..8b752866
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker-config.ts
@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+
+/**
+ * Configuration service for the NgbTimepicker component.
+ * You can inject this service, typically in your root component, and customize the values of its properties in
+ * order to provide default values for all the timepickers used in the application.
+ */
+@Injectable()
+export class NgbTimepickerConfig {
+ public meridian = false;
+ public spinners = true;
+ public seconds = false;
+ public hourStep = 1;
+ public minuteStep = 1;
+ public secondStep = 1;
+ public disabled = false;
+ public readonlyInputs = false;
+ public size: 'small' | 'medium' | 'large' = 'medium';
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.less b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.less
new file mode 100644
index 00000000..60acfa6b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.less
@@ -0,0 +1,163 @@
+@import "../../assets/components/themes/default/theme.less";
+@import "../../assets/components/themes/common/plx-input.less";
+@import "../../assets/components/themes/common/plx-button.less";
+.oes-time-table .chevron::before {
+ border-style: solid;
+ border-width: 0.29em 0.29em 0 0;
+ content: '';
+ display: inline-block;
+ height: 0.69em;
+ left: 0.05em;
+ position: relative;
+ top: 0.15em;
+ transform: rotate(-45deg);
+ -webkit-transform: rotate(-45deg);
+ -ms-transform: rotate(-45deg);
+ vertical-align: middle;
+ width: 0.71em;
+}
+
+.oes-time-table .chevron.bottom:before {
+ top: -.3em;
+ -webkit-transform: rotate(135deg);
+ -ms-transform: rotate(135deg);
+ transform: rotate(135deg);
+}
+
+.oes-time-table .btn-link {
+ border: none!important;
+ cursor: pointer;
+ outline: 0;
+ display: block;
+}
+
+.oes-time-table .btn-link.disabled {
+ cursor: not-allowed;
+ opacity: .65;
+}
+
+.oes-time-control {
+ text-align: center;
+}
+
+.datapicker-form-control {
+ width: auto !important;
+ display: inline-block;
+}
+
+.oes-time-table .ict-stretch{
+
+ font-size: 8px;
+}
+
+.oes-time-table .ict-shrink{
+ font-size: 8px;
+}
+.time-pick-bk{
+ background-color: #fff;
+}
+
+.btn-link:focus, .btn-link:hover{
+ text-decoration: none;
+}
+.oes-time-control{
+ border: 0;
+ width: 30px !important;
+ padding: 3px 0;
+ margin: 0;
+ font-size: @font-size;
+}
+
+.oes-time-control:hover{
+ background-color: #e6e6e6;
+ color:#000;
+ cursor: pointer;
+}
+
+
+.oes-time-control-foucs-bk{
+ background-color: #00abff !important;
+ color:#fff!important;
+
+}
+
+.oes-time-separator{
+ margin: 0 -5px;
+}
+.oes-time-group,.oes-time-group:hover{
+
+ border-bottom: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ border-top: 1px solid #ccc;
+ border-radius: 0.2em;
+ }
+ .oes-time-btns,.oes-time-btns:hover{
+
+ border-bottom: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ border-top: 1px solid #ccc;
+ border-radius: 0.2em;
+ padding: 0 0 7px 0 !important;
+
+ }
+
+ .oes-time-btns-wrapper {
+ margin-top:-3px;
+ transform:scale(0.6,0.6);
+ }
+
+ .i18nTimeDes,.i18nTimeDes:hover{
+
+ padding: 0 5px 0px 0;
+
+ }
+
+ .oes-time-btn{
+
+ height: 5px;
+ }
+
+
+ .oes-time-table{
+ margin-bottom: 10px;
+ }
+
+.hour-table{
+
+ font-size:12px;
+}
+
+.hour-table td{
+
+ padding: 5px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ cursor: pointer;
+}
+.oes-time-btn-shrink{
+ position: relative;
+ top:-5px;
+ left:0px;
+ color:#CCC;
+}
+
+.oes-time-btn-stretch{
+ position: relative;
+ left:0px;
+ color:#CCC;
+}
+.owl-calendar-timer-invalid{
+ color: #acacac;
+}
+.owl-calendar-timer-selected{
+ background-color: #00abff;
+ color: #FFFFFF;
+ border-radius: 1.2em;
+}
+.hour-table td:not(.owl-calendar-timer-selected):not(.owl-calendar-timer-invalid):hover {
+ background-color: #ebf6fd;
+ color: #000000;
+ border-radius: 1.2em;
+}
+
+
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.ts
new file mode 100644
index 00000000..45dd7a4a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/timepicker.ts
@@ -0,0 +1,558 @@
+import { Component, Input, Output, forwardRef, OnChanges, EventEmitter, SimpleChanges, ViewChild } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+
+import { isNumber, padNumber, toInteger, isDefined } from './util/util';
+import { NgbTime } from './time';
+import { NgbTimepickerConfig } from './timepicker-config';
+
+const NGB_TIMEPICKER_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => NgbTimepickerr),
+ multi: true
+};
+
+/**
+ * A lightweight & configurable timepicker directive.
+ */
+@Component({
+ selector: 'oes-timepickerr',
+ styleUrls: ['./timepicker.less'],
+ template: `
+ <template #popContentHour>
+
+ <table class="hour-table">
+ <tbody>
+ <tr><td (click)="selectHour(hour,$event)" *ngFor="let hour of hours1 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedHour(hour), 'owl-calendar-timer-invalid': !isValidHour(hour)}">{{hour}}</td></tr>
+ <tr><td (click)="selectHour(hour,$event)" *ngFor="let hour of hours2 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedHour(hour), 'owl-calendar-timer-invalid': !isValidHour(hour)}">{{hour}}</td></tr>
+ <tr><td (click)="selectHour(hour,$event)" *ngFor="let hour of hours3 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedHour(hour), 'owl-calendar-timer-invalid': !isValidHour(hour)}">{{hour}}</td></tr>
+
+ </tbody>
+ </table>
+
+ </template>
+
+ <template #popContentMin>
+
+ <table class="hour-table">
+ <tbody>
+ <tr><td (click)="selectMin(minuter,$event)" *ngFor="let minuter of minute1 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedMin(minuter), 'owl-calendar-timer-invalid': !isValidMin(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectMin(minuter,$event)" *ngFor="let minuter of minute2 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedMin(minuter), 'owl-calendar-timer-invalid': !isValidMin(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectMin(minuter,$event)" *ngFor="let minuter of minute3 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedMin(minuter), 'owl-calendar-timer-invalid': !isValidMin(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectMin(minuter,$event)" *ngFor="let minuter of minute4 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedMin(minuter), 'owl-calendar-timer-invalid': !isValidMin(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectMin(minuter,$event)" *ngFor="let minuter of minute5 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedMin(minuter), 'owl-calendar-timer-invalid': !isValidMin(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectMin(minuter,$event)" *ngFor="let minuter of minute6 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedMin(minuter), 'owl-calendar-timer-invalid': !isValidMin(minuter)}">{{minuter}}</td></tr>
+
+ </tbody>
+ </table>
+
+ </template>
+
+ <template #popContentSecond>
+ <table class="hour-table">
+ <tbody>
+ <tr><td (click)="selectSecond(minuter,$event)" *ngFor="let minuter of minute1 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedSec(minuter), 'owl-calendar-timer-invalid': !isValidSec(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectSecond(minuter,$event)" *ngFor="let minuter of minute2 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedSec(minuter), 'owl-calendar-timer-invalid': !isValidSec(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectSecond(minuter,$event)" *ngFor="let minuter of minute3 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedSec(minuter), 'owl-calendar-timer-invalid': !isValidSec(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectSecond(minuter,$event)" *ngFor="let minuter of minute4 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedSec(minuter), 'owl-calendar-timer-invalid': !isValidSec(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectSecond(minuter,$event)" *ngFor="let minuter of minute5 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedSec(minuter), 'owl-calendar-timer-invalid': !isValidSec(minuter)}">{{minuter}}</td></tr>
+ <tr><td (click)="selectSecond(minuter,$event)" *ngFor="let minuter of minute6 " [ngClass]=" {'owl-calendar-timer-selected': isSelectedSec(minuter), 'owl-calendar-timer-invalid': !isValidSec(minuter)}">{{minuter}}</td></tr>
+ </tbody>
+ </table>
+ </template>
+ <table class="oes-time-table">
+ <tr>
+ <td class="i18nTimeDes">
+ {{i18nTimeDes}}
+ </td>
+ <td class="oes-time-group">
+ <input placement="top" style="padding-left:1px;padding-right:1px;border: 0; width: 30px !important;padding: 3px 0; margin: 0; font-size: 12px;"
+ [oesDaterangePopover]="popContentHour" #propHour="oesDaterangePopover"
+ #hourItem type="text" (focus)="selectItem('hour')"
+ [ngClass]="{'oes-time-control-foucs-bk': currSelectedItem === 'hour'}"
+ class="form-control datapicker-form-control form-control-sm oes-time-control " maxlength="2" size="2" placeholder="HH"
+ [value]="formatHour(model?.hour)" (change)="updateHour($event.target.value)"
+ [readonly]="readonlyInputs" [disabled]="disabled">
+ <span class="oes-time-separator">&nbsp;:&nbsp;</span>
+ <input
+ [oesDaterangePopover]="popContentMin" #propMin="oesDaterangePopover"
+ #minuteItem type="text"
+ (focus)="selectItem('minute')" style="padding-left:1px;padding-right:1px;border: 0; width: 30px !important;padding: 3px 0; margin: 0; font-size: 12px;"
+ [ngClass]="{'oes-time-control-foucs-bk': currSelectedItem === 'minute'}"
+ class="form-control datapicker-form-control form-control-sm oes-time-control" maxlength="2" size="2" placeholder="MM"
+ [value]="formatMinSec(model?.minute)" (change)="updateMinute($event.target.value)"
+ [readonly]="readonlyInputs" [disabled]="disabled">
+ <span *ngIf="showSecondsTimer" class="oes-time-separator">&nbsp;:&nbsp;</span>
+ <input *ngIf="showSecondsTimer" style="padding-left:1px;padding-right:1px;border: 0; width: 30px !important;padding: 3px 0; margin: 0; font-size: 12px;"
+ [oesDaterangePopover]="popContentSecond" #propSecond="oesDaterangePopover"
+ #secondItem type="text"
+ (focus)="selectItem('second')"
+ [ngClass]="{'oes-time-control-foucs-bk': currSelectedItem === 'second'}"
+ class="form-control datapicker-form-control form-control-sm oes-time-control" maxlength="2" size="2" placeholder="SS"
+ [value]="formatMinSec(model?.second)" (change)="updateSecond($event.target.value)"
+ [readonly]="readonlyInputs" [disabled]="disabled">
+ </td>
+
+ <td class="text-center oes-time-btns">
+ <div class="oes-time-btns-wrapper">
+ <button type="button" class="btn-link btn-sm oes-time-btn oes-time-btn-shrink " (click)="changeTime(hourStep)"
+ [disabled]="disabled" [class.disabled]="disabled">
+ <span class="ict-shrink"></span>
+ </button>
+ <button type="button" class="btn-link btn-sm oes-time-btn oes-time-btn-stretch" (click)="changeTime(-hourStep)"
+ [disabled]="disabled" [class.disabled]="disabled">
+ <span class="ict-stretch"></span>
+ </button>
+ </div>
+ </td>
+ </tr>
+ </table>
+ `,
+ providers: [NGB_TIMEPICKER_VALUE_ACCESSOR]
+})
+export class NgbTimepickerr implements ControlValueAccessor,
+ OnChanges {
+ public disabled: boolean;
+ public model: NgbTime;
+ public datemodel: Date;
+ @Output() TimerChange = new EventEmitter<NgbTime>();
+ /**
+ * Whether to display 12H or 24H mode.
+ */
+ @Input() public meridian: boolean;
+
+ /**
+ * Whether to display the spinners above and below the inputs.
+ */
+ @Input() public spinners: boolean;
+
+ /**
+ * Whether to display seconds input.
+ */
+ @Input() public seconds: boolean;
+
+ /**
+ * Number of hours to increase or decrease when using a button.
+ */
+ @Input() public hourStep: number;
+
+ /**
+ * Number of minutes to increase or decrease when using a button.
+ */
+ @Input() public minuteStep: number;
+
+ /**
+ * Number of seconds to increase or decrease when using a button.
+ */
+ @Input() public secondStep: number;
+
+ /**
+ * To make timepicker readonly
+ */
+ @Input() public readonlyInputs: boolean;
+
+ /**
+ * To set the size of the inputs and button
+ */
+ @Input() public size: 'small' | 'medium' | 'large';
+
+
+
+ private _max: Date;
+ @Input()
+ get max() {
+ return this._max;
+ }
+
+ set max(val: Date) {
+ this._max = val;
+ }
+ private _min: Date;
+ @Input()
+ get min() {
+ return this._min;
+ }
+
+ set min(val: Date) {
+ this._min = val;
+ }
+
+ /**
+ * Whether to show the second's timer
+ * @default false
+ * @type {Boolean}
+ * */
+ @Input() showSecondsTimer: boolean;
+ /**
+ * datePicker的国际化描述
+ */
+ @Input() public i18nTimeDes: string;
+
+ @ViewChild('hourItem') public hourItem;
+
+ @ViewChild('minuteItem') public minuteItem;
+ @ViewChild('secondItem') public secondItem;
+
+ @ViewChild('propHour') public propHour;
+
+ @ViewChild('propMin') public propMin;
+ @ViewChild('propSecond') public propSecond;
+
+ public currSelectedItem: 'hour' | 'minute' | 'second';
+
+ public hours1 = ['00', '01', '02', '03', '04', '05', '06', '07'];
+
+ public hours2 = ['08', '09', '10', '11', '12', '13', '14', '15'];
+
+ public hours3 = ['16', '17', '18', '19', '20', '21', '22', '23'];
+
+ public minute1 = ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09'];
+
+ public minute2 = ['10', '11', '12', '13', '14', '15', '16', '17', '18', '19'];
+
+ public minute3 = ['20', '21', '22', '23', '24', '25', '26', '27', '28', '29'];
+
+ public minute4 = ['30', '31', '32', '33', '34', '35', '36', '37', '38', '39'];
+
+ public minute5 = ['40', '41', '42', '43', '44', '45', '46', '47', '48', '49'];
+
+ public minute6 = ['50', '51', '52', '53', '54', '55', '56', '57', '58', '59'];
+
+ constructor(config: NgbTimepickerConfig) {
+ this.meridian = config.meridian;
+ this.spinners = config.spinners;
+ this.seconds = config.seconds;
+ this.hourStep = config.hourStep;
+ this.minuteStep = config.minuteStep;
+ this.secondStep = config.secondStep;
+ this.disabled = config.disabled;
+ this.readonlyInputs = config.readonlyInputs;
+ this.size = config.size;
+ }
+
+ public onChange = (_: any) => {
+ // TO DO
+ }
+ public onTouched = () => {
+ // TO DO
+ }
+ public settime(date : Date)
+ {
+ if(date!=null&&date!==undefined)
+ {
+ if(this._max!==undefined&&this._max.getTime()<date.getTime())
+ {
+ date.setHours(this._max.getHours());
+ date.setMinutes(this._max.getMinutes());
+ date.setSeconds(this._max.getSeconds());
+ this.TimerChange.emit(new NgbTime(date.getHours(),date.getMinutes(),date.getSeconds()));
+ }
+ if(this._min!==undefined&&this._min.getTime()>date.getTime())
+ {
+ date.setHours(this._min.getHours());
+ date.setMinutes(this._min.getMinutes());
+ date.setSeconds(this._min.getSeconds());
+ this.TimerChange.emit(new NgbTime(date.getHours(),date.getMinutes(),date.getSeconds()));
+ }
+ }
+ if(date!==null&&date!==undefined)
+ {
+ let temptime = new NgbTime(date.getHours(),date.getMinutes(),date.getSeconds())
+ this.model = temptime;
+ this.datemodel = date;
+ }
+ else
+ {
+ let temptime = new NgbTime(0,0,0)
+ this.model = temptime;
+ this.datemodel = date;
+ }
+
+ }
+ public selectHour(hour: string, event) {
+ if(!this.isValidHour(parseInt(hour)))
+ {
+ return;
+ }
+ this.model.hour = parseInt(hour);
+ this.propHour.close();
+ this.propagateModelChange();
+ event.stopPropagation();
+ }
+
+ public selectMin(minute: string, event) {
+ if(!this.isValidMin(parseInt(minute)))
+ {
+ return;
+ }
+ this.model.minute = parseInt(minute);
+ this.propMin.close();
+ this.propagateModelChange();
+
+ event.stopPropagation();
+ }
+ public selectSecond(second: string, event) {
+ if(!this.isValidSec(parseInt(second)))
+ {
+ return;
+ }
+ this.model.second = parseInt(second);
+ this.propSecond.close();
+ this.propagateModelChange();
+
+ event.stopPropagation();
+ }
+
+ /**
+ * ###描述
+ * 单击小时或者分钟选项时触发的事件
+ *
+ *
+ * */
+
+ public selectItem(item: 'hour' | 'minute' | 'second') {
+
+ // 切换选中项
+ this.currSelectedItem = item;
+
+ if (item === 'hour') {
+
+ this.propMin?this.propMin.close():0;
+ this.propSecond?this.propSecond.close():0;
+ } else if (item === 'minute') {
+ this.propHour?this.propHour.close():0;
+ this.propSecond?this.propSecond.close():0;
+ } else if (item === 'second') {
+ this.propHour?this.propHour.close():0;
+ this.propMin?this.propMin.close():0;
+ }
+
+ this.minuteItem.nativeElement.blur();
+ this.hourItem.nativeElement.blur();
+
+ this.secondItem?this.secondItem.nativeElement.blur():0;
+
+ // 弹出时间选择列表
+ }
+
+ public changeTime(stepTime) {
+
+ if (this.currSelectedItem === 'hour') { // 如果当前选中的是小时
+
+ this.changeHour(stepTime);
+
+ } else if (this.currSelectedItem === 'minute') {
+
+ this.changeMinute(stepTime);
+ } else if (this.currSelectedItem === 'second') {
+
+ this.changeSecond(stepTime);
+ }
+
+ }
+
+
+ public writeValue(value) {
+ this.model = value ? new NgbTime(value.hour, value.minute, value.second) : new NgbTime();
+ if (!this.seconds && (!value || !isNumber(value.second))) {
+ this.model.second = 0;
+ }
+ }
+
+ public registerOnChange(fn: (value: any) => any): void { this.onChange = fn; }
+
+ public registerOnTouched(fn: () => any): void { this.onTouched = fn; }
+
+ public setDisabledState(isDisabled: boolean) { this.disabled = isDisabled; }
+
+ public changeHour(step: number) {
+ let newDate = new Date(this.datemodel.getTime());
+ newDate.setHours(newDate.getHours()+step);
+ if(!this.isValidDate(newDate))
+ {
+ return;
+ }
+ this.model.changeHour(step);
+ this.propagateModelChange();
+ }
+
+ public changeMinute(step: number) {
+ let newDate = new Date(this.datemodel.getTime());
+ newDate.setMinutes(newDate.getMinutes()+step);
+ if(!this.isValidDate(newDate))
+ {
+ return;
+ }
+ this.model.changeMinute(step);
+ this.propagateModelChange();
+ }
+
+ public changeSecond(step: number) {
+ let newDate = new Date(this.datemodel.getTime());
+ newDate.setSeconds(newDate.getSeconds()+step);
+ if(!this.isValidDate(newDate))
+ {
+ return;
+ }
+ this.model.changeSecond(step);
+ this.propagateModelChange();
+ }
+
+ public updateHour(newVal: string) {
+ this.model.updateHour(toInteger(newVal));
+ this.propagateModelChange();
+ }
+
+ public updateMinute(newVal: string) {
+ this.model.updateMinute(toInteger(newVal));
+ this.propagateModelChange();
+ }
+
+ public updateSecond(newVal: string) {
+ this.model.updateSecond(toInteger(newVal));
+ this.propagateModelChange();
+ }
+
+ public toggleMeridian() {
+ if (this.meridian) {
+ this.changeHour(12);
+ }
+ }
+
+ public formatHour(value: number) {
+ if (isNumber(value)) {
+ if (this.meridian) {
+ return padNumber(value % 12 === 0 ? 12 : value % 12);
+ } else {
+ return padNumber(value % 24);
+ }
+ } else {
+ return padNumber(NaN);
+ }
+ }
+
+ public formatMinSec(value: number) { return padNumber(value); }
+
+ public setFormControlSize() { return { 'form-control-sm': this.size === 'small', 'form-control-lg': this.size === 'large' }; }
+
+ public setButtonSize() { return { 'btn-sm': this.size === 'small', 'btn-lg': this.size === 'large' }; }
+
+
+ public ngOnChanges(changes: SimpleChanges): void {
+ if (changes['seconds'] && !this.seconds && this.model && !isNumber(this.model.second)) {
+ this.model.second = 0;
+ this.propagateModelChange(false);
+ }
+ }
+
+ private propagateModelChange(touched = true) {
+ this.TimerChange.emit(this.model);
+ if (touched) {
+ this.onTouched();
+ }
+ if (this.model.isValid(this.seconds)) {
+ this.onChange({ hour: this.model.hour, minute: this.model.minute, second: this.model.second });
+ } else {
+ this.onChange(null);
+ }
+ }
+ public closeProp()
+ {
+
+ if(this.propSecond!==undefined)
+ {
+ this.propSecond.close();
+ }
+ if(this.propMin!==undefined)
+ {
+ this.propMin.close();
+ }
+ if(this.propHour!==undefined)
+ {
+ this.propHour.close();
+ }
+ }
+ private isValidDate(date: Date)
+ {
+ let isValid = true;
+ if (isValid && this._min!==undefined&&this._min!==null) {
+ isValid = date.getTime()>=this._min.getTime();
+ }
+ if (isValid && this._max!==undefined&&this._max!==null) {
+ isValid = date.getTime()<=this._max.getTime();
+ }
+ return isValid;
+ }
+ private isSelectedMin(strvalue:any): boolean {
+ let value = parseInt(strvalue);
+ if(this.model!==null&&this.model!==undefined)
+ {
+ return this.model.minute === value;
+ }
+ else
+ {
+ return false;
+ }
+}
+ private isValidMin(strvalue:any): boolean {
+ let value = parseInt(strvalue);
+ let nowdate = new Date();
+ if(this.datemodel===undefined||this.datemodel===null)
+ {
+ }
+ else
+ {
+ nowdate = new Date(this.datemodel);
+ }
+ nowdate.setMinutes(value);
+ return this.isValidDate(nowdate);
+}
+private isSelectedSec(strvalue:any): boolean {
+ let value = parseInt(strvalue);
+ if(this.model!==null&&this.model!==undefined)
+ {
+ return this.model.second === value;
+ }
+ else
+ {
+ return false;
+ }
+}
+private isValidSec(strvalue:any): boolean {
+ let value = parseInt(strvalue);
+ let nowdate = new Date();
+ if(this.datemodel===undefined||this.datemodel===null)
+ {
+ }
+ else
+ {
+ nowdate = new Date(this.datemodel);
+ }
+ nowdate.setSeconds(value);
+ return this.isValidDate(nowdate);
+}
+private isSelectedHour(strvalue:any): boolean {
+ let value = parseInt(strvalue);
+ if(this.model!==null&&this.model!==undefined)
+ {
+ return this.model.hour === value;
+ }
+ else
+ {
+ return false;
+ }
+}
+private isValidHour(strvalue:any): boolean {
+ debugger;
+ let value = parseInt(strvalue);
+ let nowdate = new Date();
+ if(this.datemodel===undefined||this.datemodel===null)
+ {
+ }
+ else
+ {
+ nowdate = new Date(this.datemodel);
+ }
+ nowdate.setHours(value);
+ return this.isValidDate(nowdate);
+}
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/popup.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/popup.ts
new file mode 100644
index 00000000..56c26d62
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/popup.ts
@@ -0,0 +1,58 @@
+import {
+ Injector,
+ TemplateRef,
+ ViewRef,
+ ViewContainerRef,
+ Renderer,
+ ComponentRef,
+ ComponentFactory,
+ ComponentFactoryResolver
+} from '@angular/core';
+
+export class ContentRef {
+ constructor(public nodes: any[], public viewRef?: ViewRef, public componentRef?: ComponentRef<any>) {}
+}
+
+export class PopupService<T> {
+ private _windowFactory: ComponentFactory<T>;
+ private _windowRef: ComponentRef<T>;
+ private _contentRef: ContentRef;
+
+ constructor(
+ type: any, private _injector: Injector, private _viewContainerRef: ViewContainerRef, private _renderer: Renderer,
+ componentFactoryResolver: ComponentFactoryResolver) {
+ this._windowFactory = componentFactoryResolver.resolveComponentFactory<T>(type);
+ }
+
+ public open(content?: string | TemplateRef<any>, context?: any): ComponentRef<T> {
+ if (!this._windowRef) {
+ this._contentRef = this._getContentRef(content, context);
+ this._windowRef =
+ this._viewContainerRef.createComponent(this._windowFactory, 0, this._injector, this._contentRef.nodes);
+ }
+ return this._windowRef;
+ }
+
+ public close() {
+ if (this._windowRef) {
+ this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._windowRef.hostView));
+ this._windowRef = null;
+
+ if (this._contentRef.viewRef) {
+ this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._contentRef.viewRef));
+ this._contentRef = null;
+ }
+ }
+ }
+
+ private _getContentRef(content: string | TemplateRef<any>, context?: any): ContentRef {
+ if (!content) {
+ return new ContentRef([]);
+ } else if (content instanceof TemplateRef) {
+ const viewRef = this._viewContainerRef.createEmbeddedView(<TemplateRef<T>>content, context);
+ return new ContentRef([viewRef.rootNodes], viewRef);
+ } else {
+ return new ContentRef([[this._renderer.createText(null, `${content}`)]]);
+ }
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/positioning.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/positioning.ts
new file mode 100644
index 00000000..ed9005c1
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/positioning.ts
@@ -0,0 +1,153 @@
+// previous version:
+// https://github.com/angular-ui/bootstrap/blob/07c31d0731f7cb068a1932b8e01d2312b796b4ec/src/position/position.js
+export class Positioning {
+ private getStyle(element: HTMLElement, prop: string): string { return window.getComputedStyle(element)[prop]; }
+
+ private isStaticPositioned(element: HTMLElement): boolean {
+ return (this.getStyle(element, 'position') || 'static') === 'static';
+ }
+
+ private offsetParent(element: HTMLElement): HTMLElement {
+ let offsetParentEl = <HTMLElement>element.offsetParent || document.documentElement;
+
+ while (offsetParentEl && offsetParentEl !== document.documentElement && this.isStaticPositioned(offsetParentEl)) {
+ offsetParentEl = <HTMLElement>offsetParentEl.offsetParent;
+ }
+
+ return offsetParentEl || document.documentElement;
+ }
+
+ public position(element: HTMLElement, round = true): ClientRect {
+ let elPosition: ClientRect;
+ let parentOffset: ClientRect = {width: 0, height: 0, top: 0, bottom: 0, left: 0, right: 0};
+
+ if (this.getStyle(element, 'position') === 'fixed') {
+ elPosition = element.getBoundingClientRect();
+ } else {
+ const offsetParentEl = this.offsetParent(element);
+
+ elPosition = this.offset(element, false);
+
+ if (offsetParentEl !== document.documentElement) {
+ parentOffset = this.offset(offsetParentEl, false);
+ }
+
+ parentOffset.top += offsetParentEl.clientTop;
+ parentOffset.left += offsetParentEl.clientLeft;
+ }
+
+ elPosition.top -= parentOffset.top;
+ elPosition.bottom -= parentOffset.top;
+ elPosition.left -= parentOffset.left;
+ elPosition.right -= parentOffset.left;
+
+ if (round) {
+ elPosition.top = Math.round(elPosition.top);
+ elPosition.bottom = Math.round(elPosition.bottom);
+ elPosition.left = Math.round(elPosition.left);
+ elPosition.right = Math.round(elPosition.right);
+ }
+
+ return elPosition;
+ }
+
+ public offset(element: HTMLElement, round = true): ClientRect {
+ const elBcr = element.getBoundingClientRect();
+ const viewportOffset = {
+ top: window.pageYOffset - document.documentElement.clientTop,
+ left: window.pageXOffset - document.documentElement.clientLeft
+ };
+
+ let elOffset = {
+ height: elBcr.height || element.offsetHeight,
+ width: elBcr.width || element.offsetWidth,
+ top: elBcr.top + viewportOffset.top,
+ bottom: elBcr.bottom + viewportOffset.top,
+ left: elBcr.left + viewportOffset.left,
+ right: elBcr.right + viewportOffset.left
+ };
+
+ if (round) {
+ elOffset.height = Math.round(elOffset.height);
+ elOffset.width = Math.round(elOffset.width);
+ elOffset.top = Math.round(elOffset.top);
+ elOffset.bottom = Math.round(elOffset.bottom);
+ elOffset.left = Math.round(elOffset.left);
+ elOffset.right = Math.round(elOffset.right);
+ }
+
+ return elOffset;
+ }
+
+ public positionElements(hostElement: HTMLElement, targetElement: HTMLElement, placement: string, appendToBody?: boolean):
+ ClientRect {
+ const hostElPosition = appendToBody ? this.offset(hostElement, false) : this.position(hostElement, false);
+ const shiftWidth: any = {
+ left: hostElPosition.left,
+ left2: (hostElPosition.left - 85),
+ center: hostElPosition.left + hostElPosition.width / 2 - targetElement.offsetWidth / 2,
+ right: hostElPosition.left + hostElPosition.width
+ };
+ const shiftHeight: any = {
+ top: hostElPosition.top,
+ center: hostElPosition.top + hostElPosition.height / 2 - targetElement.offsetHeight / 2,
+ bottom: hostElPosition.top + hostElPosition.height
+ };
+ const targetElBCR = targetElement.getBoundingClientRect();
+ const placementPrimary = placement.split('-')[0] || 'top';
+ const placementSecondary = placement.split('-')[1] || 'center';
+
+ let targetElPosition: ClientRect = {
+ height: targetElBCR.height || targetElement.offsetHeight,
+ width: targetElBCR.width || targetElement.offsetWidth,
+ top: 0,
+ bottom: targetElBCR.height || targetElement.offsetHeight,
+ left: 0,
+ right: targetElBCR.width || targetElement.offsetWidth
+ };
+
+ switch (placementPrimary) {
+ case 'top':
+ targetElPosition.top = hostElPosition.top - targetElement.offsetHeight;
+ targetElPosition.bottom += hostElPosition.top - targetElement.offsetHeight;
+ targetElPosition.left = shiftWidth[placementSecondary];
+ targetElPosition.right += shiftWidth[placementSecondary];
+ break;
+ case 'bottom':
+ targetElPosition.top = shiftHeight[placementPrimary];
+ targetElPosition.bottom += shiftHeight[placementPrimary];
+ targetElPosition.left = shiftWidth[placementSecondary];
+ targetElPosition.right += shiftWidth[placementSecondary];
+ break;
+ case 'left':
+ targetElPosition.top = shiftHeight[placementSecondary];
+ targetElPosition.bottom += shiftHeight[placementSecondary];
+ targetElPosition.left = hostElPosition.left - targetElement.offsetWidth;
+ targetElPosition.right += hostElPosition.left - targetElement.offsetWidth;
+ break;
+ case 'right':
+ targetElPosition.top = shiftHeight[placementSecondary];
+ targetElPosition.bottom += shiftHeight[placementSecondary];
+ targetElPosition.left = shiftWidth[placementPrimary];
+ targetElPosition.right += shiftWidth[placementPrimary];
+ break;
+
+ }
+
+ targetElPosition.top = Math.round(targetElPosition.top);
+ targetElPosition.bottom = Math.round(targetElPosition.bottom);
+ targetElPosition.left = Math.round(targetElPosition.left);
+ targetElPosition.right = Math.round(targetElPosition.right);
+
+ return targetElPosition;
+ }
+}
+
+const positionService = new Positioning();
+export function positionElements(
+ hostElement: HTMLElement, targetElement: HTMLElement, placement: string, appendToBody?: boolean): void {
+ const pos = positionService.positionElements(hostElement, targetElement, placement, appendToBody);
+
+ targetElement.style.top = `${pos.top}px`;
+ targetElement.style.left = `${pos.left}px`;
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/triggers.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/triggers.ts
new file mode 100644
index 00000000..8197de5b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/triggers.ts
@@ -0,0 +1,62 @@
+export class Trigger {
+ constructor(public open: string, public close?: string) {
+ if (!close) {
+ this.close = open;
+ }
+ }
+
+ public isManual() { return this.open === 'manual' || this.close === 'manual'; }
+}
+
+const DEFAULT_ALIASES = {
+ hover: ['mouseenter', 'mouseleave']
+};
+
+export function parseTriggers(triggers: string, aliases = DEFAULT_ALIASES): Trigger[] {
+ const trimmedTriggers = (triggers || '').trim();
+
+ if (trimmedTriggers.length === 0) {
+ return [];
+ }
+
+ const parsedTriggers = trimmedTriggers.split(/\s+/).map(trigger => trigger.split(':')).map((triggerPair) => {
+ let alias = aliases[triggerPair[0]] || triggerPair;
+ return new Trigger(alias[0], alias[1]);
+ });
+
+ const manualTriggers = parsedTriggers.filter(triggerPair => triggerPair.isManual());
+
+ if (manualTriggers.length > 1) {
+ throw 'Triggers parse error: only one manual trigger is allowed';
+ }
+
+ if (manualTriggers.length === 1 && parsedTriggers.length > 1) {
+ throw 'Triggers parse error: manual trigger can\'t be mixed with other triggers';
+ }
+
+ return parsedTriggers;
+}
+
+const noopFn = () => {
+ // TO DO
+};
+
+export function listenToTriggers(renderer: any, nativeElement: any, triggers: string, openFn, closeFn, toggleFn) {
+ const parsedTriggers = parseTriggers(triggers);
+ const listeners = [];
+
+ if (parsedTriggers.length === 1 && parsedTriggers[0].isManual()) {
+ return noopFn;
+ }
+
+ parsedTriggers.forEach((trigger: Trigger) => {
+ if (trigger.open === trigger.close) {
+ listeners.push(renderer.listen(nativeElement, trigger.open, toggleFn));
+ } else {
+ listeners.push(
+ renderer.listen(nativeElement, trigger.open, openFn), renderer.listen(nativeElement, trigger.close, closeFn));
+ }
+ });
+
+ return () => { listeners.forEach(unsubscribeFn => unsubscribeFn()); };
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/util.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/util.ts
new file mode 100644
index 00000000..fcabe960
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/util/util.ts
@@ -0,0 +1,39 @@
+export function toInteger(value: any): number {
+ return parseInt(`${value}`, 10);
+}
+
+export function toString(value: any): string {
+ return (value !== undefined && value !== null) ? `${value}` : '';
+}
+
+export function getValueInRange(value: number, max: number, min = 0): number {
+ return Math.max(Math.min(value, max), min);
+}
+
+export function isString(value: any): boolean {
+ return typeof value === 'string';
+}
+
+export function isNumber(value: any): boolean {
+ return !isNaN(toInteger(value));
+}
+
+export function isInteger(value: any): boolean {
+ return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
+}
+
+export function isDefined(value: any): boolean {
+ return value !== undefined && value !== null;
+}
+
+export function padNumber(value: number) {
+ if (isNumber(value)) {
+ return value > 9? `${value}`.slice(-2):'0' + `${value}`.slice(-2);
+ } else {
+ return '';
+ }
+}
+
+export function regExpEscape(text) {
+ return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+}