1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-10 04:07:35 +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>
<app-gallery-search-query-entry
[(ngModel)]="searchQueryDTO"
(change)="onQueryChange()"
(change)="onChange()"
(ngModelChange)="onChange()"
name="search-root"
(delete)="resetQuery()">

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, forwardRef, Output } from '@angular/core';
import {Component, EventEmitter, forwardRef, Output} from '@angular/core';
import {
DistanceSearch,
ListSearchQueryTypes,
@ -12,15 +12,8 @@ import {
TextSearchQueryMatchTypes,
TextSearchQueryTypes,
} from '../../../../../../common/entities/SearchQueryDTO';
import { Utils } from '../../../../../../common/Utils';
import {
ControlValueAccessor,
UntypedFormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
import {Utils} from '../../../../../../common/Utils';
import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormControl, ValidationErrors, Validator,} from '@angular/forms';
@Component({
selector: 'app-gallery-search-query-entry',
@ -40,8 +33,7 @@ import {
],
})
export class GallerySearchQueryEntryComponent
implements ControlValueAccessor, Validator
{
implements ControlValueAccessor, Validator {
public queryEntry: SearchQueryDTO;
public SearchQueryTypesEnum: { value: string; key: SearchQueryTypes }[];
public SearchQueryTypes = SearchQueryTypes;
@ -93,7 +85,48 @@ export class GallerySearchQueryEntryComponent
}
validate(control: UntypedFormControl): ValidationErrors {
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 {
@ -110,14 +143,14 @@ export class GallerySearchQueryEntryComponent
if (this.IsListQuery) {
delete this.AsTextQuery.text;
this.AsListQuery.list = this.AsListQuery.list || [
{ type: SearchQueryTypes.any_text, text: '' } as TextSearch,
{ type: SearchQueryTypes.any_text, text: '' } as TextSearch,
{type: SearchQueryTypes.any_text, text: ''} as TextSearch,
{type: SearchQueryTypes.any_text, text: ''} as TextSearch,
];
} else {
delete this.AsListQuery.list;
}
if (this.queryEntry.type === SearchQueryTypes.distance) {
this.AsDistanceQuery.from = { text: '' };
this.AsDistanceQuery.from = {text: ''};
this.AsDistanceQuery.distance = 1;
} else {
delete this.AsDistanceQuery.from;
@ -136,12 +169,14 @@ export class GallerySearchQueryEntryComponent
this.delete.emit();
}
itemDeleted(i: number): void {
this.AsListQuery.list.splice(i, 1);
itemDeleted(index: number): void {
this.AsListQuery.list.splice(index, 1);
this.onChange();
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
public onTouched(): void {}
public onTouched(): void {
}
public writeValue(obj: SearchQueryDTO): void {
this.queryEntry = obj;
@ -159,18 +194,13 @@ export class GallerySearchQueryEntryComponent
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
private propagateChange = (_: unknown): void => {};
private propagateChange = (_: unknown): void => {
};
// eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateTouch = (_: unknown): void => {};
private propagateTouch = (_: unknown): void => {
};
}