1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-10 04:07:35 +02:00

Improving page title for easier understandable browser history #587

This commit is contained in:
Patrik J. Braun 2023-02-26 19:49:24 +01:00
parent 02e1fd3ace
commit 6a165af52f
9 changed files with 144 additions and 76 deletions

View File

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

View File

@ -11,6 +11,7 @@ import {WebConfig} from '../../../../common/config/private/WebConfig';
import {ISettingsComponent} from '../settings/template/ISettingsComponent'; import {ISettingsComponent} from '../settings/template/ISettingsComponent';
import {WebConfigClassBuilder} from '../../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder'; import {WebConfigClassBuilder} from '../../../../../node_modules/typeconfig/src/decorators/builders/WebConfigClassBuilder';
import {enumToTranslatedArray} from '../EnumTranslations'; import {enumToTranslatedArray} from '../EnumTranslations';
import {PiTitleService} from '../../model/pi-title.service';
@Component({ @Component({
selector: 'app-admin', selector: 'app-admin',
@ -32,6 +33,7 @@ export class AdminComponent implements OnInit, AfterViewInit {
public viewportScroller: ViewportScroller, public viewportScroller: ViewportScroller,
public notificationService: NotificationService, public notificationService: NotificationService,
public settingsService: SettingsService, public settingsService: SettingsService,
private piTitleService: PiTitleService
) { ) {
this.configPriorities = enumToTranslatedArray(ConfigPriority); this.configPriorities = enumToTranslatedArray(ConfigPriority);
const wc = WebConfigClassBuilder.attachPrivateInterface(new WebConfig()); const wc = WebConfigClassBuilder.attachPrivateInterface(new WebConfig());
@ -51,6 +53,7 @@ export class AdminComponent implements OnInit, AfterViewInit {
this.navigation.toLogin(); this.navigation.toLogin();
return; return;
} }
this.piTitleService.setTitle($localize`Admin`);
} }
public getCss(type: NotificationType): string { public getCss(type: NotificationType): string {

View File

@ -1,19 +1,11 @@
import { import {Component, ElementRef, OnInit, TemplateRef, ViewChild,} from '@angular/core';
Component, import {AlbumsService} from './albums.service';
ElementRef, import {BsModalService} from 'ngx-bootstrap/modal';
OnInit, import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service';
TemplateRef, import {SearchQueryTypes, TextSearch,} from '../../../../common/entities/SearchQueryDTO';
ViewChild, import {UserRoles} from '../../../../common/entities/UserDTO';
} from '@angular/core'; import {AuthenticationService} from '../../model/network/authentication.service';
import { AlbumsService } from './albums.service'; import {PiTitleService} from '../../model/pi-title.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';
@Component({ @Component({
selector: 'app-albums', selector: 'app-albums',
@ -21,23 +13,25 @@ import { AuthenticationService } from '../../model/network/authentication.servic
styleUrls: ['./albums.component.css'], styleUrls: ['./albums.component.css'],
}) })
export class AlbumsComponent implements OnInit { export class AlbumsComponent implements OnInit {
@ViewChild('container', { static: true }) container: ElementRef; @ViewChild('container', {static: true}) container: ElementRef;
public size: number; public size: number;
public savedSearch = { public savedSearch = {
name: '', name: '',
searchQuery: { type: SearchQueryTypes.any_text, text: '' } as TextSearch, searchQuery: {type: SearchQueryTypes.any_text, text: ''} as TextSearch,
}; };
private modalRef: BsModalRef; private modalRef: BsModalRef;
constructor( constructor(
public albumsService: AlbumsService, public albumsService: AlbumsService,
private modalService: BsModalService, private modalService: BsModalService,
public authenticationService: AuthenticationService public authenticationService: AuthenticationService,
private piTitleService: PiTitleService
) { ) {
this.albumsService.getAlbums().catch(console.error); this.albumsService.getAlbums().catch(console.error);
} }
ngOnInit(): void { ngOnInit(): void {
this.piTitleService.setTitle($localize`Albums`);
this.updateSize(); this.updateSize();
} }
@ -46,7 +40,7 @@ export class AlbumsComponent implements OnInit {
} }
public async openModal(template: TemplateRef<any>): Promise<void> { public async openModal(template: TemplateRef<any>): Promise<void> {
this.modalRef = this.modalService.show(template, { class: 'modal-lg' }); this.modalRef = this.modalService.show(template, {class: 'modal-lg'});
document.body.style.paddingRight = '0px'; document.body.style.paddingRight = '0px';
} }

View File

@ -1,13 +1,14 @@
import { Component, HostListener, OnDestroy } from '@angular/core'; import {Component, HostListener, OnDestroy, OnInit} from '@angular/core';
import { DuplicateService } from './duplicates.service'; import {DuplicateService} from './duplicates.service';
import { Utils } from '../../../../common/Utils'; import {Utils} from '../../../../common/Utils';
import { QueryService } from '../../model/query.service'; import {QueryService} from '../../model/query.service';
import { DuplicatesDTO } from '../../../../common/entities/DuplicatesDTO'; import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
import { DirectoryPathDTO } from '../../../../common/entities/DirectoryDTO'; import {DirectoryPathDTO} from '../../../../common/entities/DirectoryDTO';
import { Subscription } from 'rxjs'; import {Subscription} from 'rxjs';
import { Config } from '../../../../common/config/public/Config'; import {Config} from '../../../../common/config/public/Config';
import { PageHelper } from '../../model/page.helper'; import {PageHelper} from '../../model/page.helper';
import { MediaDTO } from '../../../../common/entities/MediaDTO'; import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {PiTitleService} from '../../model/pi-title.service';
interface GroupedDuplicate { interface GroupedDuplicate {
name: string; name: string;
@ -19,7 +20,7 @@ interface GroupedDuplicate {
templateUrl: './duplicates.component.html', templateUrl: './duplicates.component.html',
styleUrls: ['./duplicates.component.css'], styleUrls: ['./duplicates.component.css'],
}) })
export class DuplicateComponent implements OnDestroy { export class DuplicateComponent implements OnDestroy, OnInit {
directoryGroups: GroupedDuplicate[] = null; directoryGroups: GroupedDuplicate[] = null;
renderedDirGroups: GroupedDuplicate[] = null; renderedDirGroups: GroupedDuplicate[] = null;
renderedIndex = { renderedIndex = {
@ -35,13 +36,14 @@ export class DuplicateComponent implements OnDestroy {
constructor( constructor(
public duplicateService: DuplicateService, public duplicateService: DuplicateService,
public queryService: QueryService public queryService: QueryService,
private piTitleService: PiTitleService
) { ) {
this.duplicateService.getDuplicates().catch(console.error); this.duplicateService.getDuplicates().catch(console.error);
this.subscription = this.duplicateService.duplicates.subscribe( this.subscription = this.duplicateService.duplicates.subscribe(
(duplicates: DuplicatesDTO[]): void => { (duplicates: DuplicatesDTO[]): void => {
this.directoryGroups = []; this.directoryGroups = [];
this.renderedIndex = { group: -1, pairs: 0 }; this.renderedIndex = {group: -1, pairs: 0};
this.renderedDirGroups = []; this.renderedDirGroups = [];
this.duplicateCount = { this.duplicateCount = {
pairs: 0, pairs: 0,
@ -113,6 +115,10 @@ export class DuplicateComponent implements OnDestroy {
); );
} }
ngOnInit(): void {
this.piTitleService.setTitle($localize`Duplicates`);
}
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.subscription) { if (this.subscription) {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
@ -137,7 +143,7 @@ export class DuplicateComponent implements OnDestroy {
if ( if (
this.renderedIndex.group === this.directoryGroups.length - 1 && this.renderedIndex.group === this.directoryGroups.length - 1 &&
this.renderedIndex.pairs >= this.renderedIndex.pairs >=
this.directoryGroups[this.renderedIndex.group].duplicates.length this.directoryGroups[this.renderedIndex.group].duplicates.length
) { ) {
return; return;
} }
@ -145,7 +151,7 @@ export class DuplicateComponent implements OnDestroy {
if ( if (
this.renderedDirGroups.length === 0 || this.renderedDirGroups.length === 0 ||
this.renderedIndex.pairs >= this.renderedIndex.pairs >=
this.directoryGroups[this.renderedIndex.group].duplicates.length this.directoryGroups[this.renderedIndex.group].duplicates.length
) { ) {
this.renderedDirGroups.push({ this.renderedDirGroups.push({
name: this.directoryGroups[++this.renderedIndex.group].name, 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.renderedDirGroups[this.renderedDirGroups.length - 1].duplicates.push(
this.directoryGroups[this.renderedIndex.group].duplicates[ this.directoryGroups[this.renderedIndex.group].duplicates[
this.renderedIndex.pairs++ this.renderedIndex.pairs++
] ]
); );
this.renderTimer = window.setTimeout(this.renderMore, 0); this.renderTimer = window.setTimeout(this.renderMore, 0);

View File

@ -4,6 +4,7 @@ import { QueryService } from '../../model/query.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { PersonDTO } from '../../../../common/entities/PersonDTO'; import { PersonDTO } from '../../../../common/entities/PersonDTO';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import {PiTitleService} from '../../model/pi-title.service';
@Component({ @Component({
selector: 'app-faces', selector: 'app-faces',
@ -18,7 +19,8 @@ export class FacesComponent implements OnInit {
constructor( constructor(
public facesService: FacesService, public facesService: FacesService,
public queryService: QueryService public queryService: QueryService,
private piTitleService: PiTitleService
) { ) {
this.facesService.getPersons().catch(console.error); this.facesService.getPersons().catch(console.error);
const personCmp = (p1: PersonDTO, p2: PersonDTO) => { const personCmp = (p1: PersonDTO, p2: PersonDTO) => {
@ -33,6 +35,7 @@ export class FacesComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.piTitleService.setTitle($localize`Faces`);
this.updateSize(); this.updateSize();
} }

View File

@ -1,6 +1,6 @@
import { Utils } from '../../../../common/Utils'; import {Utils} from '../../../../common/Utils';
import { Config } from '../../../../common/config/public/Config'; import {Config} from '../../../../common/config/public/Config';
import { MediaDTO } from '../../../../common/entities/MediaDTO'; import {MediaDTO} from '../../../../common/entities/MediaDTO';
export class MediaIcon { export class MediaIcon {
protected static readonly ThumbnailMap = protected static readonly ThumbnailMap =
@ -8,7 +8,8 @@ export class MediaIcon {
protected replacementSizeCache: number | boolean = false; protected replacementSizeCache: number | boolean = false;
constructor(public media: MediaDTO) {} constructor(public media: MediaDTO) {
}
getExtension(): string { getExtension(): string {
return this.media.name.substr(this.media.name.lastIndexOf('.') + 1); 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 { getRelativePath(): string {
return ( return (
encodeURI( encodeURI(
Utils.concatUrls( this.getReadableRelativePath()
this.media.directory.path,
this.media.directory.name,
this.media.name
)
) )
// do not escape all urls with encodeURIComponent because that make the URL ugly and not needed // 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 // do not escape before concatUrls as that would make prevent optimizations

View File

@ -15,6 +15,7 @@ import {take} from 'rxjs/operators';
import {GallerySortingService} from './navigator/sorting.service'; import {GallerySortingService} from './navigator/sorting.service';
import {MediaDTO} from '../../../../common/entities/MediaDTO'; import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {FilterService} from './filter/filter.service'; import {FilterService} from './filter/filter.service';
import {PiTitleService} from '../../model/pi-title.service';
@Component({ @Component({
selector: 'app-gallery', selector: 'app-gallery',
@ -57,7 +58,8 @@ export class GalleryComponent implements OnInit, OnDestroy {
private route: ActivatedRoute, private route: ActivatedRoute,
private navigation: NavigationService, private navigation: NavigationService,
private filterService: FilterService, private filterService: FilterService,
private sortingService: GallerySortingService private sortingService: GallerySortingService,
private piTitleService: PiTitleService
) { ) {
this.mapEnabled = Config.Map.enabled; this.mapEnabled = Config.Map.enabled;
PageHelper.showScrollY(); PageHelper.showScrollY();
@ -148,7 +150,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
const searchQuery = params[QueryParams.gallery.search.query]; const searchQuery = params[QueryParams.gallery.search.query];
if (searchQuery) { if (searchQuery) {
this.galleryService.search(searchQuery).catch(console.error); this.galleryService.search(searchQuery).catch(console.error);
this.piTitleService.setSearchTitle(searchQuery);
return; return;
} }
@ -171,6 +173,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
let directoryName = params[QueryParams.gallery.directory]; let directoryName = params[QueryParams.gallery.directory];
directoryName = directoryName || ''; directoryName = directoryName || '';
this.piTitleService.setDirectoryTitle(directoryName);
this.galleryService.loadDirectory(directoryName); this.galleryService.loadDirectory(directoryName);
}; };

View File

@ -12,22 +12,19 @@ import {
ViewChild, ViewChild,
ViewChildren, ViewChildren,
} from '@angular/core'; } from '@angular/core';
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';
import { GalleryPhotoComponent } from './photo/photo.grid.gallery.component'; import {GalleryPhotoComponent} from './photo/photo.grid.gallery.component';
import { OverlayService } from '../overlay.service'; import {OverlayService} from '../overlay.service';
import { Config } from '../../../../../common/config/public/Config'; import {Config} from '../../../../../common/config/public/Config';
import { PageHelper } from '../../../model/page.helper'; 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 { ContentService } from '../content.service'; import {ContentService} from '../content.service';
import { import {MediaDTO, MediaDTOUtils,} from '../../../../../common/entities/MediaDTO';
MediaDTO, import {QueryParams} from '../../../../../common/QueryParams';
MediaDTOUtils,
} from '../../../../../common/entities/MediaDTO';
import { QueryParams } from '../../../../../common/QueryParams';
@Component({ @Component({
selector: 'app-gallery-grid', selector: 'app-gallery-grid',
@ -35,9 +32,8 @@ import { QueryParams } from '../../../../../common/QueryParams';
styleUrls: ['./grid.gallery.component.css'], styleUrls: ['./grid.gallery.component.css'],
}) })
export class GalleryGridComponent export class GalleryGridComponent
implements OnInit, OnChanges, AfterViewInit, OnDestroy implements OnInit, OnChanges, AfterViewInit, OnDestroy {
{ @ViewChild('gridContainer', {static: false}) gridContainer: ElementRef;
@ViewChild('gridContainer', { static: false }) gridContainer: ElementRef;
@ViewChildren(GalleryPhotoComponent) @ViewChildren(GalleryPhotoComponent)
gridPhotoQL: QueryList<GalleryPhotoComponent>; gridPhotoQL: QueryList<GalleryPhotoComponent>;
@Input() lightbox: GalleryLightboxComponent; @Input() lightbox: GalleryLightboxComponent;
@ -68,7 +64,8 @@ export class GalleryGridComponent
private router: Router, private router: Router,
public galleryService: ContentService, public galleryService: ContentService,
private route: ActivatedRoute private route: ActivatedRoute
) {} ) {
}
ngOnChanges(): void { ngOnChanges(): void {
this.onChange(); this.onChange();
@ -235,7 +232,7 @@ export class GalleryGridComponent
(p): boolean => this.queryService.getMediaStringId(p) === mediaStringId (p): boolean => this.queryService.getMediaStringId(p) === mediaStringId
); );
if (index === -1) { if (index === -1) {
this.router.navigate([], { queryParams: this.queryService.getParams() }); this.router.navigate([], {queryParams: this.queryService.getParams()});
return; return;
} }
// Make sure that at leas one more photo is rendered // Make sure that at leas one more photo is rendered
@ -246,7 +243,8 @@ export class GalleryGridComponent
this.renderedPhotoIndex - 1 < index + 1 && this.renderedPhotoIndex - 1 < index + 1 &&
this.renderARow() !== null this.renderARow() !== null
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
) {} ) {
}
} }
private clearRenderedPhotos(): void { private clearRenderedPhotos(): void {
@ -303,10 +301,10 @@ export class GalleryGridComponent
return ( return (
Config.Gallery.enableOnScrollRendering === false || Config.Gallery.enableOnScrollRendering === false ||
PageHelper.ScrollY >= PageHelper.ScrollY >=
document.body.clientHeight + document.body.clientHeight +
offset - offset -
window.innerHeight - window.innerHeight -
bottomOffset || bottomOffset ||
(document.body.clientHeight + offset) * 0.85 < window.innerHeight (document.body.clientHeight + offset) * 0.85 < window.innerHeight
); );
} }
@ -329,7 +327,7 @@ export class GalleryGridComponent
this.renderedPhotoIndex < this.media.length && this.renderedPhotoIndex < this.media.length &&
(this.shouldRenderMore(renderedContentHeight) === true || (this.shouldRenderMore(renderedContentHeight) === true ||
this.renderedPhotoIndex < numberOfPhotos) this.renderedPhotoIndex < numberOfPhotos)
) { ) {
const ret = this.renderARow(); const ret = this.renderARow();
if (ret === null) { if (ret === null) {
throw new Error('Grid media rendering failed'); throw new Error('Grid media rendering failed');

View File

@ -16,6 +16,7 @@ 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';
import {GridMedia} from '../grid/GridMedia'; import {GridMedia} from '../grid/GridMedia';
import {PiTitleService} from '../../../model/pi-title.service';
export enum LightboxStates { export enum LightboxStates {
Open = 1, Open = 1,
@ -72,7 +73,8 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
private router: Router, private router: Router,
private queryService: QueryService, private queryService: QueryService,
private galleryService: ContentService, 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.overlayService.showOverlay();
this.blackCanvasOpacity = 1.0; this.blackCanvasOpacity = 1.0;
this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto), false); this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto), false);
this.piTitleService.setMediaTitle(selectedPhoto.gridMedia);
} }
public hide(): void { public hide(): void {
this.router this.router
.navigate([], {queryParams: this.queryService.getParams()}) .navigate([], {queryParams: this.queryService.getParams()})
.catch(console.error); .catch(console.error);
this.piTitleService.setLastNonMedia();
} }
animatePhoto(from: Dimension, to: Dimension = from): AnimationPlayer { animatePhoto(from: Dimension, to: Dimension = from): AnimationPlayer {
@ -394,6 +398,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
), ),
}) })
.catch(console.error); .catch(console.error);
this.piTitleService.setMediaTitle(this.gridPhotoQL.get(photoIndex).gridMedia);
} }
private showPhoto(photoIndex: number, resize = true): void { private showPhoto(photoIndex: number, resize = true): void {