mirror of
https://github.com/xuthus83/pigallery2.git
synced 2024-11-03 21:04:03 +08:00
Adding slider to filters and blocking filters when no media is available #287 #improvement
This commit is contained in:
parent
8665e17deb
commit
9c96dba032
@ -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;
|
||||
}
|
||||
|
||||
|
@ -1,36 +1,84 @@
|
||||
<div class="card bg-light filter-container">
|
||||
<div class="card-body row ">
|
||||
<div class="col-12 col-md-6 col-lg-3 mt-1" *ngFor="let filter of filterService.selectedFilters | async; let i=index">
|
||||
<select [(ngModel)]="filter.filter"
|
||||
(ngModelChange)="filterService.onFilterChange()"
|
||||
class="form-control" id="gallery-filter-{{i}}">
|
||||
<option *ngFor="let f of filterService.AVAILABLE_FILTERS"
|
||||
[ngValue]="f">{{f.name}}</option>
|
||||
</select>
|
||||
<div class="filter-column">
|
||||
<ul class="list-group" *ngIf="filter.options.length > 0">
|
||||
<li
|
||||
*ngFor="let option of filter.options"
|
||||
[class.unselected]="!option.selected"
|
||||
(click)="option.selected = !option.selected; filterService.onFilterChange()"
|
||||
class="filter-option list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
|
||||
<div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-1-half col-12 d-table">
|
||||
<div
|
||||
*ngIf="ActiveFilters.dateFilter.minFilter !== NUMBER_MIN_VALUE"
|
||||
class="d-table-cell align-middle text-center">
|
||||
{{ActiveFilters.dateFilter.minFilter | date: 'longDate'}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="date-filter-wrapper col-md-9 col-12">
|
||||
<div>
|
||||
<div class="inverse-left"
|
||||
[style.width.%]="MinDatePrc"></div>
|
||||
<div class="inverse-right"
|
||||
[style.width.%]="100-MaxDatePrc"></div>
|
||||
<div class="range"
|
||||
[style.left.%]="MinDatePrc"
|
||||
[style.right.%]="100-MaxDatePrc"></div>
|
||||
<span class="thumb"
|
||||
[style.left.%]="MinDatePrc"></span>
|
||||
<span class="thumb"
|
||||
[style.left.%]="MaxDatePrc"></span>
|
||||
|
||||
</div>
|
||||
<input type="range"
|
||||
[max]="ActiveFilters.dateFilter.maxDate"
|
||||
[min]="ActiveFilters.dateFilter.minDate"
|
||||
[(ngModel)]="ActiveFilters.dateFilter.minFilter"
|
||||
step="60"
|
||||
(change)="newMinDate($event); filterService.onFilterChange()"/>
|
||||
<input type="range"
|
||||
step="60"
|
||||
[max]="ActiveFilters.dateFilter.maxDate"
|
||||
[min]="ActiveFilters.dateFilter.minDate"
|
||||
[(ngModel)]="ActiveFilters.dateFilter.maxFilter"
|
||||
(change)="newMaxDate($event); filterService.onFilterChange()"/>
|
||||
|
||||
</div>
|
||||
<div class="col-md-1-half col-12 d-table">
|
||||
<div class="d-table-cell align-middle text-center"
|
||||
*ngIf="ActiveFilters.dateFilter.maxFilter !== NUMBER_MAX_VALUE">
|
||||
{{ActiveFilters.dateFilter.maxFilter | date: 'longDate'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 col-lg-3 mt-1"
|
||||
*ngFor="let filter of ActiveFilters.selectedFilters; let i=index">
|
||||
<select [(ngModel)]="filter.filter"
|
||||
(ngModelChange)="filterService.onFilterChange()"
|
||||
class="form-control" id="gallery-filter-{{i}}">
|
||||
<option *ngFor="let f of filterService.AVAILABLE_FILTERS"
|
||||
[ngValue]="f">{{f.name}}</option>
|
||||
</select>
|
||||
<div class="filter-column">
|
||||
<ul class="list-group" *ngIf="filter.options.length > 0">
|
||||
<li
|
||||
*ngFor="let option of filter.options"
|
||||
[class.unselected]="!option.selected"
|
||||
(click)="option.selected = !option.selected; filterService.onFilterChange()"
|
||||
class="filter-option list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
|
||||
<div>
|
||||
<span (click)="toggleSelectOnly(filter, option, $event)"
|
||||
class="oi oi-pin" title="Select only this"
|
||||
[ngClass]="isOnlySelected(filter,option) ? 'filter-only-selected' : 'filter-pin'"
|
||||
i18n-title></span>
|
||||
{{option.name === undefined ? unknownText : option.name}}
|
||||
</div>
|
||||
<span class="badge badge-pill"
|
||||
[class.badge-primary]="option.selected"
|
||||
[class.badge-secondary]="!option.selected"
|
||||
>{{option.count}}</span>
|
||||
{{option.name === undefined ? unknownText : option.name}}
|
||||
</div>
|
||||
<span class="badge badge-pill"
|
||||
[class.badge-primary]="option.selected"
|
||||
[class.badge-secondary]="!option.selected"
|
||||
>{{option.count}}</span>
|
||||
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card-body text-center" *ngIf="filter.options.length === 0" i18n>Nothing to filter</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card-body text-center" *ngIf="filter.options.length === 0" i18n>Nothing to filter</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,22 +84,30 @@ export class FilterService {
|
||||
},
|
||||
];
|
||||
|
||||
public readonly selectedFilters = new BehaviorSubject<SelectedFilter[]>([
|
||||
{
|
||||
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<DirectoryContent>): Observable<DirectoryContent> {
|
||||
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();
|
||||
}
|
||||
|
@ -40,12 +40,14 @@
|
||||
</a>
|
||||
<div class="divider"> </div>
|
||||
</ng-container>
|
||||
<a class="btn btn-navbar"
|
||||
[class.btn-secondary]="showFilters"
|
||||
(click)="showFilters = ! showFilters">
|
||||
<ng-container *ngIf="ItemCount> 0">
|
||||
<a class="btn btn-navbar"
|
||||
[class.btn-secondary]="showFilters"
|
||||
(click)="showFilters = ! showFilters">
|
||||
<span class="oi oi-spreadsheet"
|
||||
title="Filters" i18n-title></span>
|
||||
</a>
|
||||
</a>
|
||||
</ng-container>
|
||||
<div class="divider"> </div>
|
||||
<div class="btn-group" dropdown placement="bottom right">
|
||||
<button id="button-alignment" dropdownToggle type="button"
|
||||
@ -70,4 +72,4 @@
|
||||
|
||||
</nav>
|
||||
|
||||
<app-gallery-filter *ngIf="showFilters"></app-gallery-filter>
|
||||
<app-gallery-filter *ngIf="showFilters && ItemCount> 0"></app-gallery-filter>
|
||||
|
@ -20,14 +20,14 @@ import {GallerySortingService} from './sorting.service';
|
||||
})
|
||||
export class GalleryNavigatorComponent {
|
||||
|
||||
SortingMethods = SortingMethods;
|
||||
sortingMethodsType: { key: number; value: string }[] = [];
|
||||
readonly config = Config;
|
||||
public SortingMethods = SortingMethods;
|
||||
public sortingMethodsType: { key: number; value: string }[] = [];
|
||||
public readonly config = Config;
|
||||
// DefaultSorting = Config.Client.Other.defaultPhotoSortingMethod;
|
||||
readonly SearchQueryTypes = SearchQueryTypes;
|
||||
wrappedContent: Observable<ContentWrapperWithError>;
|
||||
public readonly SearchQueryTypes = SearchQueryTypes;
|
||||
public wrappedContent: Observable<ContentWrapperWithError>;
|
||||
public directoryContent: Observable<DirectoryContent>;
|
||||
showFilters = false;
|
||||
public showFilters = false;
|
||||
private readonly RootFolderName: string;
|
||||
|
||||
constructor(private authService: AuthenticationService,
|
||||
|
Loading…
Reference in New Issue
Block a user