mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +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 {
|
.filter-column {
|
||||||
max-height: 12em;
|
max-height: 12em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -48,3 +55,106 @@
|
|||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
background-color: #fff;
|
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 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)"
|
<span (click)="toggleSelectOnly(filter, option, $event)"
|
||||||
class="oi oi-pin" title="Select only this"
|
class="oi oi-pin" title="Select only this"
|
||||||
[ngClass]="isOnlySelected(filter,option) ? 'filter-only-selected' : 'filter-pin'"
|
[ngClass]="isOnlySelected(filter,option) ? 'filter-only-selected' : 'filter-pin'"
|
||||||
i18n-title></span>
|
i18n-title></span>
|
||||||
{{option.name === undefined ? unknownText : option.name}}
|
{{option.name === undefined ? unknownText : option.name}}
|
||||||
</div>
|
</div>
|
||||||
<span class="badge badge-pill"
|
<span class="badge badge-pill"
|
||||||
[class.badge-primary]="option.selected"
|
[class.badge-primary]="option.selected"
|
||||||
[class.badge-secondary]="!option.selected"
|
[class.badge-secondary]="!option.selected"
|
||||||
>{{option.count}}</span>
|
>{{option.count}}</span>
|
||||||
|
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="card-body text-center" *ngIf="filter.options.length === 0" i18n>Nothing to filter</div>
|
<div class="card-body text-center" *ngIf="filter.options.length === 0" i18n>Nothing to filter</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,11 +11,38 @@ import {OnDestroy, OnInit} from '../../../../../../node_modules/@angular/core';
|
|||||||
})
|
})
|
||||||
export class GalleryFilterComponent implements OnInit, OnDestroy {
|
export class GalleryFilterComponent implements OnInit, OnDestroy {
|
||||||
public readonly unknownText;
|
public readonly unknownText;
|
||||||
|
minDate = 0;
|
||||||
|
maxDate = 100;
|
||||||
|
NUMBER_MAX_VALUE = Number.MAX_VALUE;
|
||||||
|
NUMBER_MIN_VALUE = Number.MIN_VALUE;
|
||||||
|
|
||||||
constructor(public filterService: FilterService) {
|
constructor(public filterService: FilterService) {
|
||||||
this.unknownText = '<' + $localize`unknown` + '>';
|
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 {
|
ngOnDestroy(): void {
|
||||||
setTimeout(() => this.filterService.setShowingFilters(false));
|
setTimeout(() => this.filterService.setShowingFilters(false));
|
||||||
}
|
}
|
||||||
@ -49,5 +76,21 @@ export class GalleryFilterComponent implements OnInit, OnDestroy {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.filterService.onFilterChange();
|
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[]>([
|
public readonly activeFilters = new BehaviorSubject({
|
||||||
{
|
filtersVisible: false,
|
||||||
filter: this.AVAILABLE_FILTERS[0],
|
dateFilter: {
|
||||||
options: []
|
minDate: 0,
|
||||||
}, {
|
maxDate: Date.now(),
|
||||||
filter: this.AVAILABLE_FILTERS[1],
|
minFilter: Number.MIN_VALUE,
|
||||||
options: []
|
maxFilter: Number.MAX_VALUE
|
||||||
}, {
|
},
|
||||||
filter: this.AVAILABLE_FILTERS[7],
|
selectedFilters: [
|
||||||
options: []
|
{
|
||||||
}, {
|
filter: this.AVAILABLE_FILTERS[0],
|
||||||
filter: this.AVAILABLE_FILTERS[4],
|
options: []
|
||||||
options: []
|
}, {
|
||||||
}
|
filter: this.AVAILABLE_FILTERS[1],
|
||||||
]);
|
options: []
|
||||||
filtersVisible = false;
|
}, {
|
||||||
|
filter: this.AVAILABLE_FILTERS[7],
|
||||||
|
options: []
|
||||||
|
}, {
|
||||||
|
filter: this.AVAILABLE_FILTERS[4],
|
||||||
|
options: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
@ -107,8 +115,8 @@ export class FilterService {
|
|||||||
public applyFilters(directoryContent: Observable<DirectoryContent>): Observable<DirectoryContent> {
|
public applyFilters(directoryContent: Observable<DirectoryContent>): Observable<DirectoryContent> {
|
||||||
return directoryContent.pipe(switchMap((dirContent: DirectoryContent) => {
|
return directoryContent.pipe(switchMap((dirContent: DirectoryContent) => {
|
||||||
|
|
||||||
return this.selectedFilters.pipe(map((filters: SelectedFilter[]) => {
|
return this.activeFilters.pipe(map(afilters => {
|
||||||
if (!dirContent || !dirContent.media || !this.filtersVisible) {
|
if (!dirContent || !dirContent.media || !afilters.filtersVisible) {
|
||||||
return dirContent;
|
return dirContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +128,27 @@ export class FilterService {
|
|||||||
metaFile: dirContent.metaFile
|
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
|
// get options
|
||||||
const valueMap: { [key: string]: any } = {};
|
const valueMap: { [key: string]: any } = {};
|
||||||
@ -173,16 +201,18 @@ export class FilterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onFilterChange(): void {
|
public onFilterChange(): void {
|
||||||
this.selectedFilters.next(this.selectedFilters.value);
|
this.activeFilters.next(this.activeFilters.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowingFilters(value: boolean): void {
|
setShowingFilters(value: boolean): void {
|
||||||
if (this.filtersVisible === value) {
|
if (this.activeFilters.value.filtersVisible === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.filtersVisible = value;
|
this.activeFilters.value.filtersVisible = value;
|
||||||
if (!this.filtersVisible) {
|
if (!this.activeFilters.value.filtersVisible) {
|
||||||
this.selectedFilters.value.forEach(f => f.options = []);
|
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();
|
this.onFilterChange();
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,14 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<a class="btn btn-navbar"
|
<ng-container *ngIf="ItemCount> 0">
|
||||||
[class.btn-secondary]="showFilters"
|
<a class="btn btn-navbar"
|
||||||
(click)="showFilters = ! showFilters">
|
[class.btn-secondary]="showFilters"
|
||||||
|
(click)="showFilters = ! showFilters">
|
||||||
<span class="oi oi-spreadsheet"
|
<span class="oi oi-spreadsheet"
|
||||||
title="Filters" i18n-title></span>
|
title="Filters" i18n-title></span>
|
||||||
</a>
|
</a>
|
||||||
|
</ng-container>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
<div class="btn-group" dropdown placement="bottom right">
|
<div class="btn-group" dropdown placement="bottom right">
|
||||||
<button id="button-alignment" dropdownToggle type="button"
|
<button id="button-alignment" dropdownToggle type="button"
|
||||||
@ -70,4 +72,4 @@
|
|||||||
|
|
||||||
</nav>
|
</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 {
|
export class GalleryNavigatorComponent {
|
||||||
|
|
||||||
SortingMethods = SortingMethods;
|
public SortingMethods = SortingMethods;
|
||||||
sortingMethodsType: { key: number; value: string }[] = [];
|
public sortingMethodsType: { key: number; value: string }[] = [];
|
||||||
readonly config = Config;
|
public readonly config = Config;
|
||||||
// DefaultSorting = Config.Client.Other.defaultPhotoSortingMethod;
|
// DefaultSorting = Config.Client.Other.defaultPhotoSortingMethod;
|
||||||
readonly SearchQueryTypes = SearchQueryTypes;
|
public readonly SearchQueryTypes = SearchQueryTypes;
|
||||||
wrappedContent: Observable<ContentWrapperWithError>;
|
public wrappedContent: Observable<ContentWrapperWithError>;
|
||||||
public directoryContent: Observable<DirectoryContent>;
|
public directoryContent: Observable<DirectoryContent>;
|
||||||
showFilters = false;
|
public showFilters = false;
|
||||||
private readonly RootFolderName: string;
|
private readonly RootFolderName: string;
|
||||||
|
|
||||||
constructor(private authService: AuthenticationService,
|
constructor(private authService: AuthenticationService,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user