diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index 06807470..b46611f3 100644 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -114,6 +114,8 @@ import {BlogService} from './ui/gallery/blog/blog.service'; import {PhotoFilterPipe} from './pipes/PhotoFilterPipe'; import {PreviewSettingsComponent} from './ui/settings/preview/preview.settings.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'; @Injectable() export class MyHammerConfig extends HammerGestureConfig { @@ -213,6 +215,7 @@ Marker.prototype.options.icon = iconDefault; GallerySearchQueryBuilderComponent, GalleryShareComponent, GalleryNavigatorComponent, + GalleryFilterComponent, GalleryPhotoComponent, AdminComponent, InfoPanelLightboxComponent, @@ -270,6 +273,7 @@ Marker.prototype.options.icon = iconDefault; AlbumsService, GalleryCacheService, GalleryService, + GallerySortingService, MapService, BlogService, SearchQueryParserService, diff --git a/src/frontend/app/ui/gallery/gallery.component.html b/src/frontend/app/ui/gallery/gallery.component.html index b83cea63..52022906 100644 --- a/src/frontend/app/ui/gallery/gallery.component.html +++ b/src/frontend/app/ui/gallery/gallery.component.html @@ -41,18 +41,18 @@ - + - +
+ [style.width]="blogOpen ? '100%' : 'calc(100% - 100px)'" + *ngIf="config.Client.MetaFile.markdown && (directoryContent | async)?.metaFile && ((directoryContent | async).metaFile | mdFiles).length>0"> + [mdFiles]="(directoryContent | async).metaFile | mdFiles">
+ [photos]="(directoryContent | async).media | photosOnly" + [gpxFiles]="(directoryContent | async).metaFile | gpxFiles">
- diff --git a/src/frontend/app/ui/gallery/gallery.component.ts b/src/frontend/app/ui/gallery/gallery.component.ts index 9ff5ce72..fa9b26dc 100644 --- a/src/frontend/app/ui/gallery/gallery.component.ts +++ b/src/frontend/app/ui/gallery/gallery.component.ts @@ -1,7 +1,7 @@ import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {AuthenticationService} from '../../model/network/authentication.service'; import {ActivatedRoute, Params, Router} from '@angular/router'; -import {ContentWrapperWithError, GalleryService} from './gallery.service'; +import {ContentWrapperWithError, DirectoryContent, GalleryService} from './gallery.service'; import {GalleryGridComponent} from './grid/grid.gallery.component'; import {Config} from '../../../../common/config/public/Config'; import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; @@ -12,11 +12,12 @@ import {UserRoles} from '../../../../common/entities/UserDTO'; import {interval, Observable, Subscription} from 'rxjs'; import {ContentWrapper} from '../../../../common/entities/ConentWrapper'; import {PageHelper} from '../../model/page.helper'; -import {SortingMethods} from '../../../../common/entities/SortingMethods'; import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; import {QueryParams} from '../../../../common/QueryParams'; -import {SeededRandomService} from '../../model/seededRandom.service'; -import {take} from 'rxjs/operators'; +import {map, take} from 'rxjs/operators'; +import {GallerySortingService} from './navigator/sorting.service'; +import {Media} from './Media'; +import {MediaDTO} from '../../../../common/entities/MediaDTO'; @Component({ selector: 'app-gallery', @@ -37,6 +38,8 @@ export class GalleryComponent implements OnInit, OnDestroy { public isPhotoWithLocation = false; public countDown: { day: number, hour: number, minute: number, second: number } = null; public readonly mapEnabled: boolean; + public readonly directoryContent: Observable; + public readonly mediaObs: Observable; private $counter: Observable; private subscription: { [key: string]: Subscription } = { content: null, @@ -44,7 +47,6 @@ export class GalleryComponent implements OnInit, OnDestroy { timer: null, sorting: null }; - private collator = new Intl.Collator(undefined, {numeric: true}); constructor(public galleryService: GalleryService, private authService: AuthenticationService, @@ -52,15 +54,13 @@ export class GalleryComponent implements OnInit, OnDestroy { private shareService: ShareService, private route: ActivatedRoute, private navigation: NavigationService, - private rndService: SeededRandomService) { + private sortingService: GallerySortingService) { this.mapEnabled = Config.Client.Map.enabled; + this.directoryContent = this.sortingService.applySorting(this.galleryService.directoryContent); + this.mediaObs = this.directoryContent.pipe(map(c => c?.media)); PageHelper.showScrollY(); } - get Content(): SearchResultDTO | ParentDirectoryDTO { - const cont = (this.ContentWrapper.searchResult || this.ContentWrapper.directory); - return cont ? cont : {} as any; - } get ContentWrapper(): ContentWrapperWithError { return this.galleryService.content.value; @@ -119,11 +119,11 @@ export class GalleryComponent implements OnInit, OnDestroy { this.$counter = interval(1000); this.subscription.timer = this.$counter.subscribe((x): void => this.updateTimer(x)); } - - this.subscription.sorting = this.galleryService.sorting.subscribe((): void => { - this.sortDirectories(); - }); - + /* + this.subscription.sorting = this.galleryService.sorting.subscribe((): void => { + this.sortDirectories(); + }); + */ } private onRoute = async (params: Params): Promise => { @@ -155,7 +155,7 @@ export class GalleryComponent implements OnInit, OnDestroy { media: [] }) as ParentDirectoryDTO | SearchResultDTO; this.directories = tmp.directories; - this.sortDirectories(); + // this.sortDirectories(); this.isPhotoWithLocation = false; for (const media of tmp.media as PhotoDTO[]) { @@ -169,50 +169,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; - - } - - } } diff --git a/src/frontend/app/ui/gallery/gallery.service.ts b/src/frontend/app/ui/gallery/gallery.service.ts index dd33cc1c..118b2eb2 100644 --- a/src/frontend/app/ui/gallery/gallery.service.ts +++ b/src/frontend/app/ui/gallery/gallery.service.ts @@ -1,24 +1,25 @@ import {Injectable} from '@angular/core'; import {NetworkService} from '../../model/network/network.service'; 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 {BehaviorSubject} from 'rxjs'; +import {BehaviorSubject, Observable} from 'rxjs'; import {Config} from '../../../../common/config/public/Config'; import {ShareService} from './share.service'; import {NavigationService} from '../../model/navigation.service'; -import {SortingMethods} from '../../../../common/entities/SortingMethods'; import {QueryParams} from '../../../../common/QueryParams'; -import {PG2ConfMap} from '../../../../common/PG2ConfMap'; import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; import {ErrorCodes} from '../../../../common/entities/Error'; +import {map} from 'rxjs/operators'; +import {MediaDTO} from '../../../../common/entities/MediaDTO'; +import {FileDTO} from '../../../../common/entities/FileDTO'; @Injectable() export class GalleryService { public content: BehaviorSubject; - public sorting: BehaviorSubject; + public directoryContent: Observable; lastRequest: { directory: string } = { directory: null }; @@ -31,42 +32,14 @@ export class GalleryService { private shareService: ShareService, private navigationService: NavigationService) { this.content = new BehaviorSubject(new ContentWrapperWithError()); - this.sorting = new BehaviorSubject(Config.Client.Other.defaultPhotoSortingMethod); - } - - 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); - } - } + this.directoryContent = this.content.pipe(map(c => + c.directory ? c.directory : c.searchResult + )); } setContent(content: ContentWrapperWithError): void { 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 { public error: string; } + +export interface DirectoryContent { + directories: SubDirectoryDTO[]; + media: MediaDTO[]; + metaFile: FileDTO[]; +} diff --git a/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts b/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts index 3eb5e4e3..63cc8cc2 100644 --- a/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts +++ b/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts @@ -5,14 +5,12 @@ import { ElementRef, HostListener, Input, - OnChanges, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; -import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; import {GridRowBuilder} from './GridRowBuilder'; import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component'; import {GridMedia} from './GridMedia'; @@ -20,37 +18,34 @@ import {GalleryPhotoComponent} from './photo/photo.grid.gallery.component'; import {OverlayService} from '../overlay.service'; import {Config} from '../../../../../common/config/public/Config'; import {PageHelper} from '../../../model/page.helper'; -import {Subscription} from 'rxjs'; +import {Observable, Subscription} from 'rxjs'; import {ActivatedRoute, Params, Router} from '@angular/router'; import {QueryService} from '../../../model/query.service'; import {GalleryService} from '../gallery.service'; -import {SortingMethods} from '../../../../../common/entities/SortingMethods'; import {MediaDTO, MediaDTOUtils} from '../../../../../common/entities/MediaDTO'; import {QueryParams} from '../../../../../common/QueryParams'; -import {SeededRandomService} from '../../../model/seededRandom.service'; @Component({ selector: 'app-gallery-grid', templateUrl: './grid.gallery.component.html', styleUrls: ['./grid.gallery.component.css'], }) -export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, OnDestroy { +export class GalleryGridComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('gridContainer', {static: false}) gridContainer: ElementRef; @ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList; - @Input() media: MediaDTO[]; + @Input() mediaObs: Observable; @Input() lightbox: GalleryLightboxComponent; + media: MediaDTO[]; photosToRender: GridMedia[] = []; containerWidth = 0; screenHeight = 0; public IMAGE_MARGIN = 2; isAfterViewInit = false; subscriptions: { - route: Subscription, - sorting: Subscription + route: Subscription } = { - route: null, - sorting: null + route: null }; delayedRenderUpToPhoto: string = null; private scrollListenerPhotos: GalleryPhotoComponent[] = []; @@ -66,8 +61,8 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O public queryService: QueryService, private router: Router, public galleryService: GalleryService, - private route: ActivatedRoute, - private rndService: SeededRandomService) { + private route: ActivatedRoute) { + } ngOnInit(): void { @@ -81,19 +76,17 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O this.renderUpToMedia(params[QueryParams.gallery.photo]); } }); - this.subscriptions.sorting = this.galleryService.sorting.subscribe((): void => { - this.clearRenderedPhotos(); - this.sortPhotos(); - this.renderPhotos(); + this.mediaObs.subscribe((m) => { + this.media = m || []; + this.onChange(); }); } - ngOnChanges(): void { + onChange = () => { if (this.isAfterViewInit === false) { return; } this.updateContainerDimensions(); - this.sortPhotos(); this.mergeNewPhotos(); this.helperTime = window.setTimeout((): void => { this.renderPhotos(); @@ -101,7 +94,8 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O this.renderUpToMedia(this.delayedRenderUpToPhoto); } }, 0); - } + }; + ngOnDestroy(): void { @@ -112,10 +106,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O this.subscriptions.route.unsubscribe(); this.subscriptions.route = null; } - if (this.subscriptions.sorting !== null) { - this.subscriptions.sorting.unsubscribe(); - this.subscriptions.sorting = null; - } } @HostListener('window:resize') @@ -129,7 +119,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O if (this.updateContainerDimensions() === false) { return; } - this.sortPhotos(); this.renderPhotos(renderedIndex); } @@ -137,7 +126,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O this.router.navigate([], {queryParams: this.queryService.getParams(media)}); } - ngAfterViewInit(): void { this.lightbox.setGridPhotoQL(this.gridPhotoQL); @@ -148,7 +136,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O } this.updateContainerDimensions(); - this.sortPhotos(); this.clearRenderedPhotos(); this.helperTime = window.setTimeout((): void => { this.renderPhotos(); @@ -193,6 +180,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O @HostListener('window:scroll') onScroll(): void { if (!this.onScrollFired && + this.media && // should we trigger this at all? (this.renderedPhotoIndex < this.media.length || this.scrollListenerPhotos.length > 0)) { window.requestAnimationFrame((): void => { @@ -238,51 +226,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O 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, // we do not post update galleries anymore since the preview member in the DriectoryDTO private mergeNewPhotos(): void { @@ -331,6 +274,9 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O } private renderPhotos(numberOfPhotos: number = 0): void { + if (!this.media) { + return; + } if (this.containerWidth === 0 || this.renderedPhotoIndex >= this.media.length || !this.shouldRenderMore()) { diff --git a/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html b/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html index 721bf5f2..e43cdf0e 100644 --- a/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html +++ b/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.html @@ -1,29 +1,29 @@ - diff --git a/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.ts b/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.ts index d2f79a61..a75f20a0 100644 --- a/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.ts +++ b/src/frontend/app/ui/gallery/navigator/navigator.gallery.component.ts @@ -1,15 +1,16 @@ -import {Component, Input, OnChanges} from '@angular/core'; -import {ParentDirectoryDTO} from '../../../../../common/entities/DirectoryDTO'; +import {Component} from '@angular/core'; import {RouterLink} from '@angular/router'; import {UserDTOUtils} from '../../../../../common/entities/UserDTO'; import {AuthenticationService} from '../../../model/network/authentication.service'; import {QueryService} from '../../../model/query.service'; -import {GalleryService} from '../gallery.service'; +import {ContentWrapperWithError, DirectoryContent, GalleryService} from '../gallery.service'; import {Utils} from '../../../../../common/Utils'; import {SortingMethods} from '../../../../../common/entities/SortingMethods'; import {Config} from '../../../../../common/config/public/Config'; -import {SearchResultDTO} from '../../../../../common/entities/SearchResultDTO'; import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../../common/entities/SearchQueryDTO'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {GallerySortingService} from './sorting.service'; @Component({ selector: 'app-gallery-navbar', @@ -17,103 +18,118 @@ import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../ templateUrl: './navigator.gallery.component.html', providers: [RouterLink], }) -export class GalleryNavigatorComponent implements OnChanges { - @Input() directory: ParentDirectoryDTO; - @Input() searchResult: SearchResultDTO; +export class GalleryNavigatorComponent { - routes: NavigatorPath[] = []; SortingMethods = SortingMethods; sortingMethodsType: { key: number; value: string }[] = []; - config = Config; - DefaultSorting = Config.Client.Other.defaultPhotoSortingMethod; + readonly config = Config; + // DefaultSorting = Config.Client.Other.defaultPhotoSortingMethod; readonly SearchQueryTypes = SearchQueryTypes; + wrappedContent: Observable; + public directoryContent: Observable; private readonly RootFolderName: string; + constructor(private authService: AuthenticationService, public queryService: QueryService, - public galleryService: GalleryService) { + public galleryService: GalleryService, + public sortingService: GallerySortingService) { this.sortingMethodsType = Utils.enumToArray(SortingMethods); 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 { - return this.directory ? this.directory.mediaCount : this.searchResult.media.length; + get isDirectory(): Observable { + return this.wrappedContent.pipe(map(c => !!c.directory)); } - ngOnChanges(): void { - this.getPath(); - this.DefaultSorting = this.galleryService.getDefaultSorting(this.directory); + get ItemCount(): Observable { + return this.wrappedContent.pipe(map(c => c.directory ? c.directory.mediaCount : (c.searchResult ? c.searchResult.media.length : 0))); } - getPath(): any { - if (!this.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--; + get Routes(): Observable { + return this.wrappedContent.pipe(map((c) => { + if (!c.directory) { + return []; } - } - const user = this.authService.user.value; - const arr: NavigatorPath[] = []; + const path = c.directory.path.replace(new RegExp('\\\\', 'g'), '/'); - // create root link - if (dirs.length === 0) { - arr.push({name: this.RootFolderName, route: null}); - } else { - arr.push({name: this.RootFolderName, route: UserDTOUtils.isDirectoryPathAvailable('/', user.permissions) ? '/' : null}); - } + const dirs = path.split('/'); + dirs.push(c.directory.name); - // 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}); + // 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 arr: NavigatorPath[] = []; + + // create root link + if (dirs.length === 0) { + arr.push({name: this.RootFolderName, route: null}); } 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 { + return this.wrappedContent.pipe(map(c => + this.sortingService.getDefaultSorting(c.directory) + )); } setSorting(sorting: SortingMethods): void { - this.galleryService.setSorting(sorting); + this.sortingService.setSorting(sorting); } - getDownloadZipLink(): string { - let queryParams = ''; - Object.entries(this.queryService.getParams()).forEach(e => { - queryParams += e[0] + '=' + e[1]; - }); - return Utils.concatUrls(Config.Client.urlBase, - '/api/gallery/zip/', - this.getDirectoryPath(), '?' + queryParams); + getDownloadZipLink(): Observable { + return this.wrappedContent.pipe(map((c) => { + if (!c.directory) { + return null; + } + let queryParams = ''; + Object.entries(this.queryService.getParams()).forEach(e => { + queryParams += e[0] + '=' + e[1]; + }); + return Utils.concatUrls(Config.Client.urlBase, + '/api/gallery/zip/', + c.directory.path, c.directory.name, '?' + queryParams); + })); + } - getDirectoryPath(): string { - return Utils.concatUrls(this.directory.path, this.directory.name); + getDirectoryFlattenSearchQuery(): Observable { + 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 { diff --git a/src/frontend/app/ui/gallery/navigator/sorting.service.ts b/src/frontend/app/ui/gallery/navigator/sorting.service.ts new file mode 100644 index 00000000..8229b514 --- /dev/null +++ b/src/frontend/app/ui/gallery/navigator/sorting.service.ts @@ -0,0 +1,156 @@ +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, GalleryService} from '../gallery.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; + private collator = new Intl.Collator(undefined, {numeric: true}); + + constructor(private networkService: NetworkService, + private galleryCacheService: GalleryCacheService, + private galleryService: GalleryService, + private rndService: SeededRandomService) { + this.sorting = new BehaviorSubject(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): Observable { + return directoryContent.pipe(mergeMap((c) => { + return this.sorting.pipe(map((sorting: SortingMethods) => { + if (!c) { + return c; + } + 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; + })); + })); + } + + +} + +