1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-02-03 13:22:05 +02:00

Improving search modal UI including fixing search modal delete button event propagation #587

This commit is contained in:
Patrik J. Braun 2023-03-16 23:18:52 +01:00
parent 66d20e200f
commit b9b41eded9
5 changed files with 115 additions and 109 deletions

View File

@ -8,7 +8,7 @@
<hr> <hr>
<app-gallery-search-query-entry <app-gallery-search-query-entry
[(ngModel)]="searchQueryDTO" [(ngModel)]="searchQueryDTO"
(change)="onQueryChange()" (change)="onChange()"
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
name="search-root" name="search-root"
(delete)="resetQuery()"> (delete)="resetQuery()">

View File

@ -1,23 +1,6 @@
import { import {Component, EventEmitter, forwardRef, Input, Output,} from '@angular/core';
Component, import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../../../../../common/entities/SearchQueryDTO';
EventEmitter, import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormControl, ValidationErrors, Validator,} from '@angular/forms';
forwardRef,
Input,
Output,
} from '@angular/core';
import {
SearchQueryDTO,
SearchQueryTypes,
TextSearch,
} from '../../../../../../common/entities/SearchQueryDTO';
import {
ControlValueAccessor,
UntypedFormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
import {SearchQueryParserService} from '../search-query-parser.service'; import {SearchQueryParserService} from '../search-query-parser.service';
@Component({ @Component({
@ -38,8 +21,7 @@ import { SearchQueryParserService } from '../search-query-parser.service';
], ],
}) })
export class GallerySearchQueryBuilderComponent export class GallerySearchQueryBuilderComponent
implements ControlValueAccessor, Validator implements ControlValueAccessor, Validator {
{
public searchQueryDTO: SearchQueryDTO = { public searchQueryDTO: SearchQueryDTO = {
type: SearchQueryTypes.any_text, type: SearchQueryTypes.any_text,
text: '', text: '',
@ -48,7 +30,8 @@ export class GallerySearchQueryBuilderComponent
@Input() placeholder = $localize`Search`; @Input() placeholder = $localize`Search`;
public rawSearchText = ''; public rawSearchText = '';
constructor(private searchQueryParserService: SearchQueryParserService) {} constructor(private searchQueryParserService: SearchQueryParserService) {
}
validateRawSearchText(): void { validateRawSearchText(): void {
try { try {
@ -66,12 +49,6 @@ export class GallerySearchQueryBuilderComponent
text: '', text: '',
type: SearchQueryTypes.any_text, type: SearchQueryTypes.any_text,
} as TextSearch; } as TextSearch;
}
onQueryChange(): void {
this.rawSearchText = this.searchQueryParserService.stringify(
this.searchQueryDTO
);
this.onChange(); this.onChange();
} }
@ -80,7 +57,8 @@ export class GallerySearchQueryBuilderComponent
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
public onTouched(): void {} public onTouched(): void {
}
public writeValue(obj: any): void { public writeValue(obj: any): void {
this.searchQueryDTO = obj; this.searchQueryDTO = obj;
@ -98,12 +76,17 @@ export class GallerySearchQueryBuilderComponent
} }
public onChange(): void { public onChange(): void {
this.rawSearchText = this.searchQueryParserService.stringify(
this.searchQueryDTO
);
this.propagateChange(this.searchQueryDTO); this.propagateChange(this.searchQueryDTO);
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateChange = (_: unknown): void => {}; private propagateChange = (_: unknown): void => {
};
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateTouch = (_: unknown): void => {}; private propagateTouch = (_: unknown): void => {
};
} }

View File

@ -5,14 +5,3 @@
label { label {
margin-top: 0.3rem; margin-top: 0.3rem;
} }
.match-type {
font-size: 2rem;
margin-top: -0.8rem;
font-weight: bold;
cursor: pointer;
}
.match-type.exact-match {
color: #0069d9;
}

View File

@ -51,7 +51,8 @@
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="!IsListQuery"> <ng-container *ngIf="!IsListQuery">
<div class="col-md-3"> <div class="col-lg-4 col-xl-3">
<div class="input-group">
<select <select
id="searchType" id="searchType"
name="searchType" name="searchType"
@ -61,14 +62,20 @@
<option *ngFor="let opt of SearchQueryTypesEnum" [ngValue]="opt.key">{{opt.key | stringifySearchType}} <option *ngFor="let opt of SearchQueryTypesEnum" [ngValue]="opt.key">{{opt.key | stringifySearchType}}
</option> </option>
</select> </select>
</div> <select
<div class="col-11 col-md-8" *ngIf="IsTextQuery"> id="negate"
<div class="input-group"> name="negate"
<span title="exact match" class="form-select w-auto flex-grow-0"
title="Negate"
p18n-title p18n-title
class="match-type" [(ngModel)]="SelectedMatchType"
(click)="toggleMatchType()" (ngModelChange)="onChange()">
[class.exact-match]="AsTextQuery.matchType === TextSearchQueryMatchTypes.exact_match">"</span> <option *ngFor="let mt of MatchingTypes" [ngValue]="mt">{{mt}}
</select>
</div>
</div>
<div class="col-10 col-lg" *ngIf="IsTextQuery">
<div class="input-group">
<input <input
id="searchField" id="searchField"
name="searchField" name="searchField"
@ -79,15 +86,10 @@
(change)="onChange()" (change)="onChange()"
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
type="text"/> type="text"/>
<span title="exact match"
p18n-title
class="match-type"
(click)="toggleMatchType()"
[class.exact-match]="AsTextQuery.matchType === TextSearchQueryMatchTypes.exact_match">"</span>
</div> </div>
</div> </div>
<ng-container [ngSwitch]="queryEntry.type"> <ng-container [ngSwitch]="queryEntry.type">
<div *ngSwitchCase="SearchQueryTypes.distance" class="col-11 col-md-8 "> <div *ngSwitchCase="SearchQueryTypes.distance" class="col-10 col-lg">
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<div class="input-group"> <div class="input-group">
@ -118,7 +120,7 @@
</div> </div>
</div> </div>
<!-- Range Search Query --> <!-- Range Search Query -->
<div *ngSwitchCase="SearchQueryTypes.from_date" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.from_date" class="col-10 col-lg d-flex">
<input id="from_date" <input id="from_date"
name="from_date" name="from_date"
title="From date" title="From date"
@ -129,7 +131,7 @@
class="form-control input-md rounded-2" class="form-control input-md rounded-2"
type="date"> type="date">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.to_date" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.to_date" class="col-10 col-lg d-flex">
<input id="to_date" <input id="to_date"
name="to_date" name="to_date"
title="To date" title="To date"
@ -140,7 +142,7 @@
class="form-control input-md rounded-2" class="form-control input-md rounded-2"
type="date"> type="date">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.min_rating" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.min_rating" class="col-10 col-lg d-flex">
<input id="minRating" <input id="minRating"
name="minRating" name="minRating"
title="Minimum Rating" title="Minimum Rating"
@ -153,7 +155,7 @@
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
type="number"> type="number">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.max_rating" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.max_rating" class="col-10 col-lg d-flex">
<input id="maxRating" <input id="maxRating"
name="maxRating" name="maxRating"
title="Maximum Rating" title="Maximum Rating"
@ -166,7 +168,7 @@
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
type="number"> type="number">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.min_resolution" class="col-11 col-md-8"> <div *ngSwitchCase="SearchQueryTypes.min_resolution" class="col-10 col-lg">
<div class="input-group"> <div class="input-group">
<input id="minResolution" <input id="minResolution"
name="minResolution" name="minResolution"
@ -182,7 +184,7 @@
</div> </div>
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.max_resolution" class="col-11 col-md-8"> <div *ngSwitchCase="SearchQueryTypes.max_resolution" class="col-10 col-lg">
<div class="input-group"> <div class="input-group">
<input id="maxResolution" <input id="maxResolution"
name="maxResolution" name="maxResolution"
@ -197,7 +199,7 @@
<span class="input-group-text">Mpx</span> <span class="input-group-text">Mpx</span>
</div> </div>
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.orientation" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.orientation" class="col-10 col-lg d-flex">
<div class="input-group col-md-6"> <div class="input-group col-md-6">
<select class="form-select rounded-2" <select class="form-select rounded-2"
[(ngModel)]="AsOrientationQuery.landscape" [(ngModel)]="AsOrientationQuery.landscape"
@ -212,11 +214,13 @@
</div> </div>
</div> </div>
</ng-container> </ng-container>
<div class="col-2 col-lg-1 align-self-center">
<button [ngClass]="'btn-danger'" <button [ngClass]="'btn-danger'"
class="btn float-end col-1 align-self-center" class="btn w-auto float-end"
(click)="deleteItem()"> (click)="deleteItem()">
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span> <span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
</button> </button>
</div>
</ng-container> </ng-container>
</div> </div>

View File

@ -13,14 +13,7 @@ import {
TextSearchQueryTypes, TextSearchQueryTypes,
} from '../../../../../../common/entities/SearchQueryDTO'; } from '../../../../../../common/entities/SearchQueryDTO';
import {Utils} from '../../../../../../common/Utils'; import {Utils} from '../../../../../../common/Utils';
import { import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormControl, ValidationErrors, Validator,} from '@angular/forms';
ControlValueAccessor,
UntypedFormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
@Component({ @Component({
selector: 'app-gallery-search-query-entry', selector: 'app-gallery-search-query-entry',
@ -40,8 +33,7 @@ import {
], ],
}) })
export class GallerySearchQueryEntryComponent export class GallerySearchQueryEntryComponent
implements ControlValueAccessor, Validator implements ControlValueAccessor, Validator {
{
public queryEntry: SearchQueryDTO; public queryEntry: SearchQueryDTO;
public SearchQueryTypesEnum: { value: string; key: SearchQueryTypes }[]; public SearchQueryTypesEnum: { value: string; key: SearchQueryTypes }[];
public SearchQueryTypes = SearchQueryTypes; public SearchQueryTypes = SearchQueryTypes;
@ -96,6 +88,47 @@ export class GallerySearchQueryEntryComponent
return {required: true}; return {required: true};
} }
get MatchingTypes(): string[] {
if (this.IsListQuery) {
return [];
}
if (this.IsTextQuery) {
return ['=~', '=', '!=', '!~'];
}
return ['=', '!=']; //normal negatable query
}
get SelectedMatchType(): string {
if (this.AsTextQuery.matchType !== TextSearchQueryMatchTypes.like) {
if (this.AsTextQuery.negate) {
return '!=';
} else {
return '=';
}
} else {
if (this.AsTextQuery.negate) {
return '!~';
} else {
return '=~';
}
}
}
set SelectedMatchType(value: string) {
if (this.IsListQuery) {
return;
}
this.AsTextQuery.negate = value.charAt(0) === '!';
if (!this.IsTextQuery) {
return;
}
if (value === '!~' || value === '=~') {
this.AsTextQuery.matchType = TextSearchQueryMatchTypes.like;
} else {
this.AsTextQuery.matchType = TextSearchQueryMatchTypes.exact_match;
}
}
addQuery(): void { addQuery(): void {
if (!this.IsListQuery) { if (!this.IsListQuery) {
return; return;
@ -136,12 +169,14 @@ export class GallerySearchQueryEntryComponent
this.delete.emit(); this.delete.emit();
} }
itemDeleted(i: number): void { itemDeleted(index: number): void {
this.AsListQuery.list.splice(i, 1); this.AsListQuery.list.splice(index, 1);
this.onChange();
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
public onTouched(): void {} public onTouched(): void {
}
public writeValue(obj: SearchQueryDTO): void { public writeValue(obj: SearchQueryDTO): void {
this.queryEntry = obj; this.queryEntry = obj;
@ -159,18 +194,13 @@ export class GallerySearchQueryEntryComponent
this.propagateChange(this.queryEntry); this.propagateChange(this.queryEntry);
} }
public toggleMatchType(): void {
this.AsTextQuery.matchType =
this.AsTextQuery.matchType === TextSearchQueryMatchTypes.exact_match
? TextSearchQueryMatchTypes.like
: TextSearchQueryMatchTypes.exact_match;
this.onChange();
}
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateChange = (_: unknown): void => {}; private propagateChange = (_: unknown): void => {
};
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateTouch = (_: unknown): void => {}; private propagateTouch = (_: unknown): void => {
};
} }