diff --git a/src/frontend/app/ui/gallery/filter/filter.gallery.component.css b/src/frontend/app/ui/gallery/filter/filter.gallery.component.css index 3a7b1495..6c34951d 100644 --- a/src/frontend/app/ui/gallery/filter/filter.gallery.component.css +++ b/src/frontend/app/ui/gallery/filter/filter.gallery.component.css @@ -1,3 +1,10 @@ +@media (min-width: 768px) { + .col-md-1-half { + flex: 0 0 12.5%; + max-width: 12.5%; + } +} + .filter-column { max-height: 12em; overflow-y: auto; @@ -48,3 +55,106 @@ color: #6c757d; background-color: #fff; } + +.date-filter-wrapper { + padding: 0; +} + +.date-filter-wrapper { + position: relative; + height: 7px; + margin: 1.3em 0 1.3em 0; +} + +.date-filter-wrapper > div { + position: absolute; + left: 10px; + right: 8px; + height: 7px; +} + +.date-filter-wrapper > div > .inverse-left { + position: absolute; + left: 0; + height: 7px; + border-radius: 10px; + background-color: #CCC; + margin: 0 7px; +} + +.date-filter-wrapper > div > .inverse-right { + position: absolute; + right: 0; + height: 7px; + border-radius: 10px; + background-color: #CCC; + margin: 0 7px; +} + + +.date-filter-wrapper > div > .range { + position: absolute; + left: 0; + height: 7px; + border-radius: 14px !important; + background-color: #007bff; +} + +.date-filter-wrapper > div > .thumb { + position: absolute; + top: -6px; + z-index: 2; + height: 20px; + width: 20px; + text-align: left; + margin-left: -11px; + cursor: pointer; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4); + background-color: #007bff; + border-radius: 50% !important; + outline: none; +} + +.date-filter-wrapper > input[type=range] { + position: absolute; + pointer-events: none; + z-index: 3; + height: 14px; + top: -3px; + width: 100%; + opacity: 0; +} + +div.date-filter-wrapper > input[type=range]:focus::-webkit-slider-runnable-track { + background: transparent; + border: transparent; +} + +div.date-filter-wrapper > input[type=range]:focus { + outline: none; +} + +div.date-filter-wrapper > input[type=range]::-webkit-slider-thumb { + pointer-events: all; + width: 28px; + height: 28px; + border-radius: 0 !important; + border: 0 none; + background: #007bff; + -webkit-appearance: none; +} + +div.date-filter-wrapper > input[type=range]::-ms-fill-lower { + background: transparent; + border: 0 none; +} + +div.date-filter-wrapper > input[type=range]::-ms-fill-upper { + background: transparent; + border: 0 none; +} + +div.date-filter-wrapper > input[type=range]::-ms-tooltip { + display: none; +} + diff --git a/src/frontend/app/ui/gallery/filter/filter.gallery.component.html b/src/frontend/app/ui/gallery/filter/filter.gallery.component.html index 82d1d4fe..a2dc3884 100644 --- a/src/frontend/app/ui/gallery/filter/filter.gallery.component.html +++ b/src/frontend/app/ui/gallery/filter/filter.gallery.component.html @@ -1,36 +1,84 @@
-
-
- -
-
    -
  • -
    +
    +
    +
    +
    + {{ActiveFilters.dateFilter.minFilter | date: 'longDate'}} +
    +
    +
    +
    +
    +
    +
    + + + +
    + + + +
    +
    +
    + {{ActiveFilters.dateFilter.maxFilter | date: 'longDate'}} +
    +
    +
    +
    +
    + +
    +
      +
    • + +
      - {{option.name === undefined ? unknownText : option.name}} -
      - {{option.count}} + {{option.name === undefined ? unknownText : option.name}} +
    + {{option.count}} -
  • -
-
Nothing to filter
+ + +
Nothing to filter
+
diff --git a/src/frontend/app/ui/gallery/filter/filter.gallery.component.ts b/src/frontend/app/ui/gallery/filter/filter.gallery.component.ts index a230c44b..a261d9bb 100644 --- a/src/frontend/app/ui/gallery/filter/filter.gallery.component.ts +++ b/src/frontend/app/ui/gallery/filter/filter.gallery.component.ts @@ -11,11 +11,38 @@ import {OnDestroy, OnInit} from '../../../../../../node_modules/@angular/core'; }) export class GalleryFilterComponent implements OnInit, OnDestroy { public readonly unknownText; + minDate = 0; + maxDate = 100; + NUMBER_MAX_VALUE = Number.MAX_VALUE; + NUMBER_MIN_VALUE = Number.MIN_VALUE; constructor(public filterService: FilterService) { this.unknownText = '<' + $localize`unknown` + '>'; } + get MinDatePrc(): number { + return ((this.ActiveFilters.dateFilter.minFilter - this.ActiveFilters.dateFilter.minDate) / + (this.ActiveFilters.dateFilter.maxDate - this.ActiveFilters.dateFilter.minDate)) * 100; + } + + get MaxDatePrc(): number { + return ((this.ActiveFilters.dateFilter.maxFilter - this.ActiveFilters.dateFilter.minDate) / + (this.ActiveFilters.dateFilter.maxDate - this.ActiveFilters.dateFilter.minDate)) * 100; + } + + get ActiveFilters(): { + filtersVisible: boolean, + dateFilter: { + minDate: number, + maxDate: number, + minFilter: number, + maxFilter: number + }, + selectedFilters: SelectedFilter[] + } { + return this.filterService.activeFilters.value; + } + ngOnDestroy(): void { setTimeout(() => this.filterService.setShowingFilters(false)); } @@ -49,5 +76,21 @@ export class GalleryFilterComponent implements OnInit, OnDestroy { event.stopPropagation(); this.filterService.onFilterChange(); } + + newMinDate($event: Event): void { + const diff = (this.ActiveFilters.dateFilter.maxDate - this.ActiveFilters.dateFilter.minDate) * 0.01; + if (this.ActiveFilters.dateFilter.minFilter > this.ActiveFilters.dateFilter.maxFilter - diff) { + this.ActiveFilters.dateFilter.minFilter = Math.max(this.ActiveFilters.dateFilter.maxFilter - diff, this.ActiveFilters.dateFilter.minDate); + } + this.filterService.onFilterChange(); + } + + newMaxDate($event: Event): void { + const diff = (this.ActiveFilters.dateFilter.maxDate - this.ActiveFilters.dateFilter.minDate) * 0.01; + if (this.ActiveFilters.dateFilter.maxFilter < this.ActiveFilters.dateFilter.minFilter + diff) { + this.ActiveFilters.dateFilter.maxFilter = Math.min(this.ActiveFilters.dateFilter.minFilter + diff, this.ActiveFilters.dateFilter.maxDate); + } + this.filterService.onFilterChange(); + } } diff --git a/src/frontend/app/ui/gallery/filter/filter.service.ts b/src/frontend/app/ui/gallery/filter/filter.service.ts index 6d7ed987..74cc7c32 100644 --- a/src/frontend/app/ui/gallery/filter/filter.service.ts +++ b/src/frontend/app/ui/gallery/filter/filter.service.ts @@ -84,22 +84,30 @@ export class FilterService { }, ]; - public readonly selectedFilters = new BehaviorSubject([ - { - filter: this.AVAILABLE_FILTERS[0], - options: [] - }, { - filter: this.AVAILABLE_FILTERS[1], - options: [] - }, { - filter: this.AVAILABLE_FILTERS[7], - options: [] - }, { - filter: this.AVAILABLE_FILTERS[4], - options: [] - } - ]); - filtersVisible = false; + public readonly activeFilters = new BehaviorSubject({ + filtersVisible: false, + dateFilter: { + minDate: 0, + maxDate: Date.now(), + minFilter: Number.MIN_VALUE, + maxFilter: Number.MAX_VALUE + }, + selectedFilters: [ + { + filter: this.AVAILABLE_FILTERS[0], + options: [] + }, { + filter: this.AVAILABLE_FILTERS[1], + options: [] + }, { + filter: this.AVAILABLE_FILTERS[7], + options: [] + }, { + filter: this.AVAILABLE_FILTERS[4], + options: [] + } + ] + }); constructor() { } @@ -107,8 +115,8 @@ export class FilterService { public applyFilters(directoryContent: Observable): Observable { return directoryContent.pipe(switchMap((dirContent: DirectoryContent) => { - return this.selectedFilters.pipe(map((filters: SelectedFilter[]) => { - if (!dirContent || !dirContent.media || !this.filtersVisible) { + return this.activeFilters.pipe(map(afilters => { + if (!dirContent || !dirContent.media || !afilters.filtersVisible) { return dirContent; } @@ -120,7 +128,27 @@ export class FilterService { metaFile: dirContent.metaFile }; - for (const f of filters) { + // date filters + if (c.media.length > 0) { + afilters.dateFilter.minDate = c.media.reduce((p, curr) => Math.min(p, curr.metadata.creationDate), Number.MAX_VALUE - 1); + afilters.dateFilter.maxDate = c.media.reduce((p, curr) => Math.max(p, curr.metadata.creationDate), Number.MIN_VALUE + 1); + if (afilters.dateFilter.minFilter === Number.MIN_VALUE) { + afilters.dateFilter.minFilter = afilters.dateFilter.minDate; + } + if (afilters.dateFilter.maxFilter === Number.MAX_VALUE) { + afilters.dateFilter.maxFilter = afilters.dateFilter.maxDate; + } + + c.media = c.media.filter(m => m.metadata.creationDate >= afilters.dateFilter.minFilter && m.metadata.creationDate <= afilters.dateFilter.maxFilter); + } else { + afilters.dateFilter.minDate = Number.MIN_VALUE; + afilters.dateFilter.maxDate = Number.MAX_VALUE; + afilters.dateFilter.minFilter = Number.MIN_VALUE; + afilters.dateFilter.maxFilter = Number.MAX_VALUE; + } + + // filters + for (const f of afilters.selectedFilters) { // get options const valueMap: { [key: string]: any } = {}; @@ -173,16 +201,18 @@ export class FilterService { } public onFilterChange(): void { - this.selectedFilters.next(this.selectedFilters.value); + this.activeFilters.next(this.activeFilters.value); } setShowingFilters(value: boolean): void { - if (this.filtersVisible === value) { + if (this.activeFilters.value.filtersVisible === value) { return; } - this.filtersVisible = value; - if (!this.filtersVisible) { - this.selectedFilters.value.forEach(f => f.options = []); + this.activeFilters.value.filtersVisible = value; + if (!this.activeFilters.value.filtersVisible) { + this.activeFilters.value.dateFilter.minFilter = Number.MIN_VALUE; + this.activeFilters.value.dateFilter.maxFilter = Number.MAX_VALUE; + this.activeFilters.value.selectedFilters.forEach(f => f.options = []); } this.onFilterChange(); } diff --git a/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html b/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html index 3a9052e4..c458e518 100644 --- a/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html +++ b/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html @@ -40,12 +40,14 @@
 
- + + - + +