From 10556f8fafee862fc1c53316148d44c0b0b97902 Mon Sep 17 00:00:00 2001 From: yuvalbl Date: Tue, 6 Mar 2018 17:46:25 +0200 Subject: [PATCH 01/11] feat(intervals): - add core date-picker component - add interval component (UI) - relates issues: 1133, 784 --- src/app/@ansyn/core/less/common.less | 1 + src/app/@ansyn/core/models/case.model.ts | 2 +- .../go-to/projections/geo/geo.component.less | 7 + .../go-to/projections/utm/utm.component.less | 7 + .../status-bar/status-bar.component.html | 12 +- .../status-bar/status-bar.component.less | 11 +- .../status-bar/status-bar.component.ts | 4 +- .../status-bar/models/combo-boxes.model.ts | 2 +- .../@ansyn/status-bar/status-bar.module.ts | 12 +- .../ansyn-date-picker.component.html | 2 + .../ansyn-date-picker.component.less | 15 ++ .../ansyn-date-picker.component.spec.ts | 56 +++++++ .../ansyn-date-picker.component.ts | 98 ++++++++++++ .../timeline-intervals-picker.component.html | 144 ++++++++++++++++++ .../timeline-intervals-picker.component.less | 134 ++++++++++++++++ ...imeline-intervals-picker.component.spec.ts | 24 +++ .../timeline-intervals-picker.component.ts | 39 +++++ src/styles/styles.less | 6 - 18 files changed, 558 insertions(+), 18 deletions(-) create mode 100644 src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.html create mode 100644 src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.less create mode 100644 src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts create mode 100644 src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts create mode 100644 src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html create mode 100644 src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less create mode 100644 src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts create mode 100644 src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts diff --git a/src/app/@ansyn/core/less/common.less b/src/app/@ansyn/core/less/common.less index b3fd2d416e..199f5c791b 100644 --- a/src/app/@ansyn/core/less/common.less +++ b/src/app/@ansyn/core/less/common.less @@ -9,6 +9,7 @@ @overlays-dots-color-hover: #bd0fe2; @menu-width: 90px; @menu-color: #2E2D46; +@text-disabled-color: #9D9D9D; .default-menu-item { position: relative; diff --git a/src/app/@ansyn/core/models/case.model.ts b/src/app/@ansyn/core/models/case.model.ts index 1e0295970d..26812db3b5 100644 --- a/src/app/@ansyn/core/models/case.model.ts +++ b/src/app/@ansyn/core/models/case.model.ts @@ -19,7 +19,7 @@ export interface IContextEntity extends IVisualizerEntity { } export type CaseOrientation = 'Align North' | 'User Perspective' | 'Imagery Perspective'; -export type CaseTimeFilter = 'Start - End'; +export type CaseTimeFilter = 'Start - End' | 'Intervals'; export type CaseGeoFilter = 'Pin-Point'; export interface ImageManualProcessArgs { diff --git a/src/app/@ansyn/menu-items/tools/go-to/projections/geo/geo.component.less b/src/app/@ansyn/menu-items/tools/go-to/projections/geo/geo.component.less index f3107bdfde..c44f32578e 100644 --- a/src/app/@ansyn/menu-items/tools/go-to/projections/geo/geo.component.less +++ b/src/app/@ansyn/menu-items/tools/go-to/projections/geo/geo.component.less @@ -1,4 +1,11 @@ :host { + // remove html arrow controls from input[type=number] + input[type=number]::-webkit-inner-spin-button, + input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + box-sizing: border-box; display: flex; flex-direction: column; diff --git a/src/app/@ansyn/menu-items/tools/go-to/projections/utm/utm.component.less b/src/app/@ansyn/menu-items/tools/go-to/projections/utm/utm.component.less index 6b4eca4d54..c7eedb273d 100644 --- a/src/app/@ansyn/menu-items/tools/go-to/projections/utm/utm.component.less +++ b/src/app/@ansyn/menu-items/tools/go-to/projections/utm/utm.component.less @@ -1,4 +1,11 @@ :host { + // remove html arrow controls from input[type=number] + input[type=number]::-webkit-inner-spin-button, + input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + box-sizing: border-box; display: flex; flex-direction: column; diff --git a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html index 87c3eebf13..8b72930499 100644 --- a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html +++ b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html @@ -74,21 +74,27 @@ (selectedChange)="comboBoxesChange({ timeFilter: $event })">
-
-
+
+
+ + +
diff --git a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.less b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.less index b03af06287..50e5ac4017 100644 --- a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.less +++ b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.less @@ -179,12 +179,17 @@ } .time-selection { + color: black; + min-width: 230px; background: white; - width: 230px; - height: 97px; + border: 1px solid dimgray; padding: 8px 12px 13px; flex-direction: column; - border: 1px solid dimgray; + &.time-selection--start-end{ + } + &.time-selection--intervals { + min-width: 270px; + } } } diff --git a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.ts b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.ts index ae3a49bf6a..dd74dc15ac 100644 --- a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.ts +++ b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.ts @@ -136,13 +136,13 @@ export class StatusBarComponent implements OnInit { }); } - toggleTimelineStartEndSearch() { + toggleTimeFilterEdit() { this.timeSelectionEditIcon = !this.timeSelectionEditIcon; } applyTimelinePickerResult(time: CaseTimeState) { this.store.dispatch(new SetOverlaysCriteriaAction({ time })); - this.toggleTimelineStartEndSearch(); + this.toggleTimeFilterEdit(); } layoutRender(layout: LayoutKey) { diff --git a/src/app/@ansyn/status-bar/models/combo-boxes.model.ts b/src/app/@ansyn/status-bar/models/combo-boxes.model.ts index 14925a73b7..1c5da31f38 100644 --- a/src/app/@ansyn/status-bar/models/combo-boxes.model.ts +++ b/src/app/@ansyn/status-bar/models/combo-boxes.model.ts @@ -1,7 +1,7 @@ import { CaseGeoFilter, CaseOrientation, CaseTimeFilter } from '@ansyn/core'; import { InjectionToken } from '@angular/core'; -export const timeFilters: CaseTimeFilter[] = ['Start - End']; +export const timeFilters: CaseTimeFilter[] = ['Start - End', 'Intervals']; export const geoFilters: CaseGeoFilter[] = ['Pin-Point']; export const orientations: CaseOrientation[] = ['Align North', 'User Perspective', 'Imagery Perspective']; diff --git a/src/app/@ansyn/status-bar/status-bar.module.ts b/src/app/@ansyn/status-bar/status-bar.module.ts index bea7220648..23e486c06a 100644 --- a/src/app/@ansyn/status-bar/status-bar.module.ts +++ b/src/app/@ansyn/status-bar/status-bar.module.ts @@ -1,4 +1,4 @@ -import { InjectionToken, NgModule } from '@angular/core'; +import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StatusBarComponent } from './components/status-bar/status-bar.component'; import { FormsModule } from '@angular/forms'; @@ -8,6 +8,8 @@ import { TimelineTimepickerComponent } from './components/timeline-timepicker/ti import { StoreModule } from '@ngrx/store'; import { statusBarFeatureKey, StatusBarReducer } from './reducers/status-bar.reducer'; import { comboBoxesOptions, GEO_FILTERS, ORIENTATIONS, TIME_FILTERS } from '@ansyn/status-bar/models'; +import { TimelineIntervalsPickerComponent } from '@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component'; +import { DatePickerComponent } from '@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component'; @NgModule({ @@ -17,7 +19,13 @@ import { comboBoxesOptions, GEO_FILTERS, ORIENTATIONS, TIME_FILTERS } from '@ans CoreModule, StoreModule.forFeature(statusBarFeatureKey, StatusBarReducer) ], - declarations: [StatusBarComponent, ComboBoxComponent, TimelineTimepickerComponent], + declarations: [ + StatusBarComponent, + ComboBoxComponent, + DatePickerComponent, + TimelineTimepickerComponent, + TimelineIntervalsPickerComponent + ], providers: [ { provide: ORIENTATIONS, diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.html b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.html new file mode 100644 index 0000000000..40ea69d8a2 --- /dev/null +++ b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.less b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.less new file mode 100644 index 0000000000..15bc652d23 --- /dev/null +++ b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.less @@ -0,0 +1,15 @@ +@import "../../less/common"; + +:host { + position: relative; + input.flatpickr-input { + width: 130px; + height: 20px; + padding: 0 5px; + } + i { + position: absolute; + right: 10px; + top: 4px; + } +} diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts new file mode 100644 index 0000000000..cf901b844a --- /dev/null +++ b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts @@ -0,0 +1,56 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AnsynCheckboxComponent } from './ansyn-date-picker.component'; +import { By } from '@angular/platform-browser'; + + +describe('AnsynCheckboxComponent', () => { + let component: AnsynCheckboxComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AnsynCheckboxComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnsynCheckboxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); + + it('host binding should work', () => { + component.disabled = false; + fixture.detectChanges(); + expect(fixture.elementRef.nativeElement.classList.contains('disabled')).toBe(false); + component.disabled = true; + fixture.detectChanges(); + expect(fixture.elementRef.nativeElement.classList.contains('disabled')).toBe(true); + }); + + it('check that disabled actully disabes the input and the function', () => { + const input = fixture.debugElement.query(By.css('input')).nativeElement; + spyOn(component, 'onInputClicked'); + component.disabled = true; + fixture.detectChanges(); + + input.click(); + expect(input.checked).toBe(false); + expect(component.onInputClicked).not.toHaveBeenCalled(); + component.disabled = false; + fixture.detectChanges(); + + input.click(); + expect(input.checked).toBe(true); + expect(component.onInputClicked).toHaveBeenCalled(); + }); + + +}); + diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts new file mode 100644 index 0000000000..371bc9b8b6 --- /dev/null +++ b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts @@ -0,0 +1,98 @@ +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import * as flatpickr from 'flatpickr'; + +@Component({ + selector: 'ansyn-date-picker', + templateUrl: './ansyn-date-picker.component.html', + styleUrls: ['./ansyn-date-picker.component.less'] +}) + +export class DatePickerComponent implements OnInit { + + private _datePickerValue = new Date(); + private _datePickerInstance: any; + + @ViewChild('datePicker') datePicker: ElementRef; + @Output() dateChanged = new EventEmitter(); + + @Input() public disabled = false; + + @Input() + set datePickerValue(value: Date) { + this._datePickerValue = value; + if (this._datePickerInstance) { + this._datePickerInstance.setDate(this._datePickerValue, false); + } + } + + constructor() {} + + ngOnInit() { + this._datePickerInstance = new (flatpickr)(this.datePicker.nativeElement, { + // id: 'end', + time_24hr: true, + enableTime: true, + dateFormat: 'H:i d/m/Y', + onChange: this.onDateChanged.bind(this), + plugins: [this.confirmDatePlugin({})] + }); + + this._datePickerInstance.setDate(this._datePickerValue, false); + } + + confirmDatePlugin(pluginConfig: any) { + const defaultConfig = { + confirmIcon: ' ', + confirmText: 'OK ', + showAlways: false, + theme: 'light' + }; + + const config = Object.assign(defaultConfig, pluginConfig); + + return function (fp) { + const hooks = { + onKeyDown: function onKeyDown(_, __, ___, e) { + if (fp.config.enableTime && e.key === 'Tab' && e.target === fp.amPM) { + e.preventDefault(); + fp.confirmContainer.focus(); + } else if (e.key === 'Enter' && e.target === fp.confirmContainer) { + fp.close(); + } + }, + onReady: function onReady() { + if (fp.calendarContainer === undefined) { + return; + } + + fp.confirmContainer = fp._createElement('div', 'flatpickr-confirm ' + (config.showAlways ? 'visible' : '') + ' ' + config.theme + 'Theme', config.confirmText); + + fp.confirmContainer.tabIndex = -1; + fp.confirmContainer.innerHTML += config.confirmIcon; + + fp.confirmContainer.addEventListener('click', fp.close); + fp.calendarContainer.appendChild(fp.confirmContainer); + } + } as any; + + if (!config.showAlways) { + hooks.onChange = function (dateObj, dateStr) { + const showCondition = fp.config.enableTime || fp.config.mode === 'multiple'; + if (dateStr && !fp.config.inline && showCondition) { + return fp.confirmContainer.classList.add('visible'); + } + fp.confirmContainer.classList.remove('visible'); + }; + } + + return hooks; + }; + } + + onDateChanged(event) { + if (this.disabled) { + return false; + } + this.dateChanged.emit({ event: event, time: this._datePickerValue }); + } +} diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html new file mode 100644 index 0000000000..d8f8355358 --- /dev/null +++ b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html @@ -0,0 +1,144 @@ +
+ Each: + + For last: + +
+ + +
+
+
+ Time Settings +
+
+ +
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+
+ Time Frame: +
+
    +
  • + + + + + +
  • +
  • + +
    +
    + + +
    +
    + + +
    +
    +
  • +
+
+
+ Criteria: +
+
+
+ +
+
+
+ + Before +
+ +
+ + After +
+ +
+
+
+ + +
    +
  • + +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
+
+
+
+ +
+
+ {{error}} +
+
+ Cancel + Ok +
+
diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less new file mode 100644 index 0000000000..12525a4f62 --- /dev/null +++ b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less @@ -0,0 +1,134 @@ +@import "core/less/common"; + +@vertical-section-margin: 8px 0; +@row-bottom-margin: 5px; + +:host { + span:first-child { + margin-left: 0; + } + label, input[type=radio], select { + cursor: pointer; + padding: 2px; + } + input[type=number] { + width: 35px; + padding: 2px; + } + ul { + padding: 0; + margin: 0; + li { + list-style-type: none; + margin-bottom: 5px; + } + } + .disabled, input:disabled, input:disabled + label, select:disabled { + color: @text-disabled-color; + cursor: auto; + } + .rows-container { + > div { + margin-bottom: @row-bottom-margin; + display: flex; + &:last-child { + margin-bottom: 0; + } + } + } + + .advance-options-button { + display: block; + background: none; + border: 0; + outline: 0; + padding: 0; + margin: @vertical-section-margin; + .chevron { + margin-left: 8px; + display: inline-block; + float: right; + font-size: 22px; + line-height: 14px; + transition: transform .3s linear; + + } + &.active .chevron { + transform: rotate(90deg); + } + } + .advance-options-container { + max-height: 0; + overflow: hidden; + transition: max-height .4s linear 0s; + &.active { + max-height: 500px; + } + + .advance-options-time { + .advance-options-time-intervals { + display: flex; + margin: @vertical-section-margin; + label { + white-space: nowrap; + } + ul { + display: flex; + li { + display: flex; + flex-direction: column; + margin: 0 5px; + text-align: center; + } + } + } + .advance-options-time-frame { + margin: @vertical-section-margin; + li { + display: flex; + } + label { + min-width: 50px; + } + } + } + .advance-options-strategies { + > div { + margin: @vertical-section-margin; + display: flex; + &:first-child { + margin-bottom: 10px; + } + } + + input[type=radio] { + margin-left: 0; + } + + .filter-strategy-closest { + } + } + } + + div.control-buttons { + display: flex; + justify-content: space-between; + color: @application-main-color; + a { + cursor: pointer; + &:hover { + font-weight: bold; + } + &:first-child { + margin-right: 9px; + width: 42px; + } + &:last-child { + width: 17px; + } + } + .error { + color: crimson; + } + } +} diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts new file mode 100644 index 0000000000..82307bc129 --- /dev/null +++ b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts @@ -0,0 +1,24 @@ +import { TimelineIntervalsPickerComponent } from './timeline-intervals-picker.component'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + + +describe('TimelineIntervalsPickerComponent', () => { + let component: TimelineIntervalsPickerComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TimelineIntervalsPickerComponent] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TimelineIntervalsPickerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('Component Initialised ', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts new file mode 100644 index 0000000000..ff07fb3269 --- /dev/null +++ b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts @@ -0,0 +1,39 @@ +import { Component, EventEmitter, Output } from '@angular/core'; + +import { CaseTimeState } from '@ansyn/core'; + +const HOUR_MILLISECONDS = 60 * 60 * 1000; + +@Component({ + selector: 'ansyn-timeline-intervals-picker', + templateUrl: './timeline-intervals-picker.component.html', + styleUrls: ['./timeline-intervals-picker.component.less'] +}) + +export class TimelineIntervalsPickerComponent { + timeIntervalAdvancedOptions = false; + timeFrame = 'last'; + timeIntervalFilterStrategy = 'best'; + timeFilterUnits = [ + { text: 'hours', value: HOUR_MILLISECONDS }, + { text: 'days', value: HOUR_MILLISECONDS * 24}, + { text: 'months', value: HOUR_MILLISECONDS * 24 * 30 }, + { text: 'years', value: HOUR_MILLISECONDS * 24 * 30 * 12 } + ]; + + + @Output() applyDate = new EventEmitter(); + @Output('closeComponent') closeComponent = new EventEmitter(); + + toggleIntervalAdvancedOptions() { + this.timeIntervalAdvancedOptions = !this.timeIntervalAdvancedOptions; + } + + setTimeFrameType(type) { + this.timeFrame = type; + } + + setIntervalsFilterStrategy(type) { + this.timeIntervalFilterStrategy = type; + } +} diff --git a/src/styles/styles.less b/src/styles/styles.less index 822c57ddba..959bc491c9 100644 --- a/src/styles/styles.less +++ b/src/styles/styles.less @@ -187,12 +187,6 @@ button[disabled] { } } -input[type=number]::-webkit-inner-spin-button, -input[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} - @font-face { font-family: 'AnSyn'; src: url('/assets/fonts/AnSyn/AnSyn.eot?mxpept&v=1'); From 8e34c4156a66cc7ef1ef4661188376ac175b94b5 Mon Sep 17 00:00:00 2001 From: yuvalbl Date: Mon, 12 Mar 2018 16:28:03 +0200 Subject: [PATCH 02/11] feat(intervals): - updating type caseTimeSate - full support for sending intervals caseTimeSate - validation required --- src/app/@ansyn/core/models/case.model.ts | 12 ++ .../ansyn-date-picker.component.ts | 22 ++- .../timeline-intervals-picker.component.html | 53 ++++---- .../timeline-intervals-picker.component.ts | 126 ++++++++++++++++-- 4 files changed, 169 insertions(+), 44 deletions(-) diff --git a/src/app/@ansyn/core/models/case.model.ts b/src/app/@ansyn/core/models/case.model.ts index 26812db3b5..87d3a37dd4 100644 --- a/src/app/@ansyn/core/models/case.model.ts +++ b/src/app/@ansyn/core/models/case.model.ts @@ -51,6 +51,18 @@ export interface CaseTimeState { type: 'absolute', from: Date, to: Date + intervals?: { + interval: number // in milliseconds + criteria: CaseIntervalCriteria + } +} + +export type CaseIntervalCriteriaType = 'best' | 'closest-before' | 'closest-after' | 'closest-both'; + +export interface CaseIntervalCriteria { + type: CaseIntervalCriteriaType, + before?: number, + after?: number, } export interface CaseBooleanFilterMetadata { diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts index 371bc9b8b6..dc43d44a9d 100644 --- a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts +++ b/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts @@ -9,35 +9,32 @@ import * as flatpickr from 'flatpickr'; export class DatePickerComponent implements OnInit { - private _datePickerValue = new Date(); + private _datePickerValue: Date; private _datePickerInstance: any; @ViewChild('datePicker') datePicker: ElementRef; - @Output() dateChanged = new EventEmitter(); + @Output() dateChange = new EventEmitter(); @Input() public disabled = false; @Input() - set datePickerValue(value: Date) { - this._datePickerValue = value; - if (this._datePickerInstance) { - this._datePickerInstance.setDate(this._datePickerValue, false); - } + set date(val) { + this._datePickerValue = val; } constructor() {} ngOnInit() { + // see API: https://flatpickr.js.org/options/ this._datePickerInstance = new (flatpickr)(this.datePicker.nativeElement, { // id: 'end', time_24hr: true, enableTime: true, dateFormat: 'H:i d/m/Y', - onChange: this.onDateChanged.bind(this), + defaultDate: this._datePickerValue ? this._datePickerValue : new Date(), + onChange: this.onDateChange.bind(this), plugins: [this.confirmDatePlugin({})] }); - - this._datePickerInstance.setDate(this._datePickerValue, false); } confirmDatePlugin(pluginConfig: any) { @@ -89,10 +86,11 @@ export class DatePickerComponent implements OnInit { }; } - onDateChanged(event) { + onDateChange(event) { if (this.disabled) { return false; } - this.dateChanged.emit({ event: event, time: this._datePickerValue }); + this._datePickerValue = event[0]; + this.dateChange.emit(this._datePickerValue); } } diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html index d8f8355358..1bf82b51d8 100644 --- a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html +++ b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html @@ -1,14 +1,14 @@
Each: - + For last: - +
@@ -28,19 +28,19 @@
  • - +
  • - +
  • - +
  • - +
@@ -52,10 +52,10 @@ - + - + @@ -66,11 +66,13 @@
- + +
- + +
@@ -86,8 +88,9 @@

- + @@ -95,8 +98,9 @@
- + @@ -115,17 +119,20 @@
  • + value="closest-before" checked="checked" [disabled]="timeIntervalFilterStrategy !== 'closest'" + [(ngModel)]="advancedOptionCriteriaClosest">
  • + value="closest-after" [disabled]="timeIntervalFilterStrategy !== 'closest'" + [(ngModel)]="advancedOptionCriteriaClosest">
  • + value="closest-both" [disabled]="timeIntervalFilterStrategy !== 'closest'" + [(ngModel)]="advancedOptionCriteriaClosest">
  • @@ -139,6 +146,6 @@
    Cancel - Ok + Ok
    diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts index ff07fb3269..6591510061 100644 --- a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts +++ b/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Output } from '@angular/core'; -import { CaseTimeState } from '@ansyn/core'; +import { CaseIntervalCriteria, CaseIntervalCriteriaType, CaseTimeState } from '@ansyn/core'; const HOUR_MILLISECONDS = 60 * 60 * 1000; @@ -12,19 +12,47 @@ const HOUR_MILLISECONDS = 60 * 60 * 1000; export class TimelineIntervalsPickerComponent { timeIntervalAdvancedOptions = false; - timeFrame = 'last'; - timeIntervalFilterStrategy = 'best'; - timeFilterUnits = [ - { text: 'hours', value: HOUR_MILLISECONDS }, - { text: 'days', value: HOUR_MILLISECONDS * 24}, - { text: 'months', value: HOUR_MILLISECONDS * 24 * 30 }, - { text: 'years', value: HOUR_MILLISECONDS * 24 * 30 * 12 } - ]; + timeFrame: 'last' | 'scope' = 'last'; + timeIntervalFilterStrategy: 'best' | 'closest' = 'best'; + timeFilterUnits = { + options: [ + { text: 'hours', textSingular: 'hour', value: HOUR_MILLISECONDS }, + { text: 'days', textSingular: 'day', value: HOUR_MILLISECONDS * 24 }, + { text: 'months', textSingular: 'month', value: HOUR_MILLISECONDS * 24 * 30 }, + { text: 'years', textSingular: 'year', value: HOUR_MILLISECONDS * 24 * 30 * 12 } + ], + map: new Map() + }; + public basicOptionInterval: number; + public basicOptionTimeFrame: number; + public advancedOptionInterval = {years: 0, months: 0, days: 0 , hours: 0 }; + public advancedOptionTimeFrameLast: number; + public advancedOptionTimeFrameUnits: number; + public advancedOptionStartDate = new Date(new Date().getTime() - HOUR_MILLISECONDS * 24 * 365); // default is 1 yr ago + public advancedOptionEndDate = new Date(); + public advancedOptionCriteriaBest = { before: 0, after: 0}; + public advancedOptionCriteriaClosest = 'closest-before'; @Output() applyDate = new EventEmitter(); @Output('closeComponent') closeComponent = new EventEmitter(); + constructor() { + // init map for easy search in timeFilterUnits.options + this.timeFilterUnits.options.forEach(unit => { + this.timeFilterUnits.map.set(unit.text, unit.value); + this.timeFilterUnits.map.set(unit.textSingular, unit.value) + }); + // init values + this.basicOptionInterval = this.timeFilterUnits.map.get('month'); + this.basicOptionTimeFrame = this.timeFilterUnits.map.get('year'); + this.advancedOptionInterval.months = 1; + this.advancedOptionTimeFrameLast = 1; + this.advancedOptionCriteriaBest.before = this.timeFilterUnits.map.get('hour'); + this.advancedOptionCriteriaBest.after = this.timeFilterUnits.map.get('hour'); + this.advancedOptionTimeFrameUnits = this.timeFilterUnits.map.get('year'); + } + toggleIntervalAdvancedOptions() { this.timeIntervalAdvancedOptions = !this.timeIntervalAdvancedOptions; } @@ -36,4 +64,84 @@ export class TimelineIntervalsPickerComponent { setIntervalsFilterStrategy(type) { this.timeIntervalFilterStrategy = type; } + + applyIntervalPickerEvent() { + // if (this.advancedOptionStartDatePickerValue.getTime() >= this.advancedOptionEndDatePickerValue.getTime()) { + // if invalid + if (false) { + // this.error = '* error'; + // return; + } + const timeFrame = this.getTimeFrame(); + const interval: number = this.getTimeInterval(); + const criteria: CaseIntervalCriteria = this.getCriteria(); + + const intervalsData: CaseTimeState = { + type: 'absolute', + from: timeFrame.from, + to: timeFrame.to, + intervals: { + interval, + criteria + } + }; + + this.applyDate.emit(intervalsData); + } + getTimeFrame(): { from: Date, to: Date } { + let from; + let to = new Date(); // default is 'now' + + // basic mode + if (this.timeIntervalAdvancedOptions === false) { + from = new Date(to.getTime() - this.basicOptionTimeFrame); + } else { + // advanced mode + if (this.timeFrame === 'last') { + // advanced mode: time frame > last + from = new Date(to.getTime() - this.advancedOptionTimeFrameLast * this.advancedOptionTimeFrameUnits); + } else { + // advanced mode: time frame > start - end + from = this.advancedOptionStartDate; + to = this.advancedOptionEndDate; + } + } + + return { from, to }; + } + + getTimeInterval(): number { + let interval; + // basic mode + if (this.timeIntervalAdvancedOptions === false) { + interval = this.basicOptionInterval; + } else { + // advanced mode + interval = + this.advancedOptionInterval.years * this.timeFilterUnits.map.get('years') + + this.advancedOptionInterval.months * this.timeFilterUnits.map.get('months') + + this.advancedOptionInterval.days * this.timeFilterUnits.map.get('days') + + this.advancedOptionInterval.hours * this.timeFilterUnits.map.get('hours'); + } + return +interval; + } + + getCriteria(): CaseIntervalCriteria { + let criteria: CaseIntervalCriteria = { + type: 'closest-both' // default for basic mode + }; + // basic mode + if (this.timeIntervalAdvancedOptions) { + if (this.timeIntervalFilterStrategy === 'best') { + // advanced mode: Criteria > best + criteria.type = 'best'; + criteria.before = +this.advancedOptionCriteriaBest.before; + criteria.after = +this.advancedOptionCriteriaBest.after; + } else { + // advanced mode: Criteria > closest + criteria.type = this.advancedOptionCriteriaClosest; + } + } + return criteria; + } } From 1603bfc2038fda5fb5ad07f08e08369e4ff56374 Mon Sep 17 00:00:00 2001 From: yuvalbl Date: Tue, 13 Mar 2018 14:43:24 +0200 Subject: [PATCH 03/11] feat(intervals): refactor file structure --- .../ansyn-date-picker/ansyn-date-picker.component.html | 0 .../ansyn-date-picker/ansyn-date-picker.component.less | 0 .../ansyn-date-picker/ansyn-date-picker.component.spec.ts | 0 .../components/ansyn-date-picker/ansyn-date-picker.component.ts | 0 .../timeline-intervals-picker.component.html | 0 .../timeline-intervals-picker.component.less | 0 .../timeline-intervals-picker.component.spec.ts | 0 .../timeline-intervals-picker.component.ts | 0 src/app/@ansyn/status-bar/status-bar.module.ts | 2 +- 9 files changed, 1 insertion(+), 1 deletion(-) rename src/app/{packages => @ansyn}/core/components/ansyn-date-picker/ansyn-date-picker.component.html (100%) rename src/app/{packages => @ansyn}/core/components/ansyn-date-picker/ansyn-date-picker.component.less (100%) rename src/app/{packages => @ansyn}/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts (100%) rename src/app/{packages => @ansyn}/core/components/ansyn-date-picker/ansyn-date-picker.component.ts (100%) rename src/app/{packages => @ansyn}/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html (100%) rename src/app/{packages => @ansyn}/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less (100%) rename src/app/{packages => @ansyn}/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts (100%) rename src/app/{packages => @ansyn}/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts (100%) diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.html b/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.html similarity index 100% rename from src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.html rename to src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.html diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.less b/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.less similarity index 100% rename from src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.less rename to src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.less diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts b/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts similarity index 100% rename from src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts rename to src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.spec.ts diff --git a/src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts b/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.ts similarity index 100% rename from src/app/packages/core/components/ansyn-date-picker/ansyn-date-picker.component.ts rename to src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.ts diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html similarity index 100% rename from src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html rename to src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less similarity index 100% rename from src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less rename to src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts similarity index 100% rename from src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts rename to src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.spec.ts diff --git a/src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts similarity index 100% rename from src/app/packages/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts rename to src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts diff --git a/src/app/@ansyn/status-bar/status-bar.module.ts b/src/app/@ansyn/status-bar/status-bar.module.ts index 23e486c06a..c9d57cd674 100644 --- a/src/app/@ansyn/status-bar/status-bar.module.ts +++ b/src/app/@ansyn/status-bar/status-bar.module.ts @@ -5,10 +5,10 @@ import { FormsModule } from '@angular/forms'; import { CoreModule } from '@ansyn/core'; import { ComboBoxComponent } from './components/combo-box/combo-box.component'; import { TimelineTimepickerComponent } from './components/timeline-timepicker/timeline-timepicker.component'; +import { TimelineIntervalsPickerComponent } from './components/timeline-intervals-picker/timeline-intervals-picker.component'; import { StoreModule } from '@ngrx/store'; import { statusBarFeatureKey, StatusBarReducer } from './reducers/status-bar.reducer'; import { comboBoxesOptions, GEO_FILTERS, ORIENTATIONS, TIME_FILTERS } from '@ansyn/status-bar/models'; -import { TimelineIntervalsPickerComponent } from '@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component'; import { DatePickerComponent } from '@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component'; From 3fb9d2881be0ff0e9d0bfd6f03e9f874803a79fe Mon Sep 17 00:00:00 2001 From: yuvalbl Date: Tue, 13 Mar 2018 20:40:46 +0200 Subject: [PATCH 04/11] feat(intervals): add client side interval - filter add interval log for debug on dev mode --- .../effects/filters.app.effects.ts | 15 +- src/app/@ansyn/core/utils/sorting.ts | 1 + .../overlays/services/overlays.service.ts | 138 ++++++++++++++++-- .../status-bar/status-bar.component.html | 4 +- 4 files changed, 142 insertions(+), 16 deletions(-) diff --git a/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts b/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts index 41ad6294fb..7baffc3261 100644 --- a/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts +++ b/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts @@ -26,7 +26,7 @@ import { CoreActionTypes, SetFavoriteOverlaysAction } from '@ansyn/core/actions/ import { OverlaysService } from '@ansyn/overlays/services/overlays.service'; import { coreStateSelector, ICoreState } from '@ansyn/core/reducers/core.reducer'; import { BooleanFilterMetadata } from '@ansyn/menu-items/filters/models/metadata/boolean-filter-metadata'; -import { CaseFacetsState } from '@ansyn/core'; +import { CaseFacetsState, OverlaysCriteria } from '@ansyn/core'; @Injectable() export class FiltersAppEffects { @@ -45,7 +45,7 @@ export class FiltersAppEffects { .withLatestFrom(this.store$.select(filtersStateSelector), this.store$.select(coreStateSelector), this.store$.select(overlaysStateSelector)) .filter(([action, filters, core, overlays]: [Action, IFiltersState, ICoreState, IOverlaysState]) => overlays.loaded) .map(([action, filters, core, overlays]: [Action, IFiltersState, ICoreState, IOverlaysState]) => { - const filteredOverlays = this.buildFilteredOverlays(overlays.overlays, filters, core.favoriteOverlays); + const filteredOverlays = this.buildFilteredOverlays(overlays.overlays, filters, core.favoriteOverlays, core.overlaysCriteria); return new SetFilteredOverlaysAction(filteredOverlays); }); @@ -136,7 +136,12 @@ export class FiltersAppEffects { return metadata === undefined || metadata === null; } - buildFilteredOverlays(overlays: Map, filters: IFiltersState, favoriteOverlays: Overlay[]): string[] { + buildFilteredOverlays( + overlays: Map, + filters: IFiltersState, + favoriteOverlays: Overlay[], + overlaysCriteria: OverlaysCriteria + ): string[] { const parsedFilters = Array.from(filters.filters) .map(([key, value]) => ({ key: key.modelName, @@ -146,8 +151,8 @@ export class FiltersAppEffects { if (filters.showOnlyFavorites) { return favorites; } else { - const filteredOvelrays = OverlaysService.filter(overlays, parsedFilters); - return union(favorites, filteredOvelrays); + const filteredOverlays = OverlaysService.filter(overlays, parsedFilters, overlaysCriteria.time); + return union(favorites, filteredOverlays); } } } diff --git a/src/app/@ansyn/core/utils/sorting.ts b/src/app/@ansyn/core/utils/sorting.ts index 5a4d56f6ce..d20721c000 100644 --- a/src/app/@ansyn/core/utils/sorting.ts +++ b/src/app/@ansyn/core/utils/sorting.ts @@ -1,2 +1,3 @@ // latest date first export const sortByDateDesc = (o1, o2) => o1.date > o2.date ? -1 : 1; +export const sortByDateAsc = (o1, o2) => o1.date > o2.date ? 1 : -1; diff --git a/src/app/@ansyn/overlays/services/overlays.service.ts b/src/app/@ansyn/overlays/services/overlays.service.ts index d6d3588232..ded6d0b59d 100644 --- a/src/app/@ansyn/overlays/services/overlays.service.ts +++ b/src/app/@ansyn/overlays/services/overlays.service.ts @@ -1,5 +1,5 @@ import { BaseOverlaySourceProvider } from '../models/base-overlay-source-provider.model'; -import { Inject, Injectable, InjectionToken } from '@angular/core'; +import { Inject, Injectable, InjectionToken, isDevMode } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Overlay } from '../models/overlay.model'; import { IOverlaysState, TimelineState } from '../reducers/overlays.reducer'; @@ -9,31 +9,149 @@ import * as bbox from '@turf/bbox'; import * as bboxPolygon from '@turf/bbox-polygon'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; -import { OverlaysCriteria } from '@ansyn/core'; +import { times as doNTimes } from 'lodash'; +import { CaseIntervalCriteria, CaseTimeState, OverlaysCriteria } from '@ansyn/core'; +import { sortByDateAsc, sortByDateDesc } from '@ansyn/core/utils/sorting'; export const OverlaysConfig: InjectionToken = new InjectionToken('overlays-config'); @Injectable() export class OverlaysService { - static filter(overlays: Map, filters: { key: any, filterFunc: (ovrelay: any, key: string) => boolean }[]): string[] { + static filter( + overlays: Map, + filters: { key: any, filterFunc: (ovrelay: any, key: string) => boolean }[], + timeState: CaseTimeState + ): string[] { if (!overlays) { return []; } - const overlaysData = []; - + let overlaysArray; if (!filters || !Array.isArray(filters)) { - return Array.from(overlays.keys()); + // no - filters + overlaysArray = Array.from( overlays.values() ); + } else { + // filters active + overlaysArray = []; + overlays.forEach(overlay => { + if (filters.every(filter => filter.filterFunc(overlay, filter.key))) { + overlaysArray.push(overlay); + } + }); + } + + if (timeState.intervals) { + // interval mode - limit overlays to be 1 per interval scope + overlaysArray = OverlaysService.intervalFilter(overlaysArray, timeState); + } + + return overlaysArray.length > 0 ? overlaysArray.map(o => o.id) : []; + } + // limit overlays to be 1 per interval scope + static intervalFilter(overlays: Overlay[], timeState: CaseTimeState): Overlay[] { + // set interval-size and time-frame + const intervalSize = timeState.intervals.interval; + const tf = { + startDate: timeState.from, + endDate: timeState.to, + span: timeState.to.getTime() - timeState.from.getTime(), + }; + + // calculate how many intervals + const howManyIntervals = Math.floor(tf.span / intervalSize); + + // update time-frame (when time-frame % interval is not 0) + if (tf.span % intervalSize > 0) { + tf.startDate = new Date(tf.endDate.getTime() - (intervalSize * howManyIntervals)); + tf.span = tf.endDate.getTime() - tf.startDate.getTime(); } - overlays.forEach(overlay => { - if (filters.every(filter => filter.filterFunc(overlay, filter.key))) { - overlaysData.push(overlay.id); + + // initialize intervalsArrays + const startTime = tf.startDate.getTime(); + const intervalsArrays = []; + + doNTimes(howManyIntervals, intervalIndex => { + const intervalStartMs = startTime + intervalIndex * intervalSize; + intervalsArrays.push({ + startTime: new Date(intervalStartMs), + endTime: new Date(intervalStartMs + intervalSize - 1), + pivot: new Date(intervalStartMs + intervalSize / 2 ), + overlays: [] + }) + }); + + // put each overlay in the right interval + overlays.forEach(o => { + const normalizedTime = o.date.getTime() - startTime; + if (normalizedTime < 0) { + console.warn(`overlay ${o.id} may be out of time-frame: ${tf.startDate} to ${tf.endDate}`, o); + } else { + const intervalIndex = Math.floor(normalizedTime / intervalSize); + intervalsArrays[intervalIndex].overlays.push(o); + } + }); + // return best-matching overlay by criteria + let overlaysByCriteria = OverlaysService.overlaysByCriteria(intervalsArrays, timeState.intervals.criteria); + + if (isDevMode()) { // log for debug purpose + console.table(intervalsArrays.map((obj, ind) => { + const match = overlaysByCriteria[ind]; + return { + startTime: obj.startTime.toISOString().replace('T', ' '), + endTime: obj.endTime.toISOString().replace('T', ' '), + pivot: obj.pivot.toISOString().replace('T', ' '), + overlays: obj.overlays.length, + match: match ? `id: ${match.id} time: ${match.date.toISOString().replace('T', ' ')}` : 'none', + by: timeState.intervals.criteria.type + }; + })); + } + + return overlaysByCriteria; + } + + // select best-matching overlay by criteria + static overlaysByCriteria(intervals: Array, criteria: CaseIntervalCriteria): Overlay[] { + const result: Overlay[] = []; + + // find the matching overlay for each interval by criteria + intervals.forEach(interval => { + let overlays = interval.overlays; + // const pivot = interval.pivot + if (overlays.length > 0) { + switch (criteria.type) { + case 'best': + // remove overlay outside of interval best-scope (best-before/best-after) + overlays = overlays.filter(o => { + const overlayTime: number = o.date.getTime(); + const intervalLow: number = interval.pivot - criteria.before; + const intervalHigh: number = interval.pivot + criteria.after; + return intervalLow <= overlayTime && overlayTime <= intervalHigh; + }); + // To do: sort by best; + break; + case 'closest-before': + overlays = overlays.filter(o => o.date.getTime() >= interval.pivot) // remove overlays after pivot + .sort(sortByDateDesc); // sort - latest first + break; + case 'closest-after': + overlays = overlays.filter(o => o.date.getTime() <= interval.pivot) // remove overlays before pivot + .sort(sortByDateAsc); // sort - earliest first + break; + case 'closest-both': + overlays = overlays.sort((o1, o2) => // sort - closest + Math.abs(o1.date - interval.pivot) - Math.abs(o1.date - interval.pivot)); + break; + } + if (overlays.length > 0) { + result.push(overlays[0]); + } } }); - return overlaysData; + return result; } static sort(overlays: any[]): Overlay[] { diff --git a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html index 8b72930499..c24a254001 100644 --- a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html +++ b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html @@ -92,7 +92,9 @@
    - +
    From 668469d3c7554e4c40385c1ec4da373d429cd7bc Mon Sep 17 00:00:00 2001 From: yuvalbl Date: Tue, 20 Mar 2018 16:13:41 +0200 Subject: [PATCH 05/11] feat(intervals): add validation add loading from state --- .../ansyn-date-picker.component.ts | 3 + .../overlays/services/overlays.service.ts | 6 +- .../status-bar/status-bar.component.html | 1 + .../timeline-intervals-picker.component.html | 97 ++++++----- .../timeline-intervals-picker.component.less | 40 +++-- .../timeline-intervals-picker.component.ts | 162 ++++++++++++++++-- 6 files changed, 230 insertions(+), 79 deletions(-) diff --git a/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.ts b/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.ts index dc43d44a9d..2c75648eac 100644 --- a/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.ts +++ b/src/app/@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component.ts @@ -20,6 +20,9 @@ export class DatePickerComponent implements OnInit { @Input() set date(val) { this._datePickerValue = val; + if (this._datePickerInstance) { + this._datePickerInstance.setDate(this._datePickerValue, false); + } } constructor() {} diff --git a/src/app/@ansyn/overlays/services/overlays.service.ts b/src/app/@ansyn/overlays/services/overlays.service.ts index ded6d0b59d..f096fd6061 100644 --- a/src/app/@ansyn/overlays/services/overlays.service.ts +++ b/src/app/@ansyn/overlays/services/overlays.service.ts @@ -74,10 +74,12 @@ export class OverlaysService { doNTimes(howManyIntervals, intervalIndex => { const intervalStartMs = startTime + intervalIndex * intervalSize; + const intervalEndTime = new Date(intervalStartMs + intervalSize - 1); + intervalsArrays.push({ startTime: new Date(intervalStartMs), - endTime: new Date(intervalStartMs + intervalSize - 1), - pivot: new Date(intervalStartMs + intervalSize / 2 ), + endTime: intervalEndTime, + pivot: intervalEndTime, overlays: [] }) }); diff --git a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html index c24a254001..434accbc21 100644 --- a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html +++ b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.html @@ -93,6 +93,7 @@
    diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html index 1bf82b51d8..f3824fee54 100644 --- a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html @@ -12,6 +12,11 @@
    + +
    +

    {{validation.timeFrameErr}}

    +
    + - -
    - - -
    -
    - - -
    - -
    Date: Sun, 3 Jun 2018 10:14:43 +0300 Subject: [PATCH 09/11] merge master --- .../effects/filters.app.effects.ts | 1 + .../overlays/services/overlays.service.ts | 29 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts b/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts index 7ebf8c8fbb..ecf8ad5cd2 100644 --- a/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts +++ b/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts @@ -68,6 +68,7 @@ export class FiltersAppEffects { @Effect() updateOverlayFilters$ = this.onFiltersChanges$ .withLatestFrom(this.overlaysArray$, this.store$.select(selectOverlaysCriteria)) + .filter(([[filters, showOnlyFavorite, favoriteOverlays], overlaysArray, { time }]) => Boolean(time) ) .mergeMap(([[filters, showOnlyFavorite, favoriteOverlays], overlaysArray, { time }]: [[Filters, boolean, Overlay[]], Overlay[], OverlaysCriteria]) => { const filterModels: FilterModel[] = FiltersService.pluckFilterModels(filters); const filteredOverlays: string[] = OverlaysService.buildFilteredOverlays(overlaysArray, filterModels, favoriteOverlays, showOnlyFavorite, time); diff --git a/src/app/@ansyn/overlays/services/overlays.service.ts b/src/app/@ansyn/overlays/services/overlays.service.ts index 4d58da23c0..cfab39056d 100644 --- a/src/app/@ansyn/overlays/services/overlays.service.ts +++ b/src/app/@ansyn/overlays/services/overlays.service.ts @@ -13,6 +13,7 @@ import { sortByDate, sortByDateDesc, sortByResolutionDesc } from '@ansyn/core/ut import { CaseIntervalCriteria, CaseTimeState } from '@ansyn/core/models/case.model'; import { Interval, IntervalTimeFrame } from '@ansyn/core/models/intervals.model'; import { times as doNTimes } from 'lodash'; +import { IDateRange } from '@ansyn/core/models/time.model'; export const OverlaysConfig: InjectionToken = new InjectionToken('overlays-config'); @@ -222,17 +223,37 @@ export class OverlaysService { search(params: OverlaysCriteria): Observable { let feature = params.region; + const timeRange: Array = this.getSearchTimeRange(params.time); return this._overlaySourceProvider.fetch({ dataInputFilters: Boolean(params.dataInputFilters) && params.dataInputFilters.active ? params.dataInputFilters.filters : null, limit: this.config.limit, region: feature, - timeRange: { - start: params.time.from, - end: params.time.to - } + timeRange }); } + getSearchTimeRange(timeState: CaseTimeState): Array { + const searchTimeRange = new Array(); + + if (!timeState.intervals) { + searchTimeRange.push({ + start: timeState.from, + end: timeState.to + }); + } else { + const tf: IntervalTimeFrame = OverlaysService.calculateTimeFrame(timeState); + // create array of intervals (see Interval interface) + const intervalsArrays: Array = OverlaysService.createIntervalsArray(tf, timeState); + intervalsArrays.forEach(interval => { + searchTimeRange.push({ + start: interval.startTime, + end: interval.endTime + }); + }); + } + return searchTimeRange; + } + getOverlayById(id: string, sourceType: string): Observable { return this._overlaySourceProvider.getById(id, sourceType); } From ea9f2bf9bb7201ed3f00d90f16cb541708574ee1 Mon Sep 17 00:00:00 2001 From: YairTawil Date: Tue, 5 Jun 2018 15:51:58 +0300 Subject: [PATCH 10/11] time interval component css --- src/app/@ansyn/core/models/case.model.ts | 5 +- .../filters/services/filters.service.ts | 2 +- .../time-options-interval.pipe.spec.ts | 8 + .../time-options-interval.pipe.ts | 13 ++ .../time-options.pipe.spec.ts | 8 + .../time-options.pipe.ts | 13 ++ .../timeline-intervals-picker.component.html | 52 +++++-- .../timeline-intervals-picker.component.less | 146 +++++------------- .../timeline-intervals-picker.component.ts | 2 +- .../timeline-timepicker.component.less | 1 - .../status-bar/models/combo-boxes.model.ts | 4 +- .../@ansyn/status-bar/status-bar.module.ts | 6 +- 12 files changed, 128 insertions(+), 132 deletions(-) create mode 100644 src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.spec.ts create mode 100644 src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.ts create mode 100644 src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.spec.ts create mode 100644 src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.ts diff --git a/src/app/@ansyn/core/models/case.model.ts b/src/app/@ansyn/core/models/case.model.ts index 58693b65e1..865a8863a1 100644 --- a/src/app/@ansyn/core/models/case.model.ts +++ b/src/app/@ansyn/core/models/case.model.ts @@ -24,8 +24,11 @@ export interface IContextEntity extends IVisualizerEntity { } export type CaseOrientation = 'Align North' | 'User Perspective' | 'Imagery Perspective'; -export type CaseTimeFilter = 'Start - End' | 'Intervals'; +export enum CaseTimeFilter { + 'Start - End' = 'Start - End', + 'Intervals' = 'Intervals' +} export enum CaseGeoFilter { PinPoint = 'Point', diff --git a/src/app/@ansyn/menu-items/filters/services/filters.service.ts b/src/app/@ansyn/menu-items/filters/services/filters.service.ts index b960f4a67d..f5b48f798f 100644 --- a/src/app/@ansyn/menu-items/filters/services/filters.service.ts +++ b/src/app/@ansyn/menu-items/filters/services/filters.service.ts @@ -67,7 +67,7 @@ export class FiltersService { const cloneFilters = new Map(filterState.filters); cloneFilters.set(metadataKey, cloneMetadata); const filterModels: FilterModel[] = FiltersService.pluckFilterModels(cloneFilters); - const filteredOverlays: string[] = OverlaysService.buildFilteredOverlays(Array.from(overlays.values()), filterModels, favoriteOverlays, filterState.facets.showOnlyFavorites); + const filteredOverlays: string[] = OverlaysService.buildFilteredOverlays(Array.from(overlays.values()), filterModels, favoriteOverlays, filterState.facets.showOnlyFavorites, { time: null }); metadata.resetFilteredCount(); filteredOverlays.forEach((id: string) => { const overlay = overlays.get(id); diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.spec.ts b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.spec.ts new file mode 100644 index 0000000000..bbab61280a --- /dev/null +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.spec.ts @@ -0,0 +1,8 @@ +import { TimeOptionsIntervalPipe } from './time-options-interval.pipe'; + +describe('TimeOptionsIntervalPipe', () => { + it('create an instance', () => { + const pipe = new TimeOptionsIntervalPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.ts b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.ts new file mode 100644 index 0000000000..350bc381fa --- /dev/null +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options-interval.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'timeOptionsInterval' +}) +export class TimeOptionsIntervalPipe implements PipeTransform { + + transform(value: any, args?: any): any { + const index = value.findIndex(({ value }) => value === Number(args)); + return value.splice(0, index); + } + +} diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.spec.ts b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.spec.ts new file mode 100644 index 0000000000..740169759e --- /dev/null +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.spec.ts @@ -0,0 +1,8 @@ +import { TimeOptionsPipe } from './time-options.pipe'; + +describe('TimeOptionsPipe', () => { + it('create an instance', () => { + const pipe = new TimeOptionsPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.ts b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.ts new file mode 100644 index 0000000000..b822f04b91 --- /dev/null +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/time-options.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'timeOptions' +}) +export class TimeOptionsPipe implements PipeTransform { + + transform(value: any, args?: any): any { + const index = value.findIndex(({ value }) => value === Number(args)); + return value.slice(index + 1, value.length); + } + +} diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html index f3824fee54..c0fd5e1289 100644 --- a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.html @@ -1,29 +1,47 @@ -
    - Each: - - For last: - -
    +
    + +
    +
    Each:
    + +
    + +
    +
    For last:
    + +
    -
    -

    {{validation.timeFrameErr}}

    + + + + + + + + + + + + + + + -
    +
    Time Settings diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less index ec7c385c4f..21c9966ef5 100644 --- a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.less @@ -4,9 +4,17 @@ @row-bottom-margin: 5px; :host { - span:first-child { - margin-left: 0; - } + color: white; + position: absolute; + display: flex; + box-shadow: 0 0 2px 1px white; + background: black; + padding: 5px 25px; + flex-direction: column; + top: 0; + right: 0; + transform: translate(0, calc(~'-100% - 15px')); + label, input[type=radio], select { cursor: pointer; padding: 2px; @@ -35,17 +43,35 @@ margin-bottom: 0; } - .advance-options-button { - display: block; + div.basic-options-container { + .select-box { + display: flex; + align-items: center; + margin: 6px 0; + .text { + color: white; + margin-right: 10px; + width: 50px; + display: inline-block; + white-space: pre; + } + select { + width: 70px; + } + } + } + + button.advance-options-button { + color: white; background: none; border: 0; outline: 0; padding: 0; - margin: @vertical-section-margin; + display: flex; + justify-content: flex-end; .chevron { - margin-left: 8px; + margin-left: 4px; display: inline-block; - float: right; font-size: 22px; line-height: 14px; transition: transform .3s linear; @@ -55,6 +81,7 @@ transform: rotate(90deg); } } + .advance-options-container { max-height: 0; overflow: hidden; @@ -109,9 +136,10 @@ } div.control-buttons { + margin-top: 10px; display: flex; justify-content: space-between; - color: @application-main-color; + color: @overlays-dots-color; a { cursor: pointer; &:hover { @@ -128,107 +156,9 @@ .error { color: crimson; } - } - .error-message-wrapper { - height: 15px; - .error-message { - height: 15px; - color: #d50000; - font-size: 12px; - margin: 0; - padding: 0; - } - } -} - - - -@import "core/less/common"; - -:host { - color: white; - transition: .4s; - position: absolute; - display: flex; - box-shadow: 0 0 2px 1px white; - background: black; - width: 230px; - height: 97px; - padding: 6px 12px; - flex-direction: column; - top: 0; - right: 0; - transform: translate(0, calc(~'-100% - 15px')); -} -.time-row { - display: flex; - align-items: center; - margin: 6px 0; - span { - color: white; - margin-right: 10px; - width: 75px; - display: inline-block; - white-space: pre; - } - button { - display: none; - } - label.time-row-button { - position: relative; - width: 155px; - height: 22px; - padding: 0 10px; - display: flex; - justify-content: space-around; - align-items: center; - background: white; - border: 1px solid dimgrey; - font-size: 12px; - color: white; - } - input { - background: black; - color: white; - width: 100%; - border: none; - padding: 0 10px; - left: 0; - box-sizing: border-box; - margin: 0; - position: absolute; - display: inline-block; - height: 100%; - } - i { - color: white; - position: absolute; - right: 10px; - top: 5px; } } -div.control-buttons { - margin-top: 10px; - display: flex; - justify-content: space-between; - color: @overlays-dots-color; - a { - cursor: pointer; - &:hover { - font-weight: bold; - } - &:first-child { - margin-right: 9px; - width: 42px; - } - &:last-child { - width: 17px; - } - } - .error { - color: crimson; - } -} + diff --git a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts index a7875587ae..44a6b320e5 100644 --- a/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts +++ b/src/app/@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component.ts @@ -71,7 +71,7 @@ export class TimelineIntervalsPickerComponent { } }; @Output() applyDate = new EventEmitter(); - @Output('closeComponent') closeComponent = new EventEmitter(); + @Output() closeComponent = new EventEmitter(); constructor() { // init map for easy search in timeFilterUnits.options diff --git a/src/app/@ansyn/status-bar/components/timeline-timepicker/timeline-timepicker.component.less b/src/app/@ansyn/status-bar/components/timeline-timepicker/timeline-timepicker.component.less index 5b129d5d82..fc83b5c2e8 100644 --- a/src/app/@ansyn/status-bar/components/timeline-timepicker/timeline-timepicker.component.less +++ b/src/app/@ansyn/status-bar/components/timeline-timepicker/timeline-timepicker.component.less @@ -2,7 +2,6 @@ :host { color: white; - transition: .4s; position: absolute; display: flex; box-shadow: 0 0 2px 1px white; diff --git a/src/app/@ansyn/status-bar/models/combo-boxes.model.ts b/src/app/@ansyn/status-bar/models/combo-boxes.model.ts index 149c020f88..47b94ab795 100644 --- a/src/app/@ansyn/status-bar/models/combo-boxes.model.ts +++ b/src/app/@ansyn/status-bar/models/combo-boxes.model.ts @@ -1,8 +1,8 @@ import { CaseGeoFilter, CaseOrientation, CaseTimeFilter } from '@ansyn/core/models/case.model'; import { InjectionToken } from '@angular/core'; -export const geoFilters: CaseGeoFilter[] = Object.values(CaseGeoFilter); -export const timeFilters: CaseTimeFilter[] = ['Start - End', 'Intervals']; +export const geoFilters: CaseGeoFilter[] = Object.values(CaseGeoFilter); +export const timeFilters: CaseTimeFilter[] = Object.values(CaseTimeFilter); export const orientations: CaseOrientation[] = ['Align North', 'User Perspective', 'Imagery Perspective']; export const ORIENTATIONS = new InjectionToken('ORIENTATIONS'); diff --git a/src/app/@ansyn/status-bar/status-bar.module.ts b/src/app/@ansyn/status-bar/status-bar.module.ts index 281b14b0b7..9b98fd9b20 100644 --- a/src/app/@ansyn/status-bar/status-bar.module.ts +++ b/src/app/@ansyn/status-bar/status-bar.module.ts @@ -21,6 +21,8 @@ import { ComboBoxesComponent } from './components/combo-boxes/combo-boxes.compon import { SelectedCaseBarComponent } from './components/selected-case-bar/selected-case-bar.component'; import { ComboBoxTriggerComponent } from './components/combo-box-trigger/combo-box-trigger.component'; import { ClickOutsideDirective } from './directives/click-outside.directive'; +import { TimeOptionsPipe } from './components/timeline-intervals-picker/time-options.pipe'; +import { TimeOptionsIntervalPipe } from './components/timeline-intervals-picker/time-options-interval.pipe'; @NgModule({ @@ -41,7 +43,9 @@ import { ClickOutsideDirective } from './directives/click-outside.directive'; SelectedCaseBarComponent, ComboBoxTriggerComponent, ClickOutsideDirective, - TimelineIntervalsPickerComponent + TimelineIntervalsPickerComponent, + TimeOptionsPipe, + TimeOptionsIntervalPipe ], providers: [ { From 37b683272c0db8999aaa039795358e6e75cca24d Mon Sep 17 00:00:00 2001 From: YairTawil Date: Wed, 13 Jun 2018 12:28:41 +0300 Subject: [PATCH 11/11] make unit tests pass --- .../cases/select-case.app.effects.spec.ts | 2 +- .../effects/filters.app.effects.ts | 1 - .../multiple-source-provider.spec.ts | 2 +- src/app/@ansyn/core/models/case.model.ts | 12 +++++---- .../cases/reducers/cases.reducer.spec.ts | 5 ++-- .../overlays/services/overlays.service.ts | 8 +++--- .../combo-boxes/combo-boxes.component.spec.ts | 6 ++++- .../status-bar/status-bar.component.spec.ts | 25 ++++++++++--------- 8 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/app/@ansyn/ansyn/app-effects/effects/cases/select-case.app.effects.spec.ts b/src/app/@ansyn/ansyn/app-effects/effects/cases/select-case.app.effects.spec.ts index db774fe25e..d13b7ad3f0 100644 --- a/src/app/@ansyn/ansyn/app-effects/effects/cases/select-case.app.effects.spec.ts +++ b/src/app/@ansyn/ansyn/app-effects/effects/cases/select-case.app.effects.spec.ts @@ -78,7 +78,7 @@ describe('SelectCaseAppEffects', () => { const orientation: CaseOrientation = 'Imagery Perspective', geoFilter: CaseGeoFilter = CaseGeoFilter.PinPoint, - timeFilter: CaseTimeFilter = 'Start - End', + timeFilter: CaseTimeFilter = CaseTimeFilter.StartEnd, time: CaseTimeState = { type: 'absolute', from: new Date(0), to: new Date(0) }, region: CaseRegionState = {}, dataInputFilters: CaseDataInputFiltersState = { fullyChecked: true, filters: [], active: true }, diff --git a/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts b/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts index ae7fa36e5a..581ce3c23d 100644 --- a/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts +++ b/src/app/@ansyn/ansyn/app-effects/effects/filters.app.effects.ts @@ -70,7 +70,6 @@ export class FiltersAppEffects { @Effect() updateOverlayFilters$ = this.onFiltersChanges$ .withLatestFrom(this.overlaysArray$, this.store$.select(selectOverlaysCriteria)) - .filter(([[filters, showOnlyFavorite, favoriteOverlays], overlaysArray, { time }]) => Boolean(time) ) .mergeMap(([[filters, showOnlyFavorite, favoriteOverlays], overlaysArray, { time }]: [[Filters, boolean, Overlay[]], Overlay[], OverlaysCriteria]) => { const filterModels: FilterModel[] = FiltersService.pluckFilterModels(filters); const filteredOverlays: string[] = OverlaysService.buildFilteredOverlays(overlaysArray, filterModels, favoriteOverlays, showOnlyFavorite, time); diff --git a/src/app/@ansyn/ansyn/app-providers/overlay-source-providers/multiple-source-provider.spec.ts b/src/app/@ansyn/ansyn/app-providers/overlay-source-providers/multiple-source-provider.spec.ts index 122319b180..b25a677eef 100644 --- a/src/app/@ansyn/ansyn/app-providers/overlay-source-providers/multiple-source-provider.spec.ts +++ b/src/app/@ansyn/ansyn/app-providers/overlay-source-providers/multiple-source-provider.spec.ts @@ -110,7 +110,7 @@ const regionCoordinates = [ const fetchParams: any = { limit: 250, region: turf.geometry('Polygon', regionCoordinates), - timeRange: {start: new Date().toISOString(), end: new Date().toISOString()} + timeRange: [{start: new Date().toISOString(), end: new Date().toISOString()}] }; const fetchParamsWithLimitZero: any = { ...fetchParams, limit: 0}; diff --git a/src/app/@ansyn/core/models/case.model.ts b/src/app/@ansyn/core/models/case.model.ts index 5c8aa1b21b..ad9fe2da07 100644 --- a/src/app/@ansyn/core/models/case.model.ts +++ b/src/app/@ansyn/core/models/case.model.ts @@ -31,7 +31,7 @@ export interface IContextEntity extends IVisualizerEntity { export type CaseOrientation = 'Align North' | 'User Perspective' | 'Imagery Perspective'; export enum CaseTimeFilter { - 'Start - End' = 'Start - End', + 'StartEnd' = 'Start - End', 'Intervals' = 'Intervals' } @@ -86,14 +86,16 @@ export interface CaseDataInputFiltersState { active: boolean } +export interface TimeIntervals { + interval: number // in milliseconds + criteria: CaseIntervalCriteria +} + export interface CaseTimeState { type: 'absolute', from: Date, to: Date - intervals?: { - interval: number // in milliseconds - criteria: CaseIntervalCriteria - } + intervals?: TimeIntervals; } export type CaseIntervalCriteriaType = 'best' | 'closest-before' | 'closest-after' | 'closest-both'; diff --git a/src/app/@ansyn/menu-items/cases/reducers/cases.reducer.spec.ts b/src/app/@ansyn/menu-items/cases/reducers/cases.reducer.spec.ts index aa94fdedd5..e1ce3aa45c 100644 --- a/src/app/@ansyn/menu-items/cases/reducers/cases.reducer.spec.ts +++ b/src/app/@ansyn/menu-items/cases/reducers/cases.reducer.spec.ts @@ -7,8 +7,7 @@ import { } from '../actions/cases.actions'; import { Case } from '../models/case.model'; import { casesAdapter, CasesReducer, ICasesState, initialCasesState } from './cases.reducer'; -import { CaseGeoFilter } from '@ansyn/core/models/case.model'; -import { CasesService } from '@ansyn/menu-items/cases/services/cases.service'; +import { CaseGeoFilter, CaseTimeFilter } from '@ansyn/core/models/case.model'; describe('CasesReducer', () => { @@ -26,7 +25,7 @@ describe('CasesReducer', () => { }, orientation: 'Align North', dataInputFilters: { fullyChecked: true, filters: [], active: true }, - timeFilter: 'Start - End', + timeFilter: CaseTimeFilter.StartEnd, geoFilter: CaseGeoFilter.PinPoint, region: {}, overlaysManualProcessArgs: {} diff --git a/src/app/@ansyn/overlays/services/overlays.service.ts b/src/app/@ansyn/overlays/services/overlays.service.ts index cfab39056d..3ae6dcfb2c 100644 --- a/src/app/@ansyn/overlays/services/overlays.service.ts +++ b/src/app/@ansyn/overlays/services/overlays.service.ts @@ -19,14 +19,14 @@ export const OverlaysConfig: InjectionToken = new InjectionToke @Injectable() export class OverlaysService { - static buildFilteredOverlays(overlays: Overlay[], parsedFilters: FilterModel[], favorites: Overlay[], showOnlyFavorite: boolean, timeState: CaseTimeState): string[] { + static buildFilteredOverlays(overlays: Overlay[], parsedFilters: FilterModel[], favorites: Overlay[], showOnlyFavorite: boolean, timeState?: CaseTimeState): string[] { let parsedOverlays: Overlay[] = favorites; if (!showOnlyFavorite) { const filteredOverlays = overlays.filter((overlay) => parsedFilters.every(filter => filter.filterFunc(overlay, filter.key))); parsedOverlays = [...parsedOverlays, ...filteredOverlays]; } - if (timeState.intervals) { + if (timeState && timeState.intervals) { // interval mode - limit overlays to be 1 per interval scope parsedOverlays = OverlaysService.intervalFilter(parsedOverlays, timeState); } @@ -232,8 +232,8 @@ export class OverlaysService { }); } - getSearchTimeRange(timeState: CaseTimeState): Array { - const searchTimeRange = new Array(); + getSearchTimeRange(timeState: CaseTimeState): IDateRange[] { + const searchTimeRange: IDateRange[] = []; if (!timeState.intervals) { searchTimeRange.push({ diff --git a/src/app/@ansyn/status-bar/components/combo-boxes/combo-boxes.component.spec.ts b/src/app/@ansyn/status-bar/components/combo-boxes/combo-boxes.component.spec.ts index 1227a9a44f..1e7f069d86 100644 --- a/src/app/@ansyn/status-bar/components/combo-boxes/combo-boxes.component.spec.ts +++ b/src/app/@ansyn/status-bar/components/combo-boxes/combo-boxes.component.spec.ts @@ -12,6 +12,8 @@ import { IStatusBarState, statusBarFeatureKey, StatusBarReducer } from '../../re import { statusBarFlagsItemsEnum } from '@ansyn/status-bar/models/status-bar-flag-items.model'; import { UpdateStatusFlagsAction } from '@ansyn/status-bar/actions/status-bar.actions'; import { ClickOutsideDirective } from '@ansyn/status-bar/directives/click-outside.directive'; +import { TimelineIntervalsPickerComponent } from '@ansyn/status-bar/components/timeline-intervals-picker/timeline-intervals-picker.component'; +import { DatePickerComponent } from '@ansyn/core/components/ansyn-date-picker/ansyn-date-picker.component'; describe('ComboBoxesComponent', () => { let component: ComboBoxesComponent; @@ -19,11 +21,13 @@ describe('ComboBoxesComponent', () => { const mockComboBoxComponent = MockComponent({ selector: 'ansyn-combo-box', inputs: ['options', 'selected', 'renderFunction', 'comboBoxToolTipDescription'], outputs: ['comboBoxToolTipDescription', 'selectedChange'] }); const ansynTreeView = MockComponent({ selector: 'ansyn-tree-view', outputs: ['closeTreeView'] }); const ansynComboTrigger = MockComponent({ selector: 'ansyn-combo-box-trigger', inputs: ['isActive', 'comboBoxToolTipDescription', 'render'] }); + const mockTimelineIntervalsPickerComponent = MockComponent({ selector: 'ansyn-timeline-intervals-picker', inputs: ['currentTimeState'] }); + let store: Store; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ComboBoxesComponent, mockComboBoxComponent, TimelineTimepickerComponent, ansynTreeView, ansynComboTrigger, ClickOutsideDirective], + declarations: [ComboBoxesComponent, mockComboBoxComponent, TimelineTimepickerComponent, mockTimelineIntervalsPickerComponent, ansynTreeView, ansynComboTrigger, ClickOutsideDirective], imports: [StoreModule.forRoot({ [coreFeatureKey]: CoreReducer, [statusBarFeatureKey]: StatusBarReducer }), EffectsModule.forRoot([])], providers: [ { diff --git a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.spec.ts b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.spec.ts index dd43c86612..62c9091e51 100644 --- a/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.spec.ts +++ b/src/app/@ansyn/status-bar/components/status-bar/status-bar.component.spec.ts @@ -2,16 +2,7 @@ import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing' import { StatusBarComponent } from './status-bar.component'; import { Store, StoreModule } from '@ngrx/store'; import { IStatusBarState, statusBarFeatureKey, StatusBarReducer } from '../../reducers/status-bar.reducer'; -import { ExpandAction, UpdateStatusFlagsAction } from '../../actions/status-bar.actions'; -import { StatusBarModule } from '../../status-bar.module'; import { EffectsModule } from '@ngrx/effects'; -import { LoggerConfig } from '@ansyn/core/models/logger.config'; -import { StatusBarConfig } from '../../models/statusBar.config'; -import { statusBarFlagsItemsEnum } from '../../models/status-bar-flag-items.model'; -import { GoAdjacentOverlay } from '@ansyn/core/actions/core.actions'; -import { comboBoxesOptions, GEO_FILTERS, ORIENTATIONS, TIME_FILTERS } from '../../models/combo-boxes.model'; -import { ALERTS } from '@ansyn/core/alerts/alerts.model'; -import { ImageryStatusComponent } from '@ansyn/core/components/imagery-status/imagery-status.component'; import { MockComponent } from '@ansyn/core/test/mock-component'; import { coreFeatureKey, CoreReducer } from '@ansyn/core/reducers/core.reducer'; @@ -19,14 +10,24 @@ describe('StatusBarComponent', () => { let component: StatusBarComponent; let fixture: ComponentFixture; let store: Store; - const mockImageryStatusComponent = MockComponent({ selector: 'ansyn-imagery-status', inputs: ['overlay', 'active', 'mapId'], outputs: ['backToWorldView']}) - const mockSelectedCaseBarComponent = MockComponent({ selector: 'ansyn-selected-case-bar', inputs: ['selectedCaseName'] }); + const mockImageryStatusComponent = MockComponent({ + selector: 'ansyn-imagery-status', + inputs: ['overlay', 'active', 'mapId'], + outputs: ['backToWorldView'] + }); + const mockSelectedCaseBarComponent = MockComponent({ + selector: 'ansyn-selected-case-bar', + inputs: ['selectedCaseName'] + }); const mockComboBoxes = MockComponent({ selector: 'ansyn-combo-boxes' }); const mockNavigationBar = MockComponent({ selector: 'ansyn-navigation-bar' }); beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [StoreModule.forRoot({ [statusBarFeatureKey]: StatusBarReducer, [coreFeatureKey]: CoreReducer }), EffectsModule.forRoot([])], + imports: [StoreModule.forRoot({ + [statusBarFeatureKey]: StatusBarReducer, + [coreFeatureKey]: CoreReducer + }), EffectsModule.forRoot([])], declarations: [StatusBarComponent, /* mock */ mockImageryStatusComponent,