Interface conversion, search bar layout

This commit is contained in:
Geomitron
2023-12-09 18:21:01 -06:00
parent d689843f27
commit ece0f75b99
37 changed files with 1531 additions and 760 deletions

View File

@@ -1,4 +1,4 @@
<div id="searchMenu" class="ui bottom attached borderless menu">
<!-- <div id="searchMenu" class="ui bottom attached borderless menu">
<div class="item">
<div class="ui icon input" [class.loading]="isLoading()">
<input #searchBox type="text" placeholder=" Search..." (keyup.enter)="onSearch(searchBox.value)" />
@@ -237,4 +237,334 @@
</div>
</div>
</div>
</div> -->
<div class="collapse navbar grid bg-base-100 overflow-visible rounded-none" [ngClass]="showAdvanced ? 'collapse-open' : 'collapse-close'">
<div class="flex flex-wrap justify-end gap-1">
<!-- Search Input -->
<div class="flex items-center order-3 md:order-2 xl:order-2 flex-none w-full md:w-auto md:flex-1 xl:flex-grow-[6] min-w-[21rem]">
<div class="form-control w-full">
<input type="text" [formControl]="searchControl" placeholder="What do you feel like playing today?" class="input input-bordered pr-14" />
</div>
<i class="bi bi-search -ml-11"></i>
</div>
<div class="basis-full h-0 order-4 xl:order-7"></div>
<div class="flex order-5 md:order-5 xl:order-3">
<!-- Instrument Dropdown -->
<div class="dropdown">
<label tabindex="0" class="btn btn-neutral rounded-btn rounded-r-none my-1">
@if (instrument) {
<img class="w-8 hidden sm:block" src="assets/images/instruments/{{ instrument }}.png" />
}
{{ instrumentDisplay(instrument) }}
</label>
<ul tabindex="0" class="menu dropdown-content z-[2] p-2 shadow bg-neutral text-neutral-content rounded-box w-64">
<li>
<a (click)="setInstrument(null, $event)">{{ instrumentDisplay(null) }}</a>
</li>
@for (instrument of instruments; track $index) {
<li>
<a (click)="setInstrument(instrument, $event)">
<img class="w-8" src="assets/images/instruments/{{ instrument }}.png" />
{{ instrumentDisplay(instrument) }}
</a>
</li>
}
</ul>
</div>
<!-- Difficulty Dropdown -->
<div class="dropdown">
<label tabindex="0" class="btn btn-neutral rounded-btn rounded-l-none my-1">{{ difficultyDisplay(difficulty) }}</label>
<ul tabindex="0" class="menu dropdown-content z-[2] p-2 shadow bg-neutral text-neutral-content rounded-box w-40">
<li>
<a (click)="setDifficulty(null, $event)">{{ difficultyDisplay(null) }}</a>
</li>
@for (difficulty of difficulties; track $index) {
<li>
<a (click)="setDifficulty(difficulty, $event)">{{ difficultyDisplay(difficulty) }}</a>
</li>
}
</ul>
</div>
</div>
<!-- Advanced Search -->
<div class="order-6 md:order-6 xl:order-4 xl:flex-grow-[5]">
<button class="btn btn-ghost" (click)="setShowAdvanced(!showAdvanced)">
Advanced Search
<div class="cursor-pointer swap swap-rotate" [class.swap-active]="showAdvanced">
<i class="swap-off bi bi-chevron-down"></i>
<i class="swap-on bi bi-chevron-up"></i>
</div>
</button>
</div>
</div>
<div class="collapse-content justify-center">
<form [formGroup]="advancedSearchForm">
<div class="flex flex-wrap gap-5 justify-center">
<div>
<table class="table table-xs">
<thead>
<tr>
<th>
<div
class="tooltip tooltip-bottom font-normal [text-wrap:balance]"
data-tip='Search for text in these specific chart properties. Note: you can put a minus sign (-) before words to return only results without that word. (e.g. "Dragon -Dragonforce")'>
<span class="font-bold underline decoration-dotted cursor-help">Search by</span>
</div>
</th>
<th>
<div
class="tooltip tooltip-bottom font-normal [text-wrap:balance]"
data-tip="Only include results that match perfectly. (not case sensitive)">
<span class="font-bold underline decoration-dotted cursor-help">Exact</span>
</div>
</th>
<th>
<div class="tooltip tooltip-bottom font-normal [text-wrap:balance]" data-tip="Do not include results that match this.">
<span class="font-bold underline decoration-dotted cursor-help">Exclude</span>
</div>
</th>
</tr>
</thead>
<tbody>
<tr class="border-b-0" formGroupName="name">
<td><input type="text" placeholder="Name" class="input input-bordered input-sm" formControlName="value" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exact" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exclude" /></td>
</tr>
<tr class="border-b-0" formGroupName="artist">
<td><input type="text" placeholder="Artist" class="input input-bordered input-sm" formControlName="value" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exact" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exclude" /></td>
</tr>
<tr class="border-b-0" formGroupName="album">
<td><input type="text" placeholder="Album" class="input input-bordered input-sm" formControlName="value" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exact" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exclude" /></td>
</tr>
<tr class="border-b-0" formGroupName="genre">
<td><input type="text" placeholder="Genre" class="input input-bordered input-sm" formControlName="value" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exact" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exclude" /></td>
</tr>
<tr class="border-b-0" formGroupName="year">
<td><input type="text" placeholder="Year" class="input input-bordered input-sm" formControlName="value" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exact" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exclude" /></td>
</tr>
<tr class="border-b-0" formGroupName="charter">
<td><input type="text" placeholder="Charter" class="input input-bordered input-sm" formControlName="value" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exact" /></td>
<td><input type="checkbox" class="checkbox" formControlName="exclude" /></td>
</tr>
</tbody>
</table>
</div>
<div class="flex flex-col gap-2 justify-end">
<table class="table table-xs">
<tbody>
<tr class="border-b-0">
<td class="text-sm">Length (minutes)</td>
<td>
<div class="join">
<input type="number" placeholder="Min" class="input input-bordered join-item input-sm w-16" formControlName="minLength" />
<input type="number" placeholder="Max" class="input input-bordered join-item input-sm w-16" formControlName="maxLength" />
</div>
</td>
</tr>
<tr class="border-b-0">
<td class="text-sm">
<span
class="label-text underline decoration-dotted cursor-help tooltip [text-wrap:balance]"
data-tip="Also known as chart difficulty. Typically a number between 0 and 6.">
Intensity
</span>
</td>
<td>
<div class="join">
<input type="number" placeholder="Min" class="input input-bordered join-item input-sm w-16" formControlName="minIntensity" />
<input type="number" placeholder="Max" class="input input-bordered join-item input-sm w-16" formControlName="maxIntensity" />
</div>
</td>
</tr>
<tr class="border-b-0">
<td class="text-sm">Average NPS</td>
<td>
<div class="join">
<input type="number" placeholder="Min" class="input input-bordered join-item input-sm w-16" formControlName="minAverageNPS" />
<input type="number" placeholder="Max" class="input input-bordered join-item input-sm w-16" formControlName="maxAverageNPS" />
</div>
</td>
</tr>
<tr class="border-b-0">
<td class="text-sm">Max NPS</td>
<td>
<div class="join">
<input type="number" placeholder="Min" class="input input-bordered join-item input-sm w-16" formControlName="minMaxNPS" />
<input type="number" placeholder="Max" class="input input-bordered join-item input-sm w-16" formControlName="maxMaxNPS" />
</div>
</td>
</tr>
<tr class="border-b-0">
<td class="text-sm">
<span
class="label-text underline decoration-dotted cursor-help tooltip [text-wrap:balance]"
data-tip="The date of the last time this chart was modified in Google Drive.">
Modified After
</span>
</td>
<td>
<input
type="date"
min="2012-01-01"
[max]="todayDate"
placeholder="YYYY/MM/DD"
class="input input-bordered join-item input-sm w-32"
formControlName="modifiedAfter"
(blur)="startValidation = true"
[class.input-error]="advancedSearchForm.invalid && startValidation" />
</td>
</tr>
<tr class="border-b-0">
<td class="text-sm">
<span
class="label-text underline decoration-dotted cursor-help tooltip [text-wrap:balance]"
data-tip="The MD5 hash of the chart folder or .sng file. You can enter multiple values if they are separated by commas.">
Hash
</span>
</td>
<td>
<input type="text" class="input input-bordered join-item input-sm w-32" formControlName="hash" />
</td>
</tr>
</tbody>
</table>
</div>
<div class="flex flex-col justify-between">
<div class="flex gap-2">
<div class="flex flex-col">
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input
#hasSoloSections
type="checkbox"
class="toggle toggle-sm"
[indeterminate]="true"
(click)="clickCheckbox('hasSoloSections', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasSoloSections') === null">
{{ formValue('hasSoloSections') === false ? 'No ' : '' }}Solo Sections
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input
#hasForcedNotes
type="checkbox"
class="toggle toggle-sm"
[indeterminate]="true"
(click)="clickCheckbox('hasForcedNotes', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasForcedNotes') === null">
{{ formValue('hasForcedNotes') === false ? 'No ' : '' }}Forced Notes
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input
#hasOpenNotes
type="checkbox"
class="toggle toggle-sm"
[indeterminate]="true"
(click)="clickCheckbox('hasOpenNotes', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasOpenNotes') === null">
{{ formValue('hasOpenNotes') === false ? 'No ' : '' }}Open Notes
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input
#hasTapNotes
type="checkbox"
class="toggle toggle-sm"
[indeterminate]="true"
(click)="clickCheckbox('hasTapNotes', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasTapNotes') === null">
{{ formValue('hasTapNotes') === false ? 'No ' : '' }}Tap Notes
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input type="checkbox" class="toggle toggle-sm" [indeterminate]="true" (click)="clickCheckbox('hasLyrics', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasLyrics') === null">
{{ formValue('hasLyrics') === false ? 'No ' : '' }}Lyrics
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input type="checkbox" class="toggle toggle-sm" [indeterminate]="true" (click)="clickCheckbox('hasVocals', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasVocals') === null">
{{ formValue('hasVocals') === false ? 'No ' : '' }}Vocals
</span>
</label>
</div>
</div>
<div class="flex flex-col">
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input
#hasRollLanes
type="checkbox"
class="toggle toggle-sm"
[indeterminate]="true"
(click)="clickCheckbox('hasRollLanes', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasRollLanes') === null">
{{ formValue('hasRollLanes') === false ? 'No ' : '' }}Roll Lanes
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input #has2xKick type="checkbox" class="toggle toggle-sm" [indeterminate]="true" (click)="clickCheckbox('has2xKick', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('has2xKick') === null">
{{ formValue('has2xKick') === false ? 'No ' : '' }}2x Kick
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input type="checkbox" class="toggle toggle-sm" [indeterminate]="true" (click)="clickCheckbox('hasIssues', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasIssues') === null">
{{ formValue('hasIssues') === false ? 'No ' : '' }}Chart Issues
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input type="checkbox" class="toggle toggle-sm" [indeterminate]="true" (click)="clickCheckbox('hasVideoBackground', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('hasVideoBackground') === null">
{{ formValue('hasVideoBackground') === false ? 'No ' : '' }}Video Background
</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-normal gap-2">
<input type="checkbox" class="toggle toggle-sm" [indeterminate]="true" (click)="clickCheckbox('modchart', $event)" />
<span class="label-text" [class.text-opacity-70]="formValue('modchart') === null">
{{ formValue('modchart') === false ? 'Not a ' : '' }}Modchart
</span>
</label>
</div>
</div>
</div>
<button class="btn btn-sm btn-primary" [class.btn-disabled]="advancedSearchForm.invalid && startValidation" (click)="searchAdvanced()">
Search{{ advancedSearchForm.invalid && startValidation ? ' ("Modified After" is invalid)' : '' }}
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,51 +0,0 @@
#searchMenu {
flex-wrap: wrap;
margin-bottom: 0em;
border-radius: 0px;
}
#searchMenu>.item {
padding: .3em .4em;
min-height: inherit;
}
#searchMenu>.item:first-child {
box-sizing: content-box;
}
#searchIcon {
cursor: default;
box-sizing: border-box;
}
#advancedSearchForm {
margin: 0em;
border-width: 0px;
overflow: hidden;
transition: max-height 350ms cubic-bezier(0.45, 0, 0.55, 1);
max-height: 243.913px; /* This is its preferred height. Transition needs a static target number to work. */
}
.collapsed {
max-height: 0px !important;
}
#quantityDropdownItem, #similarityDropdownItem {
opacity: 1;
visibility: visible;
max-width: 100vw;
max-height: 100vh;
transition: visibility 0s, opacity 350ms cubic-bezier(0.45, 0, 0.55, 1);
}
.hidden {
opacity: 0 !important;
visibility: hidden !important;
max-width: 0px !important;
max-height: 0px !important;
transition:
opacity 350ms cubic-bezier(0.45, 0, 0.55, 1),
visibility 0s linear 350ms,
max-width 0s linear 350ms,
max-height 0s linear 350ms !important;
}

