diff --git a/src/frontend/app/model/pi-title.service.ts b/src/frontend/app/model/pi-title.service.ts new file mode 100644 index 00000000..345c3604 --- /dev/null +++ b/src/frontend/app/model/pi-title.service.ts @@ -0,0 +1,51 @@ +import {Injectable} from '@angular/core'; +import {Config} from '../../../common/config/public/Config'; +import {Title} from '@angular/platform-browser'; +import {GridMedia} from '../ui/gallery/grid/GridMedia'; +import {SearchQueryParserService} from '../ui/gallery/search/search-query-parser.service'; +import {SearchQueryDTO} from '../../../common/entities/SearchQueryDTO'; + +@Injectable({ + providedIn: 'root' +}) +export class PiTitleService { + + private lastNonMedia: string = null; + + constructor( + private titleService: Title, + private searchQueryParserService: SearchQueryParserService) { + } + + setTitle(title: string) { + if (title) { + this.titleService.setTitle(Config.Server.applicationTitle + ' - ' + title); + } else { + this.titleService.setTitle(Config.Server.applicationTitle); + } + } + + setSearchTitle(searchQuery: SearchQueryDTO | string) { + let query: SearchQueryDTO = searchQuery as SearchQueryDTO; + if (typeof searchQuery === 'string') { + query = JSON.parse(searchQuery); + } + this.lastNonMedia = this.searchQueryParserService.stringify(query); + this.setTitle(this.lastNonMedia); + } + + setDirectoryTitle(path: string) { + this.lastNonMedia = path; + this.setTitle(this.lastNonMedia); + } + + setMediaTitle(media: GridMedia) { + this.setTitle(media.getReadableRelativePath()); + } + + setLastNonMedia() { + if (this.lastNonMedia) { + this.setTitle(this.lastNonMedia); + } + } +} diff --git a/src/frontend/app/ui/admin/admin.component.ts b/src/frontend/app/ui/admin/admin.component.ts index d8e7f236..627c2b98 100644 --- a/src/frontend/app/ui/admin/admin.component.ts +++ b/src/frontend/app/ui/admin/admin.component.ts @@ -11,6 +11,7 @@ import {WebConfig} from '../../../../common/config/private/WebConfig'; import {ISettingsComponent} from '../settings/template/ISettingsComponent'; import {WebConfigClassBuilder} from '../../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder'; import {enumToTranslatedArray} from '../EnumTranslations'; +import {PiTitleService} from '../../model/pi-title.service'; @Component({ selector: 'app-admin', @@ -32,6 +33,7 @@ export class AdminComponent implements OnInit, AfterViewInit { public viewportScroller: ViewportScroller, public notificationService: NotificationService, public settingsService: SettingsService, + private piTitleService: PiTitleService ) { this.configPriorities = enumToTranslatedArray(ConfigPriority); const wc = WebConfigClassBuilder.attachPrivateInterface(new WebConfig()); @@ -51,6 +53,7 @@ export class AdminComponent implements OnInit, AfterViewInit { this.navigation.toLogin(); return; } + this.piTitleService.setTitle($localize`Admin`); } public getCss(type: NotificationType): string { diff --git a/src/frontend/app/ui/albums/albums.component.ts b/src/frontend/app/ui/albums/albums.component.ts index 01cb8a3f..6847a807 100644 --- a/src/frontend/app/ui/albums/albums.component.ts +++ b/src/frontend/app/ui/albums/albums.component.ts @@ -1,19 +1,11 @@ -import { - Component, - ElementRef, - OnInit, - TemplateRef, - ViewChild, -} from '@angular/core'; -import { AlbumsService } from './albums.service'; -import { BsModalService } from 'ngx-bootstrap/modal'; -import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; -import { - SearchQueryTypes, - TextSearch, -} from '../../../../common/entities/SearchQueryDTO'; -import { UserRoles } from '../../../../common/entities/UserDTO'; -import { AuthenticationService } from '../../model/network/authentication.service'; +import {Component, ElementRef, OnInit, TemplateRef, ViewChild,} from '@angular/core'; +import {AlbumsService} from './albums.service'; +import {BsModalService} from 'ngx-bootstrap/modal'; +import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service'; +import {SearchQueryTypes, TextSearch,} from '../../../../common/entities/SearchQueryDTO'; +import {UserRoles} from '../../../../common/entities/UserDTO'; +import {AuthenticationService} from '../../model/network/authentication.service'; +import {PiTitleService} from '../../model/pi-title.service'; @Component({ selector: 'app-albums', @@ -21,23 +13,25 @@ import { AuthenticationService } from '../../model/network/authentication.servic styleUrls: ['./albums.component.css'], }) export class AlbumsComponent implements OnInit { - @ViewChild('container', { static: true }) container: ElementRef; + @ViewChild('container', {static: true}) container: ElementRef; public size: number; public savedSearch = { name: '', - searchQuery: { type: SearchQueryTypes.any_text, text: '' } as TextSearch, + searchQuery: {type: SearchQueryTypes.any_text, text: ''} as TextSearch, }; private modalRef: BsModalRef; constructor( public albumsService: AlbumsService, private modalService: BsModalService, - public authenticationService: AuthenticationService + public authenticationService: AuthenticationService, + private piTitleService: PiTitleService ) { this.albumsService.getAlbums().catch(console.error); } ngOnInit(): void { + this.piTitleService.setTitle($localize`Albums`); this.updateSize(); } @@ -46,7 +40,7 @@ export class AlbumsComponent implements OnInit { } public async openModal(template: TemplateRef): Promise { - this.modalRef = this.modalService.show(template, { class: 'modal-lg' }); + this.modalRef = this.modalService.show(template, {class: 'modal-lg'}); document.body.style.paddingRight = '0px'; } diff --git a/src/frontend/app/ui/duplicates/duplicates.component.ts b/src/frontend/app/ui/duplicates/duplicates.component.ts index 302db2ee..89ef1b54 100644 --- a/src/frontend/app/ui/duplicates/duplicates.component.ts +++ b/src/frontend/app/ui/duplicates/duplicates.component.ts @@ -1,13 +1,14 @@ -import { Component, HostListener, OnDestroy } from '@angular/core'; -import { DuplicateService } from './duplicates.service'; -import { Utils } from '../../../../common/Utils'; -import { QueryService } from '../../model/query.service'; -import { DuplicatesDTO } from '../../../../common/entities/DuplicatesDTO'; -import { DirectoryPathDTO } from '../../../../common/entities/DirectoryDTO'; -import { Subscription } from 'rxjs'; -import { Config } from '../../../../common/config/public/Config'; -import { PageHelper } from '../../model/page.helper'; -import { MediaDTO } from '../../../../common/entities/MediaDTO'; +import {Component, HostListener, OnDestroy, OnInit} from '@angular/core'; +import {DuplicateService} from './duplicates.service'; +import {Utils} from '../../../../common/Utils'; +import {QueryService} from '../../model/query.service'; +import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; +import {DirectoryPathDTO} from '../../../../common/entities/DirectoryDTO'; +import {Subscription} from 'rxjs'; +import {Config} from '../../../../common/config/public/Config'; +import {PageHelper} from '../../model/page.helper'; +import {MediaDTO} from '../../../../common/entities/MediaDTO'; +import {PiTitleService} from '../../model/pi-title.service'; interface GroupedDuplicate { name: string; @@ -19,7 +20,7 @@ interface GroupedDuplicate { templateUrl: './duplicates.component.html', styleUrls: ['./duplicates.component.css'], }) -export class DuplicateComponent implements OnDestroy { +export class DuplicateComponent implements OnDestroy, OnInit { directoryGroups: GroupedDuplicate[] = null; renderedDirGroups: GroupedDuplicate[] = null; renderedIndex = { @@ -35,13 +36,14 @@ export class DuplicateComponent implements OnDestroy { constructor( public duplicateService: DuplicateService, - public queryService: QueryService + public queryService: QueryService, + private piTitleService: PiTitleService ) { this.duplicateService.getDuplicates().catch(console.error); this.subscription = this.duplicateService.duplicates.subscribe( (duplicates: DuplicatesDTO[]): void => { this.directoryGroups = []; - this.renderedIndex = { group: -1, pairs: 0 }; + this.renderedIndex = {group: -1, pairs: 0}; this.renderedDirGroups = []; this.duplicateCount = { pairs: 0, @@ -113,6 +115,10 @@ export class DuplicateComponent implements OnDestroy { ); } + ngOnInit(): void { + this.piTitleService.setTitle($localize`Duplicates`); + } + ngOnDestroy(): void { if (this.subscription) { this.subscription.unsubscribe(); @@ -137,7 +143,7 @@ export class DuplicateComponent implements OnDestroy { if ( this.renderedIndex.group === this.directoryGroups.length - 1 && this.renderedIndex.pairs >= - this.directoryGroups[this.renderedIndex.group].duplicates.length + this.directoryGroups[this.renderedIndex.group].duplicates.length ) { return; } @@ -145,7 +151,7 @@ export class DuplicateComponent implements OnDestroy { if ( this.renderedDirGroups.length === 0 || this.renderedIndex.pairs >= - this.directoryGroups[this.renderedIndex.group].duplicates.length + this.directoryGroups[this.renderedIndex.group].duplicates.length ) { this.renderedDirGroups.push({ name: this.directoryGroups[++this.renderedIndex.group].name, @@ -156,7 +162,7 @@ export class DuplicateComponent implements OnDestroy { this.renderedDirGroups[this.renderedDirGroups.length - 1].duplicates.push( this.directoryGroups[this.renderedIndex.group].duplicates[ this.renderedIndex.pairs++ - ] + ] ); this.renderTimer = window.setTimeout(this.renderMore, 0); diff --git a/src/frontend/app/ui/faces/faces.component.ts b/src/frontend/app/ui/faces/faces.component.ts index e7a35cc6..5e86014a 100644 --- a/src/frontend/app/ui/faces/faces.component.ts +++ b/src/frontend/app/ui/faces/faces.component.ts @@ -4,6 +4,7 @@ import { QueryService } from '../../model/query.service'; import { map } from 'rxjs/operators'; import { PersonDTO } from '../../../../common/entities/PersonDTO'; import { Observable } from 'rxjs'; +import {PiTitleService} from '../../model/pi-title.service'; @Component({ selector: 'app-faces', @@ -18,7 +19,8 @@ export class FacesComponent implements OnInit { constructor( public facesService: FacesService, - public queryService: QueryService + public queryService: QueryService, + private piTitleService: PiTitleService ) { this.facesService.getPersons().catch(console.error); const personCmp = (p1: PersonDTO, p2: PersonDTO) => { @@ -33,6 +35,7 @@ export class FacesComponent implements OnInit { } ngOnInit(): void { + this.piTitleService.setTitle($localize`Faces`); this.updateSize(); } diff --git a/src/frontend/app/ui/gallery/MediaIcon.ts b/src/frontend/app/ui/gallery/MediaIcon.ts index f8aaf8ed..a99ea0d9 100644 --- a/src/frontend/app/ui/gallery/MediaIcon.ts +++ b/src/frontend/app/ui/gallery/MediaIcon.ts @@ -1,6 +1,6 @@ -import { Utils } from '../../../../common/Utils'; -import { Config } from '../../../../common/config/public/Config'; -import { MediaDTO } from '../../../../common/entities/MediaDTO'; +import {Utils} from '../../../../common/Utils'; +import {Config} from '../../../../common/config/public/Config'; +import {MediaDTO} from '../../../../common/entities/MediaDTO'; export class MediaIcon { protected static readonly ThumbnailMap = @@ -8,7 +8,8 @@ export class MediaIcon { protected replacementSizeCache: number | boolean = false; - constructor(public media: MediaDTO) {} + constructor(public media: MediaDTO) { + } getExtension(): string { return this.media.name.substr(this.media.name.lastIndexOf('.') + 1); @@ -28,14 +29,18 @@ export class MediaIcon { ); } + getReadableRelativePath(): string { + return Utils.concatUrls( + this.media.directory.path, + this.media.directory.name, + this.media.name + ); + } + getRelativePath(): string { return ( encodeURI( - Utils.concatUrls( - this.media.directory.path, - this.media.directory.name, - this.media.name - ) + this.getReadableRelativePath() ) // do not escape all urls with encodeURIComponent because that make the URL ugly and not needed // do not escape before concatUrls as that would make prevent optimizations diff --git a/src/frontend/app/ui/gallery/gallery.component.ts b/src/frontend/app/ui/gallery/gallery.component.ts index 3ef8f7fa..86ed588d 100644 --- a/src/frontend/app/ui/gallery/gallery.component.ts +++ b/src/frontend/app/ui/gallery/gallery.component.ts @@ -15,6 +15,7 @@ import {take} from 'rxjs/operators'; import {GallerySortingService} from './navigator/sorting.service'; import {MediaDTO} from '../../../../common/entities/MediaDTO'; import {FilterService} from './filter/filter.service'; +import {PiTitleService} from '../../model/pi-title.service'; @Component({ selector: 'app-gallery', @@ -57,7 +58,8 @@ export class GalleryComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private navigation: NavigationService, private filterService: FilterService, - private sortingService: GallerySortingService + private sortingService: GallerySortingService, + private piTitleService: PiTitleService ) { this.mapEnabled = Config.Map.enabled; PageHelper.showScrollY(); @@ -148,7 +150,7 @@ export class GalleryComponent implements OnInit, OnDestroy { const searchQuery = params[QueryParams.gallery.search.query]; if (searchQuery) { this.galleryService.search(searchQuery).catch(console.error); - + this.piTitleService.setSearchTitle(searchQuery); return; } @@ -171,6 +173,7 @@ export class GalleryComponent implements OnInit, OnDestroy { let directoryName = params[QueryParams.gallery.directory]; directoryName = directoryName || ''; + this.piTitleService.setDirectoryTitle(directoryName); this.galleryService.loadDirectory(directoryName); }; 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 d13c45ba..1423145d 100644 --- a/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts +++ b/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts @@ -12,22 +12,19 @@ import { ViewChild, ViewChildren, } from '@angular/core'; -import { GridRowBuilder } from './GridRowBuilder'; -import { GalleryLightboxComponent } from '../lightbox/lightbox.gallery.component'; -import { GridMedia } from './GridMedia'; -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 { ActivatedRoute, Params, Router } from '@angular/router'; -import { QueryService } from '../../../model/query.service'; -import { ContentService } from '../content.service'; -import { - MediaDTO, - MediaDTOUtils, -} from '../../../../../common/entities/MediaDTO'; -import { QueryParams } from '../../../../../common/QueryParams'; +import {GridRowBuilder} from './GridRowBuilder'; +import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component'; +import {GridMedia} from './GridMedia'; +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 {ActivatedRoute, Params, Router} from '@angular/router'; +import {QueryService} from '../../../model/query.service'; +import {ContentService} from '../content.service'; +import {MediaDTO, MediaDTOUtils,} from '../../../../../common/entities/MediaDTO'; +import {QueryParams} from '../../../../../common/QueryParams'; @Component({ selector: 'app-gallery-grid', @@ -35,9 +32,8 @@ import { QueryParams } from '../../../../../common/QueryParams'; styleUrls: ['./grid.gallery.component.css'], }) export class GalleryGridComponent - implements OnInit, OnChanges, AfterViewInit, OnDestroy -{ - @ViewChild('gridContainer', { static: false }) gridContainer: ElementRef; + implements OnInit, OnChanges, AfterViewInit, OnDestroy { + @ViewChild('gridContainer', {static: false}) gridContainer: ElementRef; @ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList; @Input() lightbox: GalleryLightboxComponent; @@ -68,7 +64,8 @@ export class GalleryGridComponent private router: Router, public galleryService: ContentService, private route: ActivatedRoute - ) {} + ) { + } ngOnChanges(): void { this.onChange(); @@ -235,7 +232,7 @@ export class GalleryGridComponent (p): boolean => this.queryService.getMediaStringId(p) === mediaStringId ); if (index === -1) { - this.router.navigate([], { queryParams: this.queryService.getParams() }); + this.router.navigate([], {queryParams: this.queryService.getParams()}); return; } // Make sure that at leas one more photo is rendered @@ -246,7 +243,8 @@ export class GalleryGridComponent this.renderedPhotoIndex - 1 < index + 1 && this.renderARow() !== null // eslint-disable-next-line no-empty - ) {} + ) { + } } private clearRenderedPhotos(): void { @@ -303,10 +301,10 @@ export class GalleryGridComponent return ( Config.Gallery.enableOnScrollRendering === false || PageHelper.ScrollY >= - document.body.clientHeight + - offset - - window.innerHeight - - bottomOffset || + document.body.clientHeight + + offset - + window.innerHeight - + bottomOffset || (document.body.clientHeight + offset) * 0.85 < window.innerHeight ); } @@ -329,7 +327,7 @@ export class GalleryGridComponent this.renderedPhotoIndex < this.media.length && (this.shouldRenderMore(renderedContentHeight) === true || this.renderedPhotoIndex < numberOfPhotos) - ) { + ) { const ret = this.renderARow(); if (ret === null) { throw new Error('Grid media rendering failed'); diff --git a/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts b/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts index b3df59de..db9ad824 100644 --- a/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts +++ b/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts @@ -16,6 +16,7 @@ import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; import {ControlsLightboxComponent} from './controls/controls.lightbox.gallery.component'; import {SupportedFormats} from '../../../../../common/SupportedFormats'; import {GridMedia} from '../grid/GridMedia'; +import {PiTitleService} from '../../../model/pi-title.service'; export enum LightboxStates { Open = 1, @@ -72,7 +73,8 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { private router: Router, private queryService: QueryService, private galleryService: ContentService, - private route: ActivatedRoute + private route: ActivatedRoute, + private piTitleService: PiTitleService ) { } @@ -239,12 +241,14 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { this.overlayService.showOverlay(); this.blackCanvasOpacity = 1.0; this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto), false); + this.piTitleService.setMediaTitle(selectedPhoto.gridMedia); } public hide(): void { this.router .navigate([], {queryParams: this.queryService.getParams()}) .catch(console.error); + this.piTitleService.setLastNonMedia(); } animatePhoto(from: Dimension, to: Dimension = from): AnimationPlayer { @@ -394,6 +398,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { ), }) .catch(console.error); + this.piTitleService.setMediaTitle(this.gridPhotoQL.get(photoIndex).gridMedia); } private showPhoto(photoIndex: number, resize = true): void {