You've already forked pigallery2
mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-07-13 01:20:23 +02:00
Merge pull request #425 from bpatrik/feature/filter
Feature/filter #287
This commit is contained in:
@ -3,7 +3,7 @@ import {BrowserModule, HAMMER_GESTURE_CONFIG, HammerGestureConfig, HammerModule}
|
|||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
import {AppComponent} from './app.component';
|
import {AppComponent} from './app.component';
|
||||||
import {UserService} from './model/network/user.service';
|
import {UserService} from './model/network/user.service';
|
||||||
import {GalleryService} from './ui/gallery/gallery.service';
|
import {ContentService} from './ui/gallery/content.service';
|
||||||
import {NetworkService} from './model/network/network.service';
|
import {NetworkService} from './model/network/network.service';
|
||||||
import {GalleryCacheService} from './ui/gallery/cache.gallery.service';
|
import {GalleryCacheService} from './ui/gallery/cache.gallery.service';
|
||||||
import {FullScreenService} from './ui/gallery/fullscreen.service';
|
import {FullScreenService} from './ui/gallery/fullscreen.service';
|
||||||
@ -114,6 +114,9 @@ import {BlogService} from './ui/gallery/blog/blog.service';
|
|||||||
import {PhotoFilterPipe} from './pipes/PhotoFilterPipe';
|
import {PhotoFilterPipe} from './pipes/PhotoFilterPipe';
|
||||||
import {PreviewSettingsComponent} from './ui/settings/preview/preview.settings.component';
|
import {PreviewSettingsComponent} from './ui/settings/preview/preview.settings.component';
|
||||||
import {GallerySearchFieldComponent} from './ui/gallery/search/search-field/search-field.gallery.component';
|
import {GallerySearchFieldComponent} from './ui/gallery/search/search-field/search-field.gallery.component';
|
||||||
|
import {GalleryFilterComponent} from './ui/gallery/filter/filter.gallery.component';
|
||||||
|
import {GallerySortingService} from './ui/gallery/navigator/sorting.service';
|
||||||
|
import {FilterService} from './ui/gallery/filter/filter.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MyHammerConfig extends HammerGestureConfig {
|
export class MyHammerConfig extends HammerGestureConfig {
|
||||||
@ -213,6 +216,7 @@ Marker.prototype.options.icon = iconDefault;
|
|||||||
GallerySearchQueryBuilderComponent,
|
GallerySearchQueryBuilderComponent,
|
||||||
GalleryShareComponent,
|
GalleryShareComponent,
|
||||||
GalleryNavigatorComponent,
|
GalleryNavigatorComponent,
|
||||||
|
GalleryFilterComponent,
|
||||||
GalleryPhotoComponent,
|
GalleryPhotoComponent,
|
||||||
AdminComponent,
|
AdminComponent,
|
||||||
InfoPanelLightboxComponent,
|
InfoPanelLightboxComponent,
|
||||||
@ -269,7 +273,9 @@ Marker.prototype.options.icon = iconDefault;
|
|||||||
UserService,
|
UserService,
|
||||||
AlbumsService,
|
AlbumsService,
|
||||||
GalleryCacheService,
|
GalleryCacheService,
|
||||||
GalleryService,
|
ContentService,
|
||||||
|
FilterService,
|
||||||
|
GallerySortingService,
|
||||||
MapService,
|
MapService,
|
||||||
BlogService,
|
BlogService,
|
||||||
SearchQueryParserService,
|
SearchQueryParserService,
|
||||||
|
@ -3,7 +3,7 @@ import {ShareService} from '../ui/gallery/share.service';
|
|||||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||||
import {QueryParams} from '../../../common/QueryParams';
|
import {QueryParams} from '../../../common/QueryParams';
|
||||||
import {Utils} from '../../../common/Utils';
|
import {Utils} from '../../../common/Utils';
|
||||||
import {GalleryService} from '../ui/gallery/gallery.service';
|
import {ContentService} from '../ui/gallery/content.service';
|
||||||
import {Config} from '../../../common/config/public/Config';
|
import {Config} from '../../../common/config/public/Config';
|
||||||
import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ export class QueryService {
|
|||||||
|
|
||||||
|
|
||||||
constructor(private shareService: ShareService,
|
constructor(private shareService: ShareService,
|
||||||
private galleryService: GalleryService) {
|
private galleryService: ContentService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMediaStringId(media: MediaDTO): string {
|
getMediaStringId(media: MediaDTO): string {
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {NetworkService} from '../../model/network/network.service';
|
import {NetworkService} from '../../model/network/network.service';
|
||||||
import {ContentWrapper} from '../../../../common/entities/ConentWrapper';
|
import {ContentWrapper} from '../../../../common/entities/ConentWrapper';
|
||||||
import {DirectoryDTOUtils, ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
import {DirectoryDTOUtils, ParentDirectoryDTO, SubDirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||||
import {GalleryCacheService} from './cache.gallery.service';
|
import {GalleryCacheService} from './cache.gallery.service';
|
||||||
import {BehaviorSubject} from 'rxjs';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {Config} from '../../../../common/config/public/Config';
|
import {Config} from '../../../../common/config/public/Config';
|
||||||
import {ShareService} from './share.service';
|
import {ShareService} from './share.service';
|
||||||
import {NavigationService} from '../../model/navigation.service';
|
import {NavigationService} from '../../model/navigation.service';
|
||||||
import {SortingMethods} from '../../../../common/entities/SortingMethods';
|
|
||||||
import {QueryParams} from '../../../../common/QueryParams';
|
import {QueryParams} from '../../../../common/QueryParams';
|
||||||
import {PG2ConfMap} from '../../../../common/PG2ConfMap';
|
|
||||||
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
||||||
import {ErrorCodes} from '../../../../common/entities/Error';
|
import {ErrorCodes} from '../../../../common/entities/Error';
|
||||||
|
import {map} from 'rxjs/operators';
|
||||||
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
|
import {FileDTO} from '../../../../common/entities/FileDTO';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GalleryService {
|
export class ContentService {
|
||||||
|
|
||||||
public content: BehaviorSubject<ContentWrapperWithError>;
|
public content: BehaviorSubject<ContentWrapperWithError>;
|
||||||
public sorting: BehaviorSubject<SortingMethods>;
|
public directoryContent: Observable<DirectoryContent>;
|
||||||
lastRequest: { directory: string } = {
|
lastRequest: { directory: string } = {
|
||||||
directory: null
|
directory: null
|
||||||
};
|
};
|
||||||
@ -31,42 +32,14 @@ export class GalleryService {
|
|||||||
private shareService: ShareService,
|
private shareService: ShareService,
|
||||||
private navigationService: NavigationService) {
|
private navigationService: NavigationService) {
|
||||||
this.content = new BehaviorSubject<ContentWrapperWithError>(new ContentWrapperWithError());
|
this.content = new BehaviorSubject<ContentWrapperWithError>(new ContentWrapperWithError());
|
||||||
this.sorting = new BehaviorSubject<SortingMethods>(Config.Client.Other.defaultPhotoSortingMethod);
|
this.directoryContent = this.content.pipe(map(c =>
|
||||||
}
|
c.directory ? c.directory : c.searchResult
|
||||||
|
));
|
||||||
getDefaultSorting(directory: ParentDirectoryDTO): SortingMethods {
|
|
||||||
if (directory && directory.metaFile) {
|
|
||||||
for (const file in PG2ConfMap.sorting) {
|
|
||||||
if (directory.metaFile.some(f => f.name === file)) {
|
|
||||||
return (PG2ConfMap.sorting as any)[file];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Config.Client.Other.defaultPhotoSortingMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSorting(sorting: SortingMethods): void {
|
|
||||||
this.sorting.next(sorting);
|
|
||||||
if (this.content.value.directory) {
|
|
||||||
if (sorting !== this.getDefaultSorting(this.content.value.directory)) {
|
|
||||||
this.galleryCacheService.setSorting(this.content.value.directory, sorting);
|
|
||||||
} else {
|
|
||||||
this.galleryCacheService.removeSorting(this.content.value.directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setContent(content: ContentWrapperWithError): void {
|
setContent(content: ContentWrapperWithError): void {
|
||||||
this.content.next(content);
|
this.content.next(content);
|
||||||
if (content.directory) {
|
|
||||||
const sort = this.galleryCacheService.getSorting(content.directory);
|
|
||||||
if (sort !== null) {
|
|
||||||
this.sorting.next(sort);
|
|
||||||
} else {
|
|
||||||
this.sorting.next(this.getDefaultSorting(content.directory));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -161,3 +134,9 @@ export class GalleryService {
|
|||||||
export class ContentWrapperWithError extends ContentWrapper {
|
export class ContentWrapperWithError extends ContentWrapper {
|
||||||
public error: string;
|
public error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DirectoryContent {
|
||||||
|
directories: SubDirectoryDTO[];
|
||||||
|
media: MediaDTO[];
|
||||||
|
metaFile: FileDTO[];
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
.filter-column {
|
||||||
|
max-height: 12em;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-option {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
margin-top: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container > card-body {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselected {
|
||||||
|
color: #6c757d;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<div class="card bg-light filter-container">
|
||||||
|
<div class="card-body row ">
|
||||||
|
<div class="col-3" *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">
|
||||||
|
{{option.name === undefined ? unknownText : option.name}}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,29 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
|
import {FilterService} from './filter.service';
|
||||||
|
import {OnDestroy, OnInit} from '../../../../../../node_modules/@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-gallery-filter',
|
||||||
|
styleUrls: ['./filter.gallery.component.css'],
|
||||||
|
templateUrl: './filter.gallery.component.html',
|
||||||
|
providers: [RouterLink],
|
||||||
|
})
|
||||||
|
export class GalleryFilterComponent implements OnInit, OnDestroy {
|
||||||
|
public readonly unknownText;
|
||||||
|
|
||||||
|
constructor(public filterService: FilterService) {
|
||||||
|
this.unknownText = '<' + $localize`unknown` + '>';
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
setTimeout(() => this.filterService.setShowingFilters(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
this.filterService.setShowingFilters(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
173
src/frontend/app/ui/gallery/filter/filter.service.ts
Normal file
173
src/frontend/app/ui/gallery/filter/filter.service.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
|
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||||
|
import {DirectoryContent} from '../content.service';
|
||||||
|
import {map, mergeMap} from 'rxjs/operators';
|
||||||
|
|
||||||
|
export enum FilterRenderType {
|
||||||
|
enum = 1, range = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Filter {
|
||||||
|
name: string;
|
||||||
|
mapFn: (m: PhotoDTO) => (string | number)[] | (string | number);
|
||||||
|
renderType: FilterRenderType;
|
||||||
|
isArrayValue?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectedFilter {
|
||||||
|
filter: Filter;
|
||||||
|
options: { name: string, count: number, selected: boolean }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FilterService {
|
||||||
|
public readonly AVAILABLE_FILTERS: Filter[] = [
|
||||||
|
{
|
||||||
|
name: $localize`Keywords`,
|
||||||
|
mapFn: (m: PhotoDTO): string[] => m.metadata.keywords,
|
||||||
|
renderType: FilterRenderType.enum,
|
||||||
|
isArrayValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $localize`Faces`,
|
||||||
|
mapFn: (m: PhotoDTO): string[] => m.metadata.faces?.map(f => f.name),
|
||||||
|
renderType: FilterRenderType.enum,
|
||||||
|
isArrayValue: true,
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
name: $localize`Date`,
|
||||||
|
mapFn: (m: PhotoDTO): number => m.metadata.creationDate,
|
||||||
|
renderType: FilterRenderType.date
|
||||||
|
},*/
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
name: $localize`Rating`,
|
||||||
|
mapFn: (m: PhotoDTO): number => m.metadata.rating,
|
||||||
|
renderType: FilterRenderType.enum
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $localize`Camera`,
|
||||||
|
mapFn: (m: PhotoDTO): string => m.metadata.cameraData?.model,
|
||||||
|
renderType: FilterRenderType.enum
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $localize`City`,
|
||||||
|
mapFn: (m: PhotoDTO): string => m.metadata.positionData?.city,
|
||||||
|
renderType: FilterRenderType.enum
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $localize`State`,
|
||||||
|
mapFn: (m: PhotoDTO): string => m.metadata.positionData?.state,
|
||||||
|
renderType: FilterRenderType.enum
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $localize`Country`,
|
||||||
|
mapFn: (m: PhotoDTO): string => m.metadata.positionData?.country,
|
||||||
|
renderType: FilterRenderType.enum
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
public readonly selectedFilters = new BehaviorSubject<SelectedFilter[]>([
|
||||||
|
{
|
||||||
|
filter: this.AVAILABLE_FILTERS[0],
|
||||||
|
options: []
|
||||||
|
}, {
|
||||||
|
filter: this.AVAILABLE_FILTERS[1],
|
||||||
|
options: []
|
||||||
|
}, {
|
||||||
|
filter: this.AVAILABLE_FILTERS[4],
|
||||||
|
options: []
|
||||||
|
}, {
|
||||||
|
filter: this.AVAILABLE_FILTERS[2],
|
||||||
|
options: []
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
filtersVisible = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyFilters(directoryContent: Observable<DirectoryContent>): Observable<DirectoryContent> {
|
||||||
|
return directoryContent.pipe(mergeMap((dirContent: DirectoryContent) => {
|
||||||
|
return this.selectedFilters.pipe(map((filters: SelectedFilter[]) => {
|
||||||
|
if (!dirContent || !dirContent.media || !this.filtersVisible) {
|
||||||
|
return dirContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clone, so the original won't get overwritten
|
||||||
|
const c = {
|
||||||
|
media: dirContent.media,
|
||||||
|
directories: dirContent.directories,
|
||||||
|
metaFile: dirContent.metaFile
|
||||||
|
};
|
||||||
|
for (const f of filters) {
|
||||||
|
|
||||||
|
// get options
|
||||||
|
const valueMap: { [key: string]: any } = {};
|
||||||
|
f.options.forEach(o => {
|
||||||
|
valueMap[o.name] = o;
|
||||||
|
o.count = 0; // reset count so unknown option can be removed at the end
|
||||||
|
});
|
||||||
|
|
||||||
|
if (f.filter.isArrayValue) {
|
||||||
|
c.media.forEach(m => {
|
||||||
|
(f.filter.mapFn(m as PhotoDTO) as string[])?.forEach(v => {
|
||||||
|
valueMap[v] = valueMap[v] || {name: v, count: 0, selected: true};
|
||||||
|
valueMap[v].count++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
c.media.forEach(m => {
|
||||||
|
const key = f.filter.mapFn(m as PhotoDTO) as string;
|
||||||
|
valueMap[key] = valueMap[key] || {name: key, count: 0, selected: true};
|
||||||
|
valueMap[key].count++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
f.options = Object.values(valueMap).filter(o => o.count > 0).sort((a, b) => b.count - a.count);
|
||||||
|
|
||||||
|
|
||||||
|
// apply filters
|
||||||
|
f.options.forEach(opt => {
|
||||||
|
if (opt.selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (f.filter.isArrayValue) {
|
||||||
|
c.media = c.media.filter(m => {
|
||||||
|
const mapped = (f.filter.mapFn(m as PhotoDTO) as string[]);
|
||||||
|
if (!mapped) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return mapped.indexOf(opt.name) === -1;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
c.media = c.media.filter(m => (f.filter.mapFn(m as PhotoDTO) as string) !== opt.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public onFilterChange(): void {
|
||||||
|
this.selectedFilters.next(this.selectedFilters.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowingFilters(value: boolean): void {
|
||||||
|
if (this.filtersVisible === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.filtersVisible = value;
|
||||||
|
if (!this.filtersVisible) {
|
||||||
|
this.selectedFilters.value.forEach(f => f.options = []);
|
||||||
|
}
|
||||||
|
this.onFilterChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -41,18 +41,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Its safe to hand over both as only one should have a value (search result or dir listing)-->
|
<!-- Its safe to hand over both as only one should have a value (search result or dir listing)-->
|
||||||
<app-gallery-navbar [searchResult]="ContentWrapper.searchResult"
|
<app-gallery-navbar></app-gallery-navbar>
|
||||||
[directory]="ContentWrapper.directory"></app-gallery-navbar>
|
|
||||||
|
|
||||||
|
|
||||||
<app-gallery-directories class="directories" [directories]="directories"></app-gallery-directories>
|
<app-gallery-directories class="directories"
|
||||||
|
[directories]="directoryContent.directories || []"></app-gallery-directories>
|
||||||
|
|
||||||
<div class="blog-map-row">
|
<div class="blog-map-row">
|
||||||
<div class="blog-wrapper"
|
<div class="blog-wrapper"
|
||||||
[style.width]="blogOpen ? '100%' : 'calc(100% - 100px)'"
|
[style.width]="blogOpen ? '100%' : 'calc(100% - 100px)'"
|
||||||
*ngIf="config.Client.MetaFile.markdown && Content.metaFile && (Content.metaFile | mdFiles).length>0">
|
*ngIf="config.Client.MetaFile.markdown && directoryContent.metaFile && (directoryContent.metaFile | mdFiles).length>0">
|
||||||
<app-gallery-blog [collapsed]="!blogOpen"
|
<app-gallery-blog [collapsed]="!blogOpen"
|
||||||
[mdFiles]="Content.metaFile | mdFiles"></app-gallery-blog>
|
[mdFiles]="directoryContent.metaFile | mdFiles"></app-gallery-blog>
|
||||||
|
|
||||||
|
|
||||||
<button class="btn btn-blog-details" (click)="blogOpen=!blogOpen"><span
|
<button class="btn btn-blog-details" (click)="blogOpen=!blogOpen"><span
|
||||||
@ -60,10 +60,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
||||||
[photos]="Content.media | photosOnly"
|
[photos]="directoryContent.media | photosOnly"
|
||||||
[gpxFiles]="Content.metaFile | gpxFiles"></app-gallery-map>
|
[gpxFiles]="directoryContent.metaFile | gpxFiles"></app-gallery-map>
|
||||||
</div>
|
</div>
|
||||||
<app-gallery-grid [media]="Content.media"
|
<app-gallery-grid [media]="directoryContent.media"
|
||||||
[lightbox]="lightbox"></app-gallery-grid>
|
[lightbox]="lightbox"></app-gallery-grid>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||||
import {AuthenticationService} from '../../model/network/authentication.service';
|
import {AuthenticationService} from '../../model/network/authentication.service';
|
||||||
import {ActivatedRoute, Params, Router} from '@angular/router';
|
import {ActivatedRoute, Params, Router} from '@angular/router';
|
||||||
import {ContentWrapperWithError, GalleryService} from './gallery.service';
|
import {ContentService, ContentWrapperWithError, DirectoryContent} from './content.service';
|
||||||
import {GalleryGridComponent} from './grid/grid.gallery.component';
|
import {GalleryGridComponent} from './grid/grid.gallery.component';
|
||||||
import {Config} from '../../../../common/config/public/Config';
|
import {Config} from '../../../../common/config/public/Config';
|
||||||
import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
|
||||||
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
|
|
||||||
import {ShareService} from './share.service';
|
import {ShareService} from './share.service';
|
||||||
import {NavigationService} from '../../model/navigation.service';
|
import {NavigationService} from '../../model/navigation.service';
|
||||||
import {UserRoles} from '../../../../common/entities/UserDTO';
|
import {UserRoles} from '../../../../common/entities/UserDTO';
|
||||||
import {interval, Observable, Subscription} from 'rxjs';
|
import {interval, Observable, Subscription} from 'rxjs';
|
||||||
import {ContentWrapper} from '../../../../common/entities/ConentWrapper';
|
|
||||||
import {PageHelper} from '../../model/page.helper';
|
import {PageHelper} from '../../model/page.helper';
|
||||||
import {SortingMethods} from '../../../../common/entities/SortingMethods';
|
|
||||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||||
import {QueryParams} from '../../../../common/QueryParams';
|
import {QueryParams} from '../../../../common/QueryParams';
|
||||||
import {SeededRandomService} from '../../model/seededRandom.service';
|
|
||||||
import {take} from 'rxjs/operators';
|
import {take} from 'rxjs/operators';
|
||||||
|
import {GallerySortingService} from './navigator/sorting.service';
|
||||||
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
|
import {FilterService} from './filter/filter.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery',
|
selector: 'app-gallery',
|
||||||
@ -33,10 +31,11 @@ export class GalleryComponent implements OnInit, OnDestroy {
|
|||||||
public blogOpen = false;
|
public blogOpen = false;
|
||||||
|
|
||||||
config = Config;
|
config = Config;
|
||||||
public directories: SubDirectoryDTO[] = [];
|
|
||||||
public isPhotoWithLocation = false;
|
public isPhotoWithLocation = false;
|
||||||
public countDown: { day: number, hour: number, minute: number, second: number } = null;
|
public countDown: { day: number, hour: number, minute: number, second: number } = null;
|
||||||
public readonly mapEnabled: boolean;
|
public readonly mapEnabled: boolean;
|
||||||
|
public directoryContent: DirectoryContent;
|
||||||
|
public readonly mediaObs: Observable<MediaDTO[]>;
|
||||||
private $counter: Observable<number>;
|
private $counter: Observable<number>;
|
||||||
private subscription: { [key: string]: Subscription } = {
|
private subscription: { [key: string]: Subscription } = {
|
||||||
content: null,
|
content: null,
|
||||||
@ -44,23 +43,19 @@ export class GalleryComponent implements OnInit, OnDestroy {
|
|||||||
timer: null,
|
timer: null,
|
||||||
sorting: null
|
sorting: null
|
||||||
};
|
};
|
||||||
private collator = new Intl.Collator(undefined, {numeric: true});
|
|
||||||
|
|
||||||
constructor(public galleryService: GalleryService,
|
constructor(public galleryService: ContentService,
|
||||||
private authService: AuthenticationService,
|
private authService: AuthenticationService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private shareService: ShareService,
|
private shareService: ShareService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private navigation: NavigationService,
|
private navigation: NavigationService,
|
||||||
private rndService: SeededRandomService) {
|
private filterService: FilterService,
|
||||||
|
private sortingService: GallerySortingService) {
|
||||||
this.mapEnabled = Config.Client.Map.enabled;
|
this.mapEnabled = Config.Client.Map.enabled;
|
||||||
PageHelper.showScrollY();
|
PageHelper.showScrollY();
|
||||||
}
|
}
|
||||||
|
|
||||||
get Content(): SearchResultDTO | ParentDirectoryDTO {
|
|
||||||
const cont = (this.ContentWrapper.searchResult || this.ContentWrapper.directory);
|
|
||||||
return cont ? cont : {} as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ContentWrapper(): ContentWrapperWithError {
|
get ContentWrapper(): ContentWrapperWithError {
|
||||||
return this.galleryService.content.value;
|
return this.galleryService.content.value;
|
||||||
@ -112,18 +107,21 @@ export class GalleryComponent implements OnInit, OnDestroy {
|
|||||||
this.showSearchBar = Config.Client.Search.enabled && this.authService.canSearch();
|
this.showSearchBar = Config.Client.Search.enabled && this.authService.canSearch();
|
||||||
this.showShare = Config.Client.Sharing.enabled && this.authService.isAuthorized(UserRoles.User);
|
this.showShare = Config.Client.Sharing.enabled && this.authService.isAuthorized(UserRoles.User);
|
||||||
this.showRandomPhotoBuilder = Config.Client.RandomPhoto.enabled && this.authService.isAuthorized(UserRoles.User);
|
this.showRandomPhotoBuilder = Config.Client.RandomPhoto.enabled && this.authService.isAuthorized(UserRoles.User);
|
||||||
this.subscription.content = this.galleryService.content.subscribe(this.onContentChange);
|
this.subscription.content = this.sortingService.applySorting(
|
||||||
|
this.filterService.applyFilters(this.galleryService.directoryContent)).subscribe((dc: DirectoryContent) => {
|
||||||
|
this.onContentChange(dc);
|
||||||
|
});
|
||||||
this.subscription.route = this.route.params.subscribe(this.onRoute);
|
this.subscription.route = this.route.params.subscribe(this.onRoute);
|
||||||
|
|
||||||
if (this.shareService.isSharing()) {
|
if (this.shareService.isSharing()) {
|
||||||
this.$counter = interval(1000);
|
this.$counter = interval(1000);
|
||||||
this.subscription.timer = this.$counter.subscribe((x): void => this.updateTimer(x));
|
this.subscription.timer = this.$counter.subscribe((x): void => this.updateTimer(x));
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
this.subscription.sorting = this.galleryService.sorting.subscribe((): void => {
|
this.subscription.sorting = this.galleryService.sorting.subscribe((): void => {
|
||||||
this.sortDirectories();
|
this.sortDirectories();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoute = async (params: Params): Promise<void> => {
|
private onRoute = async (params: Params): Promise<void> => {
|
||||||
@ -149,16 +147,17 @@ export class GalleryComponent implements OnInit, OnDestroy {
|
|||||||
this.galleryService.loadDirectory(directoryName);
|
this.galleryService.loadDirectory(directoryName);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onContentChange = (content: ContentWrapper): void => {
|
private onContentChange = (content: DirectoryContent): void => {
|
||||||
const tmp = (content.searchResult || content.directory || {
|
if (!content) {
|
||||||
directories: [],
|
return;
|
||||||
media: []
|
}
|
||||||
}) as ParentDirectoryDTO | SearchResultDTO;
|
this.directoryContent = content;
|
||||||
this.directories = tmp.directories;
|
|
||||||
this.sortDirectories();
|
// enforce change detection on grid
|
||||||
|
this.directoryContent.media = this.directoryContent.media?.slice();
|
||||||
this.isPhotoWithLocation = false;
|
this.isPhotoWithLocation = false;
|
||||||
|
|
||||||
for (const media of tmp.media as PhotoDTO[]) {
|
for (const media of content.media as PhotoDTO[]) {
|
||||||
if (media.metadata &&
|
if (media.metadata &&
|
||||||
media.metadata.positionData &&
|
media.metadata.positionData &&
|
||||||
media.metadata.positionData.GPSData &&
|
media.metadata.positionData.GPSData &&
|
||||||
@ -169,50 +168,4 @@ export class GalleryComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private sortDirectories(): void {
|
|
||||||
if (!this.directories) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (this.galleryService.sorting.value) {
|
|
||||||
case SortingMethods.ascRating: // directories does not have rating
|
|
||||||
case SortingMethods.ascName:
|
|
||||||
this.directories.sort((a, b) => this.collator.compare(a.name, b.name));
|
|
||||||
break;
|
|
||||||
case SortingMethods.ascDate:
|
|
||||||
if (Config.Client.Other.enableDirectorySortingByDate === true) {
|
|
||||||
this.directories.sort((a, b) => a.lastModified - b.lastModified);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.directories.sort((a, b) => this.collator.compare(a.name, b.name));
|
|
||||||
break;
|
|
||||||
case SortingMethods.descRating: // directories does not have rating
|
|
||||||
case SortingMethods.descName:
|
|
||||||
this.directories.sort((a, b) => this.collator.compare(b.name, a.name));
|
|
||||||
break;
|
|
||||||
case SortingMethods.descDate:
|
|
||||||
if (Config.Client.Other.enableDirectorySortingByDate === true) {
|
|
||||||
this.directories.sort((a, b) => b.lastModified - a.lastModified);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.directories.sort((a, b) => this.collator.compare(b.name, a.name));
|
|
||||||
break;
|
|
||||||
case SortingMethods.random:
|
|
||||||
this.rndService.setSeed(this.directories.length);
|
|
||||||
this.directories.sort((a, b): number => {
|
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}).sort((): number => {
|
|
||||||
return this.rndService.get() - 0.5;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
ViewChild,
|
ViewChild,
|
||||||
ViewChildren
|
ViewChildren
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
|
||||||
import {GridRowBuilder} from './GridRowBuilder';
|
import {GridRowBuilder} from './GridRowBuilder';
|
||||||
import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component';
|
import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component';
|
||||||
import {GridMedia} from './GridMedia';
|
import {GridMedia} from './GridMedia';
|
||||||
@ -23,34 +22,30 @@ import {PageHelper} from '../../../model/page.helper';
|
|||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {ActivatedRoute, Params, Router} from '@angular/router';
|
import {ActivatedRoute, Params, Router} from '@angular/router';
|
||||||
import {QueryService} from '../../../model/query.service';
|
import {QueryService} from '../../../model/query.service';
|
||||||
import {GalleryService} from '../gallery.service';
|
import {ContentService} from '../content.service';
|
||||||
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
|
||||||
import {MediaDTO, MediaDTOUtils} from '../../../../../common/entities/MediaDTO';
|
import {MediaDTO, MediaDTOUtils} from '../../../../../common/entities/MediaDTO';
|
||||||
import {QueryParams} from '../../../../../common/QueryParams';
|
import {QueryParams} from '../../../../../common/QueryParams';
|
||||||
import {SeededRandomService} from '../../../model/seededRandom.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-grid',
|
selector: 'app-gallery-grid',
|
||||||
templateUrl: './grid.gallery.component.html',
|
templateUrl: './grid.gallery.component.html',
|
||||||
styleUrls: ['./grid.gallery.component.css'],
|
styleUrls: ['./grid.gallery.component.css'],
|
||||||
})
|
})
|
||||||
export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, OnDestroy {
|
export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild('gridContainer', {static: false}) gridContainer: ElementRef;
|
@ViewChild('gridContainer', {static: false}) gridContainer: ElementRef;
|
||||||
@ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
|
@ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
|
||||||
@Input() media: MediaDTO[];
|
|
||||||
@Input() lightbox: GalleryLightboxComponent;
|
@Input() lightbox: GalleryLightboxComponent;
|
||||||
|
@Input() media: MediaDTO[];
|
||||||
photosToRender: GridMedia[] = [];
|
photosToRender: GridMedia[] = [];
|
||||||
containerWidth = 0;
|
containerWidth = 0;
|
||||||
screenHeight = 0;
|
screenHeight = 0;
|
||||||
public IMAGE_MARGIN = 2;
|
public IMAGE_MARGIN = 2;
|
||||||
isAfterViewInit = false;
|
isAfterViewInit = false;
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
route: Subscription,
|
route: Subscription
|
||||||
sorting: Subscription
|
|
||||||
} = {
|
} = {
|
||||||
route: null,
|
route: null
|
||||||
sorting: null
|
|
||||||
};
|
};
|
||||||
delayedRenderUpToPhoto: string = null;
|
delayedRenderUpToPhoto: string = null;
|
||||||
private scrollListenerPhotos: GalleryPhotoComponent[] = [];
|
private scrollListenerPhotos: GalleryPhotoComponent[] = [];
|
||||||
@ -65,9 +60,13 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
private changeDetector: ChangeDetectorRef,
|
private changeDetector: ChangeDetectorRef,
|
||||||
public queryService: QueryService,
|
public queryService: QueryService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
public galleryService: GalleryService,
|
public galleryService: ContentService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute) {
|
||||||
private rndService: SeededRandomService) {
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -81,19 +80,13 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
this.renderUpToMedia(params[QueryParams.gallery.photo]);
|
this.renderUpToMedia(params[QueryParams.gallery.photo]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.subscriptions.sorting = this.galleryService.sorting.subscribe((): void => {
|
|
||||||
this.clearRenderedPhotos();
|
|
||||||
this.sortPhotos();
|
|
||||||
this.renderPhotos();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
onChange = () => {
|
||||||
if (this.isAfterViewInit === false) {
|
if (this.isAfterViewInit === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.updateContainerDimensions();
|
this.updateContainerDimensions();
|
||||||
this.sortPhotos();
|
|
||||||
this.mergeNewPhotos();
|
this.mergeNewPhotos();
|
||||||
this.helperTime = window.setTimeout((): void => {
|
this.helperTime = window.setTimeout((): void => {
|
||||||
this.renderPhotos();
|
this.renderPhotos();
|
||||||
@ -101,7 +94,8 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
this.renderUpToMedia(this.delayedRenderUpToPhoto);
|
this.renderUpToMedia(this.delayedRenderUpToPhoto);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
|
||||||
@ -112,10 +106,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
this.subscriptions.route.unsubscribe();
|
this.subscriptions.route.unsubscribe();
|
||||||
this.subscriptions.route = null;
|
this.subscriptions.route = null;
|
||||||
}
|
}
|
||||||
if (this.subscriptions.sorting !== null) {
|
|
||||||
this.subscriptions.sorting.unsubscribe();
|
|
||||||
this.subscriptions.sorting = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize')
|
@HostListener('window:resize')
|
||||||
@ -129,7 +119,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
if (this.updateContainerDimensions() === false) {
|
if (this.updateContainerDimensions() === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.sortPhotos();
|
|
||||||
this.renderPhotos(renderedIndex);
|
this.renderPhotos(renderedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +126,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
this.router.navigate([], {queryParams: this.queryService.getParams(media)});
|
this.router.navigate([], {queryParams: this.queryService.getParams(media)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.lightbox.setGridPhotoQL(this.gridPhotoQL);
|
this.lightbox.setGridPhotoQL(this.gridPhotoQL);
|
||||||
|
|
||||||
@ -148,7 +136,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.updateContainerDimensions();
|
this.updateContainerDimensions();
|
||||||
this.sortPhotos();
|
|
||||||
this.clearRenderedPhotos();
|
this.clearRenderedPhotos();
|
||||||
this.helperTime = window.setTimeout((): void => {
|
this.helperTime = window.setTimeout((): void => {
|
||||||
this.renderPhotos();
|
this.renderPhotos();
|
||||||
@ -193,6 +180,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
@HostListener('window:scroll')
|
@HostListener('window:scroll')
|
||||||
onScroll(): void {
|
onScroll(): void {
|
||||||
if (!this.onScrollFired &&
|
if (!this.onScrollFired &&
|
||||||
|
this.media &&
|
||||||
// should we trigger this at all?
|
// should we trigger this at all?
|
||||||
(this.renderedPhotoIndex < this.media.length || this.scrollListenerPhotos.length > 0)) {
|
(this.renderedPhotoIndex < this.media.length || this.scrollListenerPhotos.length > 0)) {
|
||||||
window.requestAnimationFrame((): void => {
|
window.requestAnimationFrame((): void => {
|
||||||
@ -238,51 +226,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
this.changeDetector.detectChanges();
|
this.changeDetector.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
private collator = new Intl.Collator(undefined, {numeric: true});
|
|
||||||
|
|
||||||
private sortPhotos(): void {
|
|
||||||
switch (this.galleryService.sorting.value) {
|
|
||||||
case SortingMethods.ascName:
|
|
||||||
this.media.sort((a: PhotoDTO, b: PhotoDTO) => this.collator.compare(a.name, b.name));
|
|
||||||
break;
|
|
||||||
case SortingMethods.descName:
|
|
||||||
this.media.sort((a: PhotoDTO, b: PhotoDTO) => this.collator.compare(b.name, a.name));
|
|
||||||
break;
|
|
||||||
case SortingMethods.ascDate:
|
|
||||||
this.media.sort((a: PhotoDTO, b: PhotoDTO): number => {
|
|
||||||
return a.metadata.creationDate - b.metadata.creationDate;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case SortingMethods.descDate:
|
|
||||||
this.media.sort((a: PhotoDTO, b: PhotoDTO): number => {
|
|
||||||
return b.metadata.creationDate - a.metadata.creationDate;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case SortingMethods.ascRating:
|
|
||||||
this.media.sort((a: PhotoDTO, b: PhotoDTO) => (a.metadata.rating || 0) - (b.metadata.rating || 0));
|
|
||||||
break;
|
|
||||||
case SortingMethods.descRating:
|
|
||||||
this.media.sort((a: PhotoDTO, b: PhotoDTO) => (b.metadata.rating || 0) - (a.metadata.rating || 0));
|
|
||||||
break;
|
|
||||||
case SortingMethods.random:
|
|
||||||
this.rndService.setSeed(this.media.length);
|
|
||||||
this.media.sort((a: PhotoDTO, b: PhotoDTO): number => {
|
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}).sort((): number => {
|
|
||||||
return this.rndService.get() - 0.5;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is deprecated,
|
// TODO: This is deprecated,
|
||||||
// we do not post update galleries anymore since the preview member in the DriectoryDTO
|
// we do not post update galleries anymore since the preview member in the DriectoryDTO
|
||||||
private mergeNewPhotos(): void {
|
private mergeNewPhotos(): void {
|
||||||
@ -331,6 +274,9 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderPhotos(numberOfPhotos: number = 0): void {
|
private renderPhotos(numberOfPhotos: number = 0): void {
|
||||||
|
if (!this.media) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.containerWidth === 0 ||
|
if (this.containerWidth === 0 ||
|
||||||
this.renderedPhotoIndex >= this.media.length ||
|
this.renderedPhotoIndex >= this.media.length ||
|
||||||
!this.shouldRenderMore()) {
|
!this.shouldRenderMore()) {
|
||||||
|
@ -11,7 +11,7 @@ import {PageHelper} from '../../../model/page.helper';
|
|||||||
import {QueryService} from '../../../model/query.service';
|
import {QueryService} from '../../../model/query.service';
|
||||||
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
|
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
|
||||||
import {QueryParams} from '../../../../../common/QueryParams';
|
import {QueryParams} from '../../../../../common/QueryParams';
|
||||||
import {GalleryService} from '../gallery.service';
|
import {ContentService} from '../content.service';
|
||||||
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||||
import {ControlsLightboxComponent} from './controls/controls.lightbox.gallery.component';
|
import {ControlsLightboxComponent} from './controls/controls.lightbox.gallery.component';
|
||||||
import {SupportedFormats} from '../../../../../common/SupportedFormats';
|
import {SupportedFormats} from '../../../../../common/SupportedFormats';
|
||||||
@ -64,7 +64,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
private builder: AnimationBuilder,
|
private builder: AnimationBuilder,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private galleryService: GalleryService,
|
private galleryService: ContentService,
|
||||||
private route: ActivatedRoute) {
|
private route: ActivatedRoute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,15 +12,15 @@
|
|||||||
|
|
||||||
.photos-count {
|
.photos-count {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-download {
|
.btn-navbar {
|
||||||
padding-left: 1px;
|
|
||||||
padding-right: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
margin: 5px 5px 5px 10px;
|
margin-left: 5px;
|
||||||
border-left: 1px solid #6c757d;
|
border-left: 1px solid #6c757d;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ ol {
|
|||||||
min-width: 13rem;
|
min-width: 13rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item{
|
.dropdown-item {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,51 +1,58 @@
|
|||||||
<nav class="nav-container" aria-label="breadcrumb">
|
<nav class="nav-container" aria-label="breadcrumb">
|
||||||
<ol *ngIf="directory" id="directory-path" class="breadcrumb">
|
<ol *ngIf="isDirectory | async" id="directory-path" class="breadcrumb">
|
||||||
<li *ngFor="let path of routes" class="breadcrumb-item">
|
<li *ngFor="let path of Routes | async" class="breadcrumb-item">
|
||||||
<a *ngIf="path.route" [routerLink]="['/gallery',path.route]"
|
<a *ngIf="path.route" [routerLink]="['/gallery',path.route]"
|
||||||
[queryParams]="queryService.getParams()">{{path.name}}</a>
|
[queryParams]="queryService.getParams()">{{path.name}}</a>
|
||||||
<ng-container *ngIf="!path.route">{{path.name}}</ng-container>
|
<ng-container *ngIf="!path.route">{{path.name}}</ng-container>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<ol *ngIf="searchResult" class="breadcrumb">
|
<ol *ngIf="!(isDirectory | async)" class="breadcrumb">
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<ng-container i18n>Searching for:</ng-container>
|
<ng-container i18n>Searching for:</ng-container>
|
||||||
<strong> {{searchResult.searchQuery | searchQuery}}</strong>
|
<strong> {{(wrappedContent | async)?.searchResult?.searchQuery | searchQuery}}</strong>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div class="right-side">
|
<div class="right-side">
|
||||||
<ng-container *ngIf="ItemCount > 0 && config.Client.Other.NavBar.showItemCount">
|
<ng-container *ngIf="(ItemCount | async) > 0 && config.Client.Other.NavBar.showItemCount">
|
||||||
<div class="photos-count">
|
<div class="photos-count">
|
||||||
{{ItemCount}} <span i18n>items</span>
|
{{ItemCount | async}} <span i18n>items</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="config.Client.Other.enableDownloadZip && directory && ItemCount > 0">
|
<ng-container *ngIf="config.Client.Other.enableDownloadZip && (isDirectory | async) && (ItemCount | async) > 0">
|
||||||
<a [href]="getDownloadZipLink()"
|
<a [href]="getDownloadZipLink() | async"
|
||||||
class="btn btn-download">
|
class="btn btn-navbar">
|
||||||
<span class="oi oi-data-transfer-download"
|
<span class="oi oi-data-transfer-download"
|
||||||
title="download" i18n-title></span>
|
title="download" i18n-title></span>
|
||||||
</a>
|
</a>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="config.Client.Other.enableDirectoryFlattening && directory">
|
<ng-container *ngIf="config.Client.Other.enableDirectoryFlattening && (isDirectory | async)">
|
||||||
<a
|
<a
|
||||||
[routerLink]="['/search', getDirectoryFlattenSearchQuery()]"
|
[routerLink]="['/search', getDirectoryFlattenSearchQuery() | async]"
|
||||||
class="btn btn-download">
|
class="btn btn-navbar">
|
||||||
<span class="oi oi-fork"
|
<span class="oi oi-fork"
|
||||||
title="Flatten directory" i18n-title></span>
|
title="Flatten directory" i18n-title></span>
|
||||||
</a>
|
</a>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<a class="btn btn-navbar"
|
||||||
|
[class.btn-secondary]="showFilters"
|
||||||
|
(click)="showFilters = ! showFilters">
|
||||||
|
<span class="oi oi-spreadsheet"
|
||||||
|
title="Filters" i18n-title></span>
|
||||||
|
</a>
|
||||||
|
<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"
|
||||||
class="btn btn-secondary dropdown-toggle"
|
class="btn btn-secondary dropdown-toggle"
|
||||||
[ngClass]="{'btn-secondary':galleryService.sorting.value !== DefaultSorting}"
|
[ngClass]="{'btn-secondary':sortingService.sorting.value !== (DefaultSorting | async)}"
|
||||||
aria-controls="dropdown-alignment"
|
aria-controls="dropdown-alignment"
|
||||||
[innerHTML]="galleryService.sorting.value| iconizeSorting">
|
[innerHTML]="sortingService.sorting.value | iconizeSorting">
|
||||||
</button>
|
</button>
|
||||||
<ul id="dropdown-alignment" *dropdownMenu class="dropdown-menu dropdown-menu-right"
|
<ul id="dropdown-alignment" *dropdownMenu class="dropdown-menu dropdown-menu-right"
|
||||||
role="menu" aria-labelledby="button-alignment">
|
role="menu" aria-labelledby="button-alignment">
|
||||||
@ -63,3 +70,4 @@
|
|||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<app-gallery-filter *ngIf="showFilters"></app-gallery-filter>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import {Component, Input, OnChanges} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ParentDirectoryDTO} from '../../../../../common/entities/DirectoryDTO';
|
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
import {UserDTOUtils} from '../../../../../common/entities/UserDTO';
|
import {UserDTOUtils} from '../../../../../common/entities/UserDTO';
|
||||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||||
import {QueryService} from '../../../model/query.service';
|
import {QueryService} from '../../../model/query.service';
|
||||||
import {GalleryService} from '../gallery.service';
|
import {ContentService, ContentWrapperWithError, DirectoryContent} from '../content.service';
|
||||||
import {Utils} from '../../../../../common/Utils';
|
import {Utils} from '../../../../../common/Utils';
|
||||||
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
||||||
import {Config} from '../../../../../common/config/public/Config';
|
import {Config} from '../../../../../common/config/public/Config';
|
||||||
import {SearchResultDTO} from '../../../../../common/entities/SearchResultDTO';
|
|
||||||
import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../../common/entities/SearchQueryDTO';
|
import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../../common/entities/SearchQueryDTO';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {map} from 'rxjs/operators';
|
||||||
|
import {GallerySortingService} from './sorting.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-navbar',
|
selector: 'app-gallery-navbar',
|
||||||
@ -17,103 +18,118 @@ import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../
|
|||||||
templateUrl: './navigator.gallery.component.html',
|
templateUrl: './navigator.gallery.component.html',
|
||||||
providers: [RouterLink],
|
providers: [RouterLink],
|
||||||
})
|
})
|
||||||
export class GalleryNavigatorComponent implements OnChanges {
|
export class GalleryNavigatorComponent {
|
||||||
@Input() directory: ParentDirectoryDTO;
|
|
||||||
@Input() searchResult: SearchResultDTO;
|
|
||||||
|
|
||||||
routes: NavigatorPath[] = [];
|
|
||||||
SortingMethods = SortingMethods;
|
SortingMethods = SortingMethods;
|
||||||
sortingMethodsType: { key: number; value: string }[] = [];
|
sortingMethodsType: { key: number; value: string }[] = [];
|
||||||
config = Config;
|
readonly config = Config;
|
||||||
DefaultSorting = Config.Client.Other.defaultPhotoSortingMethod;
|
// DefaultSorting = Config.Client.Other.defaultPhotoSortingMethod;
|
||||||
readonly SearchQueryTypes = SearchQueryTypes;
|
readonly SearchQueryTypes = SearchQueryTypes;
|
||||||
|
wrappedContent: Observable<ContentWrapperWithError>;
|
||||||
|
public directoryContent: Observable<DirectoryContent>;
|
||||||
|
showFilters = false;
|
||||||
private readonly RootFolderName: string;
|
private readonly RootFolderName: string;
|
||||||
|
|
||||||
constructor(private authService: AuthenticationService,
|
constructor(private authService: AuthenticationService,
|
||||||
public queryService: QueryService,
|
public queryService: QueryService,
|
||||||
public galleryService: GalleryService) {
|
public galleryService: ContentService,
|
||||||
|
public sortingService: GallerySortingService) {
|
||||||
this.sortingMethodsType = Utils.enumToArray(SortingMethods);
|
this.sortingMethodsType = Utils.enumToArray(SortingMethods);
|
||||||
this.RootFolderName = $localize`Images`;
|
this.RootFolderName = $localize`Images`;
|
||||||
|
this.wrappedContent = this.galleryService.content;
|
||||||
|
this.directoryContent = this.wrappedContent.pipe(map(c => c.directory ? c.directory : c.searchResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
get ItemCount(): number {
|
get isDirectory(): Observable<boolean> {
|
||||||
return this.directory ? this.directory.mediaCount : this.searchResult.media.length;
|
return this.wrappedContent.pipe(map(c => !!c.directory));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
get ItemCount(): Observable<number> {
|
||||||
this.getPath();
|
return this.wrappedContent.pipe(map(c => c.directory ? c.directory.mediaCount : (c.searchResult ? c.searchResult.media.length : 0)));
|
||||||
this.DefaultSorting = this.galleryService.getDefaultSorting(this.directory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPath(): any {
|
get Routes(): Observable<NavigatorPath[]> {
|
||||||
if (!this.directory) {
|
return this.wrappedContent.pipe(map((c) => {
|
||||||
return [];
|
if (!c.directory) {
|
||||||
}
|
return [];
|
||||||
|
|
||||||
const path = this.directory.path.replace(new RegExp('\\\\', 'g'), '/');
|
|
||||||
|
|
||||||
const dirs = path.split('/');
|
|
||||||
dirs.push(this.directory.name);
|
|
||||||
|
|
||||||
// removing empty strings
|
|
||||||
for (let i = 0; i < dirs.length; i++) {
|
|
||||||
if (!dirs[i] || 0 === dirs[i].length || '.' === dirs[i]) {
|
|
||||||
dirs.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const user = this.authService.user.value;
|
const path = c.directory.path.replace(new RegExp('\\\\', 'g'), '/');
|
||||||
const arr: NavigatorPath[] = [];
|
|
||||||
|
|
||||||
// create root link
|
const dirs = path.split('/');
|
||||||
if (dirs.length === 0) {
|
dirs.push(c.directory.name);
|
||||||
arr.push({name: this.RootFolderName, route: null});
|
|
||||||
} else {
|
|
||||||
arr.push({name: this.RootFolderName, route: UserDTOUtils.isDirectoryPathAvailable('/', user.permissions) ? '/' : null});
|
|
||||||
}
|
|
||||||
|
|
||||||
// create rest navigation
|
// removing empty strings
|
||||||
dirs.forEach((name, index) => {
|
for (let i = 0; i < dirs.length; i++) {
|
||||||
const route = dirs.slice(0, dirs.indexOf(name) + 1).join('/');
|
if (!dirs[i] || 0 === dirs[i].length || '.' === dirs[i]) {
|
||||||
if (dirs.length - 1 === index) {
|
dirs.splice(i, 1);
|
||||||
arr.push({name, route: null});
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.authService.user.value;
|
||||||
|
const arr: NavigatorPath[] = [];
|
||||||
|
|
||||||
|
// create root link
|
||||||
|
if (dirs.length === 0) {
|
||||||
|
arr.push({name: this.RootFolderName, route: null});
|
||||||
} else {
|
} else {
|
||||||
arr.push({name, route: UserDTOUtils.isDirectoryPathAvailable(route, user.permissions) ? route : null});
|
arr.push({name: this.RootFolderName, route: UserDTOUtils.isDirectoryPathAvailable('/', user.permissions) ? '/' : null});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
// create rest navigation
|
||||||
|
dirs.forEach((name, index) => {
|
||||||
|
const route = dirs.slice(0, dirs.indexOf(name) + 1).join('/');
|
||||||
|
if (dirs.length - 1 === index) {
|
||||||
|
arr.push({name, route: null});
|
||||||
|
} else {
|
||||||
|
arr.push({name, route: UserDTOUtils.isDirectoryPathAvailable(route, user.permissions) ? route : null});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.routes = arr;
|
return arr;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
get DefaultSorting(): Observable<SortingMethods> {
|
||||||
|
return this.wrappedContent.pipe(map(c =>
|
||||||
|
this.sortingService.getDefaultSorting(c.directory)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
setSorting(sorting: SortingMethods): void {
|
setSorting(sorting: SortingMethods): void {
|
||||||
this.galleryService.setSorting(sorting);
|
this.sortingService.setSorting(sorting);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDownloadZipLink(): string {
|
getDownloadZipLink(): Observable<string> {
|
||||||
let queryParams = '';
|
return this.wrappedContent.pipe(map((c) => {
|
||||||
Object.entries(this.queryService.getParams()).forEach(e => {
|
if (!c.directory) {
|
||||||
queryParams += e[0] + '=' + e[1];
|
return null;
|
||||||
});
|
}
|
||||||
return Utils.concatUrls(Config.Client.urlBase,
|
let queryParams = '';
|
||||||
'/api/gallery/zip/',
|
Object.entries(this.queryService.getParams()).forEach(e => {
|
||||||
this.getDirectoryPath(), '?' + queryParams);
|
queryParams += e[0] + '=' + e[1];
|
||||||
|
});
|
||||||
|
return Utils.concatUrls(Config.Client.urlBase,
|
||||||
|
'/api/gallery/zip/',
|
||||||
|
c.directory.path, c.directory.name, '?' + queryParams);
|
||||||
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirectoryPath(): string {
|
getDirectoryFlattenSearchQuery(): Observable<string> {
|
||||||
return Utils.concatUrls(this.directory.path, this.directory.name);
|
return this.wrappedContent.pipe(map((c) => {
|
||||||
|
if (!c.directory) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.stringify({
|
||||||
|
type: SearchQueryTypes.directory,
|
||||||
|
matchType: TextSearchQueryMatchTypes.like,
|
||||||
|
text: Utils.concatUrls('./', c.directory.path, c.directory.name)
|
||||||
|
} as TextSearch);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirectoryFlattenSearchQuery(): string {
|
|
||||||
return JSON.stringify({
|
|
||||||
type: SearchQueryTypes.directory,
|
|
||||||
matchType: TextSearchQueryMatchTypes.like,
|
|
||||||
text: Utils.concatUrls('./', this.directory.path, this.directory.name)
|
|
||||||
} as TextSearch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavigatorPath {
|
interface NavigatorPath {
|
||||||
|
161
src/frontend/app/ui/gallery/navigator/sorting.service.ts
Normal file
161
src/frontend/app/ui/gallery/navigator/sorting.service.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {NetworkService} from '../../../model/network/network.service';
|
||||||
|
import {ParentDirectoryDTO} from '../../../../../common/entities/DirectoryDTO';
|
||||||
|
import {GalleryCacheService} from '../cache.gallery.service';
|
||||||
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
|
import {Config} from '../../../../../common/config/public/Config';
|
||||||
|
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
||||||
|
import {PG2ConfMap} from '../../../../../common/PG2ConfMap';
|
||||||
|
import {DirectoryContent, ContentService} from '../content.service';
|
||||||
|
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||||
|
import {map, mergeMap} from 'rxjs/operators';
|
||||||
|
import {SeededRandomService} from '../../../model/seededRandom.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GallerySortingService {
|
||||||
|
public sorting: BehaviorSubject<SortingMethods>;
|
||||||
|
private collator = new Intl.Collator(undefined, {numeric: true});
|
||||||
|
|
||||||
|
constructor(private networkService: NetworkService,
|
||||||
|
private galleryCacheService: GalleryCacheService,
|
||||||
|
private galleryService: ContentService,
|
||||||
|
private rndService: SeededRandomService) {
|
||||||
|
this.sorting = new BehaviorSubject<SortingMethods>(Config.Client.Other.defaultPhotoSortingMethod);
|
||||||
|
this.galleryService.content.subscribe((c) => {
|
||||||
|
if (c.directory) {
|
||||||
|
const sort = this.galleryCacheService.getSorting(c.directory);
|
||||||
|
if (sort !== null) {
|
||||||
|
this.sorting.next(sort);
|
||||||
|
} else {
|
||||||
|
this.sorting.next(this.getDefaultSorting(c.directory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultSorting(directory: ParentDirectoryDTO): SortingMethods {
|
||||||
|
if (directory && directory.metaFile) {
|
||||||
|
for (const file in PG2ConfMap.sorting) {
|
||||||
|
if (directory.metaFile.some(f => f.name === file)) {
|
||||||
|
return (PG2ConfMap.sorting as any)[file];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Config.Client.Other.defaultPhotoSortingMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSorting(sorting: SortingMethods): void {
|
||||||
|
this.sorting.next(sorting);
|
||||||
|
if (this.galleryService.content.value.directory) {
|
||||||
|
if (sorting !== this.getDefaultSorting(this.galleryService.content.value.directory)) {
|
||||||
|
this.galleryCacheService.setSorting(this.galleryService.content.value.directory, sorting);
|
||||||
|
} else {
|
||||||
|
this.galleryCacheService.removeSorting(this.galleryService.content.value.directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public applySorting(directoryContent: Observable<DirectoryContent>): Observable<DirectoryContent> {
|
||||||
|
return directoryContent.pipe(mergeMap((dirContent) => {
|
||||||
|
return this.sorting.pipe(map((sorting: SortingMethods) => {
|
||||||
|
if (!dirContent) {
|
||||||
|
return dirContent;
|
||||||
|
}
|
||||||
|
const c = {
|
||||||
|
media: dirContent.media,
|
||||||
|
directories: dirContent.directories,
|
||||||
|
metaFile: dirContent.metaFile
|
||||||
|
};
|
||||||
|
if (c.directories) {
|
||||||
|
switch (sorting) {
|
||||||
|
case SortingMethods.ascRating: // directories do not have rating
|
||||||
|
case SortingMethods.ascName:
|
||||||
|
c.directories.sort((a, b) => this.collator.compare(a.name, b.name));
|
||||||
|
break;
|
||||||
|
case SortingMethods.ascDate:
|
||||||
|
if (Config.Client.Other.enableDirectorySortingByDate === true) {
|
||||||
|
c.directories.sort((a, b) => a.lastModified - b.lastModified);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c.directories.sort((a, b) => this.collator.compare(a.name, b.name));
|
||||||
|
break;
|
||||||
|
case SortingMethods.descRating: // directories do not have rating
|
||||||
|
case SortingMethods.descName:
|
||||||
|
c.directories.sort((a, b) => this.collator.compare(b.name, a.name));
|
||||||
|
break;
|
||||||
|
case SortingMethods.descDate:
|
||||||
|
if (Config.Client.Other.enableDirectorySortingByDate === true) {
|
||||||
|
c.directories.sort((a, b) => b.lastModified - a.lastModified);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c.directories.sort((a, b) => this.collator.compare(b.name, a.name));
|
||||||
|
break;
|
||||||
|
case SortingMethods.random:
|
||||||
|
this.rndService.setSeed(c.directories.length);
|
||||||
|
c.directories.sort((a, b): number => {
|
||||||
|
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}).sort((): number => {
|
||||||
|
return this.rndService.get() - 0.5;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.media) {
|
||||||
|
switch (sorting) {
|
||||||
|
case SortingMethods.ascName:
|
||||||
|
c.media.sort((a: PhotoDTO, b: PhotoDTO) => this.collator.compare(a.name, b.name));
|
||||||
|
break;
|
||||||
|
case SortingMethods.descName:
|
||||||
|
c.media.sort((a: PhotoDTO, b: PhotoDTO) => this.collator.compare(b.name, a.name));
|
||||||
|
break;
|
||||||
|
case SortingMethods.ascDate:
|
||||||
|
c.media.sort((a: PhotoDTO, b: PhotoDTO): number => {
|
||||||
|
return a.metadata.creationDate - b.metadata.creationDate;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SortingMethods.descDate:
|
||||||
|
c.media.sort((a: PhotoDTO, b: PhotoDTO): number => {
|
||||||
|
return b.metadata.creationDate - a.metadata.creationDate;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SortingMethods.ascRating:
|
||||||
|
c.media.sort((a: PhotoDTO, b: PhotoDTO) => (a.metadata.rating || 0) - (b.metadata.rating || 0));
|
||||||
|
break;
|
||||||
|
case SortingMethods.descRating:
|
||||||
|
c.media.sort((a: PhotoDTO, b: PhotoDTO) => (b.metadata.rating || 0) - (a.metadata.rating || 0));
|
||||||
|
break;
|
||||||
|
case SortingMethods.random:
|
||||||
|
this.rndService.setSeed(c.media.length);
|
||||||
|
c.media.sort((a: PhotoDTO, b: PhotoDTO): number => {
|
||||||
|
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}).sort((): number => {
|
||||||
|
return this.rndService.get() - 0.5;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import {Component, OnDestroy, OnInit, TemplateRef} from '@angular/core';
|
import {Component, OnDestroy, OnInit, TemplateRef} from '@angular/core';
|
||||||
import {GalleryService} from '../gallery.service';
|
import {ContentService} from '../content.service';
|
||||||
import {ContentWrapper} from '../../../../../common/entities/ConentWrapper';
|
import {ContentWrapper} from '../../../../../common/entities/ConentWrapper';
|
||||||
import {Config} from '../../../../../common/config/public/Config';
|
import {Config} from '../../../../../common/config/public/Config';
|
||||||
import {NotificationService} from '../../../model/notification.service';
|
import {NotificationService} from '../../../model/notification.service';
|
||||||
@ -31,7 +31,7 @@ export class RandomQueryBuilderGalleryComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private readonly subscription: Subscription = null;
|
private readonly subscription: Subscription = null;
|
||||||
|
|
||||||
constructor(public galleryService: GalleryService,
|
constructor(public galleryService: ContentService,
|
||||||
private notification: NotificationService,
|
private notification: NotificationService,
|
||||||
private searchQueryParserService: SearchQueryParserService,
|
private searchQueryParserService: SearchQueryParserService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Component, OnDestroy, TemplateRef} from '@angular/core';
|
import {Component, OnDestroy, TemplateRef} from '@angular/core';
|
||||||
import {AutoCompleteService} from './autocomplete.service';
|
import {AutoCompleteService} from './autocomplete.service';
|
||||||
import {ActivatedRoute, Params, Router, RouterLink} from '@angular/router';
|
import {ActivatedRoute, Params, Router, RouterLink} from '@angular/router';
|
||||||
import {GalleryService} from '../gallery.service';
|
import {ContentService} from '../content.service';
|
||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {NavigationService} from '../../../model/navigation.service';
|
import {NavigationService} from '../../../model/navigation.service';
|
||||||
import {QueryParams} from '../../../../../common/QueryParams';
|
import {QueryParams} from '../../../../../common/QueryParams';
|
||||||
@ -34,7 +34,7 @@ export class GallerySearchComponent implements OnDestroy {
|
|||||||
|
|
||||||
constructor(private autoCompleteService: AutoCompleteService,
|
constructor(private autoCompleteService: AutoCompleteService,
|
||||||
private searchQueryParserService: SearchQueryParserService,
|
private searchQueryParserService: SearchQueryParserService,
|
||||||
private galleryService: GalleryService,
|
private galleryService: ContentService,
|
||||||
private albumService: AlbumsService,
|
private albumService: AlbumsService,
|
||||||
private navigationService: NavigationService,
|
private navigationService: NavigationService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Component, OnDestroy, OnInit, TemplateRef} from '@angular/core';
|
import {Component, OnDestroy, OnInit, TemplateRef} from '@angular/core';
|
||||||
import {Utils} from '../../../../../common/Utils';
|
import {Utils} from '../../../../../common/Utils';
|
||||||
import {ShareService} from '../share.service';
|
import {ShareService} from '../share.service';
|
||||||
import {GalleryService} from '../gallery.service';
|
import {ContentService} from '../content.service';
|
||||||
import {ContentWrapper} from '../../../../../common/entities/ConentWrapper';
|
import {ContentWrapper} from '../../../../../common/entities/ConentWrapper';
|
||||||
import {SharingDTO} from '../../../../../common/entities/SharingDTO';
|
import {SharingDTO} from '../../../../../common/entities/SharingDTO';
|
||||||
import {Config} from '../../../../../common/config/public/Config';
|
import {Config} from '../../../../../common/config/public/Config';
|
||||||
@ -44,7 +44,7 @@ export class GalleryShareComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constructor(private sharingService: ShareService,
|
constructor(private sharingService: ShareService,
|
||||||
public galleryService: GalleryService,
|
public galleryService: ContentService,
|
||||||
private notification: NotificationService,
|
private notification: NotificationService,
|
||||||
private modalService: BsModalService) {
|
private modalService: BsModalService) {
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user