View File

@@ -1,78 +1,246 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'
import { AbstractControl, FormBuilder, FormControl } from '@angular/forms'
import dayjs from 'dayjs'
import { distinctUntilChanged, switchMap, throttleTime } from 'rxjs'
import { Difficulty, Instrument } from 'scan-chart'
import { SearchService } from 'src-angular/app/core/services/search.service'
import { getDefaultSearch } from '../../../../../src-shared/interfaces/search.interface'
import { difficulties, difficultyDisplay, instrumentDisplay, instruments } from 'src-shared/UtilFunctions'
@Component({
selector: 'app-search-bar',
templateUrl: './search-bar.component.html',
styleUrls: ['./search-bar.component.scss'],
})
export class SearchBarComponent implements AfterViewInit {
export class SearchBarComponent implements OnInit, AfterViewInit {
@ViewChild('searchIcon', { static: true }) searchIcon: ElementRef
@ViewChild('quantityDropdown', { static: true }) quantityDropdown: ElementRef
@ViewChild('similarityDropdown', { static: true }) similarityDropdown: ElementRef
@ViewChild('diffSlider', { static: true }) diffSlider: ElementRef
@ViewChild('hasSoloSections') hasSoloSections: ElementRef<HTMLInputElement>
@ViewChild('hasForcedNotes') hasForcedNotes: ElementRef<HTMLInputElement>
@ViewChild('hasOpenNotes') hasOpenNotes: ElementRef<HTMLInputElement>
@ViewChild('hasTapNotes') hasTapNotes: ElementRef<HTMLInputElement>
@ViewChild('hasRollLanes') hasRollLanes: ElementRef<HTMLInputElement>
@ViewChild('has2xKick') has2xKick: ElementRef<HTMLInputElement>
isError = false
showAdvanced = false
searchSettings = getDefaultSearch()
private sliderInitialized = false
public showAdvanced = false
public instruments = instruments
public difficulties = difficulties
public instrumentDisplay = instrumentDisplay
public difficultyDisplay = difficultyDisplay
constructor(public searchService: SearchService) { }
public advancedSearchForm: ReturnType<this['getAdvancedSearchForm']>
public startValidation = false
constructor(
private searchService: SearchService,
private fb: FormBuilder,
) { }
ngOnInit() {
this.searchControl.valueChanges.pipe(
throttleTime(400, undefined, { leading: true, trailing: true }),
distinctUntilChanged(),
switchMap(search => this.searchService.search(search || '*'))
).subscribe()
this.initializeAdvancedSearchForm()
}
ngAfterViewInit() {
// TODO
// $(this.searchIcon.nativeElement).popup({
// onShow: () => this.isError, // Only show the popup if there is an error
// })
this.searchService.onSearchErrorStateUpdate(isError => {
this.isError = isError
this.updateDisabledControls()
this.searchService.instrument.valueChanges.subscribe(() => {
this.updateDisabledControls()
})
// $(this.quantityDropdown.nativeElement).dropdown({
// onChange: (value: string) => {
// this.searchSettings.quantity = value as 'all' | 'any'
// },
// })
// $(this.similarityDropdown.nativeElement).dropdown({
// onChange: (value: string) => {
// this.searchSettings.similarity = value as 'similar' | 'exact'
// },
// })
}
onSearch(query: string) {
this.searchSettings.query = query
this.searchSettings.limit = 50 + 1
this.searchSettings.offset = 0
this.searchService.newSearch(this.searchSettings)
}
onAdvancedSearchClick() {
this.showAdvanced = !this.showAdvanced
if (!this.sliderInitialized) {
setTimeout(() => { // Initialization requires this element to not be collapsed
// TODO
// $(this.diffSlider.nativeElement).slider({
// min: 0,
// max: 6,
// start: 0,
// end: 6,
// step: 1,
// onChange: (_length: number, min: number, max: number) => {
// this.searchSettings.minDiff = min
// this.searchSettings.maxDiff = max
// },
// })
}, 50)
this.sliderInitialized = true
setShowAdvanced(showAdvanced: boolean) {
this.showAdvanced = showAdvanced
if (showAdvanced) {
this.startValidation = false
this.searchControl.disable()
} else {
this.searchControl.enable()
}
}
isLoading() {
return this.searchService.isLoading()
get searchControl() {
return this.searchService.searchControl
}
get instrument() {
return this.searchService.instrument.value
}
setInstrument(instrument: Instrument | null, event: MouseEvent) {
this.searchService.instrument.setValue(instrument)
if (event.target instanceof HTMLElement) {
event.target.parentElement?.parentElement?.blur()
}
}
get difficulty() {
return this.searchService.difficulty.value
}
setDifficulty(difficulty: Difficulty | null, event: MouseEvent) {
this.searchService.difficulty.setValue(difficulty)
if (event.target instanceof HTMLElement) {
event.target.parentElement?.parentElement?.blur()
}
}
get logoType() {
switch (localStorage.getItem('theme')) {
case 'emerald': return 'emerald'
case 'halloween': return 'halloween'
case 'lemonade': return 'lemonade'
case 'night': return 'night'
case 'synthwave': return 'synthwave'
case 'aqua': return 'orange'
case 'valentine': return 'valentine'
case 'winter': return 'winter'
case 'aren': return 'aren'
case 'froogs': return 'froogs'
default: return 'default'
}
}
get todayDate() {
return dayjs().format('YYYY-MM-DD')
}
// TODO: run this when infinite scroll should happen
// @HostListener("window:scroll", [])
// onScroll(): void {
// if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
// if (this.searchService.areMorePages && !this.searchService.searchLoading) {
// this.searchService.search(this.searchControl.value || '*', true).subscribe()
// }
// }
// }
initializeAdvancedSearchForm() {
this.advancedSearchForm = this.getAdvancedSearchForm() as ReturnType<this['getAdvancedSearchForm']>
for (const key of ['name', 'artist', 'album', 'genre', 'year', 'charter'] as const) {
this.advancedSearchForm.get(key)?.get('exact')?.disable()
this.advancedSearchForm.get(key)?.get('exclude')?.disable()
this.advancedSearchForm.get(key)?.get('value')?.valueChanges.subscribe(value => {
if (value) {
this.advancedSearchForm.get(key)?.get('exact')?.enable()
this.advancedSearchForm.get(key)?.get('exclude')?.enable()
} else {
this.advancedSearchForm.get(key)?.get('exact')?.disable()
this.advancedSearchForm.get(key)?.get('exact')?.setValue(false)
this.advancedSearchForm.get(key)?.get('exclude')?.disable()
this.advancedSearchForm.get(key)?.get('exclude')?.setValue(false)
}
})
}
}
updateDisabledControls() {
const isDrums = this.searchService.instrument.value === 'drums'
const isAny = this.searchService.instrument.value === null
const explanation = 'Not available for the current instrument.'
this.hasSoloSections.nativeElement.disabled = isDrums && !isAny
this.hasForcedNotes.nativeElement.disabled = isDrums && !isAny
this.hasOpenNotes.nativeElement.disabled = isDrums && !isAny
this.hasTapNotes.nativeElement.disabled = isDrums && !isAny
this.hasRollLanes.nativeElement.disabled = !isDrums && !isAny
this.has2xKick.nativeElement.disabled = !isDrums && !isAny
this.hasSoloSections.nativeElement.title = isDrums && !isAny ? explanation : ''
this.hasForcedNotes.nativeElement.title = isDrums && !isAny ? explanation : ''
this.hasOpenNotes.nativeElement.title = isDrums && !isAny ? explanation : ''
this.hasTapNotes.nativeElement.title = isDrums && !isAny ? explanation : ''
this.hasRollLanes.nativeElement.title = !isDrums && !isAny ? explanation : ''
this.has2xKick.nativeElement.title = !isDrums && !isAny ? explanation : ''
if (!isAny) {
if (isDrums) {
this.advancedSearchForm.get('hasSoloSections')?.setValue(null)
this.advancedSearchForm.get('hasForcedNotes')?.setValue(null)
this.advancedSearchForm.get('hasOpenNotes')?.setValue(null)
this.advancedSearchForm.get('hasTapNotes')?.setValue(null)
this.hasSoloSections.nativeElement.indeterminate = true
this.hasForcedNotes.nativeElement.indeterminate = true
this.hasOpenNotes.nativeElement.indeterminate = true
this.hasTapNotes.nativeElement.indeterminate = true
} else {
this.advancedSearchForm.get('hasRollLanes')?.setValue(null)
this.advancedSearchForm.get('has2xKick')?.setValue(null)
this.hasRollLanes.nativeElement.indeterminate = true
this.has2xKick.nativeElement.indeterminate = true
}
}
}
getAdvancedSearchForm() {
return this.fb.group({
name: this.fb.nonNullable.group({ value: '', exact: false, exclude: false }),
artist: this.fb.nonNullable.group({ value: '', exact: false, exclude: false }),
album: this.fb.nonNullable.group({ value: '', exact: false, exclude: false }),
genre: this.fb.nonNullable.group({ value: '', exact: false, exclude: false }),
year: this.fb.nonNullable.group({ value: '', exact: false, exclude: false }),
charter: this.fb.nonNullable.group({ value: '', exact: false, exclude: false }),
minLength: null as number | null,
maxLength: null as number | null,
minIntensity: null as number | null,
maxIntensity: null as number | null,
minAverageNPS: null as number | null,
maxAverageNPS: null as number | null,
minMaxNPS: null as number | null,
maxMaxNPS: null as number | null,
modifiedAfter: this.fb.nonNullable.control('', { validators: dateVaidator }),
hash: this.fb.nonNullable.control(''),
hasSoloSections: null as boolean | null,
hasForcedNotes: null as boolean | null,
hasOpenNotes: null as boolean | null,
hasTapNotes: null as boolean | null,
hasLyrics: null as boolean | null,
hasVocals: null as boolean | null,
hasRollLanes: null as boolean | null,
has2xKick: null as boolean | null,
hasIssues: null as boolean | null,
hasVideoBackground: null as boolean | null,
modchart: null as boolean | null,
})
}
clickCheckbox(key: string, event: MouseEvent) {
if (event.target instanceof HTMLInputElement) {
const control = this.advancedSearchForm.get(key) as FormControl<boolean | null>
if (control.value === true) {
control.setValue(false)
event.target.checked = false
} else if (control.value === false) {
control.setValue(null)
event.target.checked = false
event.target.indeterminate = true
} else if (control.value === null) {
control.setValue(true)
event.target.checked = true
event.target.indeterminate = false
}
}
}
formValue(key: string) {
return this.advancedSearchForm.get(key)?.value
}
searchAdvanced() {
this.startValidation = true
if (this.advancedSearchForm.valid && !this.searchService.searchLoading) {
this.searchService.advancedSearch({
instrument: this.instrument,
difficulty: this.difficulty,
...this.advancedSearchForm.getRawValue(),
}).subscribe()
}
}
}
function dateVaidator(control: AbstractControl) {
if (control.value && isNaN(Date.parse(control.value))) {
return { 'dateVaidator': true }
}
return null
}