1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

Implementing grouping selector button

This commit is contained in:
Patrik J. Braun 2023-08-29 22:14:38 +02:00
parent 3f35c35a13
commit 434e6aaacd
13 changed files with 214 additions and 95 deletions

View File

@ -1,7 +1,7 @@
import {ConfigTemplateEntry, DefaultsJobs,} from '../../../../common/entities/job/JobDTO';
import {Job} from './Job';
import {backendTexts} from '../../../../common/BackendTexts';
import {SortingMethods} from '../../../../common/entities/SortingMethods';
import {SortingByTypes} from '../../../../common/entities/SortingMethods';
import {DatePatternFrequency, DatePatternSearch, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
import {ObjectManagers} from '../../ObjectManagers';
import {PhotoEntity} from '../../database/enitites/PhotoEntity';
@ -30,7 +30,8 @@ export class TopPickSendJob extends Job<{
daysLength: 7,
frequency: DatePatternFrequency.every_year
} as DatePatternSearch,
sortBy: [SortingMethods.descRating, SortingMethods.descPersonCount],
sortBy: [{method: SortingByTypes.Rating, ascending: false},
{method: SortingByTypes.PersonCount, ascending: false}],
pick: 5
}] as MediaPickDTO[],
}, {

View File

@ -1,23 +1,23 @@
import {SortingByTypes, SortingMethod} from './entities/SortingMethods';
import {Utils} from './Utils';
/**
* This contains the action of the supported list of *.pg2conf files.
* These files are passed down to the client as metaFiles (like photos and directories)
*/
export const PG2ConfMap = {
sorting: {
'.order_descending_name.pg2conf': {method: SortingByTypes.Name, ascending: false} as SortingMethod,
'.order_ascending_name.pg2conf': {method: SortingByTypes.Name, ascending: true} as SortingMethod,
'.order_descending_date.pg2conf': {method: SortingByTypes.Date, ascending: false} as SortingMethod,
'.order_ascending_date.pg2conf': {method: SortingByTypes.Date, ascending: true} as SortingMethod,
'.order_descending_rating.pg2conf': {method: SortingByTypes.Rating, ascending: false} as SortingMethod,
'.order_ascending_rating.pg2conf': {method: SortingByTypes.Rating, ascending: true} as SortingMethod,
'.order_random.pg2conf': {method: SortingByTypes.Rating, ascending: null} as SortingMethod,
'.order_descending_person_count.pg2conf': {method: SortingByTypes.PersonCount, ascending: false} as SortingMethod,
'.order_ascending_person_count.pg2conf': {method: SortingByTypes.PersonCount, ascending: true} as SortingMethod,
},
export const PG2ConfMap: { sorting: { [key: string]: SortingMethod } } = {
sorting: {}
};
Utils.enumToArray(SortingByTypes).forEach(kv => {
if (kv.key === SortingByTypes.random) {
PG2ConfMap.sorting['.order_random.pg2conf'] = {method: kv.key, ascending: null} as SortingMethod;
return;
}
PG2ConfMap.sorting['.order_descending' + kv.value.toLowerCase() + '.pg2conf'] = {method: kv.key, ascending: false} as SortingMethod;
PG2ConfMap.sorting['.order_ascending' + kv.value.toLowerCase() + '.pg2conf'] = {method: kv.key, ascending: true} as SortingMethod;
});
/**
* These files are processed on the server side,
* do not get passed down to the client or saved to the DB

View File

@ -944,6 +944,26 @@ export class ClientGalleryConfig {
})
defaultSearchSortingMethod: ClientSortingConfig = new ClientSortingConfig(SortingByTypes.Date, false);
@ConfigProperty({
type: ClientSortingConfig,
tags: {
name: $localize`Default grouping`,
priority: ConfigPriority.advanced,
},
description: $localize`Default grouping method for photo and video in a directory results.`
})
defaultPhotoGroupingMethod: ClientSortingConfig = new ClientSortingConfig(SortingByTypes.Date, true);
@ConfigProperty({
type: ClientSortingConfig,
tags: {
name: $localize`Default search grouping`,
priority: ConfigPriority.advanced,
},
description: $localize`Default grouping method for photo and video in a search results.`
})
defaultSearchGroupingMethod: ClientSortingConfig = new ClientSortingConfig(SortingByTypes.Date, false);
@ConfigProperty({
tags: {
name: $localize`Sort directories by date`,

View File

@ -178,6 +178,7 @@ import {
import {SortingMethodIconComponent} from './ui/sorting-method-icon/sorting-method-icon.component';
import {SafeHtmlPipe} from './pipes/SafeHTMLPipe';
import {DatePipe} from '@angular/common';
import {ParseIntPipe} from './pipes/ParseIntPipe';
@Injectable()
export class MyHammerConfig extends HammerGestureConfig {
@ -191,7 +192,7 @@ export class MyHammerConfig extends HammerGestureConfig {
export class CustomUrlSerializer implements UrlSerializer {
private defaultUrlSerializer: DefaultUrlSerializer =
new DefaultUrlSerializer();
new DefaultUrlSerializer();
parse(url: string): UrlTree {
// Encode parentheses
@ -202,9 +203,9 @@ export class CustomUrlSerializer implements UrlSerializer {
serialize(tree: UrlTree): string {
return this.defaultUrlSerializer
.serialize(tree)
.replace(/%28/g, '(')
.replace(/%29/g, ')');
.serialize(tree)
.replace(/%28/g, '(')
.replace(/%29/g, ')');
}
}
@ -320,6 +321,7 @@ Marker.prototype.options.icon = MarkerFactory.defIcon;
StringifySearchType,
FileDTOToPathPipe,
PhotoFilterPipe,
ParseIntPipe,
UsersComponent,
SharingsListComponent,
SortingMethodIconComponent,

View File

@ -0,0 +1,10 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({name: 'parseInt'})
export class ParseIntPipe implements PipeTransform {
transform(num: string): number {
return parseInt(num);
}
}

View File

@ -1,5 +1,4 @@
import {Pipe, PipeTransform} from '@angular/core';
import {ConfigPriority} from '../../../common/config/public/ClientConfig';
import {EnumTranslations} from '../ui/EnumTranslations';
@Pipe({name: 'stringifyEnum'})

View File

@ -1,7 +1,15 @@
<div #gridContainer [style.width]="renderDelayTimer ? containerWidth+'px' : ''">
<ng-container *ngIf="mediaToRender?.length > 0">
<ng-container *ngFor="let group of mediaToRender">
<div *ngIf="group.name" class="mt-4 mb-3"><h6 class="ms-2">{{group.name}}</h6></div>
<ng-container *ngIf="group.name">
<ng-container [ngSwitch]="sortingService.grouping.value.method">
<div *ngSwitchCase="SortingByTypes.Rating" class="mt-4 mb-3"><h6 class="ms-2">
<ng-icon *ngFor="let i of [0,1,2,3,4]" [name]="(i < (group.name | parseInt)) ? 'ionStar' : 'ionStarOutline'"></ng-icon>
</h6></div>
<div *ngSwitchCase="SortingByTypes.PersonCount" class="mt-4 mb-3"><h6 class="ms-2">{{group.name}} <ng-icon class="ms-1" name="ionPeopleOutline"></ng-icon></h6></div>
<div *ngSwitchDefault class="mt-4 mb-3"><h6 class="ms-2">{{group.name}}</h6></div>
</ng-container>
</ng-container>
<div class="media-grid">
<app-gallery-grid-photo
*ngFor="let gridPhoto of group.media"

View File

@ -25,8 +25,8 @@ import {QueryService} from '../../../model/query.service';
import {ContentService} from '../content.service';
import {MediaDTO, MediaDTOUtils,} from '../../../../../common/entities/MediaDTO';
import {QueryParams} from '../../../../../common/QueryParams';
import {MediaGroup} from '../navigator/sorting.service';
import {SimpleChanges} from '../../../../../../node_modules/@angular/core';
import {GallerySortingService, MediaGroup} from '../navigator/sorting.service';
import {SortingByTypes} from '../../../../../common/entities/SortingMethods';
@Component({
selector: 'app-gallery-grid',
@ -58,6 +58,7 @@ export class GalleryGridComponent
private onScrollFired = false;
private helperTime: number = null;
public renderDelayTimer: number = null; // delays render on resize
public readonly SortingByTypes = SortingByTypes;
constructor(
private overlayService: OverlayService,
@ -65,13 +66,23 @@ export class GalleryGridComponent
public queryService: QueryService,
private router: Router,
public galleryService: ContentService,
public sortingService: GallerySortingService,
private route: ActivatedRoute
) {
}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
this.onChange();
ngOnChanges(): void {
if (this.isAfterViewInit === false) {
return;
}
this.updateContainerDimensions();
this.mergeNewPhotos();
this.helperTime = window.setTimeout((): void => {
this.renderPhotos();
if (this.delayedRenderUpToPhoto) {
this.renderUpToMedia(this.delayedRenderUpToPhoto);
}
}, 0);
}
ngOnInit(): void {
@ -92,20 +103,6 @@ export class GalleryGridComponent
);
}
onChange = () => {
if (this.isAfterViewInit === false) {
return;
}
this.updateContainerDimensions();
this.mergeNewPhotos();
this.helperTime = window.setTimeout((): void => {
this.renderPhotos();
if (this.delayedRenderUpToPhoto) {
this.renderUpToMedia(this.delayedRenderUpToPhoto);
}
}, 0);
};
ngOnDestroy(): void {
if (this.helperTime != null) {
clearTimeout(this.helperTime);
@ -170,9 +167,15 @@ export class GalleryGridComponent
}
// TODO: This is deprecated,
// we do not post update galleries anymore since the preview member in the DriectoryDTO
// Merging photos after new sorting and filter was applied
private mergeNewPhotos(): void {
if (this.mediaToRender.length === 0) {
return;
}
if (this.mediaGroups?.length === 0) {
this.clearRenderedPhotos();
return;
}
// merge new data with old one
const lastSameIndex = {groups: 0, media: 0};
let lastRowId = 0;
@ -195,7 +198,7 @@ export class GalleryGridComponent
// delete last row if the length of the two are not equal
if (!diffFound && this.mediaGroups[i].media.length < this.mediaToRender[i].media.length) {
lastRowId = this.mediaToRender[i].media[this.mediaToRender[i].media.length].rowId;
lastRowId = this.mediaToRender[i].media[this.mediaToRender[i].media.length - 1].rowId;
for (let j = this.mediaToRender[i].media.length - 2; j >= 0; --j) {
const gridMedia = this.mediaToRender[i].media[j];
if (gridMedia.rowId !== lastRowId) {
@ -211,9 +214,17 @@ export class GalleryGridComponent
this.clearRenderedPhotos();
return;
}
this.mediaToRender.splice(lastSameIndex.groups, this.mediaToRender.length - lastSameIndex.groups);
// only delete the whole group if all media is different
if (lastSameIndex.media === 0) {
this.mediaToRender.splice(lastSameIndex.groups, this.mediaToRender.length - lastSameIndex.groups);
return;
}
this.mediaToRender.splice(lastSameIndex.groups + 1, this.mediaToRender.length - lastSameIndex.groups);
const media = this.mediaToRender[lastSameIndex.groups].media;
media.splice(lastSameIndex.media, media.length - lastSameIndex.media);
}
}

View File

@ -53,3 +53,10 @@ app-gallery-filter {
--bs-text-opacity: 1;
color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;
}
.grouping-icon{
display: inline-block;
font-size: xx-small;
vertical-align: top;
line-height: 1;
}

View File

@ -29,7 +29,7 @@
<ng-container *ngIf="config.Gallery.enableDownloadZip && isDirectory && ItemCount > 0">
<a [href]="getDownloadZipLink()"
class="btn btn-outline-secondary btn-navigator">
<ng-icon name="ionDownloadOutline" title="download" i18n-title></ng-icon>
<ng-icon name="ionDownloadOutline" title="Download" i18n-title></ng-icon>
</a>
<div class="divider">&nbsp;</div>
</ng-container>
@ -53,23 +53,35 @@
</a>
<div class="divider">&nbsp;</div>
</ng-container>
<div class="btn-group" dropdown #dropdown="bs-dropdown" placement="bottom right">
<div class="btn-group" dropdown #dropdown="bs-dropdown" placement="bottom right"
[autoClose]="false"
title="Sort and group" i18n-title>
<button id="button-alignment" dropdownToggle type="button"
class="btn dropdown-toggle btn-outline-secondary btn-navigator"
[class.btn-secondary]="sortingService.sorting.value !== DefaultSorting"
[class.btn-outline-secondary]="sortingService.sorting.value == DefaultSorting"
[class.btn-secondary]="!isDefaultSortingAndGrouping()"
[class.btn-outline-secondary]="isDefaultSortingAndGrouping()"
aria-controls="sorting-dropdown">
<ng-icon
[name]="sortingService.sorting.value.ascending ? 'ionArrowDownOutline' : 'ionArrowUpOutline'"></ng-icon>
<ng-icon *ngIf="sortingService.sorting.value.ascending !== null"
[name]="!sortingService.sorting.value.ascending ? 'ionArrowDownOutline' : 'ionArrowUpOutline'"></ng-icon>
<app-sorting-method-icon [method]="sortingService.sorting.value.method"></app-sorting-method-icon>
<div class="grouping-icon" *ngIf="sortingService.grouping.value.method !== null">
<div>
<ng-icon
[name]="!sortingService.grouping.value.ascending ? 'ionArrowDownOutline' : 'ionArrowUpOutline'"></ng-icon>
<app-sorting-method-icon [method]="sortingService.grouping.value.method"></app-sorting-method-icon>
</div>
<div class="ps-1" i18n>
group
</div>
</div>
</button>
<div id="sorting-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right"
role="menu" aria-labelledby="button-alignment">
<div class="row flex-nowrap">
<div class="col-6 p-1 border-end">
<span class="ps-2">Sorting</span>
<div class="col p-1 border-end">
<h6 class="ps-2">Sorting</h6>
<div class="row">
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.sorting.value.method == type.key"
*ngFor="let type of sortingByTypes"
(click)="setSortingBy(type.key)">
@ -78,58 +90,69 @@
</div>
<div class="d-inline-block">{{type.key | stringifySorting}}</div>
</div>
<hr>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.sorting.value.ascending == true"
(click)="setSortingAscending(true)">
<div class="me-2 d-inline-block">
<ng-icon name="">ionArrowUpOutline</ng-icon>
<ng-container *ngIf="sortingService.sorting.value.method != SortingByTypes.random">
<hr>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.sorting.value.ascending == true"
(click)="setSortingAscending(true)">
<div class="me-2 d-inline-block">
<ng-icon name="ionArrowUpOutline"></ng-icon>
</div>
<div class="d-inline-block" i18n>ascending</div>
</div>
<div class="d-inline-block" i18n>ascending</div>
</div>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.sorting.value.ascending == false"
(click)="setSortingAscending(false)">
<div class="me-2 d-inline-block">
<ng-icon name="ionArrowDownOutline"></ng-icon>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.sorting.value.ascending == false"
(click)="setSortingAscending(false)">
<div class="me-2 d-inline-block">
<ng-icon name="ionArrowDownOutline"></ng-icon>
</div>
<div class="d-inline-block" i18n>descending</div>
</div>
<div class="d-inline-block" i18n>descending</div>
</div>
</ng-container>
</div>
</div>
<div class="col-6 p-1">
<span class="ps-2">Grouping</span>
<div class="col p-1">
<h6 class="ps-2">Grouping</h6>
<div class="row">
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.grouping.value.method == type.key"
*ngFor="let type of sortingByTypes"
*ngFor="let type of groupingByTypes"
(click)="setGroupingBy(type.key)">
<div class="me-2 d-inline-block">
<app-sorting-method-icon [method]="type.key"></app-sorting-method-icon>
</div>
<div class="d-inline-block">{{type.key | stringifySorting}}</div>
</div>
<hr>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.grouping.value.method == null"
(click)="setGroupingBy(null)">
<div class="me-2 d-inline-block">
<ng-icon name="ionCloseOutline"></ng-icon>
</div>
<div class="d-inline-block" i18n>don't group</div>
</div>
<ng-container *ngIf="sortingService.grouping.value.method !== null">
<hr>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.grouping.value.ascending == true"
(click)="setGroupingAscending(true)">
<div class="me-2 d-inline-block">
<ng-icon name="ionArrowUpOutline"></ng-icon>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.grouping.value.ascending == true"
(click)="setGroupingAscending(true)">
<div class="me-2 d-inline-block">
<ng-icon name="ionArrowUpOutline"></ng-icon>
</div>
<div class="d-inline-block" i18n>ascending</div>
</div>
<div class="d-inline-block" i18n>ascending</div>
</div>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.grouping.value.ascending == false"
(click)="setGroupingAscending(false)">
<div class="me-2 d-inline-block">
<ng-icon name="ionArrowDownOutline"></ng-icon>
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
[class.active]="sortingService.grouping.value.ascending == false"
(click)="setGroupingAscending(false)">
<div class="me-2 d-inline-block">
<ng-icon name="ionArrowDownOutline"></ng-icon>
</div>
<div class="d-inline-block" i18n>descending</div>
</div>
<div class="d-inline-block" i18n>descending</div>
</div>
</ng-container>
</div>
</div>
</div>

View File

@ -6,7 +6,7 @@ import {AuthenticationService} from '../../../model/network/authentication.servi
import {QueryService} from '../../../model/query.service';
import {ContentService, ContentWrapperWithError, DirectoryContent,} from '../content.service';
import {Utils} from '../../../../../common/Utils';
import {SortingByTypes, SortingMethod} from '../../../../../common/entities/SortingMethods';
import {SortingByTypes} from '../../../../../common/entities/SortingMethods';
import {Config} from '../../../../../common/config/public/Config';
import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes,} from '../../../../../common/entities/SearchQueryDTO';
import {Observable} from 'rxjs';
@ -23,8 +23,9 @@ import {FilterService} from '../filter/filter.service';
providers: [RouterLink],
})
export class GalleryNavigatorComponent {
public SortingByTypes = SortingByTypes;
public sortingByTypes: { key: number; value: string }[] = [];
public readonly SortingByTypes = SortingByTypes;
public readonly sortingByTypes: { key: number; value: string }[] = [];
public readonly groupingByTypes: { key: number; value: string }[] = [];
public readonly config = Config;
// DefaultSorting = Config.Gallery.defaultPhotoSortingMethod;
public readonly SearchQueryTypes = SearchQueryTypes;
@ -53,6 +54,8 @@ export class GalleryNavigatorComponent {
public sanitizer: DomSanitizer
) {
this.sortingByTypes = Utils.enumToArray(SortingByTypes);
// can't group by random
this.groupingByTypes = this.sortingByTypes.filter(s => s.key !== SortingByTypes.random);
this.RootFolderName = $localize`Home`;
this.wrappedContent = this.galleryService.content;
this.directoryContent = this.wrappedContent.pipe(
@ -137,22 +140,40 @@ export class GalleryNavigatorComponent {
: 0;
}
get DefaultSorting(): SortingMethod {
return this.sortingService.getDefaultSorting(
isDefaultSortingAndGrouping(): boolean {
return this.sortingService.isDefaultSortingAndGrouping(
this.galleryService.content.value
);
}
setSortingBy(sorting: SortingByTypes): void {
const s = {method: sorting, ascending: this.sortingService.sorting.value.ascending};
// random does not have a direction
if (sorting === SortingByTypes.random) {
s.ascending = null;
} else if (s.ascending === null) {
s.ascending = true;
}
this.sortingService.setSorting(s);
this.sortingService.setGrouping(s);
// you cannot group by random
if (sorting === SortingByTypes.random) {
return;
}
// if grouping is disabled, do not update it
if (this.sortingService.grouping.value.method !== null) {
this.sortingService.setGrouping(s);
}
}
setSortingAscending(asc: boolean) {
const s = {method: this.sortingService.sorting.value.method, ascending: asc};
this.sortingService.setSorting(s);
this.sortingService.setGrouping(s);
// if grouping is disabled, do not update it
if (this.sortingService.grouping.value.method !== null) {
this.sortingService.setGrouping(s);
}
}
setGroupingBy(grouping: SortingByTypes): void {

View File

@ -46,6 +46,15 @@ export class GallerySortingService {
});
}
isDefaultSortingAndGrouping(cw: ContentWrapper): boolean {
const defS = this.getDefaultSorting(cw);
const defG = this.getDefaultGrouping(cw);
const s = this.sorting.value;
const g = this.grouping.value;
return s.method === defS.method && s.ascending === defS.ascending &&
g.method === defG.method && g.ascending === defG.ascending;
}
getDefaultSorting(cw: ContentWrapper): SortingMethod {
if (cw.directory && cw.directory.metaFile) {
for (const file in PG2ConfMap.sorting) {
@ -60,6 +69,14 @@ export class GallerySortingService {
return Config.Gallery.defaultPhotoSortingMethod;
}
getDefaultGrouping(cw: ContentWrapper): SortingMethod {
if (cw.searchResult) {
return Config.Gallery.defaultSearchGroupingMethod;
}
return Config.Gallery.defaultPhotoGroupingMethod;
}
setSorting(sorting: SortingMethod): void {
this.sorting.next(sorting);
if (this.galleryService.content.value) {
@ -204,7 +221,7 @@ export class GallerySortingService {
groupFN = (m: MediaDTO) => this.datePipe.transform(m.metadata.creationDate, 'longDate');
break;
case SortingByTypes.Name:
groupFN = (m: MediaDTO) => m.name.at(0).toLowerCase();
groupFN = (m: MediaDTO) => m.name.at(0).toUpperCase();
break;
case SortingByTypes.Rating:
groupFN = (m: MediaDTO) => ((m as PhotoDTO).metadata.rating || 0).toString();

View File

@ -1,6 +1,6 @@
<ng-container [ngSwitch]="method">
<ng-container *ngSwitchCase="SortingByTypes.Name">
<span>A</span>
<ng-icon name="ionTextOutline"></ng-icon>
</ng-container>
<ng-container *ngSwitchCase="SortingByTypes.Rating">
<ng-icon name="ionStarOutline"></ng-icon>