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

Implementing add saved search button on the albums page #45

This commit is contained in:
Patrik J. Braun 2021-05-28 23:17:29 +02:00
parent c31e7b8e3a
commit fc854f7295
10 changed files with 292 additions and 30 deletions

View File

@ -39,7 +39,7 @@ export class ClientSearchConfig {
@SubConfigClass()
export class ClientAlbumConfig {
@ConfigProperty()
enabled: boolean = true;
enabled: boolean = false;
}
@SubConfigClass()

View File

@ -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,

View File

@ -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;
}

View File

@ -2,15 +2,73 @@
<div body #container class="container-fluid">
<app-album *ngFor="let album of albumsService.albums | async"
[album]="album"
[size]="size"></app-album>
[album]="album"
[size]="size"></app-album>
<div class="add-saved-search btn btn-secondary"
[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>:(
<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">&times;</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>

View File

@ -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<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

View File

@ -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;
}

View File

@ -0,0 +1,15 @@
<app-gallery-search-field [(ngModel)]="rawSearchText"
(ngModelChange)="validateRawSearchText()"
(search)="search.emit()"
name="form-search-field">
</app-gallery-search-field>
<hr>
<app-gallery-search-query-entry
[(ngModel)]="searchQueryDTO"
(change)="onQueryChange()"
(ngModelChange)="onChange()"
name="search-root"
(delete)="resetQuery()">
</app-gallery-search-query-entry>

View File

@ -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<void>();
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 => {
};
}

View File

@ -36,24 +36,19 @@
<div class="modal-body">
<form #searchPanelForm="ngForm" class="form-horizontal">
<app-gallery-search-field [(ngModel)]="rawSearchText"
(ngModelChange)="validateRawSearchText()"
(search)="Search()"
name="form-search-field">
</app-gallery-search-field>
<hr>
<app-gallery-search-query-entry
<app-gallery-search-query-builder
name="search-query-builder"
[(ngModel)]="searchQueryDTO"
(change)="onQueryChange()"
name="search-root"
(delete)="resetQuery()">
(ngModelChange)="onQueryChange()"
(search)="Search()">
</app-gallery-search-query-builder>
</app-gallery-search-query-entry>
<div class="input-group-btn float-right row" style="display: block">
<button class="btn btn-secondary mr-2" type="button"
<button *ngIf="AlbumsEnabled"
class="btn btn-secondary mr-2" type="button"
[disabled]="rawSearchText == ''"
(click)="openSaveSearchModal(saveSearchModal)">
<span class="oi oi-folder"></span> Save

View File

@ -10,6 +10,7 @@ import {BsModalService} from 'ngx-bootstrap/modal';
import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service';
import {SearchQueryParserService} from './search-query-parser.service';
import {AlbumsService} from '../../albums/albums.service';
import {Config} from '../../../../../common/config/public/Config';
@Component({
selector: 'app-gallery-search',
@ -24,10 +25,11 @@ export class GallerySearchComponent implements OnDestroy {
mouseOverAutoComplete = false;
readonly SearchQueryTypes: typeof SearchQueryTypes;
public readonly MetadataSearchQueryTypes: { value: string; key: SearchQueryTypes }[];
public saveSearchName: string;
AlbumsEnabled = Config.Client.Album.enabled;
private searchModalRef: BsModalRef;
private readonly subscription: Subscription = null;
private saveSearchModalRef: BsModalRef;
public saveSearchName: string;
constructor(private autoCompleteService: AutoCompleteService,
private searchQueryParserService: SearchQueryParserService,
@ -69,6 +71,11 @@ export class GallerySearchComponent implements OnDestroy {
document.body.style.paddingRight = '0px';
}
public hideSearchModal(): void {
this.searchModalRef.hide();
this.searchModalRef = null;
}
public async openSaveSearchModal(template: TemplateRef<any>): Promise<void> {
this.saveSearchModalRef = this.modalService.show(template, {class: 'modal-lg'});
document.body.style.paddingRight = '0px';
@ -79,20 +86,14 @@ export class GallerySearchComponent implements OnDestroy {
this.saveSearchModalRef = null;
}
public hideSearchModal(): void {
this.searchModalRef.hide();
this.searchModalRef = null;
}
resetQuery(): void {
this.searchQueryDTO = ({text: '', type: SearchQueryTypes.any_text} as TextSearch);
}
onQueryChange(): void {
console.log('cahnge', this.searchQueryDTO);
this.rawSearchText = this.searchQueryParserService.stringify(this.searchQueryDTO);
// this.validateRawSearchText();
}
validateRawSearchText(): void {
try {
this.searchQueryDTO = this.searchQueryParserService.parse(this.rawSearchText);