diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts
index 57bf3f0d..f959c9ac 100644
--- a/src/common/config/public/ClientConfig.ts
+++ b/src/common/config/public/ClientConfig.ts
@@ -39,7 +39,7 @@ export class ClientSearchConfig {
@SubConfigClass()
export class ClientAlbumConfig {
@ConfigProperty()
- enabled: boolean = true;
+ enabled: boolean = false;
}
@SubConfigClass()
diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts
index 4e55f0d8..d2d33406 100644
--- a/src/frontend/app/app.module.ts
+++ b/src/frontend/app/app.module.ts
@@ -103,6 +103,7 @@ import {icon, Marker} from 'leaflet';
import {AlbumsComponent} from './ui/albums/albums.component';
import {AlbumComponent} from './ui/albums/album/album.component';
import {AlbumsService} from './ui/albums/albums.service';
+import {GallerySearchQueryBuilderComponent} from './ui/gallery/search/query-builder/query-bulder.gallery.component';
@Injectable()
@@ -196,6 +197,7 @@ Marker.prototype.options.icon = iconDefault;
GallerySearchComponent,
GallerySearchQueryEntryComponent,
GallerySearchFieldComponent,
+ GallerySearchQueryBuilderComponent,
GalleryShareComponent,
GalleryNavigatorComponent,
GalleryPhotoComponent,
diff --git a/src/frontend/app/ui/albums/albums.component.css b/src/frontend/app/ui/albums/albums.component.css
index 4bc91196..43f801a6 100644
--- a/src/frontend/app/ui/albums/albums.component.css
+++ b/src/frontend/app/ui/albums/albums.component.css
@@ -3,11 +3,27 @@ app-album {
display: inline-block;
}
-.no-item-msg{
+.no-item-msg {
height: 100vh;
text-align: center;
}
-.no-face-msg h2{
+.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;
+}
diff --git a/src/frontend/app/ui/albums/albums.component.html b/src/frontend/app/ui/albums/albums.component.html
index 2f485587..ac6efa9f 100644
--- a/src/frontend/app/ui/albums/albums.component.html
+++ b/src/frontend/app/ui/albums/albums.component.html
@@ -2,15 +2,73 @@
+ [album]="album"
+ [size]="size">
+
+
+
+
+ Add saved search
+
+
-
:( No albums to show.
+ :(
+ No albums to show.
+
+
+
+
+
+
+
diff --git a/src/frontend/app/ui/albums/albums.component.ts b/src/frontend/app/ui/albums/albums.component.ts
index 2b24ec85..28e7e6da 100644
--- a/src/frontend/app/ui/albums/albums.component.ts
+++ b/src/frontend/app/ui/albums/albums.component.ts
@@ -1,5 +1,8 @@
-import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
+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';
@Component({
selector: 'app-albums',
@@ -9,9 +12,14 @@ import {AlbumsService} from './albums.service';
export class AlbumsComponent 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) {
+ constructor(public albumsService: AlbumsService,
+ private modalService: BsModalService) {
this.albumsService.getAlbums().catch(console.error);
}
@@ -20,6 +28,22 @@ export class AlbumsComponent implements OnInit {
this.updateSize();
}
+
+ public async openModal(template: TemplateRef): Promise {
+ 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 {
+ await this.albumsService.addSavedSearch(this.savedSearch.name, this.savedSearch.searchQuery);
+ this.hideModal();
+ }
+
private updateSize(): void {
const size = 220 + 5;
// body - container margin
diff --git a/src/frontend/app/ui/gallery/search/query-builder/query-builder.gallery.component.css b/src/frontend/app/ui/gallery/search/query-builder/query-builder.gallery.component.css
new file mode 100644
index 00000000..f63a984a
--- /dev/null
+++ b/src/frontend/app/ui/gallery/search/query-builder/query-builder.gallery.component.css
@@ -0,0 +1,69 @@
+.autocomplete-list {
+ position: absolute;
+ left: 0;
+ top: 34px;
+ background-color: white;
+ width: 100%;
+ border: 1px solid #ccc;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ padding: 5px 0;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+ z-index: 7;
+}
+
+.insert-button {
+ margin-right: -15px;
+ display: none;
+ margin-top: 2px;
+}
+
+.autocomplete-item-selected .insert-button {
+ display: block;
+}
+@media (hover: none) {
+ .insert-button {
+ display: block;
+ }
+}
+
+.autocomplete-item-selected .insert-button:hover {
+ color: black;
+}
+
+.autocomplete-item {
+ cursor: pointer;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ font-size: 17px;
+}
+
+.autocomplete-item {
+ color: #333;
+ padding: 0 20px;
+ line-height: 1.42857143;
+ font-weight: 400;
+ display: block;
+}
+
+.autocomplete-item-selected {
+ background-color: #007bff;
+ color: #FFF;
+}
+
+
+.search-text {
+ z-index: 6;
+ width: 100%;
+ background: transparent;
+}
+
+.search-hint {
+ left: 0;
+ z-index: 1;
+ width: 100%;
+ position: absolute;
+ margin-left: 0 !important;
+}
+
diff --git a/src/frontend/app/ui/gallery/search/query-builder/query-builder.gallery.component.html b/src/frontend/app/ui/gallery/search/query-builder/query-builder.gallery.component.html
new file mode 100644
index 00000000..2976710a
--- /dev/null
+++ b/src/frontend/app/ui/gallery/search/query-builder/query-builder.gallery.component.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/src/frontend/app/ui/gallery/search/query-builder/query-bulder.gallery.component.ts b/src/frontend/app/ui/gallery/search/query-builder/query-bulder.gallery.component.ts
new file mode 100644
index 00000000..24237a5b
--- /dev/null
+++ b/src/frontend/app/ui/gallery/search/query-builder/query-bulder.gallery.component.ts
@@ -0,0 +1,82 @@
+import {Component, EventEmitter, forwardRef, Output} from '@angular/core';
+import {SearchQueryDTO, SearchQueryTypes, TextSearch} from '../../../../../../common/entities/SearchQueryDTO';
+import {ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator} from '@angular/forms';
+import {SearchQueryParserService} from '../search-query-parser.service';
+
+@Component({
+ selector: 'app-gallery-search-query-builder',
+ templateUrl: './query-builder.gallery.component.html',
+ styleUrls: ['./query-builder.gallery.component.css'],
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => GallerySearchQueryBuilderComponent),
+ multi: true
+ },
+ {
+ provide: NG_VALIDATORS,
+ useExisting: forwardRef(() => GallerySearchQueryBuilderComponent),
+ multi: true
+ }
+ ]
+})
+export class GallerySearchQueryBuilderComponent implements ControlValueAccessor, Validator {
+ public searchQueryDTO: SearchQueryDTO = {type: SearchQueryTypes.any_text, text: ''} as TextSearch;
+ @Output() search = new EventEmitter();
+ public rawSearchText = '';
+
+
+ constructor(
+ private searchQueryParserService: SearchQueryParserService) {
+ }
+
+
+ validateRawSearchText(): void {
+ try {
+ this.searchQueryDTO = this.searchQueryParserService.parse(this.rawSearchText);
+ this.onChange();
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+
+ resetQuery(): void {
+ this.searchQueryDTO = ({text: '', type: SearchQueryTypes.any_text} as TextSearch);
+ }
+
+ onQueryChange(): void {
+ this.rawSearchText = this.searchQueryParserService.stringify(this.searchQueryDTO);
+ this.onChange();
+ }
+
+ validate(control: FormControl): ValidationErrors {
+ return {required: true};
+ }
+
+ public onTouched(): void {
+ }
+
+ public writeValue(obj: any): void {
+ this.searchQueryDTO = obj;
+ }
+
+ registerOnChange(fn: (_: any) => void): void {
+ this.propagateChange = fn;
+ }
+
+ registerOnTouched(fn: () => void): void {
+ this.propagateTouch = fn;
+ }
+
+ public onChange(): void {
+ this.propagateChange(this.searchQueryDTO);
+ }
+
+
+ private propagateChange = (_: any): void => {
+ };
+
+ private propagateTouch = (_: any): void => {
+ };
+}
diff --git a/src/frontend/app/ui/gallery/search/search.gallery.component.html b/src/frontend/app/ui/gallery/search/search.gallery.component.html
index 7643a825..92257f0a 100644
--- a/src/frontend/app/ui/gallery/search/search.gallery.component.html
+++ b/src/frontend/app/ui/gallery/search/search.gallery.component.html
@@ -36,24 +36,19 @@