Improve filter dialog style
Signed-off-by: trivernis <trivernis@protonmail.com>pull/4/head
parent
6cb91bf263
commit
05c2aa3507
@ -1,16 +1,33 @@
|
|||||||
.remove-button {
|
.remove-button, .remove-button-inner-list {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(0.5em - 15px);
|
|
||||||
right: 0;
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
top: calc(0.5em - 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-list {
|
mat-list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
background-color: #353535;
|
||||||
|
border-radius: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-list-item.or-filter-list-item {
|
mat-list-item.or-filter-list-item {
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
::ng-deep .mat-list-item-content {
|
||||||
|
padding-right: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-span {
|
||||||
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
<mat-form-field>
|
||||||
|
<mat-label>
|
||||||
|
Enter a tag
|
||||||
|
</mat-label>
|
||||||
|
<input #tagInput
|
||||||
|
[formControl]="formControl"
|
||||||
|
matInput
|
||||||
|
(keydown.enter)="addTagByInput($event)"
|
||||||
|
[matAutocomplete]="auto">
|
||||||
|
<mat-autocomplete #auto (optionSelected)="addTagByAutocomplete($event)">
|
||||||
|
<mat-option *ngFor="let tag of autosuggestTags | async" [value]="tag">
|
||||||
|
{{tag}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TagInputComponent } from './tag-input.component';
|
||||||
|
|
||||||
|
describe('TagInputComponent', () => {
|
||||||
|
let component: TagInputComponent;
|
||||||
|
let fixture: ComponentFixture<TagInputComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ TagInputComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TagInputComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
Component, ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
Input, OnChanges,
|
||||||
|
OnInit,
|
||||||
|
Output, SimpleChanges,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import {Tag} from "../../../models/Tag";
|
||||||
|
import {FormControl} from "@angular/forms";
|
||||||
|
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
import {debounceTime, delay, map, startWith} from "rxjs/operators";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tag-input',
|
||||||
|
templateUrl: './tag-input.component.html',
|
||||||
|
styleUrls: ['./tag-input.component.scss']
|
||||||
|
})
|
||||||
|
export class TagInputComponent implements OnChanges{
|
||||||
|
|
||||||
|
@Input() availableTags: Tag[] = [];
|
||||||
|
@Input() allowNegation: boolean = false;
|
||||||
|
@Input() allowInvalid: boolean = false;
|
||||||
|
@Output() tagAdded = new EventEmitter<string>();
|
||||||
|
|
||||||
|
@ViewChild("tagInput") tagInput!: ElementRef<HTMLInputElement>;
|
||||||
|
public formControl = new FormControl();
|
||||||
|
public autosuggestTags: Observable<string[]>;
|
||||||
|
private tagsForAutocomplete: string[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.tagsForAutocomplete = this.availableTags.map(t => t.getNormalizedOutput());
|
||||||
|
this.autosuggestTags = this.formControl.valueChanges.pipe(
|
||||||
|
startWith(null),
|
||||||
|
debounceTime(250),
|
||||||
|
map((tag: string | null) => tag ? this.filterSuggestionTag(tag) : this.tagsForAutocomplete.slice(0, 20)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes["availableTags"]) {
|
||||||
|
this.tagsForAutocomplete = this.availableTags.map(t => t.getNormalizedOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTagByInput(event: any): void {
|
||||||
|
this.addTag(this.formControl.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTagByAutocomplete(event: MatAutocompleteSelectedEvent): void {
|
||||||
|
this.addTag(event.option.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addTag(value: string) {
|
||||||
|
const tag = this.normalizeTag(value);
|
||||||
|
if (tag.length > 0 && (this.allowInvalid || this.checkTagValid(tag))) {
|
||||||
|
this.tagAdded.emit(tag);
|
||||||
|
this.formControl.setValue("");
|
||||||
|
this.tagInput.nativeElement.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterSuggestionTag(tag: string) {
|
||||||
|
let normalizedTag = this.normalizeTag(tag);
|
||||||
|
const negated = normalizedTag.startsWith("-") && this.allowNegation;
|
||||||
|
normalizedTag = this.allowNegation? normalizedTag.replace(/^-/, "") : normalizedTag;
|
||||||
|
|
||||||
|
return this.tagsForAutocomplete.filter(
|
||||||
|
t => t.includes(normalizedTag))
|
||||||
|
.map(t => negated ? "-" + t: t)
|
||||||
|
.sort((l, r) => this.compareSuggestionTags(normalizedTag, l, r))
|
||||||
|
.slice(0, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkTagValid(tag: string): boolean {
|
||||||
|
if (this.allowNegation) {
|
||||||
|
tag = tag.replace(/^-/, "");
|
||||||
|
}
|
||||||
|
return this.tagsForAutocomplete.includes(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes the tag by removing whitespaces
|
||||||
|
* @param {string} tag
|
||||||
|
* @returns {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private normalizeTag(tag: string): string {
|
||||||
|
let normalizedTag = tag.trim();
|
||||||
|
let parts = normalizedTag.split(":");
|
||||||
|
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const namespace = parts.shift()!.trim();
|
||||||
|
const name = parts.join(":").trim();
|
||||||
|
return namespace + ":" + name;
|
||||||
|
} else {
|
||||||
|
return normalizedTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private compareSuggestionTags(query: string, l: string, r: string): number {
|
||||||
|
if (l.startsWith(query) && !r.startsWith(query)) {
|
||||||
|
return -1;
|
||||||
|
} else if (!l.startsWith(query) && r.startsWith(query)) {
|
||||||
|
return 1;
|
||||||
|
} else if (l.length < r.length) {
|
||||||
|
return -1;
|
||||||
|
} else if (l.length > r.length) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return l.localeCompare(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue