mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
Add grid size changer #716
This commit is contained in:
parent
232e91c6fc
commit
26d94e0482
@ -5,6 +5,7 @@ import {UserRoles} from '../../entities/UserDTO';
|
|||||||
import {ConfigProperty, SubConfigClass} from 'typeconfig/common';
|
import {ConfigProperty, SubConfigClass} from 'typeconfig/common';
|
||||||
import {SearchQueryDTO} from '../../entities/SearchQueryDTO';
|
import {SearchQueryDTO} from '../../entities/SearchQueryDTO';
|
||||||
import {DefaultsJobs} from '../../entities/job/JobDTO';
|
import {DefaultsJobs} from '../../entities/job/JobDTO';
|
||||||
|
import {GridSizes} from '../../entities/GridSizes';
|
||||||
|
|
||||||
declare let $localize: (s: TemplateStringsArray) => string;
|
declare let $localize: (s: TemplateStringsArray) => string;
|
||||||
if (typeof $localize === 'undefined') {
|
if (typeof $localize === 'undefined') {
|
||||||
@ -986,6 +987,17 @@ export class ClientGalleryConfig {
|
|||||||
})
|
})
|
||||||
defaultSearchGroupingMethod: ClientGroupingConfig = new ClientGroupingConfig(GroupByTypes.Date, false);
|
defaultSearchGroupingMethod: ClientGroupingConfig = new ClientGroupingConfig(GroupByTypes.Date, false);
|
||||||
|
|
||||||
|
@ConfigProperty({
|
||||||
|
type: GridSizes,
|
||||||
|
tags: {
|
||||||
|
name: $localize`Default grid size`,
|
||||||
|
githubIssue: 716,
|
||||||
|
priority: ConfigPriority.advanced,
|
||||||
|
},
|
||||||
|
description: $localize`Default grid size that is used to render photos and videos.`
|
||||||
|
})
|
||||||
|
defaultGidSize: GridSizes = GridSizes.medium;
|
||||||
|
|
||||||
@ConfigProperty({
|
@ConfigProperty({
|
||||||
tags: {
|
tags: {
|
||||||
name: $localize`Sort directories by date`,
|
name: $localize`Sort directories by date`,
|
||||||
|
7
src/common/entities/GridSizes.ts
Normal file
7
src/common/entities/GridSizes.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum GridSizes {
|
||||||
|
extraSmall = 10,
|
||||||
|
small = 20,
|
||||||
|
medium = 30,
|
||||||
|
large = 40,
|
||||||
|
extraLarge = 50
|
||||||
|
}
|
@ -112,6 +112,7 @@ import {NgIconsModule} from '@ng-icons/core';
|
|||||||
import {
|
import {
|
||||||
ionAddOutline,
|
ionAddOutline,
|
||||||
ionAlbumsOutline,
|
ionAlbumsOutline,
|
||||||
|
ionAppsOutline,
|
||||||
ionArrowDownOutline,
|
ionArrowDownOutline,
|
||||||
ionArrowUpOutline,
|
ionArrowUpOutline,
|
||||||
ionBrowsersOutline,
|
ionBrowsersOutline,
|
||||||
@ -137,6 +138,7 @@ import {
|
|||||||
ionFunnelOutline,
|
ionFunnelOutline,
|
||||||
ionGitBranchOutline,
|
ionGitBranchOutline,
|
||||||
ionGlobeOutline,
|
ionGlobeOutline,
|
||||||
|
ionGridOutline,
|
||||||
ionHammerOutline,
|
ionHammerOutline,
|
||||||
ionImageOutline,
|
ionImageOutline,
|
||||||
ionImagesOutline,
|
ionImagesOutline,
|
||||||
@ -162,6 +164,7 @@ import {
|
|||||||
ionSettingsOutline,
|
ionSettingsOutline,
|
||||||
ionShareSocialOutline,
|
ionShareSocialOutline,
|
||||||
ionShuffleOutline,
|
ionShuffleOutline,
|
||||||
|
ionSquareOutline,
|
||||||
ionStar,
|
ionStar,
|
||||||
ionStarOutline,
|
ionStarOutline,
|
||||||
ionStopOutline,
|
ionStopOutline,
|
||||||
@ -176,7 +179,6 @@ import {
|
|||||||
ionVolumeMuteOutline,
|
ionVolumeMuteOutline,
|
||||||
ionWarningOutline
|
ionWarningOutline
|
||||||
} from '@ng-icons/ionicons';
|
} from '@ng-icons/ionicons';
|
||||||
import {SortingMethodIconComponent} from './ui/sorting-method-icon/sorting-method-icon.component';
|
|
||||||
import {SafeHtmlPipe} from './pipes/SafeHTMLPipe';
|
import {SafeHtmlPipe} from './pipes/SafeHTMLPipe';
|
||||||
import {DatePipe} from '@angular/common';
|
import {DatePipe} from '@angular/common';
|
||||||
import {ParseIntPipe} from './pipes/ParseIntPipe';
|
import {ParseIntPipe} from './pipes/ParseIntPipe';
|
||||||
@ -185,6 +187,10 @@ import {
|
|||||||
} from './ui/settings/template/settings-entry/sorting-method/sorting-method.settings-entry.component';
|
} from './ui/settings/template/settings-entry/sorting-method/sorting-method.settings-entry.component';
|
||||||
import {ContentLoaderService} from './ui/gallery/contentLoader.service';
|
import {ContentLoaderService} from './ui/gallery/contentLoader.service';
|
||||||
import {FileDTOToRelativePathPipe} from './pipes/FileDTOToRelativePathPipe';
|
import {FileDTOToRelativePathPipe} from './pipes/FileDTOToRelativePathPipe';
|
||||||
|
import {StringifyGridSize} from './pipes/StringifyGridSize';
|
||||||
|
import {GalleryNavigatorService} from './ui/gallery/navigator/navigator.service';
|
||||||
|
import {GridSizeIconComponent} from './ui/utils/grid-size-icon/grid-size-icon.component';
|
||||||
|
import {SortingMethodIconComponent} from './ui/utils/sorting-method-icon/sorting-method-icon.component';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MyHammerConfig extends HammerGestureConfig {
|
export class MyHammerConfig extends HammerGestureConfig {
|
||||||
@ -246,7 +252,8 @@ Marker.prototype.options.icon = MarkerFactory.defIcon;
|
|||||||
ionFlagOutline, ionGlobeOutline, ionPieChartOutline, ionStopOutline,
|
ionFlagOutline, ionGlobeOutline, ionPieChartOutline, ionStopOutline,
|
||||||
ionTimeOutline, ionCheckmarkOutline, ionPulseOutline, ionResizeOutline,
|
ionTimeOutline, ionCheckmarkOutline, ionPulseOutline, ionResizeOutline,
|
||||||
ionCloudOutline, ionChatboxOutline, ionServerOutline, ionFileTrayFullOutline, ionBrushOutline,
|
ionCloudOutline, ionChatboxOutline, ionServerOutline, ionFileTrayFullOutline, ionBrushOutline,
|
||||||
ionBrowsersOutline, ionUnlinkOutline
|
ionBrowsersOutline, ionUnlinkOutline, ionSquareOutline, ionGridOutline,
|
||||||
|
ionAppsOutline
|
||||||
}),
|
}),
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
TooltipModule.forRoot(),
|
TooltipModule.forRoot(),
|
||||||
@ -325,6 +332,7 @@ Marker.prototype.options.icon = MarkerFactory.defIcon;
|
|||||||
StringifySearchQuery,
|
StringifySearchQuery,
|
||||||
StringifyEnum,
|
StringifyEnum,
|
||||||
StringifySearchType,
|
StringifySearchType,
|
||||||
|
StringifyGridSize,
|
||||||
FileDTOToPathPipe,
|
FileDTOToPathPipe,
|
||||||
FileDTOToRelativePathPipe,
|
FileDTOToRelativePathPipe,
|
||||||
PhotoFilterPipe,
|
PhotoFilterPipe,
|
||||||
@ -332,6 +340,7 @@ Marker.prototype.options.icon = MarkerFactory.defIcon;
|
|||||||
UsersComponent,
|
UsersComponent,
|
||||||
SharingsListComponent,
|
SharingsListComponent,
|
||||||
SortingMethodIconComponent,
|
SortingMethodIconComponent,
|
||||||
|
GridSizeIconComponent,
|
||||||
SafeHtmlPipe,
|
SafeHtmlPipe,
|
||||||
SortingMethodSettingsEntryComponent
|
SortingMethodSettingsEntryComponent
|
||||||
],
|
],
|
||||||
@ -350,6 +359,7 @@ Marker.prototype.options.icon = MarkerFactory.defIcon;
|
|||||||
ContentLoaderService,
|
ContentLoaderService,
|
||||||
FilterService,
|
FilterService,
|
||||||
GallerySortingService,
|
GallerySortingService,
|
||||||
|
GalleryNavigatorService,
|
||||||
MapService,
|
MapService,
|
||||||
BlogService,
|
BlogService,
|
||||||
SearchQueryParserService,
|
SearchQueryParserService,
|
||||||
|
12
src/frontend/app/pipes/StringifyGridSize.ts
Normal file
12
src/frontend/app/pipes/StringifyGridSize.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
import {EnumTranslations} from '../ui/EnumTranslations';
|
||||||
|
import {GridSizes} from '../../../common/entities/GridSizes';
|
||||||
|
|
||||||
|
@Pipe({name: 'stringifyGridSize'})
|
||||||
|
export class StringifyGridSize implements PipeTransform {
|
||||||
|
|
||||||
|
transform(gs: GridSizes): string {
|
||||||
|
return EnumTranslations[GridSizes[gs]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import {ReIndexingSensitivity} from '../../../common/config/private/PrivateConfi
|
|||||||
import {SearchQueryTypes} from '../../../common/entities/SearchQueryDTO';
|
import {SearchQueryTypes} from '../../../common/entities/SearchQueryDTO';
|
||||||
import {ConfigStyle} from './settings/settings.service';
|
import {ConfigStyle} from './settings/settings.service';
|
||||||
import {SortByTypes,GroupByTypes} from '../../../common/entities/SortingMethods';
|
import {SortByTypes,GroupByTypes} from '../../../common/entities/SortingMethods';
|
||||||
|
import {GridSizes} from '../../../common/entities/GridSizes';
|
||||||
|
|
||||||
export const EnumTranslations: Record<string, string> = {};
|
export const EnumTranslations: Record<string, string> = {};
|
||||||
export const enumToTranslatedArray = (EnumType: any): { key: number; value: string }[] => {
|
export const enumToTranslatedArray = (EnumType: any): { key: number; value: string }[] => {
|
||||||
@ -55,6 +56,12 @@ EnumTranslations[SortByTypes[SortByTypes.FileSize]] = $localize`file size`;
|
|||||||
EnumTranslations[GroupByTypes[GroupByTypes.NoGrouping]] = $localize`don't group`;
|
EnumTranslations[GroupByTypes[GroupByTypes.NoGrouping]] = $localize`don't group`;
|
||||||
|
|
||||||
|
|
||||||
|
EnumTranslations[GridSizes[GridSizes.extraSmall]] = $localize`extra small`;
|
||||||
|
EnumTranslations[GridSizes[GridSizes.small]] = $localize`small`;
|
||||||
|
EnumTranslations[GridSizes[GridSizes.medium]] = $localize`medium`;
|
||||||
|
EnumTranslations[GridSizes[GridSizes.large]] = $localize`big`;
|
||||||
|
EnumTranslations[GridSizes[GridSizes.extraLarge]] = $localize`extra large`;
|
||||||
|
|
||||||
EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.url]] = $localize`Url`;
|
EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.url]] = $localize`Url`;
|
||||||
EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.search]] = $localize`Search`;
|
EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.search]] = $localize`Search`;
|
||||||
EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.gallery]] = $localize`Gallery`;
|
EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.gallery]] = $localize`Gallery`;
|
||||||
|
@ -10,6 +10,7 @@ import {SearchQueryDTO, SearchQueryTypes,} from '../../../../common/entities/Sea
|
|||||||
import {ContentWrapper} from '../../../../common/entities/ConentWrapper';
|
import {ContentWrapper} from '../../../../common/entities/ConentWrapper';
|
||||||
import {ContentWrapperWithError} from './contentLoader.service';
|
import {ContentWrapperWithError} from './contentLoader.service';
|
||||||
import {ThemeModes} from '../../../../common/config/public/ClientConfig';
|
import {ThemeModes} from '../../../../common/config/public/ClientConfig';
|
||||||
|
import {GridSizes} from '../../../../common/entities/GridSizes';
|
||||||
|
|
||||||
interface CacheItem<T> {
|
interface CacheItem<T> {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
@ -24,6 +25,7 @@ export class GalleryCacheService {
|
|||||||
private static readonly SEARCH_PREFIX = 'SEARCH:';
|
private static readonly SEARCH_PREFIX = 'SEARCH:';
|
||||||
private static readonly SORTING_PREFIX = 'SORTING:';
|
private static readonly SORTING_PREFIX = 'SORTING:';
|
||||||
private static readonly GROUPING_PREFIX = 'GROUPING:';
|
private static readonly GROUPING_PREFIX = 'GROUPING:';
|
||||||
|
private static readonly GRID_SIZE_PREFIX = 'GRID_SIZE:';
|
||||||
private static readonly VERSION = 'VERSION';
|
private static readonly VERSION = 'VERSION';
|
||||||
private static readonly SLIDESHOW_SPEED = 'SLIDESHOW_SPEED';
|
private static readonly SLIDESHOW_SPEED = 'SLIDESHOW_SPEED';
|
||||||
private static THEME_MODE = 'THEME_MODE';
|
private static THEME_MODE = 'THEME_MODE';
|
||||||
@ -36,8 +38,8 @@ export class GalleryCacheService {
|
|||||||
|
|
||||||
const onNewVersion = (ver: string) => {
|
const onNewVersion = (ver: string) => {
|
||||||
if (
|
if (
|
||||||
ver !== null &&
|
ver !== null &&
|
||||||
localStorage.getItem(GalleryCacheService.VERSION) !== ver
|
localStorage.getItem(GalleryCacheService.VERSION) !== ver
|
||||||
) {
|
) {
|
||||||
GalleryCacheService.deleteCache();
|
GalleryCacheService.deleteCache();
|
||||||
localStorage.setItem(GalleryCacheService.VERSION, ver);
|
localStorage.setItem(GalleryCacheService.VERSION, ver);
|
||||||
@ -49,7 +51,7 @@ export class GalleryCacheService {
|
|||||||
|
|
||||||
private static wasAReload(): boolean {
|
private static wasAReload(): boolean {
|
||||||
const perfEntries = performance.getEntriesByType(
|
const perfEntries = performance.getEntriesByType(
|
||||||
'navigation'
|
'navigation'
|
||||||
) as PerformanceNavigationTiming[];
|
) as PerformanceNavigationTiming[];
|
||||||
return perfEntries && perfEntries[0] && perfEntries[0].type === 'reload';
|
return perfEntries && perfEntries[0] && perfEntries[0].type === 'reload';
|
||||||
}
|
}
|
||||||
@ -59,8 +61,8 @@ export class GalleryCacheService {
|
|||||||
if (tmp != null) {
|
if (tmp != null) {
|
||||||
const value: CacheItem<ContentWrapperWithError> = JSON.parse(tmp);
|
const value: CacheItem<ContentWrapperWithError> = JSON.parse(tmp);
|
||||||
if (
|
if (
|
||||||
value.timestamp <
|
value.timestamp <
|
||||||
Date.now() - Config.Search.searchCacheTimeout
|
Date.now() - Config.Search.searchCacheTimeout
|
||||||
) {
|
) {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
return null;
|
return null;
|
||||||
@ -76,14 +78,14 @@ export class GalleryCacheService {
|
|||||||
const toRemove = [];
|
const toRemove = [];
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
if (
|
if (
|
||||||
localStorage.key(i).startsWith(GalleryCacheService.CONTENT_PREFIX) ||
|
localStorage.key(i).startsWith(GalleryCacheService.CONTENT_PREFIX) ||
|
||||||
localStorage.key(i).startsWith(GalleryCacheService.SEARCH_PREFIX) ||
|
localStorage.key(i).startsWith(GalleryCacheService.SEARCH_PREFIX) ||
|
||||||
localStorage
|
localStorage
|
||||||
.key(i)
|
.key(i)
|
||||||
.startsWith(GalleryCacheService.INSTANT_SEARCH_PREFIX) ||
|
.startsWith(GalleryCacheService.INSTANT_SEARCH_PREFIX) ||
|
||||||
localStorage
|
localStorage
|
||||||
.key(i)
|
.key(i)
|
||||||
.startsWith(GalleryCacheService.AUTO_COMPLETE_PREFIX)
|
.startsWith(GalleryCacheService.AUTO_COMPLETE_PREFIX)
|
||||||
) {
|
) {
|
||||||
toRemove.push(localStorage.key(i));
|
toRemove.push(localStorage.key(i));
|
||||||
}
|
}
|
||||||
@ -154,9 +156,9 @@ export class GalleryCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setSortOrGroup(
|
private setSortOrGroup(
|
||||||
prefix: string,
|
prefix: string,
|
||||||
cw: ContentWrapper,
|
cw: ContentWrapper,
|
||||||
sorting: SortingMethod | GroupingMethod
|
sorting: SortingMethod | GroupingMethod
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
let key = prefix;
|
let key = prefix;
|
||||||
@ -172,23 +174,62 @@ export class GalleryCacheService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeGridSize(cw: ContentWrapperWithError): void {
|
||||||
|
let key = GalleryCacheService.GRID_SIZE_PREFIX;
|
||||||
|
if (cw?.searchResult?.searchQuery) {
|
||||||
|
key += JSON.stringify(cw.searchResult.searchQuery);
|
||||||
|
} else {
|
||||||
|
key += cw?.directory?.path + '/' + cw?.directory?.name;
|
||||||
|
}
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
getGridSize(cw: ContentWrapperWithError): GridSizes {
|
||||||
|
let key = GalleryCacheService.GRID_SIZE_PREFIX;
|
||||||
|
if (cw?.searchResult?.searchQuery) {
|
||||||
|
key += JSON.stringify(cw.searchResult.searchQuery);
|
||||||
|
} else {
|
||||||
|
key += cw?.directory?.path + '/' + cw?.directory?.name;
|
||||||
|
}
|
||||||
|
const tmp = localStorage.getItem(key);
|
||||||
|
if (tmp != null) {
|
||||||
|
return parseInt(tmp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGridSize(cw: ContentWrapperWithError, gs: GridSizes) {
|
||||||
|
try {
|
||||||
|
let key = GalleryCacheService.GRID_SIZE_PREFIX;
|
||||||
|
if (cw?.searchResult?.searchQuery) {
|
||||||
|
key += JSON.stringify(cw.searchResult.searchQuery);
|
||||||
|
} else {
|
||||||
|
key += cw?.directory?.path + '/' + cw?.directory?.name;
|
||||||
|
}
|
||||||
|
localStorage.setItem(key, gs.toString());
|
||||||
|
} catch (e) {
|
||||||
|
this.reset();
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getAutoComplete(
|
public getAutoComplete(
|
||||||
text: string,
|
text: string,
|
||||||
type: SearchQueryTypes
|
type: SearchQueryTypes
|
||||||
): IAutoCompleteItem[] {
|
): IAutoCompleteItem[] {
|
||||||
if (Config.Gallery.enableCache === false) {
|
if (Config.Gallery.enableCache === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const key =
|
const key =
|
||||||
GalleryCacheService.AUTO_COMPLETE_PREFIX +
|
GalleryCacheService.AUTO_COMPLETE_PREFIX +
|
||||||
text +
|
text +
|
||||||
(type ? '_' + type : '');
|
(type ? '_' + type : '');
|
||||||
const tmp = localStorage.getItem(key);
|
const tmp = localStorage.getItem(key);
|
||||||
if (tmp != null) {
|
if (tmp != null) {
|
||||||
const value: CacheItem<IAutoCompleteItem[]> = JSON.parse(tmp);
|
const value: CacheItem<IAutoCompleteItem[]> = JSON.parse(tmp);
|
||||||
if (
|
if (
|
||||||
value.timestamp <
|
value.timestamp <
|
||||||
Date.now() - Config.Search.AutoComplete.cacheTimeout
|
Date.now() - Config.Search.AutoComplete.cacheTimeout
|
||||||
) {
|
) {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
return null;
|
return null;
|
||||||
@ -199,17 +240,17 @@ export class GalleryCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setAutoComplete(
|
public setAutoComplete(
|
||||||
text: string,
|
text: string,
|
||||||
type: SearchQueryTypes,
|
type: SearchQueryTypes,
|
||||||
items: Array<IAutoCompleteItem>
|
items: Array<IAutoCompleteItem>
|
||||||
): void {
|
): void {
|
||||||
if (Config.Gallery.enableCache === false) {
|
if (Config.Gallery.enableCache === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const key =
|
const key =
|
||||||
GalleryCacheService.AUTO_COMPLETE_PREFIX +
|
GalleryCacheService.AUTO_COMPLETE_PREFIX +
|
||||||
text +
|
text +
|
||||||
(type ? '_' + type : '');
|
(type ? '_' + type : '');
|
||||||
const tmp: CacheItem<Array<IAutoCompleteItem>> = {
|
const tmp: CacheItem<Array<IAutoCompleteItem>> = {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
item: items,
|
item: items,
|
||||||
@ -256,7 +297,7 @@ export class GalleryCacheService {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const value = localStorage.getItem(
|
const value = localStorage.getItem(
|
||||||
GalleryCacheService.CONTENT_PREFIX + Utils.concatUrls(directoryName)
|
GalleryCacheService.CONTENT_PREFIX + Utils.concatUrls(directoryName)
|
||||||
);
|
);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
@ -273,8 +314,8 @@ export class GalleryCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const key =
|
const key =
|
||||||
GalleryCacheService.CONTENT_PREFIX +
|
GalleryCacheService.CONTENT_PREFIX +
|
||||||
Utils.concatUrls(cw.directory.path, cw.directory.name);
|
Utils.concatUrls(cw.directory.path, cw.directory.name);
|
||||||
if (cw.directory.isPartial === true && localStorage.getItem(key)) {
|
if (cw.directory.isPartial === true && localStorage.getItem(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -299,8 +340,8 @@ export class GalleryCacheService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const directoryKey =
|
const directoryKey =
|
||||||
GalleryCacheService.CONTENT_PREFIX +
|
GalleryCacheService.CONTENT_PREFIX +
|
||||||
Utils.concatUrls(media.directory.path, media.directory.name);
|
Utils.concatUrls(media.directory.path, media.directory.name);
|
||||||
const value = localStorage.getItem(directoryKey);
|
const value = localStorage.getItem(directoryKey);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
const directory: ParentDirectoryDTO = JSON.parse(value);
|
const directory: ParentDirectoryDTO = JSON.parse(value);
|
||||||
@ -332,8 +373,8 @@ export class GalleryCacheService {
|
|||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
localStorage.setItem('currentUser', currentUserStr);
|
localStorage.setItem('currentUser', currentUserStr);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
GalleryCacheService.VERSION,
|
GalleryCacheService.VERSION,
|
||||||
this.versionService.version.value
|
this.versionService.version.value
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignoring errors
|
// ignoring errors
|
||||||
|
@ -26,6 +26,8 @@ import {MediaDTO, MediaDTOUtils,} from '../../../../../common/entities/MediaDTO'
|
|||||||
import {QueryParams} from '../../../../../common/QueryParams';
|
import {QueryParams} from '../../../../../common/QueryParams';
|
||||||
import {GallerySortingService, MediaGroup} from '../navigator/sorting.service';
|
import {GallerySortingService, MediaGroup} from '../navigator/sorting.service';
|
||||||
import {GroupByTypes} from '../../../../../common/entities/SortingMethods';
|
import {GroupByTypes} from '../../../../../common/entities/SortingMethods';
|
||||||
|
import {GalleryNavigatorService} from '../navigator/navigator.service';
|
||||||
|
import {GridSizes} from '../../../../../common/entities/GridSizes';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-grid',
|
selector: 'app-gallery-grid',
|
||||||
@ -33,7 +35,7 @@ import {GroupByTypes} from '../../../../../common/entities/SortingMethods';
|
|||||||
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>;
|
||||||
@ -45,9 +47,11 @@ export class GalleryGridComponent
|
|||||||
public IMAGE_MARGIN = 2;
|
public IMAGE_MARGIN = 2;
|
||||||
isAfterViewInit = false;
|
isAfterViewInit = false;
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
|
girdSize: Subscription;
|
||||||
route: Subscription;
|
route: Subscription;
|
||||||
} = {
|
} = {
|
||||||
route: null,
|
route: null,
|
||||||
|
girdSize: null
|
||||||
};
|
};
|
||||||
delayedRenderUpToPhoto: string = null;
|
delayedRenderUpToPhoto: string = null;
|
||||||
private scrollListenerPhotos: GalleryPhotoComponent[] = [];
|
private scrollListenerPhotos: GalleryPhotoComponent[] = [];
|
||||||
@ -61,12 +65,13 @@ export class GalleryGridComponent
|
|||||||
public readonly blogOpen = Config.Gallery.InlineBlogStartsOpen;
|
public readonly blogOpen = Config.Gallery.InlineBlogStartsOpen;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private overlayService: OverlayService,
|
private overlayService: OverlayService,
|
||||||
private changeDetector: ChangeDetectorRef,
|
private changeDetector: ChangeDetectorRef,
|
||||||
public queryService: QueryService,
|
public queryService: QueryService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
public sortingService: GallerySortingService,
|
public sortingService: GallerySortingService,
|
||||||
private route: ActivatedRoute
|
public navigatorService: GalleryNavigatorService,
|
||||||
|
private route: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,30 +81,57 @@ export class GalleryGridComponent
|
|||||||
}
|
}
|
||||||
this.updateContainerDimensions();
|
this.updateContainerDimensions();
|
||||||
this.mergeNewPhotos();
|
this.mergeNewPhotos();
|
||||||
this.helperTime = window.setTimeout((): void => {
|
this.renderMinimalPhotos();
|
||||||
this.renderPhotos();
|
|
||||||
if (this.delayedRenderUpToPhoto) {
|
|
||||||
this.renderUpToMedia(this.delayedRenderUpToPhoto);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.subscriptions.route = this.route.queryParams.subscribe(
|
this.subscriptions.route = this.route.queryParams.subscribe(
|
||||||
(params: Params): void => {
|
(params: Params): void => {
|
||||||
if (
|
if (
|
||||||
params[QueryParams.gallery.photo] &&
|
params[QueryParams.gallery.photo] &&
|
||||||
params[QueryParams.gallery.photo] !== ''
|
params[QueryParams.gallery.photo] !== ''
|
||||||
) {
|
) {
|
||||||
this.delayedRenderUpToPhoto = params[QueryParams.gallery.photo];
|
this.delayedRenderUpToPhoto = params[QueryParams.gallery.photo];
|
||||||
if (!this.mediaGroups?.length) {
|
if (!this.mediaGroups?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderUpToMedia(params[QueryParams.gallery.photo]);
|
this.renderUpToMedia(params[QueryParams.gallery.photo]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.subscriptions.girdSize = this.navigatorService.girdSize.subscribe(gs => {
|
||||||
|
switch (gs) {
|
||||||
|
case GridSizes.extraSmall:
|
||||||
|
this.TARGET_COL_COUNT = 12;
|
||||||
|
this.MIN_ROW_COUNT = 5;
|
||||||
|
this.MAX_ROW_COUNT = 10;
|
||||||
|
break;
|
||||||
|
case GridSizes.small:
|
||||||
|
this.TARGET_COL_COUNT = 8;
|
||||||
|
this.MIN_ROW_COUNT = 3;
|
||||||
|
this.MAX_ROW_COUNT = 8;
|
||||||
|
break;
|
||||||
|
case GridSizes.medium:
|
||||||
|
this.TARGET_COL_COUNT = 5;
|
||||||
|
this.MIN_ROW_COUNT = 2;
|
||||||
|
this.MAX_ROW_COUNT = 5;
|
||||||
|
break;
|
||||||
|
case GridSizes.large:
|
||||||
|
this.TARGET_COL_COUNT = 2;
|
||||||
|
this.MIN_ROW_COUNT = 1;
|
||||||
|
this.MAX_ROW_COUNT = 3;
|
||||||
|
break;
|
||||||
|
case GridSizes.extraLarge:
|
||||||
|
this.TARGET_COL_COUNT = 1;
|
||||||
|
this.MIN_ROW_COUNT = 1;
|
||||||
|
this.MAX_ROW_COUNT = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.clearRenderedPhotos();
|
||||||
|
this.renderMinimalPhotos();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@ -114,6 +146,10 @@ export class GalleryGridComponent
|
|||||||
this.subscriptions.route.unsubscribe();
|
this.subscriptions.route.unsubscribe();
|
||||||
this.subscriptions.route = null;
|
this.subscriptions.route = null;
|
||||||
}
|
}
|
||||||
|
if (this.subscriptions.girdSize !== null) {
|
||||||
|
this.subscriptions.girdSize.unsubscribe();
|
||||||
|
this.subscriptions.girdSize = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize')
|
@HostListener('window:resize')
|
||||||
@ -137,6 +173,18 @@ export class GalleryGridComponent
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Renders some photos. If nothing specified, this amount should be enough
|
||||||
|
* */
|
||||||
|
private renderMinimalPhotos() {
|
||||||
|
this.helperTime = window.setTimeout((): void => {
|
||||||
|
this.renderPhotos();
|
||||||
|
if (this.delayedRenderUpToPhoto) {
|
||||||
|
this.renderUpToMedia(this.delayedRenderUpToPhoto);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
photoClicked(media: MediaDTO): void {
|
photoClicked(media: MediaDTO): void {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: this.queryService.getParams(media),
|
queryParams: this.queryService.getParams(media),
|
||||||
@ -149,19 +197,14 @@ export class GalleryGridComponent
|
|||||||
if (Config.Gallery.enableOnScrollThumbnailPrioritising === true) {
|
if (Config.Gallery.enableOnScrollThumbnailPrioritising === true) {
|
||||||
this.gridPhotoQL.changes.subscribe((): void => {
|
this.gridPhotoQL.changes.subscribe((): void => {
|
||||||
this.scrollListenerPhotos = this.gridPhotoQL.filter(
|
this.scrollListenerPhotos = this.gridPhotoQL.filter(
|
||||||
(pc): boolean => pc.ScrollListener
|
(pc): boolean => pc.ScrollListener
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateContainerDimensions();
|
this.updateContainerDimensions();
|
||||||
this.clearRenderedPhotos();
|
this.clearRenderedPhotos();
|
||||||
this.helperTime = window.setTimeout((): void => {
|
this.renderMinimalPhotos();
|
||||||
this.renderPhotos();
|
|
||||||
if (this.delayedRenderUpToPhoto) {
|
|
||||||
this.renderUpToMedia(this.delayedRenderUpToPhoto);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
this.isAfterViewInit = true;
|
this.isAfterViewInit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +268,7 @@ export class GalleryGridComponent
|
|||||||
|
|
||||||
// if all check passed, nothing to delete from the last group
|
// if all check passed, nothing to delete from the last group
|
||||||
if (!diffFound &&
|
if (!diffFound &&
|
||||||
lastOkIndex.media == this.mediaGroups[lastOkIndex.groups].media.length - 1) {
|
lastOkIndex.media == this.mediaGroups[lastOkIndex.groups].media.length - 1) {
|
||||||
firstDeleteIndex.groups = lastOkIndex.groups;
|
firstDeleteIndex.groups = lastOkIndex.groups;
|
||||||
firstDeleteIndex.media = lastOkIndex.media + 1;
|
firstDeleteIndex.media = lastOkIndex.media + 1;
|
||||||
}
|
}
|
||||||
@ -248,16 +291,16 @@ export class GalleryGridComponent
|
|||||||
|
|
||||||
public renderARow(): number {
|
public renderARow(): number {
|
||||||
if (
|
if (
|
||||||
!this.isMoreToRender() ||
|
!this.isMoreToRender() ||
|
||||||
this.containerWidth === 0
|
this.containerWidth === 0
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// step group
|
// step group
|
||||||
if (this.mediaToRender.length == 0 ||
|
if (this.mediaToRender.length == 0 ||
|
||||||
this.mediaToRender[this.mediaToRender.length - 1].media.length >=
|
this.mediaToRender[this.mediaToRender.length - 1].media.length >=
|
||||||
this.mediaGroups[this.mediaToRender.length - 1].media.length) {
|
this.mediaGroups[this.mediaToRender.length - 1].media.length) {
|
||||||
this.mediaToRender.push({
|
this.mediaToRender.push({
|
||||||
name: this.mediaGroups[this.mediaToRender.length].name,
|
name: this.mediaGroups[this.mediaToRender.length].name,
|
||||||
date: this.mediaGroups[this.mediaToRender.length].date,
|
date: this.mediaGroups[this.mediaToRender.length].date,
|
||||||
@ -269,10 +312,10 @@ export class GalleryGridComponent
|
|||||||
const minRowHeight = this.screenHeight / this.MAX_ROW_COUNT;
|
const minRowHeight = this.screenHeight / this.MAX_ROW_COUNT;
|
||||||
|
|
||||||
const photoRowBuilder = new GridRowBuilder(
|
const photoRowBuilder = new GridRowBuilder(
|
||||||
this.mediaGroups[this.mediaToRender.length - 1].media,
|
this.mediaGroups[this.mediaToRender.length - 1].media,
|
||||||
this.mediaToRender[this.mediaToRender.length - 1].media.length,
|
this.mediaToRender[this.mediaToRender.length - 1].media.length,
|
||||||
this.IMAGE_MARGIN,
|
this.IMAGE_MARGIN,
|
||||||
this.containerWidth - this.overlayService.getPhantomScrollbarWidth()
|
this.containerWidth - this.overlayService.getPhantomScrollbarWidth()
|
||||||
);
|
);
|
||||||
|
|
||||||
photoRowBuilder.addPhotos(this.TARGET_COL_COUNT);
|
photoRowBuilder.addPhotos(this.TARGET_COL_COUNT);
|
||||||
@ -285,13 +328,13 @@ export class GalleryGridComponent
|
|||||||
const noFullRow = photoRowBuilder.calcRowHeight() > maxRowHeight;
|
const noFullRow = photoRowBuilder.calcRowHeight() > maxRowHeight;
|
||||||
// if the row is not full, make it average sized
|
// if the row is not full, make it average sized
|
||||||
const rowHeight = noFullRow ? (minRowHeight + maxRowHeight) / 2 :
|
const rowHeight = noFullRow ? (minRowHeight + maxRowHeight) / 2 :
|
||||||
Math.min(photoRowBuilder.calcRowHeight(), maxRowHeight);
|
Math.min(photoRowBuilder.calcRowHeight(), maxRowHeight);
|
||||||
const imageHeight = rowHeight - this.IMAGE_MARGIN * 2;
|
const imageHeight = rowHeight - this.IMAGE_MARGIN * 2;
|
||||||
|
|
||||||
photoRowBuilder.getPhotoRow().forEach((media): void => {
|
photoRowBuilder.getPhotoRow().forEach((media): void => {
|
||||||
const imageWidth = imageHeight * MediaDTOUtils.calcAspectRatio(media);
|
const imageWidth = imageHeight * MediaDTOUtils.calcAspectRatio(media);
|
||||||
this.mediaToRender[this.mediaToRender.length - 1].media.push(
|
this.mediaToRender[this.mediaToRender.length - 1].media.push(
|
||||||
new GridMedia(media, imageWidth, imageHeight, this.mediaToRender[this.mediaToRender.length - 1].media.length)
|
new GridMedia(media, imageWidth, imageHeight, this.mediaToRender[this.mediaToRender.length - 1].media.length)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -302,23 +345,23 @@ export class GalleryGridComponent
|
|||||||
@HostListener('window:scroll')
|
@HostListener('window:scroll')
|
||||||
onScroll(): void {
|
onScroll(): void {
|
||||||
if (
|
if (
|
||||||
!this.onScrollFired &&
|
!this.onScrollFired &&
|
||||||
this.mediaGroups &&
|
this.mediaGroups &&
|
||||||
// should we trigger this at all?
|
// should we trigger this at all?
|
||||||
(this.isMoreToRender() ||
|
(this.isMoreToRender() ||
|
||||||
this.scrollListenerPhotos.length > 0)
|
this.scrollListenerPhotos.length > 0)
|
||||||
) {
|
) {
|
||||||
window.requestAnimationFrame((): void => {
|
window.requestAnimationFrame((): void => {
|
||||||
this.renderPhotos();
|
this.renderPhotos();
|
||||||
|
|
||||||
if (Config.Gallery.enableOnScrollThumbnailPrioritising === true) {
|
if (Config.Gallery.enableOnScrollThumbnailPrioritising === true) {
|
||||||
this.scrollListenerPhotos.forEach(
|
this.scrollListenerPhotos.forEach(
|
||||||
(pc: GalleryPhotoComponent): void => {
|
(pc: GalleryPhotoComponent): void => {
|
||||||
pc.onScroll();
|
pc.onScroll();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.scrollListenerPhotos = this.scrollListenerPhotos.filter(
|
this.scrollListenerPhotos = this.scrollListenerPhotos.filter(
|
||||||
(pc): boolean => pc.ScrollListener
|
(pc): boolean => pc.ScrollListener
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +383,7 @@ export class GalleryGridComponent
|
|||||||
let mediaIndex = -1;
|
let mediaIndex = -1;
|
||||||
for (let i = 0; i < this.mediaGroups.length; ++i) {
|
for (let i = 0; i < this.mediaGroups.length; ++i) {
|
||||||
mediaIndex = this.mediaGroups[i].media.findIndex(
|
mediaIndex = this.mediaGroups[i].media.findIndex(
|
||||||
(p): boolean => this.queryService.getMediaStringId(p) === mediaStringId
|
(p): boolean => this.queryService.getMediaStringId(p) === mediaStringId
|
||||||
);
|
);
|
||||||
if (mediaIndex !== -1) {
|
if (mediaIndex !== -1) {
|
||||||
groupIndex = i;
|
groupIndex = i;
|
||||||
@ -356,11 +399,11 @@ export class GalleryGridComponent
|
|||||||
// so not required to render more, but the scrollbar does not trigger more photos to render
|
// so not required to render more, but the scrollbar does not trigger more photos to render
|
||||||
// (on lightbox navigation)
|
// (on lightbox navigation)
|
||||||
while (
|
while (
|
||||||
(this.mediaToRender.length - 1 < groupIndex &&
|
(this.mediaToRender.length - 1 < groupIndex &&
|
||||||
this.mediaToRender[this.mediaToRender.length - 1]?.media?.length < mediaIndex) &&
|
this.mediaToRender[this.mediaToRender.length - 1]?.media?.length < mediaIndex) &&
|
||||||
this.renderARow() !== null
|
this.renderARow() !== null
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,13 +421,13 @@ export class GalleryGridComponent
|
|||||||
private shouldRenderMore(offset = 0): boolean {
|
private shouldRenderMore(offset = 0): boolean {
|
||||||
const bottomOffset = this.getMaxRowHeight() * 2;
|
const bottomOffset = this.getMaxRowHeight() * 2;
|
||||||
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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,9 +436,9 @@ export class GalleryGridComponent
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.containerWidth === 0 ||
|
this.containerWidth === 0 ||
|
||||||
!this.isMoreToRender() ||
|
!this.isMoreToRender() ||
|
||||||
!this.shouldRenderMore()
|
!this.shouldRenderMore()
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -403,10 +446,10 @@ export class GalleryGridComponent
|
|||||||
let renderedContentHeight = 0;
|
let renderedContentHeight = 0;
|
||||||
|
|
||||||
while (
|
while (
|
||||||
this.isMoreToRender() &&
|
this.isMoreToRender() &&
|
||||||
(this.shouldRenderMore(renderedContentHeight) === true ||
|
(this.shouldRenderMore(renderedContentHeight) === true ||
|
||||||
this.getNumberOfRenderedMedia() < numberOfPhotos)
|
this.getNumberOfRenderedMedia() < 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');
|
||||||
@ -417,7 +460,7 @@ export class GalleryGridComponent
|
|||||||
|
|
||||||
private isMoreToRender() {
|
private isMoreToRender() {
|
||||||
return this.mediaToRender.length < this.mediaGroups.length ||
|
return this.mediaToRender.length < this.mediaGroups.length ||
|
||||||
(this.mediaToRender[this.mediaToRender.length - 1]?.media.length || 0) < this.mediaGroups[this.mediaToRender.length - 1]?.media.length;
|
(this.mediaToRender[this.mediaToRender.length - 1]?.media.length || 0) < this.mediaGroups[this.mediaToRender.length - 1]?.media.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNumberOfRenderedMedia() {
|
getNumberOfRenderedMedia() {
|
||||||
@ -433,9 +476,9 @@ export class GalleryGridComponent
|
|||||||
PageHelper.showScrollY();
|
PageHelper.showScrollY();
|
||||||
// if the width changed a bit or the height changed a lot
|
// if the width changed a bit or the height changed a lot
|
||||||
if (
|
if (
|
||||||
this.containerWidth !== this.gridContainer.nativeElement.parentElement.clientWidth ||
|
this.containerWidth !== this.gridContainer.nativeElement.parentElement.clientWidth ||
|
||||||
this.screenHeight < window.innerHeight * 0.75 ||
|
this.screenHeight < window.innerHeight * 0.75 ||
|
||||||
this.screenHeight > window.innerHeight * 1.25
|
this.screenHeight > window.innerHeight * 1.25
|
||||||
) {
|
) {
|
||||||
this.screenHeight = window.innerHeight;
|
this.screenHeight = window.innerHeight;
|
||||||
this.containerWidth = this.gridContainer.nativeElement.parentElement.clientWidth;
|
this.containerWidth = this.gridContainer.nativeElement.parentElement.clientWidth;
|
||||||
|
@ -1,167 +1,195 @@
|
|||||||
<div #navigator class="container-fluid pt-1 pb-1 pe-0 ps-0 bg-body-secondary">
|
<div #navigator class="container-fluid pt-1 pb-1 pe-0 ps-0 bg-body-secondary">
|
||||||
<nav class="d-md-flex row" aria-label="breadcrumb">
|
<nav class="d-md-flex row" aria-label="breadcrumb">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<ol *ngIf="isDirectory" id="directory-path" class="mb-0 mt-1 breadcrumb">
|
<ol *ngIf="isDirectory" id="directory-path" class="mb-0 mt-1 breadcrumb">
|
||||||
<li *ngFor="let path of routes | async" class="breadcrumb-item">
|
<li *ngFor="let path of routes | async" class="breadcrumb-item">
|
||||||
<a *ngIf="path.route" [routerLink]="['/gallery',path.route]"
|
<a *ngIf="path.route" [routerLink]="['/gallery',path.route]"
|
||||||
[title]="path.title || ''"
|
[title]="path.title || ''"
|
||||||
[queryParams]="queryService.getParams()">{{path.name}}</a>
|
[queryParams]="queryService.getParams()">{{path.name}}</a>
|
||||||
<ng-container *ngIf="!path.route">{{path.name}}</ng-container>
|
<ng-container *ngIf="!path.route">{{path.name}}</ng-container>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<ol *ngIf="isSearch" class="mb-0 mt-1 breadcrumb">
|
<ol *ngIf="isSearch" class="mb-0 mt-1 breadcrumb">
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<ng-container i18n>Searching for:</ng-container>
|
<ng-container i18n>Searching for:</ng-container>
|
||||||
<strong> {{contentLoaderService.content.value?.searchResult?.searchQuery | searchQuery}}</strong>
|
<strong> {{contentLoaderService.content.value?.searchResult?.searchQuery | searchQuery}}</strong>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="ms-auto text-end col-auto">
|
|
||||||
<ng-container *ngIf="ItemCount> 0 && config.Gallery.NavBar.showItemCount">
|
|
||||||
<div class="photos-count">
|
|
||||||
{{ItemCount}} <span i18n>items</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"> </div>
|
<div class="ms-auto text-end col-auto">
|
||||||
</ng-container>
|
<ng-container *ngIf="ItemCount> 0 && config.Gallery.NavBar.showItemCount">
|
||||||
|
<div class="photos-count">
|
||||||
|
{{ItemCount}} <span i18n>items</span>
|
||||||
|
</div>
|
||||||
|
<div class="divider"> </div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="config.Gallery.enableDownloadZip && isDirectory && ItemCount > 0">
|
<ng-container *ngIf="config.Gallery.enableDownloadZip && isDirectory && ItemCount > 0">
|
||||||
<a [href]="getDownloadZipLink()"
|
<a [href]="getDownloadZipLink()"
|
||||||
class="btn btn-outline-secondary btn-navigator">
|
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>
|
</a>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="config.Gallery.enableDirectoryFlattening && isDirectory && authService.canSearch()">
|
<ng-container *ngIf="config.Gallery.enableDirectoryFlattening && isDirectory && authService.canSearch()">
|
||||||
<a
|
<a
|
||||||
[routerLink]="['/search', getDirectoryFlattenSearchQuery()]"
|
[routerLink]="['/search', getDirectoryFlattenSearchQuery()]"
|
||||||
class="btn btn-outline-secondary btn-navigator">
|
class="btn btn-outline-secondary btn-navigator">
|
||||||
<ng-icon name="ionGitBranchOutline"
|
<ng-icon name="ionGitBranchOutline"
|
||||||
title="Show all subdirectories" i18n-title></ng-icon>
|
title="Show all subdirectories" i18n-title></ng-icon>
|
||||||
</a>
|
</a>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="ItemCount> 0">
|
<ng-container *ngIf="ItemCount> 0">
|
||||||
<a class="btn btn-outline-secondary btn-navigator"
|
<a class="btn btn-outline-secondary btn-navigator"
|
||||||
[class.btn-secondary]="filterService.activeFilters.value.areFiltersActive"
|
[class.btn-secondary]="filterService.activeFilters.value.areFiltersActive"
|
||||||
[class.btn-outline-secondary]="!filterService.activeFilters.value.areFiltersActive"
|
[class.btn-outline-secondary]="!filterService.activeFilters.value.areFiltersActive"
|
||||||
(click)="showFilters = ! showFilters">
|
(click)="showFilters = ! showFilters">
|
||||||
<ng-icon name="ionFunnelOutline"
|
<ng-icon name="ionFunnelOutline"
|
||||||
title="Filters" i18n-title></ng-icon>
|
title="Filters" i18n-title></ng-icon>
|
||||||
</a>
|
</a>
|
||||||
<div class="divider"> </div>
|
<div class="divider"> </div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="btn-group" dropdown #dropdown="bs-dropdown" placement="bottom right"
|
<div class="btn-group" dropdown #dropdown="bs-dropdown" placement="bottom right"
|
||||||
[insideClick]="true"
|
[insideClick]="true"
|
||||||
title="Sort and group" i18n-title>
|
title="Sort and group" i18n-title>
|
||||||
<button id="button-alignment" dropdownToggle type="button"
|
<button id="button-alignment" dropdownToggle type="button"
|
||||||
class="btn dropdown-toggle btn-outline-secondary btn-navigator"
|
class="btn dropdown-toggle btn-outline-secondary btn-navigator"
|
||||||
[class.btn-secondary]="!isDefaultSortingAndGrouping()"
|
[class.btn-secondary]="!isDefaultSortingAndGrouping()"
|
||||||
[class.btn-outline-secondary]="isDefaultSortingAndGrouping()"
|
[class.btn-outline-secondary]="isDefaultSortingAndGrouping()"
|
||||||
aria-controls="sorting-dropdown">
|
aria-controls="sorting-dropdown">
|
||||||
<ng-icon *ngIf="sortingService.sorting.value.ascending !== null"
|
<ng-icon *ngIf="sortingService.sorting.value.ascending !== null"
|
||||||
[name]="!sortingService.sorting.value.ascending ? 'ionArrowDownOutline' : 'ionArrowUpOutline'"></ng-icon>
|
[name]="!sortingService.sorting.value.ascending ? 'ionArrowDownOutline' : 'ionArrowUpOutline'"></ng-icon>
|
||||||
<app-sorting-method-icon [method]="sortingService.sorting.value.method"></app-sorting-method-icon>
|
<app-sorting-method-icon [method]="sortingService.sorting.value.method"></app-sorting-method-icon>
|
||||||
<div class="grouping-icon" *ngIf="sortingService.grouping.value.method !== GroupByTypes.NoGrouping">
|
<div class="grouping-icon" *ngIf="sortingService.grouping.value.method !== GroupByTypes.NoGrouping">
|
||||||
<div>
|
<div>
|
||||||
<ng-icon
|
<ng-icon
|
||||||
[name]="!sortingService.grouping.value.ascending ? 'ionArrowDownOutline' : 'ionArrowUpOutline'"></ng-icon>
|
[name]="!sortingService.grouping.value.ascending ? 'ionArrowDownOutline' : 'ionArrowUpOutline'"></ng-icon>
|
||||||
<app-sorting-method-icon [method]="sortingService.grouping.value.method"></app-sorting-method-icon>
|
<app-sorting-method-icon
|
||||||
</div>
|
[method]="sortingService.grouping.value.method"></app-sorting-method-icon>
|
||||||
<div class="ps-1" i18n>
|
</div>
|
||||||
group
|
<div class="ps-1" i18n>
|
||||||
</div>
|
group
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
<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 p-1 border-end">
|
|
||||||
<h6 class="ps-2" i18n>Sorting
|
|
||||||
<button class="btn btn-outline-primary btn-group-follow btn-sm"
|
|
||||||
[class.btn-outline-primary]="groupingFollowSorting"
|
|
||||||
[class.btn-outline-secondary]="!groupingFollowSorting"
|
|
||||||
(click)="groupingFollowSorting=!groupingFollowSorting"
|
|
||||||
title="Grouping follows sorting" i18n-title>
|
|
||||||
<ng-icon class="" name="ionLinkOutline"></ng-icon>
|
|
||||||
</button>
|
</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 p-1 border-end">
|
||||||
|
<h6 class="ps-2" i18n>Sorting
|
||||||
|
<button class="btn btn-outline-primary btn-group-follow btn-sm"
|
||||||
|
[class.btn-outline-primary]="groupingFollowSorting"
|
||||||
|
[class.btn-outline-secondary]="!groupingFollowSorting"
|
||||||
|
(click)="groupingFollowSorting=!groupingFollowSorting"
|
||||||
|
title="Grouping follows sorting" i18n-title>
|
||||||
|
<ng-icon class="" name="ionLinkOutline"></ng-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<div class="row">
|
<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"
|
[class.active]="sortingService.sorting.value.method == type.key"
|
||||||
*ngFor="let type of sortingByTypes"
|
*ngFor="let type of sortingByTypes"
|
||||||
(click)="setSortingBy(type.key)">
|
(click)="setSortingBy(type.key)">
|
||||||
<div class="me-2 d-inline-block">
|
<div class="me-2 d-inline-block">
|
||||||
<app-sorting-method-icon [method]="type.key"></app-sorting-method-icon>
|
<app-sorting-method-icon [method]="type.key"></app-sorting-method-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-inline-block">{{type.key | stringifySorting}}</div>
|
<div class="d-inline-block">{{type.key | stringifySorting}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="isDirectionalSort(sortingService.sorting.value.method)">
|
||||||
|
<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="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>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col p-1">
|
||||||
|
<h6 class="ps-2" i18n>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 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>
|
||||||
|
<ng-container *ngIf="isDirectionalSort(sortingService.grouping.value.method)">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<div class="d-inline-block" i18n>descending</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="isDirectionalSort(sortingService.sorting.value.method)">
|
|
||||||
<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="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>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col p-1">
|
<div class="divider"> </div>
|
||||||
<h6 class="ps-2" i18n>Grouping</h6>
|
<div class="btn-group" dropdown #dropdown="bs-dropdown" placement="bottom right"
|
||||||
<div class="row">
|
[insideClick]="true"
|
||||||
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
|
title="Grid size" i18n-title>
|
||||||
[class.active]="sortingService.grouping.value.method == type.key"
|
<button id="button-grid-size" dropdownToggle type="button"
|
||||||
*ngFor="let type of groupingByTypes"
|
class="btn dropdown-toggle btn-outline-secondary btn-navigator"
|
||||||
(click)="setGroupingBy(type.key)">
|
[class.btn-secondary]="!navigatorService.isDefaultGridSize()"
|
||||||
<div class="me-2 d-inline-block">
|
[class.btn-outline-secondary]="navigatorService.isDefaultGridSize()"
|
||||||
<app-sorting-method-icon [method]="type.key"></app-sorting-method-icon>
|
aria-controls="grid-size-dropdown">
|
||||||
</div>
|
<app-grid-size-icon [method]="navigatorService.girdSize.value"></app-grid-size-icon>
|
||||||
<div class="d-inline-block">{{type.key | stringifySorting}}</div>
|
</button>
|
||||||
|
<div id="grid-size-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right"
|
||||||
|
role="menu" aria-labelledby="button-alignment">
|
||||||
|
<h6 class="ps-2" i18n>Grid size</h6>
|
||||||
|
<div class="dropdown-item sorting-grouping-item ps-3 pe-3" role="menuitem"
|
||||||
|
[class.active]="navigatorService.girdSize.value == type.key"
|
||||||
|
*ngFor="let type of gridSizes"
|
||||||
|
(click)="navigatorService.setGridSize(type.key)">
|
||||||
|
<div class="me-2 d-inline-block">
|
||||||
|
<app-grid-size-icon [method]="type.key"></app-grid-size-icon>
|
||||||
|
</div>
|
||||||
|
<div class="d-inline-block">{{type.key | stringifyGridSize}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="isDirectionalSort(sortingService.grouping.value.method)">
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<div class="d-inline-block" i18n>descending</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,6 +15,8 @@ import {PageHelper} from '../../../model/page.helper';
|
|||||||
import {BsDropdownDirective} from 'ngx-bootstrap/dropdown';
|
import {BsDropdownDirective} from 'ngx-bootstrap/dropdown';
|
||||||
import {FilterService} from '../filter/filter.service';
|
import {FilterService} from '../filter/filter.service';
|
||||||
import {ContentLoaderService, ContentWrapperWithError, DirectoryContent} from '../contentLoader.service';
|
import {ContentLoaderService, ContentWrapperWithError, DirectoryContent} from '../contentLoader.service';
|
||||||
|
import {GalleryNavigatorService} from './navigator.service';
|
||||||
|
import {GridSizes} from '../../../../../common/entities/GridSizes';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-navbar',
|
selector: 'app-gallery-navbar',
|
||||||
@ -25,6 +27,7 @@ import {ContentLoaderService, ContentWrapperWithError, DirectoryContent} from '.
|
|||||||
export class GalleryNavigatorComponent {
|
export class GalleryNavigatorComponent {
|
||||||
public readonly sortingByTypes: { key: number; value: string }[] = [];
|
public readonly sortingByTypes: { key: number; value: string }[] = [];
|
||||||
public readonly groupingByTypes: { key: number; value: string }[] = [];
|
public readonly groupingByTypes: { key: number; value: string }[] = [];
|
||||||
|
public readonly gridSizes: { key: number; value: string }[] = [];
|
||||||
public readonly config = Config;
|
public readonly config = Config;
|
||||||
// DefaultSorting = Config.Gallery.defaultPhotoSortingMethod;
|
// DefaultSorting = Config.Gallery.defaultPhotoSortingMethod;
|
||||||
public readonly SearchQueryTypes = SearchQueryTypes;
|
public readonly SearchQueryTypes = SearchQueryTypes;
|
||||||
@ -45,81 +48,83 @@ export class GalleryNavigatorComponent {
|
|||||||
public groupingFollowSorting = true; // if grouping should be set after sorting automatically
|
public groupingFollowSorting = true; // if grouping should be set after sorting automatically
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public authService: AuthenticationService,
|
public authService: AuthenticationService,
|
||||||
public queryService: QueryService,
|
public queryService: QueryService,
|
||||||
public contentLoaderService: ContentLoaderService,
|
public contentLoaderService: ContentLoaderService,
|
||||||
public filterService: FilterService,
|
public filterService: FilterService,
|
||||||
public sortingService: GallerySortingService,
|
public sortingService: GallerySortingService,
|
||||||
private router: Router,
|
public navigatorService: GalleryNavigatorService,
|
||||||
public sanitizer: DomSanitizer
|
private router: Router,
|
||||||
|
public sanitizer: DomSanitizer
|
||||||
) {
|
) {
|
||||||
this.sortingByTypes = Utils.enumToArray(SortByTypes);
|
this.sortingByTypes = Utils.enumToArray(SortByTypes);
|
||||||
// can't group by random
|
// can't group by random
|
||||||
this.groupingByTypes = Utils.enumToArray(GroupByTypes);
|
this.groupingByTypes = Utils.enumToArray(GroupByTypes);
|
||||||
|
this.gridSizes = Utils.enumToArray(GridSizes);
|
||||||
this.RootFolderName = $localize`Home`;
|
this.RootFolderName = $localize`Home`;
|
||||||
this.wrappedContent = this.contentLoaderService.content;
|
this.wrappedContent = this.contentLoaderService.content;
|
||||||
this.directoryContent = this.wrappedContent.pipe(
|
this.directoryContent = this.wrappedContent.pipe(
|
||||||
map((c) => (c.directory ? c.directory : c.searchResult))
|
map((c) => (c.directory ? c.directory : c.searchResult))
|
||||||
);
|
);
|
||||||
this.routes = this.contentLoaderService.content.pipe(
|
this.routes = this.contentLoaderService.content.pipe(
|
||||||
map((c) => {
|
map((c) => {
|
||||||
this.parentPath = null;
|
this.parentPath = null;
|
||||||
if (!c.directory) {
|
if (!c.directory) {
|
||||||
return [];
|
return [];
|
||||||
}
|
|
||||||
|
|
||||||
const path = c.directory.path.replace(new RegExp('\\\\', 'g'), '/');
|
|
||||||
|
|
||||||
const dirs = path.split('/');
|
|
||||||
dirs.push(c.directory.name);
|
|
||||||
|
|
||||||
// removing empty strings
|
|
||||||
for (let i = 0; i < dirs.length; i++) {
|
|
||||||
if (!dirs[i] || 0 === dirs[i].length || '.' === dirs[i]) {
|
|
||||||
dirs.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const user = this.authService.user.value;
|
const path = c.directory.path.replace(new RegExp('\\\\', 'g'), '/');
|
||||||
const arr: NavigatorPath[] = [];
|
|
||||||
|
|
||||||
// create root link
|
const dirs = path.split('/');
|
||||||
if (dirs.length === 0) {
|
dirs.push(c.directory.name);
|
||||||
arr.push({name: this.RootFolderName, route: null});
|
|
||||||
} else {
|
|
||||||
arr.push({
|
|
||||||
name: this.RootFolderName,
|
|
||||||
route: UserDTOUtils.isDirectoryPathAvailable('/', user.permissions)
|
|
||||||
? '/'
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// create rest navigation
|
// removing empty strings
|
||||||
dirs.forEach((name, index) => {
|
for (let i = 0; i < dirs.length; i++) {
|
||||||
const route = dirs.slice(0, index + 1).join('/');
|
if (!dirs[i] || 0 === dirs[i].length || '.' === dirs[i]) {
|
||||||
if (dirs.length - 1 === index) {
|
dirs.splice(i, 1);
|
||||||
arr.push({name, route: null});
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.authService.user.value;
|
||||||
|
const arr: NavigatorPath[] = [];
|
||||||
|
|
||||||
|
// create root link
|
||||||
|
if (dirs.length === 0) {
|
||||||
|
arr.push({name: this.RootFolderName, route: null});
|
||||||
} else {
|
} else {
|
||||||
arr.push({
|
arr.push({
|
||||||
name,
|
name: this.RootFolderName,
|
||||||
route: UserDTOUtils.isDirectoryPathAvailable(route, user.permissions)
|
route: UserDTOUtils.isDirectoryPathAvailable('/', user.permissions)
|
||||||
? route
|
? '/'
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// parent directory has a shortcut to navigate to
|
// create rest navigation
|
||||||
if (arr.length >= 2 && arr[arr.length - 2].route) {
|
dirs.forEach((name, index) => {
|
||||||
this.parentPath = arr[arr.length - 2].route;
|
const route = dirs.slice(0, index + 1).join('/');
|
||||||
arr[arr.length - 2].title = $localize`key: alt + up`;
|
if (dirs.length - 1 === index) {
|
||||||
}
|
arr.push({name, route: null});
|
||||||
return arr;
|
} else {
|
||||||
|
arr.push({
|
||||||
|
name,
|
||||||
|
route: UserDTOUtils.isDirectoryPathAvailable(route, user.permissions)
|
||||||
|
? route
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// parent directory has a shortcut to navigate to
|
||||||
|
if (arr.length >= 2 && arr[arr.length - 2].route) {
|
||||||
|
this.parentPath = arr[arr.length - 2].route;
|
||||||
|
arr[arr.length - 2].title = $localize`key: alt + up`;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,18 +139,19 @@ export class GalleryNavigatorComponent {
|
|||||||
get ItemCount(): number {
|
get ItemCount(): number {
|
||||||
const c = this.contentLoaderService.content.value;
|
const c = this.contentLoaderService.content.value;
|
||||||
return c.directory
|
return c.directory
|
||||||
? c.directory.mediaCount
|
? c.directory.mediaCount
|
||||||
: c.searchResult
|
: c.searchResult
|
||||||
? c.searchResult.media.length
|
? c.searchResult.media.length
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefaultSortingAndGrouping(): boolean {
|
isDefaultSortingAndGrouping(): boolean {
|
||||||
return this.sortingService.isDefaultSortingAndGrouping(
|
return this.sortingService.isDefaultSortingAndGrouping(
|
||||||
this.contentLoaderService.content.value
|
this.contentLoaderService.content.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
isDirectionalSort(value: number) {
|
isDirectionalSort(value: number) {
|
||||||
return Utils.isValidEnumInt(SortByDirectionalTypes, value);
|
return Utils.isValidEnumInt(SortByDirectionalTypes, value);
|
||||||
}
|
}
|
||||||
@ -161,8 +167,8 @@ export class GalleryNavigatorComponent {
|
|||||||
this.sortingService.setSorting(s);
|
this.sortingService.setSorting(s);
|
||||||
// you cannot group by random
|
// you cannot group by random
|
||||||
if (!this.isDirectionalSort(sorting) ||
|
if (!this.isDirectionalSort(sorting) ||
|
||||||
// if grouping is disabled, do not update it
|
// if grouping is disabled, do not update it
|
||||||
this.sortingService.grouping.value.method === GroupByTypes.NoGrouping || !this.groupingFollowSorting
|
this.sortingService.grouping.value.method === GroupByTypes.NoGrouping || !this.groupingFollowSorting
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -202,12 +208,12 @@ export class GalleryNavigatorComponent {
|
|||||||
queryParams += e[0] + '=' + e[1];
|
queryParams += e[0] + '=' + e[1];
|
||||||
});
|
});
|
||||||
return Utils.concatUrls(
|
return Utils.concatUrls(
|
||||||
Config.Server.urlBase,
|
Config.Server.urlBase,
|
||||||
Config.Server.apiPath,
|
Config.Server.apiPath,
|
||||||
'/gallery/zip/',
|
'/gallery/zip/',
|
||||||
c.directory.path,
|
c.directory.path,
|
||||||
c.directory.name,
|
c.directory.name,
|
||||||
'?' + queryParams
|
'?' + queryParams
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,8 +235,8 @@ export class GalleryNavigatorComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.router.navigate(['/gallery', this.parentPath],
|
this.router.navigate(['/gallery', this.parentPath],
|
||||||
{queryParams: this.queryService.getParams()})
|
{queryParams: this.queryService.getParams()})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:keydown', ['$event'])
|
@HostListener('window:keydown', ['$event'])
|
||||||
|
61
src/frontend/app/ui/gallery/navigator/navigator.service.ts
Normal file
61
src/frontend/app/ui/gallery/navigator/navigator.service.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {GalleryCacheService} from '../cache.gallery.service';
|
||||||
|
import {BehaviorSubject} from 'rxjs';
|
||||||
|
import {Config} from '../../../../../common/config/public/Config';
|
||||||
|
import {ContentLoaderService} from '../contentLoader.service';
|
||||||
|
import {GridSizes} from '../../../../../common/entities/GridSizes';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GalleryNavigatorService {
|
||||||
|
public girdSize: BehaviorSubject<GridSizes>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private galleryCacheService: GalleryCacheService,
|
||||||
|
private galleryService: ContentLoaderService,
|
||||||
|
) {
|
||||||
|
|
||||||
|
// TODO load def instead
|
||||||
|
this.girdSize = new BehaviorSubject(this.getDefaultGridSize());
|
||||||
|
this.galleryService.content.subscribe((c) => {
|
||||||
|
if (c) {
|
||||||
|
if (c) {
|
||||||
|
const gs = this.galleryCacheService.getGridSize(c);
|
||||||
|
if (gs !== null) {
|
||||||
|
this.girdSize.next(gs);
|
||||||
|
} else {
|
||||||
|
this.girdSize.next(this.getDefaultGridSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setGridSize(gs: GridSizes) {
|
||||||
|
this.girdSize.next(gs);
|
||||||
|
if (this.galleryService.content.value) {
|
||||||
|
if (
|
||||||
|
!this.isDefaultGridSize()
|
||||||
|
) {
|
||||||
|
this.galleryCacheService.setGridSize(
|
||||||
|
this.galleryService.content.value,
|
||||||
|
gs
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.galleryCacheService.removeGridSize(
|
||||||
|
this.galleryService.content.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDefaultGridSize(): boolean {
|
||||||
|
return this.girdSize.value === this.getDefaultGridSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getDefaultGridSize(): GridSizes {
|
||||||
|
return Config.Gallery.defaultGidSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
.line-height-0 {
|
||||||
|
line-height: 0;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<ng-container [ngSwitch]="method">
|
||||||
|
<ng-container *ngSwitchCase="GridSizes.extraSmall">
|
||||||
|
<ng-icon name="ionAppsOutline"></ng-icon>
|
||||||
|
</ng-container>
|
||||||
|
<div class="align-middle" *ngSwitchCase="GridSizes.small">
|
||||||
|
<div class="d-flex" style="margin-bottom: 0.1em">
|
||||||
|
<ng-icon class="line-height-0" strokeWidth="80" size="0.33em" name="ionSquareOutline"></ng-icon>
|
||||||
|
<ng-icon class="line-height-0" strokeWidth="80" size="0.33em" name="ionSquareOutline"></ng-icon>
|
||||||
|
<ng-icon class="line-height-0" strokeWidth="80" size="0.33em" name="ionSquareOutline"></ng-icon>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<ng-icon class="line-height-0" strokeWidth="80" size="0.33em" name="ionSquareOutline"></ng-icon>
|
||||||
|
<ng-icon class="line-height-0" strokeWidth="80" size="0.33em" name="ionSquareOutline"></ng-icon>
|
||||||
|
<ng-icon class="line-height-0" strokeWidth="80" size="0.33em" name="ionSquareOutline"></ng-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngSwitchCase="GridSizes.medium">
|
||||||
|
<ng-icon name="ionGridOutline"></ng-icon>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="GridSizes.large">
|
||||||
|
<ng-icon strokeWidth="70" class="line-height-0 align-middle" size="0.5em" name="ionSquareOutline"></ng-icon>
|
||||||
|
<ng-icon strokeWidth="70" class="line-height-0 align-middle" size="0.5em" name="ionSquareOutline"></ng-icon>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="GridSizes.extraLarge">
|
||||||
|
<ng-icon name="ionSquareOutline"></ng-icon>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
@ -0,0 +1,12 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {GridSizes} from '../../../../../common/entities/GridSizes';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-grid-size-icon',
|
||||||
|
templateUrl: './grid-size-icon.component.html',
|
||||||
|
styleUrls: ['./grid-size-icon.component.css']
|
||||||
|
})
|
||||||
|
export class GridSizeIconComponent {
|
||||||
|
@Input() method: number;
|
||||||
|
public readonly GridSizes = GridSizes;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import {Component, Input} from '@angular/core';
|
import {Component, Input} from '@angular/core';
|
||||||
import {GroupSortByTypes} from '../../../../common/entities/SortingMethods';
|
import {GroupSortByTypes} from '../../../../../common/entities/SortingMethods';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sorting-method-icon',
|
selector: 'app-sorting-method-icon',
|
||||||
@ -8,5 +8,5 @@ import {GroupSortByTypes} from '../../../../common/entities/SortingMethods';
|
|||||||
})
|
})
|
||||||
export class SortingMethodIconComponent {
|
export class SortingMethodIconComponent {
|
||||||
@Input() method: number;
|
@Input() method: number;
|
||||||
GroupSortByTypes = GroupSortByTypes;
|
public readonly GroupSortByTypes = GroupSortByTypes;
|
||||||
}
|
}
|
@ -58,7 +58,7 @@ ng-icon {
|
|||||||
font-size: 1.15em
|
font-size: 1.15em
|
||||||
}
|
}
|
||||||
|
|
||||||
ng-icon svg {
|
ng-icon:not([strokeWidth]) svg {
|
||||||
vertical-align: unset;
|
vertical-align: unset;
|
||||||
--ng-icon__stroke-width: 40;
|
--ng-icon__stroke-width: 40;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user