mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
Reading saved searches from .saved_searches.pg2conf
#45
This commit is contained in:
parent
77b2d7e92a
commit
a9e88f1b7d
@ -5,7 +5,14 @@ export interface IAlbumManager {
|
|||||||
/**
|
/**
|
||||||
* Creates a saved search type of album
|
* Creates a saved search type of album
|
||||||
*/
|
*/
|
||||||
addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise<void>;
|
addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise<void>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a saved search type of album if the album is not yet exists
|
||||||
|
* lockAlbum: Album cannot be removed from the UI
|
||||||
|
*/
|
||||||
|
addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an album
|
* Deletes an album
|
||||||
|
@ -3,10 +3,12 @@ import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
|||||||
import {IAlbumManager} from '../interfaces/IAlbumManager';
|
import {IAlbumManager} from '../interfaces/IAlbumManager';
|
||||||
|
|
||||||
export class AlbumManager implements IAlbumManager {
|
export class AlbumManager implements IAlbumManager {
|
||||||
|
addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise<void> {
|
||||||
public async addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise<void> {
|
|
||||||
throw new Error('not supported by memory DB');
|
throw new Error('not supported by memory DB');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise<void> {
|
||||||
|
throw new Error('not supported by memory DB');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteAlbum(id: number): Promise<void> {
|
public async deleteAlbum(id: number): Promise<void> {
|
||||||
|
@ -7,10 +7,11 @@ import {ProjectPath} from '../../../ProjectPath';
|
|||||||
import {Config} from '../../../../common/config/private/Config';
|
import {Config} from '../../../../common/config/private/Config';
|
||||||
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
||||||
import {ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig';
|
import {ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig';
|
||||||
|
import {ServerPG2ConfMap} from '../../../../common/PG2ConfMap';
|
||||||
|
|
||||||
export class GalleryManager implements IGalleryManager {
|
export class GalleryManager implements IGalleryManager {
|
||||||
|
|
||||||
public listDirectory(relativeDirectoryName: string, knownLastModified?: number, knownLastScanned?: number): Promise<DirectoryDTO> {
|
public async listDirectory(relativeDirectoryName: string, knownLastModified?: number, knownLastScanned?: number): Promise<DirectoryDTO> {
|
||||||
// If it seems that the content did not changed, do not work on it
|
// If it seems that the content did not changed, do not work on it
|
||||||
if (knownLastModified && knownLastScanned) {
|
if (knownLastModified && knownLastScanned) {
|
||||||
const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName));
|
const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName));
|
||||||
@ -21,7 +22,9 @@ export class GalleryManager implements IGalleryManager {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DiskManager.scanDirectory(relativeDirectoryName);
|
const dir = await DiskManager.scanDirectory(relativeDirectoryName);
|
||||||
|
dir.metaFile = dir.metaFile.filter(m => !ServerPG2ConfMap[m.name]);
|
||||||
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import {ObjectManagers} from '../../ObjectManagers';
|
|||||||
import {ISQLSearchManager} from './ISearchManager';
|
import {ISQLSearchManager} from './ISearchManager';
|
||||||
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
||||||
import {SavedSearchEntity} from './enitites/album/SavedSearchEntity';
|
import {SavedSearchEntity} from './enitites/album/SavedSearchEntity';
|
||||||
import { IAlbumManager } from '../interfaces/IAlbumManager';
|
import {IAlbumManager} from '../interfaces/IAlbumManager';
|
||||||
|
|
||||||
export class AlbumManager implements IAlbumManager{
|
export class AlbumManager implements IAlbumManager {
|
||||||
private static async fillPreviewToAlbum(album: AlbumBaseDTO): Promise<void> {
|
private static async fillPreviewToAlbum(album: AlbumBaseDTO): Promise<void> {
|
||||||
if (!(album as SavedSearchDTO).searchQuery) {
|
if (!(album as SavedSearchDTO).searchQuery) {
|
||||||
throw new Error('no search query present');
|
throw new Error('no search query present');
|
||||||
@ -17,15 +17,31 @@ export class AlbumManager implements IAlbumManager{
|
|||||||
.getPreview((album as SavedSearchDTO).searchQuery);
|
.getPreview((album as SavedSearchDTO).searchQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise<void> {
|
public async addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum: boolean): Promise<void> {
|
||||||
const connection = await SQLConnection.getConnection();
|
const connection = await SQLConnection.getConnection();
|
||||||
await connection.getRepository(SavedSearchEntity).insert({name, searchQuery});
|
const album = await connection.getRepository(SavedSearchEntity)
|
||||||
|
.findOne({name, searchQuery});
|
||||||
|
if (album) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addSavedSearch(name, searchQuery, lockedAlbum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise<void> {
|
||||||
|
const connection = await SQLConnection.getConnection();
|
||||||
|
await connection.getRepository(SavedSearchEntity).insert({name, searchQuery, locked: lockedAlbum});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteAlbum(id: number): Promise<void> {
|
public async deleteAlbum(id: number): Promise<void> {
|
||||||
const connection = await SQLConnection.getConnection();
|
const connection = await SQLConnection.getConnection();
|
||||||
await connection.getRepository(AlbumBaseEntity).delete({id});
|
|
||||||
|
if (await connection.getRepository(AlbumBaseEntity)
|
||||||
|
.count({id, locked: false}) !== 1) {
|
||||||
|
throw new Error('Could not delete album, id:' + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.getRepository(AlbumBaseEntity).delete({id, locked: false});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAlbums(): Promise<AlbumBaseDTO[]> {
|
public async getAlbums(): Promise<AlbumBaseDTO[]> {
|
||||||
|
@ -17,6 +17,11 @@ import {ObjectManagers} from '../../ObjectManagers';
|
|||||||
import {IIndexingManager} from '../interfaces/IIndexingManager';
|
import {IIndexingManager} from '../interfaces/IIndexingManager';
|
||||||
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
||||||
import {Logger} from '../../../Logger';
|
import {Logger} from '../../../Logger';
|
||||||
|
import {ServerPG2ConfMap, ServerSidePG2ConfAction} from '../../../../common/PG2ConfMap';
|
||||||
|
import {ProjectPath} from '../../../ProjectPath';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
||||||
|
|
||||||
const LOG_TAG = '[IndexingManager]';
|
const LOG_TAG = '[IndexingManager]';
|
||||||
|
|
||||||
@ -31,6 +36,21 @@ export class IndexingManager implements IIndexingManager {
|
|||||||
return this.SavingReady !== null;
|
return this.SavingReady !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async processServerSidePG2Conf(files: FileDTO[]): Promise<void> {
|
||||||
|
for (const f of files) {
|
||||||
|
if (ServerPG2ConfMap[f.name] === ServerSidePG2ConfAction.SAVED_SEARCH) {
|
||||||
|
const fullMediaPath = path.join(ProjectPath.ImageFolder, f.directory.path, f.directory.name, f.name);
|
||||||
|
|
||||||
|
Logger.silly(LOG_TAG, 'Saving saved searches to DB from:', fullMediaPath);
|
||||||
|
const savedSearches: { name: string, searchQuery: SearchQueryDTO }[] =
|
||||||
|
JSON.parse(await fs.promises.readFile(fullMediaPath, 'utf8'));
|
||||||
|
for (const s of savedSearches) {
|
||||||
|
await ObjectManagers.getInstance().AlbumManager.addIfNotExistSavedSearch(s.name, s.searchQuery, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
|
public indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
|
||||||
return new Promise(async (resolve, reject): Promise<void> => {
|
return new Promise(async (resolve, reject): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@ -41,8 +61,17 @@ export class IndexingManager implements IIndexingManager {
|
|||||||
scannedDirectory.preview.readyThumbnails = [];
|
scannedDirectory.preview.readyThumbnails = [];
|
||||||
}
|
}
|
||||||
scannedDirectory.media.forEach((p): any[] => p.readyThumbnails = []);
|
scannedDirectory.media.forEach((p): any[] => p.readyThumbnails = []);
|
||||||
|
|
||||||
|
// filter server side pg2conf
|
||||||
|
const serverSideConfs = scannedDirectory.metaFile.filter(m => ServerPG2ConfMap[m.name]);
|
||||||
|
scannedDirectory.metaFile = scannedDirectory.metaFile.filter(m => !ServerPG2ConfMap[m.name]);
|
||||||
|
|
||||||
resolve(scannedDirectory);
|
resolve(scannedDirectory);
|
||||||
|
|
||||||
|
// process server side pg2conf
|
||||||
|
await IndexingManager.processServerSidePG2Conf(serverSideConfs);
|
||||||
|
|
||||||
|
// save directory to DB
|
||||||
this.queueForSave(scannedDirectory).catch(console.error);
|
this.queueForSave(scannedDirectory).catch(console.error);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -15,6 +15,12 @@ export class AlbumBaseEntity implements AlbumBaseDTO {
|
|||||||
@Column(columnCharsetCS)
|
@Column(columnCharsetCS)
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locked albums are not possible to remove
|
||||||
|
*/
|
||||||
|
@Column({default: false})
|
||||||
|
locked: boolean;
|
||||||
|
|
||||||
// not saving to database, it is only assigned when querying the DB
|
// not saving to database, it is only assigned when querying the DB
|
||||||
public preview: MediaEntity;
|
public preview: MediaEntity;
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
export const DataStructureVersion = 22;
|
export const DataStructureVersion = 23;
|
||||||
|
@ -14,3 +14,18 @@ export const PG2ConfMap = {
|
|||||||
'.order_random.pg2conf': SortingMethods.random
|
'.order_random.pg2conf': SortingMethods.random
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These files are processed on the server side,
|
||||||
|
* do not get passed down to the client or saved to the DB
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export enum ServerSidePG2ConfAction {
|
||||||
|
SAVED_SEARCH = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServerPG2ConfMap: { [key: string]: ServerSidePG2ConfAction } = {
|
||||||
|
'.saved_searches.pg2conf': ServerSidePG2ConfAction.SAVED_SEARCH
|
||||||
|
};
|
||||||
|
|
||||||
|
@ -20,13 +20,14 @@ export const SupportedFormats = {
|
|||||||
// These formats need to be transcoded (with the build-in ffmpeg support)
|
// These formats need to be transcoded (with the build-in ffmpeg support)
|
||||||
TranscodeNeed: {
|
TranscodeNeed: {
|
||||||
// based on libvips, all supported formats for sharp: https://github.com/libvips/libvips
|
// based on libvips, all supported formats for sharp: https://github.com/libvips/libvips
|
||||||
// all supported formats for gm: http://www.graphicsmagick.org/GraphicsMagick.html
|
|
||||||
Photos: [] as string[],
|
Photos: [] as string[],
|
||||||
Videos: [
|
Videos: [
|
||||||
'avi', 'mkv', 'mov', 'wmv', 'flv', 'mts', 'm2ts', 'mpg', '3gp', 'm4v', 'mpeg', 'vob',
|
'avi', 'mkv', 'mov', 'wmv', 'flv', 'mts', 'm2ts', 'mpg', '3gp', 'm4v', 'mpeg', 'vob',
|
||||||
'divx', 'xvid', 'ts'
|
'divx', 'xvid', 'ts'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// --------------------------------------------
|
||||||
|
// Below this, it is autogenerated, DO NOT EDIT
|
||||||
WithDots: {
|
WithDots: {
|
||||||
Photos: [] as string[],
|
Photos: [] as string[],
|
||||||
Videos: [] as string[],
|
Videos: [] as string[],
|
||||||
|
@ -3,5 +3,6 @@ import {PreviewPhotoDTO} from '../PhotoDTO';
|
|||||||
export interface AlbumBaseDTO {
|
export interface AlbumBaseDTO {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
preview: PreviewPhotoDTO;
|
preview?: PreviewPhotoDTO;
|
||||||
|
locked: boolean;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ import {SearchQueryDTO} from '../SearchQueryDTO';
|
|||||||
export interface SavedSearchDTO extends AlbumBaseDTO {
|
export interface SavedSearchDTO extends AlbumBaseDTO {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
preview: PreviewPhotoDTO;
|
preview?: PreviewPhotoDTO;
|
||||||
|
locked: boolean;
|
||||||
|
|
||||||
searchQuery: SearchQueryDTO;
|
searchQuery: SearchQueryDTO;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
.delete {
|
.info-button{
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.delete {
|
.delete {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all .05s ease-in-out;
|
transition: all .05s ease-in-out;
|
||||||
|
@ -19,10 +19,14 @@
|
|||||||
<!--Info box -->
|
<!--Info box -->
|
||||||
<div class="info">
|
<div class="info">
|
||||||
{{album.name}}
|
{{album.name}}
|
||||||
<span *ngIf="CanUpdate"
|
<span *ngIf="CanUpdate && !album.locked"
|
||||||
(click)="deleteAlbum($event)"
|
(click)="deleteAlbum($event)"
|
||||||
class="delete oi oi-trash float-right"></span>
|
class="info-button delete oi oi-trash float-right"></span>
|
||||||
|
|
||||||
|
<span *ngIf="album.locked"
|
||||||
|
title="Album is locked, cannot be deleted from the webpage."
|
||||||
|
i18n-title
|
||||||
|
class="info-button oi oi-lock-locked float-right"></span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
[size]="size"></app-album>
|
[size]="size"></app-album>
|
||||||
|
|
||||||
<div class="add-saved-search btn btn-secondary"
|
<div class="add-saved-search btn btn-secondary"
|
||||||
|
*ngIf="CanCreateAlbum"
|
||||||
[style.width.px]="size"
|
[style.width.px]="size"
|
||||||
[style.height.px]="size"
|
[style.height.px]="size"
|
||||||
(click)="openModal(modal)">
|
(click)="openModal(modal)">
|
||||||
|
@ -3,6 +3,8 @@ import {AlbumsService} from './albums.service';
|
|||||||
import {BsModalService} from 'ngx-bootstrap/modal';
|
import {BsModalService} from 'ngx-bootstrap/modal';
|
||||||
import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service';
|
import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service';
|
||||||
import {SearchQueryTypes, TextSearch} from '../../../../common/entities/SearchQueryDTO';
|
import {SearchQueryTypes, TextSearch} from '../../../../common/entities/SearchQueryDTO';
|
||||||
|
import {UserRoles} from '../../../../common/entities/UserDTO';
|
||||||
|
import {AuthenticationService} from '../../model/network/authentication.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-albums',
|
selector: 'app-albums',
|
||||||
@ -19,7 +21,8 @@ export class AlbumsComponent implements OnInit {
|
|||||||
private modalRef: BsModalRef;
|
private modalRef: BsModalRef;
|
||||||
|
|
||||||
constructor(public albumsService: AlbumsService,
|
constructor(public albumsService: AlbumsService,
|
||||||
private modalService: BsModalService) {
|
private modalService: BsModalService,
|
||||||
|
public authenticationService: AuthenticationService) {
|
||||||
this.albumsService.getAlbums().catch(console.error);
|
this.albumsService.getAlbums().catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +32,11 @@ export class AlbumsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get CanCreateAlbum(): boolean {
|
||||||
|
return this.authenticationService.user.getValue().role >= UserRoles.Admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async openModal(template: TemplateRef<any>): Promise<void> {
|
public async openModal(template: TemplateRef<any>): Promise<void> {
|
||||||
this.modalRef = this.modalService.show(template, {class: 'modal-lg'});
|
this.modalRef = this.modalService.show(template, {class: 'modal-lg'});
|
||||||
document.body.style.paddingRight = '0px';
|
document.body.style.paddingRight = '0px';
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
app-album {
|
||||||
|
margin: 2px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-item-msg {
|
||||||
|
height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-face-msg h2 {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-saved-search {
|
||||||
|
vertical-align: baseline;
|
||||||
|
position: absolute;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-saved-search .text {
|
||||||
|
position: relative;
|
||||||
|
top: calc(50% - 40px);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-saved-search .text .oi {
|
||||||
|
font-size: 80px;
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<app-frame>
|
||||||
|
|
||||||
|
<div body #container class="container-fluid">
|
||||||
|
<app-album *ngFor="let album of albumsService.albums | async"
|
||||||
|
[album]="album"
|
||||||
|
[size]="size"></app-album>
|
||||||
|
|
||||||
|
<div class="add-saved-search btn btn-secondary"
|
||||||
|
*ngIf="CanCreateAlbum"
|
||||||
|
[style.width.px]="size"
|
||||||
|
[style.height.px]="size"
|
||||||
|
(click)="openModal(modal)">
|
||||||
|
<div class="text">
|
||||||
|
<span class="oi oi-plus" aria-hidden="true"> </span><br/>
|
||||||
|
<span i18n>Add saved search</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex no-item-msg"
|
||||||
|
*ngIf="(albumsService.albums | async) && (albumsService.albums | async).length == 0">
|
||||||
|
<div class="flex-fill">
|
||||||
|
<h2>:(
|
||||||
|
<ng-container i18n>No albums to show.</ng-container>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</app-frame>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #modal>
|
||||||
|
<!-- sharing Modal-->
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" i18n>Add Saved Search</h5>
|
||||||
|
<button type="button" class="close" (click)="hideModal()" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form #savedSearchPanelForm="ngForm" class="form-horizontal">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="saveSearchName">Album name</label>
|
||||||
|
<input
|
||||||
|
id="saveSearchName"
|
||||||
|
name="saveSearchName"
|
||||||
|
placeholder="Search text"
|
||||||
|
class="form-control input-md"
|
||||||
|
[(ngModel)]="savedSearch.name"
|
||||||
|
type="text"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="album-search-query-builder">Search query</label>
|
||||||
|
<app-gallery-search-query-builder
|
||||||
|
id="album-search-query-builder"
|
||||||
|
name="album-search-query-builder"
|
||||||
|
[(ngModel)]="savedSearch.searchQuery">
|
||||||
|
</app-gallery-search-query-builder>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="input-group-btn float-right row" style="display: block">
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="button"
|
||||||
|
[disabled]="savedSearch.searchQuery.text == ''"
|
||||||
|
(click)="saveSearch()">
|
||||||
|
<span class="oi oi-folder"></span> Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
@ -0,0 +1,64 @@
|
|||||||
|
import {Component, ElementRef, OnInit, TemplateRef, ViewChild} from '@angular/core';
|
||||||
|
import {AlbumsService} from './albums.service';
|
||||||
|
import {BsModalService} from 'ngx-bootstrap/modal';
|
||||||
|
import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service';
|
||||||
|
import {SearchQueryTypes, TextSearch} from '../../../../common/entities/SearchQueryDTO';
|
||||||
|
import {UserRoles} from '../../../../common/entities/UserDTO';
|
||||||
|
import {AuthenticationService} from '../../model/network/authentication.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-albums',
|
||||||
|
templateUrl: './albums.component.html',
|
||||||
|
styleUrls: ['./albums.component.css']
|
||||||
|
})
|
||||||
|
export class SavedSearchPopupComponent implements OnInit {
|
||||||
|
@ViewChild('container', {static: true}) container: ElementRef;
|
||||||
|
public size: number;
|
||||||
|
public savedSearch = {
|
||||||
|
name: '',
|
||||||
|
searchQuery: {type: SearchQueryTypes.any_text, text: ''} as TextSearch
|
||||||
|
};
|
||||||
|
private modalRef: BsModalRef;
|
||||||
|
|
||||||
|
constructor(public albumsService: AlbumsService,
|
||||||
|
private modalService: BsModalService,
|
||||||
|
public authenticationService: AuthenticationService) {
|
||||||
|
this.albumsService.getAlbums().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.updateSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get CanCreateAlbum(): boolean {
|
||||||
|
return this.authenticationService.user.getValue().role >= UserRoles.Admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async openModal(template: TemplateRef<any>): Promise<void> {
|
||||||
|
this.modalRef = this.modalService.show(template, {class: 'modal-lg'});
|
||||||
|
document.body.style.paddingRight = '0px';
|
||||||
|
}
|
||||||
|
|
||||||
|
public hideModal(): void {
|
||||||
|
this.modalRef.hide();
|
||||||
|
this.modalRef = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSearch(): Promise<void> {
|
||||||
|
await this.albumsService.addSavedSearch(this.savedSearch.name, this.savedSearch.searchQuery);
|
||||||
|
this.hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateSize(): void {
|
||||||
|
const size = 220 + 5;
|
||||||
|
// body - container margin
|
||||||
|
const containerWidth = this.container.nativeElement.clientWidth - 30;
|
||||||
|
this.size = (containerWidth / Math.round((containerWidth / size))) - 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<div class="input-group-btn float-right row" style="display: block">
|
<div class="input-group-btn float-right row" style="display: block">
|
||||||
|
|
||||||
<button *ngIf="AlbumsEnabled"
|
<button *ngIf="CanCreateAlbum"
|
||||||
class="btn btn-secondary mr-2" type="button"
|
class="btn btn-secondary mr-2" type="button"
|
||||||
[disabled]="rawSearchText == ''"
|
[disabled]="rawSearchText == ''"
|
||||||
(click)="openSaveSearchModal(saveSearchModal)">
|
(click)="openSaveSearchModal(saveSearchModal)">
|
||||||
|
@ -11,6 +11,8 @@ import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service';
|
|||||||
import {SearchQueryParserService} from './search-query-parser.service';
|
import {SearchQueryParserService} from './search-query-parser.service';
|
||||||
import {AlbumsService} from '../../albums/albums.service';
|
import {AlbumsService} from '../../albums/albums.service';
|
||||||
import {Config} from '../../../../../common/config/public/Config';
|
import {Config} from '../../../../../common/config/public/Config';
|
||||||
|
import {UserRoles} from '../../../../../common/entities/UserDTO';
|
||||||
|
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-search',
|
selector: 'app-gallery-search',
|
||||||
@ -26,7 +28,6 @@ export class GallerySearchComponent implements OnDestroy {
|
|||||||
readonly SearchQueryTypes: typeof SearchQueryTypes;
|
readonly SearchQueryTypes: typeof SearchQueryTypes;
|
||||||
public readonly MetadataSearchQueryTypes: { value: string; key: SearchQueryTypes }[];
|
public readonly MetadataSearchQueryTypes: { value: string; key: SearchQueryTypes }[];
|
||||||
public saveSearchName: string;
|
public saveSearchName: string;
|
||||||
AlbumsEnabled = Config.Client.Album.enabled;
|
|
||||||
private searchModalRef: BsModalRef;
|
private searchModalRef: BsModalRef;
|
||||||
private readonly subscription: Subscription = null;
|
private readonly subscription: Subscription = null;
|
||||||
private saveSearchModalRef: BsModalRef;
|
private saveSearchModalRef: BsModalRef;
|
||||||
@ -38,7 +39,8 @@ export class GallerySearchComponent implements OnDestroy {
|
|||||||
private navigationService: NavigationService,
|
private navigationService: NavigationService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
public router: Router,
|
public router: Router,
|
||||||
private modalService: BsModalService) {
|
private modalService: BsModalService,
|
||||||
|
public authenticationService: AuthenticationService) {
|
||||||
|
|
||||||
this.SearchQueryTypes = SearchQueryTypes;
|
this.SearchQueryTypes = SearchQueryTypes;
|
||||||
this.MetadataSearchQueryTypes = MetadataSearchQueryTypes.map((v) => ({key: v, value: SearchQueryTypes[v]}));
|
this.MetadataSearchQueryTypes = MetadataSearchQueryTypes.map((v) => ({key: v, value: SearchQueryTypes[v]}));
|
||||||
@ -55,6 +57,12 @@ export class GallerySearchComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get CanCreateAlbum(): boolean {
|
||||||
|
return Config.Client.Album.enabled &&
|
||||||
|
this.authenticationService.user.getValue().role >= UserRoles.Admin;
|
||||||
|
}
|
||||||
|
|
||||||
get HTMLSearchQuery(): string {
|
get HTMLSearchQuery(): string {
|
||||||
return JSON.stringify(this.searchQueryDTO);
|
return JSON.stringify(this.searchQueryDTO);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLCo
|
|||||||
import {AlbumBaseEntity} from '../../../../../src/backend/model/database/sql/enitites/album/AlbumBaseEntity';
|
import {AlbumBaseEntity} from '../../../../../src/backend/model/database/sql/enitites/album/AlbumBaseEntity';
|
||||||
import {Utils} from '../../../../../src/common/Utils';
|
import {Utils} from '../../../../../src/common/Utils';
|
||||||
import {MediaDTO} from '../../../../../src/common/entities/MediaDTO';
|
import {MediaDTO} from '../../../../../src/common/entities/MediaDTO';
|
||||||
|
import {SavedSearchDTO} from '../../../../../src/common/entities/album/SavedSearchDTO';
|
||||||
|
|
||||||
|
|
||||||
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
|
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
|
||||||
@ -117,8 +118,9 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => {
|
|||||||
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([{
|
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Test Album',
|
name: 'Test Album',
|
||||||
|
locked: false,
|
||||||
searchQuery: query
|
searchQuery: query
|
||||||
}]);
|
} as SavedSearchDTO]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete album', async () => {
|
it('should delete album', async () => {
|
||||||
@ -129,29 +131,42 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => {
|
|||||||
|
|
||||||
|
|
||||||
await am.addSavedSearch('Test Album', Utils.clone(query));
|
await am.addSavedSearch('Test Album', Utils.clone(query));
|
||||||
await am.addSavedSearch('Test Album2', Utils.clone(query));
|
await am.addSavedSearch('Test Album2', Utils.clone(query), true);
|
||||||
|
|
||||||
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([
|
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Test Album',
|
name: 'Test Album',
|
||||||
|
locked: false,
|
||||||
searchQuery: query
|
searchQuery: query
|
||||||
},
|
} as SavedSearchDTO,
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Test Album2',
|
name: 'Test Album2',
|
||||||
|
locked: true,
|
||||||
searchQuery: query
|
searchQuery: query
|
||||||
}]);
|
} as SavedSearchDTO]);
|
||||||
|
|
||||||
await am.deleteAlbum(1);
|
await am.deleteAlbum(1);
|
||||||
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([{
|
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Test Album2',
|
name: 'Test Album2',
|
||||||
|
locked: true,
|
||||||
searchQuery: query
|
searchQuery: query
|
||||||
}]);
|
} as SavedSearchDTO]);
|
||||||
|
|
||||||
await am.deleteAlbum(2);
|
try {
|
||||||
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([]);
|
await am.deleteAlbum(2);
|
||||||
|
expect(false).to.be.equal(true); // should not reach
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).to.equal('Could not delete album, id:2');
|
||||||
|
}
|
||||||
|
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([{
|
||||||
|
id: 2,
|
||||||
|
name: 'Test Album2',
|
||||||
|
locked: true,
|
||||||
|
searchQuery: query
|
||||||
|
} as SavedSearchDTO]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -166,8 +181,9 @@ describe('AlbumManager', (sqlHelper: DBTestHelper) => {
|
|||||||
id: 1,
|
id: 1,
|
||||||
name: 'Test Album',
|
name: 'Test Album',
|
||||||
searchQuery: query,
|
searchQuery: query,
|
||||||
|
locked: false,
|
||||||
preview: toAlbumPreview(p)
|
preview: toAlbumPreview(p)
|
||||||
}]));
|
} as SavedSearchDTO]));
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user