adding randomized sorting

This commit is contained in:
Patrik J. Braun 2019-01-27 17:27:41 -05:00
parent 38a99b1db2
commit a613373537
8 changed files with 153 additions and 96 deletions

View File

@ -1,3 +1,3 @@
export enum SortingMethods {
ascName = 1, descName = 2, ascDate = 3, descDate = 4
ascName = 1, descName = 2, ascDate = 3, descDate = 4, random = 5
}

View File

@ -73,6 +73,7 @@ import {FileSizePipe} from './pipes/FileSizePipe';
import {DuplicateService} from './duplicates/duplicates.service';
import {DuplicateComponent} from './duplicates/duplicates.component';
import {DuplicatesPhotoComponent} from './duplicates/photo/photo.duplicates.component';
import {SeededRandomService} from './model/seededRandom.service';
@Injectable()
@ -189,6 +190,7 @@ export function translationsFactory(locale: string) {
FullScreenService,
NavigationService,
SettingsService,
SeededRandomService,
OverlayService,
QueryService,
DuplicateService,

View File

@ -10,12 +10,13 @@ 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, Subscription, Observable} from 'rxjs';
import {interval, Observable, Subscription} from 'rxjs';
import {ContentWrapper} from '../../../common/entities/ConentWrapper';
import {PageHelper} from '../model/page.helper';
import {SortingMethods} from '../../../common/entities/SortingMethods';
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {QueryParams} from '../../../common/QueryParams';
import {SeededRandomService} from '../model/seededRandom.service';
@Component({
selector: 'app-gallery',
@ -32,6 +33,9 @@ export class GalleryComponent implements OnInit, OnDestroy {
public directories: DirectoryDTO[] = [];
public isPhotoWithLocation = false;
public countDown: { day: number, hour: number, minute: number, second: number } = null;
public mapEnabled = true;
readonly SearchTypes: typeof SearchTypes;
private $counter: Observable<number>;
private subscription: { [key: string]: Subscription } = {
content: null,
@ -39,16 +43,14 @@ export class GalleryComponent implements OnInit, OnDestroy {
timer: null,
sorting: null
};
public countDown: { day: number, hour: number, minute: number, second: number } = null;
public mapEnabled = true;
readonly SearchTypes: typeof SearchTypes;
constructor(public _galleryService: GalleryService,
private _authService: AuthenticationService,
private _router: Router,
private shareService: ShareService,
private _route: ActivatedRoute,
private _navigation: NavigationService) {
private _navigation: NavigationService,
private rndService: SeededRandomService) {
this.mapEnabled = Config.Client.Map.enabled;
this.SearchTypes = SearchTypes;
@ -70,6 +72,46 @@ export class GalleryComponent implements OnInit, OnDestroy {
this.countDown.second = t % 60;
}
ngOnDestroy() {
if (this.subscription.content !== null) {
this.subscription.content.unsubscribe();
}
if (this.subscription.route !== null) {
this.subscription.route.unsubscribe();
}
if (this.subscription.timer !== null) {
this.subscription.timer.unsubscribe();
}
if (this.subscription.sorting !== null) {
this.subscription.sorting.unsubscribe();
}
}
async ngOnInit() {
await this.shareService.wait();
if (!this._authService.isAuthenticated() &&
(!this.shareService.isSharing() ||
(this.shareService.isSharing() && Config.Client.Sharing.passwordProtected === true))) {
return this._navigation.toLogin();
}
this.showSearchBar = Config.Client.Search.enabled && this._authService.isAuthorized(UserRoles.Guest);
this.showShare = Config.Client.Sharing.enabled && this._authService.isAuthorized(UserRoles.User);
this.showRandomPhotoBuilder = Config.Client.RandomPhoto.enabled && this._authService.isAuthorized(UserRoles.Guest);
this.subscription.content = this._galleryService.content.subscribe(this.onContentChange);
this.subscription.route = this._route.params.subscribe(this.onRoute);
if (this.shareService.isSharing()) {
this.$counter = interval(1000);
this.subscription.timer = this.$counter.subscribe((x) => this.updateTimer(x));
}
this.subscription.sorting = this._galleryService.sorting.subscribe(() => {
this.sortDirectories();
});
}
private onRoute = async (params: Params) => {
const searchText = params[QueryParams.gallery.searchText];
if (searchText && searchText !== '') {
@ -100,21 +142,6 @@ export class GalleryComponent implements OnInit, OnDestroy {
};
ngOnDestroy() {
if (this.subscription.content !== null) {
this.subscription.content.unsubscribe();
}
if (this.subscription.route !== null) {
this.subscription.route.unsubscribe();
}
if (this.subscription.timer !== null) {
this.subscription.timer.unsubscribe();
}
if (this.subscription.sorting !== null) {
this.subscription.sorting.unsubscribe();
}
}
private onContentChange = (content: ContentWrapper) => {
const ascdirSorter = (a: DirectoryDTO, b: DirectoryDTO) => {
return a.name.localeCompare(b.name);
@ -169,34 +196,24 @@ export class GalleryComponent implements OnInit, OnDestroy {
return 0;
});
break;
case SortingMethods.random:
this.rndService.setSeed(this.directories.length);
this.directories.sort((a: DirectoryDTO, b: DirectoryDTO) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return 1;
}
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return -1;
}
return 0;
}).sort(() => {
return this.rndService.get() - 0.5;
});
break;
}
}
async ngOnInit() {
await this.shareService.wait();
if (!this._authService.isAuthenticated() &&
(!this.shareService.isSharing() ||
(this.shareService.isSharing() && Config.Client.Sharing.passwordProtected === true))) {
return this._navigation.toLogin();
}
this.showSearchBar = Config.Client.Search.enabled && this._authService.isAuthorized(UserRoles.Guest);
this.showShare = Config.Client.Sharing.enabled && this._authService.isAuthorized(UserRoles.User);
this.showRandomPhotoBuilder = Config.Client.RandomPhoto.enabled && this._authService.isAuthorized(UserRoles.Guest);
this.subscription.content = this._galleryService.content.subscribe(this.onContentChange);
this.subscription.route = this._route.params.subscribe(this.onRoute);
if (this.shareService.isSharing()) {
this.$counter = interval(1000);
this.subscription.timer = this.$counter.subscribe((x) => this.updateTimer(x));
}
this.subscription.sorting = this._galleryService.sorting.subscribe(() => {
this.sortDirectories();
});
}
}

View File

@ -7,10 +7,10 @@ import {
Input,
OnChanges,
OnDestroy,
OnInit,
QueryList,
ViewChild,
ViewChildren,
OnInit
ViewChildren
} from '@angular/core';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {GridRowBuilder} from './GridRowBuilder';
@ -27,7 +27,7 @@ import {GalleryService} from '../gallery.service';
import {SortingMethods} from '../../../../common/entities/SortingMethods';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {QueryParams} from '../../../../common/QueryParams';
import {Media} from '../Media';
import {SeededRandomService} from '../../model/seededRandom.service';
@Component({
selector: 'app-gallery-grid',
@ -38,24 +38,13 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
@ViewChild('gridContainer') gridContainer: ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
private scrollListenerPhotos: GalleryPhotoComponent[] = [];
@Input() media: MediaDTO[];
@Input() lightbox: GalleryLightboxComponent;
photosToRender: Array<GridMedia> = [];
containerWidth = 0;
screenHeight = 0;
public IMAGE_MARGIN = 2;
private TARGET_COL_COUNT = 5;
private MIN_ROW_COUNT = 2;
private MAX_ROW_COUNT = 5;
private onScrollFired = false;
private helperTime: number = null;
isAfterViewInit = false;
private renderedPhotoIndex = 0;
subscriptions: {
route: Subscription,
sorting: Subscription
@ -64,13 +53,21 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
sorting: null
};
delayedRenderUpToPhoto: string = null;
private scrollListenerPhotos: GalleryPhotoComponent[] = [];
private TARGET_COL_COUNT = 5;
private MIN_ROW_COUNT = 2;
private MAX_ROW_COUNT = 5;
private onScrollFired = false;
private helperTime: number = null;
private renderedPhotoIndex = 0;
constructor(private overlayService: OverlayService,
private changeDetector: ChangeDetectorRef,
public queryService: QueryService,
private router: Router,
public galleryService: GalleryService,
private route: ActivatedRoute) {
private route: ActivatedRoute,
private rndService: SeededRandomService) {
}
ngOnInit() {
@ -159,17 +156,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
this.isAfterViewInit = true;
}
private renderUpToMedia(mediaStringId: string) {
const index = this.media.findIndex(p => this.queryService.getMediaStringId(p) === mediaStringId);
if (index === -1) {
this.router.navigate([], {queryParams: this.queryService.getParams()});
return;
}
while (this.renderedPhotoIndex < index && this.renderARow()) {
}
}
public renderARow(): number {
if (this.renderedPhotoIndex >= this.media.length
|| this.containerWidth === 0) {
@ -204,6 +190,37 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
return rowHeight;
}
@HostListener('window:scroll')
onScroll() {
if (!this.onScrollFired &&
// should we trigger this at all?
(this.renderedPhotoIndex < this.media.length || this.scrollListenerPhotos.length > 0)) {
window.requestAnimationFrame(() => {
this.renderPhotos();
if (Config.Client.Other.enableOnScrollThumbnailPrioritising === true) {
this.scrollListenerPhotos.forEach((pc: GalleryPhotoComponent) => {
pc.onScroll();
});
this.scrollListenerPhotos = this.scrollListenerPhotos.filter(pc => pc.ScrollListener);
}
this.onScrollFired = false;
});
this.onScrollFired = true;
}
}
private renderUpToMedia(mediaStringId: string) {
const index = this.media.findIndex(p => this.queryService.getMediaStringId(p) === mediaStringId);
if (index === -1) {
this.router.navigate([], {queryParams: this.queryService.getParams()});
return;
}
while (this.renderedPhotoIndex < index && this.renderARow()) {
}
}
private clearRenderedPhotos() {
this.photosToRender = [];
this.renderedPhotoIndex = 0;
@ -244,6 +261,20 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
return b.metadata.creationDate - a.metadata.creationDate;
});
break;
case SortingMethods.random:
this.rndService.setSeed(this.media.length);
this.media.sort((a: PhotoDTO, b: PhotoDTO) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
}
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return 1;
}
return 0;
}).sort(() => {
return this.rndService.get() - 0.5;
});
break;
}
@ -281,7 +312,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
}
/**
* Returns true, if scroll is >= 70% to render more images.
* Or of onscroll rendering is off: return always to render all the images at once
@ -294,28 +324,6 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|| (document.body.clientHeight + offset) * 0.85 < window.innerHeight;
}
@HostListener('window:scroll')
onScroll() {
if (!this.onScrollFired &&
// should we trigger this at all?
(this.renderedPhotoIndex < this.media.length || this.scrollListenerPhotos.length > 0)) {
window.requestAnimationFrame(() => {
this.renderPhotos();
if (Config.Client.Other.enableOnScrollThumbnailPrioritising === true) {
this.scrollListenerPhotos.forEach((pc: GalleryPhotoComponent) => {
pc.onScroll();
});
this.scrollListenerPhotos = this.scrollListenerPhotos.filter(pc => pc.ScrollListener);
}
this.onScrollFired = false;
});
this.onScrollFired = true;
}
}
private renderPhotos(numberOfPhotos: number = 0) {
if (this.containerWidth === 0 ||
this.renderedPhotoIndex >= this.media.length ||

View File

@ -24,7 +24,7 @@ ol {
}
.dropdown-menu {
min-width: 16rem;
min-width: 10rem;
}
.row {

View File

@ -0,0 +1,26 @@
import {Injectable} from '@angular/core';
@Injectable()
export class SeededRandomService {
private static readonly baseSeed = Math.random() * 2147483647;
private seed: number;
constructor() {
this.setSeed(0);
if (this.seed <= 0) {
this.seed += 2147483646;
}
}
setSeed(seed: number) {
this.seed = (SeededRandomService.baseSeed + seed) % 2147483647; // shifting with 16 to the left
}
get() {
this.seed = (this.seed * 16807 % 2147483647);
return this.seed / 2147483647;
}
}

View File

@ -14,6 +14,8 @@ export class IconizeSortingMethod implements PipeTransform {
return '<span class="oi oi-sort-ascending"></span>';
case SortingMethods.descDate:
return '<span class="oi oi-sort-descending"></span>';
case SortingMethods.random:
return '<span class="oi oi-random"></span>';
}
}
}

View File

@ -18,6 +18,8 @@ export class StringifySortingMethod implements PipeTransform {
return this.i18n('ascending date');
case SortingMethods.descDate:
return this.i18n('descending date');
case SortingMethods.random:
return this.i18n('random');
}
}
}