1
0
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:
Patrik J. Braun 2022-02-22 13:19:09 +01:00
parent 8665e17deb
commit 9c96dba032
6 changed files with 293 additions and 60 deletions

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -40,12 +40,14 @@
</a>
<div class="divider">&nbsp;</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">&nbsp;</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>

View File

@ -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,