You've already forked pigallery2
							
							
				mirror of
				https://github.com/bpatrik/pigallery2.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	Implementing filter #287
This commit is contained in:
		| @@ -3,7 +3,7 @@ import {BrowserModule, HAMMER_GESTURE_CONFIG, HammerGestureConfig, HammerModule} | ||||
| import {FormsModule} from '@angular/forms'; | ||||
| import {AppComponent} from './app.component'; | ||||
| 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 {GalleryCacheService} from './ui/gallery/cache.gallery.service'; | ||||
| import {FullScreenService} from './ui/gallery/fullscreen.service'; | ||||
| @@ -116,6 +116,7 @@ import {PreviewSettingsComponent} from './ui/settings/preview/preview.settings.c | ||||
| 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() | ||||
| export class MyHammerConfig extends HammerGestureConfig { | ||||
| @@ -272,7 +273,8 @@ Marker.prototype.options.icon = iconDefault; | ||||
|     UserService, | ||||
|     AlbumsService, | ||||
|     GalleryCacheService, | ||||
|     GalleryService, | ||||
|     ContentService, | ||||
|     FilterService, | ||||
|     GallerySortingService, | ||||
|     MapService, | ||||
|     BlogService, | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import {ShareService} from '../ui/gallery/share.service'; | ||||
| import {MediaDTO} from '../../../common/entities/MediaDTO'; | ||||
| import {QueryParams} from '../../../common/QueryParams'; | ||||
| 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 {ParentDirectoryDTO, SubDirectoryDTO} from '../../../common/entities/DirectoryDTO'; | ||||
|  | ||||
| @@ -12,7 +12,7 @@ export class QueryService { | ||||
|  | ||||
|  | ||||
|   constructor(private shareService: ShareService, | ||||
|               private galleryService: GalleryService) { | ||||
|               private galleryService: ContentService) { | ||||
|   } | ||||
|  | ||||
|   getMediaStringId(media: MediaDTO): string { | ||||
|   | ||||
| @@ -16,7 +16,7 @@ import {FileDTO} from '../../../../common/entities/FileDTO'; | ||||
| 
 | ||||
| 
 | ||||
| @Injectable() | ||||
| export class GalleryService { | ||||
| export class ContentService { | ||||
| 
 | ||||
|   public content: BehaviorSubject<ContentWrapperWithError>; | ||||
|   public directoryContent: Observable<DirectoryContent>; | ||||
| @@ -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(); | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -45,14 +45,14 @@ | ||||
|  | ||||
|  | ||||
|       <app-gallery-directories class="directories" | ||||
|                                [directories]="(directoryContent | async)?.directories || []"></app-gallery-directories> | ||||
|                                [directories]="directoryContent.directories || []"></app-gallery-directories> | ||||
|  | ||||
|       <div class="blog-map-row"> | ||||
|         <div class="blog-wrapper" | ||||
|              [style.width]="blogOpen ?  '100%' : 'calc(100% - 100px)'" | ||||
|              *ngIf="config.Client.MetaFile.markdown  && (directoryContent | async)?.metaFile && ((directoryContent | async).metaFile | mdFiles).length>0"> | ||||
|              *ngIf="config.Client.MetaFile.markdown  && directoryContent.metaFile && (directoryContent.metaFile | mdFiles).length>0"> | ||||
|           <app-gallery-blog [collapsed]="!blogOpen" | ||||
|                             [mdFiles]="(directoryContent | async).metaFile | mdFiles"></app-gallery-blog> | ||||
|                             [mdFiles]="directoryContent.metaFile | mdFiles"></app-gallery-blog> | ||||
|  | ||||
|  | ||||
|           <button class="btn btn-blog-details" (click)="blogOpen=!blogOpen"><span | ||||
| @@ -60,10 +60,10 @@ | ||||
|           </button> | ||||
|         </div> | ||||
|         <app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled" | ||||
|                          [photos]="(directoryContent | async).media | photosOnly" | ||||
|                          [gpxFiles]="(directoryContent | async).metaFile | gpxFiles"></app-gallery-map> | ||||
|                          [photos]="directoryContent.media | photosOnly" | ||||
|                          [gpxFiles]="directoryContent.metaFile | gpxFiles"></app-gallery-map> | ||||
|       </div> | ||||
|       <app-gallery-grid [mediaObs]="mediaObs" | ||||
|       <app-gallery-grid [media]="directoryContent.media" | ||||
|                         [lightbox]="lightbox"></app-gallery-grid> | ||||
|  | ||||
|     </ng-container> | ||||
|   | ||||
| @@ -1,23 +1,20 @@ | ||||
| import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; | ||||
| import {AuthenticationService} from '../../model/network/authentication.service'; | ||||
| import {ActivatedRoute, Params, Router} from '@angular/router'; | ||||
| import {ContentWrapperWithError, DirectoryContent, GalleryService} from './gallery.service'; | ||||
| import {ContentService, ContentWrapperWithError, DirectoryContent} from './content.service'; | ||||
| import {GalleryGridComponent} from './grid/grid.gallery.component'; | ||||
| 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 {NavigationService} from '../../model/navigation.service'; | ||||
| 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 {PhotoDTO} from '../../../../common/entities/PhotoDTO'; | ||||
| import {QueryParams} from '../../../../common/QueryParams'; | ||||
| import {map, take} from 'rxjs/operators'; | ||||
| import {take} from 'rxjs/operators'; | ||||
| import {GallerySortingService} from './navigator/sorting.service'; | ||||
| import {Media} from './Media'; | ||||
| import {MediaDTO} from '../../../../common/entities/MediaDTO'; | ||||
| import {FilterService} from './filter/filter.service'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-gallery', | ||||
| @@ -34,11 +31,10 @@ export class GalleryComponent implements OnInit, OnDestroy { | ||||
|   public blogOpen = false; | ||||
|  | ||||
|   config = Config; | ||||
|   public directories: SubDirectoryDTO[] = []; | ||||
|   public isPhotoWithLocation = false; | ||||
|   public countDown: { day: number, hour: number, minute: number, second: number } = null; | ||||
|   public readonly mapEnabled: boolean; | ||||
|   public readonly directoryContent: Observable<DirectoryContent>; | ||||
|   public directoryContent: DirectoryContent; | ||||
|   public readonly mediaObs: Observable<MediaDTO[]>; | ||||
|   private $counter: Observable<number>; | ||||
|   private subscription: { [key: string]: Subscription } = { | ||||
| @@ -48,16 +44,15 @@ export class GalleryComponent implements OnInit, OnDestroy { | ||||
|     sorting: null | ||||
|   }; | ||||
|  | ||||
|   constructor(public galleryService: GalleryService, | ||||
|   constructor(public galleryService: ContentService, | ||||
|               private authService: AuthenticationService, | ||||
|               private router: Router, | ||||
|               private shareService: ShareService, | ||||
|               private route: ActivatedRoute, | ||||
|               private navigation: NavigationService, | ||||
|               private filterService: FilterService, | ||||
|               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(); | ||||
|   } | ||||
|  | ||||
| @@ -112,7 +107,10 @@ export class GalleryComponent implements OnInit, OnDestroy { | ||||
|     this.showSearchBar = Config.Client.Search.enabled && this.authService.canSearch(); | ||||
|     this.showShare = Config.Client.Sharing.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); | ||||
|  | ||||
|     if (this.shareService.isSharing()) { | ||||
| @@ -149,16 +147,17 @@ export class GalleryComponent implements OnInit, OnDestroy { | ||||
|     this.galleryService.loadDirectory(directoryName); | ||||
|   }; | ||||
|  | ||||
|   private onContentChange = (content: ContentWrapper): void => { | ||||
|     const tmp = (content.searchResult || content.directory || { | ||||
|       directories: [], | ||||
|       media: [] | ||||
|     }) as ParentDirectoryDTO | SearchResultDTO; | ||||
|     this.directories = tmp.directories; | ||||
|     // this.sortDirectories(); | ||||
|   private onContentChange = (content: DirectoryContent): void => { | ||||
|     if (!content) { | ||||
|       return; | ||||
|     } | ||||
|     this.directoryContent = content; | ||||
|  | ||||
|     // enforce change detection on grid | ||||
|     this.directoryContent.media = this.directoryContent.media?.slice(); | ||||
|     this.isPhotoWithLocation = false; | ||||
|  | ||||
|     for (const media of tmp.media as PhotoDTO[]) { | ||||
|     for (const media of content.media as PhotoDTO[]) { | ||||
|       if (media.metadata && | ||||
|         media.metadata.positionData && | ||||
|         media.metadata.positionData.GPSData && | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import { | ||||
|   ElementRef, | ||||
|   HostListener, | ||||
|   Input, | ||||
|   OnChanges, | ||||
|   OnDestroy, | ||||
|   OnInit, | ||||
|   QueryList, | ||||
| @@ -18,10 +19,10 @@ 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 {Observable, Subscription} from 'rxjs'; | ||||
| import {Subscription} from 'rxjs'; | ||||
| import {ActivatedRoute, Params, Router} from '@angular/router'; | ||||
| import {QueryService} from '../../../model/query.service'; | ||||
| import {GalleryService} from '../gallery.service'; | ||||
| import {ContentService} from '../content.service'; | ||||
| import {MediaDTO, MediaDTOUtils} from '../../../../../common/entities/MediaDTO'; | ||||
| import {QueryParams} from '../../../../../common/QueryParams'; | ||||
|  | ||||
| @@ -30,13 +31,12 @@ import {QueryParams} from '../../../../../common/QueryParams'; | ||||
|   templateUrl: './grid.gallery.component.html', | ||||
|   styleUrls: ['./grid.gallery.component.css'], | ||||
| }) | ||||
| export class GalleryGridComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
| export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { | ||||
|  | ||||
|   @ViewChild('gridContainer', {static: false}) gridContainer: ElementRef; | ||||
|   @ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>; | ||||
|   @Input() mediaObs: Observable<MediaDTO[]>; | ||||
|   @Input() lightbox: GalleryLightboxComponent; | ||||
|   media: MediaDTO[]; | ||||
|   @Input() media: MediaDTO[]; | ||||
|   photosToRender: GridMedia[] = []; | ||||
|   containerWidth = 0; | ||||
|   screenHeight = 0; | ||||
| @@ -60,11 +60,15 @@ export class GalleryGridComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|               private changeDetector: ChangeDetectorRef, | ||||
|               public queryService: QueryService, | ||||
|               private router: Router, | ||||
|               public galleryService: GalleryService, | ||||
|               public galleryService: ContentService, | ||||
|               private route: ActivatedRoute) { | ||||
|  | ||||
|   } | ||||
|  | ||||
|   ngOnChanges(): void { | ||||
|     this.onChange(); | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.subscriptions.route = this.route.queryParams.subscribe((params: Params): void => { | ||||
|       if (params[QueryParams.gallery.photo] && params[QueryParams.gallery.photo] !== '') { | ||||
| @@ -76,10 +80,6 @@ export class GalleryGridComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|         this.renderUpToMedia(params[QueryParams.gallery.photo]); | ||||
|       } | ||||
|     }); | ||||
|     this.mediaObs.subscribe((m) => { | ||||
|       this.media = m || []; | ||||
|       this.onChange(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   onChange = () => { | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import {PageHelper} from '../../../model/page.helper'; | ||||
| import {QueryService} from '../../../model/query.service'; | ||||
| import {MediaDTO} from '../../../../../common/entities/MediaDTO'; | ||||
| import {QueryParams} from '../../../../../common/QueryParams'; | ||||
| import {GalleryService} from '../gallery.service'; | ||||
| import {ContentService} from '../content.service'; | ||||
| import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; | ||||
| import {ControlsLightboxComponent} from './controls/controls.lightbox.gallery.component'; | ||||
| import {SupportedFormats} from '../../../../../common/SupportedFormats'; | ||||
| @@ -64,7 +64,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { | ||||
|               private builder: AnimationBuilder, | ||||
|               private router: Router, | ||||
|               private queryService: QueryService, | ||||
|               private galleryService: GalleryService, | ||||
|               private galleryService: ContentService, | ||||
|               private route: ActivatedRoute) { | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -12,15 +12,15 @@ | ||||
|  | ||||
| .photos-count { | ||||
|   display: inline-block; | ||||
|   padding-right: 10px; | ||||
| } | ||||
|  | ||||
| .btn-download { | ||||
|   padding-left: 1px; | ||||
|   padding-right: 1px; | ||||
| .btn-navbar { | ||||
| } | ||||
|  | ||||
|  | ||||
| .divider { | ||||
|   margin: 5px 5px 5px 10px; | ||||
|   margin-left: 5px; | ||||
|   border-left: 1px solid #6c757d; | ||||
|   display: inline-block; | ||||
| } | ||||
| @@ -33,7 +33,7 @@ ol { | ||||
|   min-width: 13rem; | ||||
| } | ||||
|  | ||||
| .dropdown-item{ | ||||
| .dropdown-item { | ||||
|   padding: 0.25rem 0.5rem; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|   <ol *ngIf="!(isDirectory | async)" class="breadcrumb"> | ||||
|     <li class="active"> | ||||
|       <ng-container i18n>Searching for:</ng-container> | ||||
|       <strong> {{(wrappedContent | async).searchQuery | searchQuery}}</strong> | ||||
|       <strong> {{(wrappedContent | async)?.searchResult?.searchQuery | searchQuery}}</strong> | ||||
|     </li> | ||||
|   </ol> | ||||
|  | ||||
| @@ -24,7 +24,7 @@ | ||||
|  | ||||
|     <ng-container *ngIf="config.Client.Other.enableDownloadZip && (isDirectory | async) && (ItemCount | async) > 0"> | ||||
|       <a [href]="getDownloadZipLink() | async" | ||||
|          class="btn  btn-download"> | ||||
|          class="btn  btn-navbar"> | ||||
|          <span class="oi oi-data-transfer-download" | ||||
|                title="download" i18n-title></span> | ||||
|       </a> | ||||
| @@ -34,12 +34,19 @@ | ||||
|     <ng-container *ngIf="config.Client.Other.enableDirectoryFlattening && (isDirectory | async)"> | ||||
|       <a | ||||
|         [routerLink]="['/search', getDirectoryFlattenSearchQuery() | async]" | ||||
|         class="btn  btn-download"> | ||||
|         class="btn  btn-navbar"> | ||||
|          <span class="oi oi-fork" | ||||
|                title="Flatten directory" i18n-title></span> | ||||
|       </a> | ||||
|       <div class="divider"> </div> | ||||
|     </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"> | ||||
|       <button id="button-alignment" dropdownToggle type="button" | ||||
|               class="btn btn-secondary dropdown-toggle" | ||||
| @@ -62,3 +69,5 @@ | ||||
|   </div> | ||||
|  | ||||
| </nav> | ||||
|  | ||||
| <app-gallery-filter *ngIf="showFilters"></app-gallery-filter> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ 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 {ContentWrapperWithError, DirectoryContent, GalleryService} from '../gallery.service'; | ||||
| import {ContentService, ContentWrapperWithError, DirectoryContent} from '../content.service'; | ||||
| import {Utils} from '../../../../../common/Utils'; | ||||
| import {SortingMethods} from '../../../../../common/entities/SortingMethods'; | ||||
| import {Config} from '../../../../../common/config/public/Config'; | ||||
| @@ -27,12 +27,12 @@ export class GalleryNavigatorComponent { | ||||
|   readonly SearchQueryTypes = SearchQueryTypes; | ||||
|   wrappedContent: Observable<ContentWrapperWithError>; | ||||
|   public directoryContent: Observable<DirectoryContent>; | ||||
|   showFilters = false; | ||||
|   private readonly RootFolderName: string; | ||||
|  | ||||
|  | ||||
|   constructor(private authService: AuthenticationService, | ||||
|               public queryService: QueryService, | ||||
|               public galleryService: GalleryService, | ||||
|               public galleryService: ContentService, | ||||
|               public sortingService: GallerySortingService) { | ||||
|     this.sortingMethodsType = Utils.enumToArray(SortingMethods); | ||||
|     this.RootFolderName = $localize`Images`; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ 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 {DirectoryContent, ContentService} from '../content.service'; | ||||
| import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; | ||||
| import {map, mergeMap} from 'rxjs/operators'; | ||||
| import {SeededRandomService} from '../../../model/seededRandom.service'; | ||||
| @@ -19,7 +19,7 @@ export class GallerySortingService { | ||||
|  | ||||
|   constructor(private networkService: NetworkService, | ||||
|               private galleryCacheService: GalleryCacheService, | ||||
|               private galleryService: GalleryService, | ||||
|               private galleryService: ContentService, | ||||
|               private rndService: SeededRandomService) { | ||||
|     this.sorting = new BehaviorSubject<SortingMethods>(Config.Client.Other.defaultPhotoSortingMethod); | ||||
|     this.galleryService.content.subscribe((c) => { | ||||
| @@ -57,11 +57,16 @@ export class GallerySortingService { | ||||
|   } | ||||
|  | ||||
|   public applySorting(directoryContent: Observable<DirectoryContent>): Observable<DirectoryContent> { | ||||
|     return directoryContent.pipe(mergeMap((c) => { | ||||
|     return directoryContent.pipe(mergeMap((dirContent) => { | ||||
|       return this.sorting.pipe(map((sorting: SortingMethods) => { | ||||
|         if (!c) { | ||||
|           return c; | ||||
|         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 | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| 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 {Config} from '../../../../../common/config/public/Config'; | ||||
| import {NotificationService} from '../../../model/notification.service'; | ||||
| @@ -31,7 +31,7 @@ export class RandomQueryBuilderGalleryComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   private readonly subscription: Subscription = null; | ||||
|  | ||||
|   constructor(public galleryService: GalleryService, | ||||
|   constructor(public galleryService: ContentService, | ||||
|               private notification: NotificationService, | ||||
|               private searchQueryParserService: SearchQueryParserService, | ||||
|               private route: ActivatedRoute, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import {Component, OnDestroy, TemplateRef} from '@angular/core'; | ||||
| import {AutoCompleteService} from './autocomplete.service'; | ||||
| import {ActivatedRoute, Params, Router, RouterLink} from '@angular/router'; | ||||
| import {GalleryService} from '../gallery.service'; | ||||
| import {ContentService} from '../content.service'; | ||||
| import {Subscription} from 'rxjs'; | ||||
| import {NavigationService} from '../../../model/navigation.service'; | ||||
| import {QueryParams} from '../../../../../common/QueryParams'; | ||||
| @@ -34,7 +34,7 @@ export class GallerySearchComponent implements OnDestroy { | ||||
|  | ||||
|   constructor(private autoCompleteService: AutoCompleteService, | ||||
|               private searchQueryParserService: SearchQueryParserService, | ||||
|               private galleryService: GalleryService, | ||||
|               private galleryService: ContentService, | ||||
|               private albumService: AlbumsService, | ||||
|               private navigationService: NavigationService, | ||||
|               private route: ActivatedRoute, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import {Component, OnDestroy, OnInit, TemplateRef} from '@angular/core'; | ||||
| import {Utils} from '../../../../../common/Utils'; | ||||
| import {ShareService} from '../share.service'; | ||||
| import {GalleryService} from '../gallery.service'; | ||||
| import {ContentService} from '../content.service'; | ||||
| import {ContentWrapper} from '../../../../../common/entities/ConentWrapper'; | ||||
| import {SharingDTO} from '../../../../../common/entities/SharingDTO'; | ||||
| import {Config} from '../../../../../common/config/public/Config'; | ||||
| @@ -44,7 +44,7 @@ export class GalleryShareComponent implements OnInit, OnDestroy { | ||||
|   }; | ||||
|  | ||||
|   constructor(private sharingService: ShareService, | ||||
|               public galleryService: GalleryService, | ||||
|               public galleryService: ContentService, | ||||
|               private  notification: NotificationService, | ||||
|               private modalService: BsModalService) { | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user