Advanced Search TS

This commit is contained in:
Geomitron
2021-04-02 22:24:06 -05:00
parent 265ae29f4b
commit e7a265529e
6 changed files with 146 additions and 67 deletions

View File

@@ -14,6 +14,7 @@ import { DownloadsModalComponent } from './components/browse/status-bar/download
import { ProgressBarDirective } from './core/directives/progress-bar.directive'
import { CheckboxDirective } from './core/directives/checkbox.directive'
import { SettingsComponent } from './components/settings/settings.component'
import { FormsModule } from '@angular/forms'
@NgModule({
declarations: [
@@ -32,7 +33,8 @@ import { SettingsComponent } from './components/settings/settings.component'
],
imports: [
BrowserModule,
AppRoutingModule
AppRoutingModule,
FormsModule
],
bootstrap: [AppComponent]
})

View File

@@ -8,8 +8,8 @@
</div>
<div id="quantityDropdownItem" [class.hidden]="!showAdvanced" class="item">
<div class="ui compact selection dropdown">
<input name="quantityMatch" type="hidden" value="all">
<div #quantityDropdown class="ui compact selection dropdown">
<input name="quantityMatch" type="hidden">
<i class="dropdown icon"></i>
<div class="text"><b>all</b> words match</div>
<div class="menu">
@@ -20,7 +20,7 @@
</div>
<div id="similarityDropdownItem" [class.hidden]="!showAdvanced" class="item">
<div class="ui compact selection dropdown">
<div #similarityDropdown class="ui compact selection dropdown">
<input name="similarityMatch" type="hidden" value="similar">
<i class="dropdown icon"></i>
<div class="text"><b>similar</b> words match</div>
@@ -41,159 +41,159 @@
<div id="advancedSearchForm" [class.collapsed]="!showAdvanced" class="ui horizontal segments">
<div class="ui secondary segment">
<h4 class="ui header">Search for:</h4>
<div class="grouped fields">
<h4 class="ui header">Search for:</h4>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="name">
<input type="checkbox" name="name" [(ngModel)]="searchSettings.fields.name">
<label>Name</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="artist">
<input type="checkbox" name="artist" [(ngModel)]="searchSettings.fields.artist">
<label>Artist</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="album">
<input type="checkbox" name="album" [(ngModel)]="searchSettings.fields.album">
<label>Album</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="genre">
<input type="checkbox" name="genre" [(ngModel)]="searchSettings.fields.genre">
<label>Genre</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="year">
<input type="checkbox" name="year" [(ngModel)]="searchSettings.fields.year">
<label>Year</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="charter">
<input type="checkbox" name="charter" [(ngModel)]="searchSettings.fields.charter">
<label>Charter</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="tag">
<input type="checkbox" name="tag" [(ngModel)]="searchSettings.fields.tag">
<label>Tag</label>
</div>
</div>
</div>
</div>
<div class="ui secondary segment">
<h4 class="ui header">Must include:</h4>
<div class="grouped fields">
<h4 class="ui header">Must include:</h4>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="sections">
<input type="checkbox" name="sections" [(ngModel)]="searchSettings.tags.sections">
<label>Sections</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="starpower">
<input type="checkbox" name="starpower" [(ngModel)]="searchSettings.tags['star power']">
<label>Star Power</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="forcing">
<input type="checkbox" name="forcing" [(ngModel)]="searchSettings.tags.forcing">
<label>Forcing</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="taps">
<input type="checkbox" name="taps" [(ngModel)]="searchSettings.tags.taps">
<label>Taps</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="lyrics">
<input type="checkbox" name="lyrics" [(ngModel)]="searchSettings.tags.lyrics">
<label>Lyrics</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="videobackground">
<input type="checkbox" name="videobackground" [(ngModel)]="searchSettings.tags.video">
<label>Video Background</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="stems">
<input type="checkbox" name="stems" [(ngModel)]="searchSettings.tags.stems">
<label>Audio Stems</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="solosections">
<input type="checkbox" name="solosections" [(ngModel)]="searchSettings.tags['solo sections']">
<label>Solo Sections</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="opennotes">
<input type="checkbox" name="opennotes" [(ngModel)]="searchSettings.tags['open notes']">
<label>Open Notes</label>
</div>
</div>
</div>
</div>
<div class="ui secondary segment">
<h4 class="ui header">Instruments:</h4>
<div class="grouped fields">
<h4 class="ui header">Instruments:</h4>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="guitar">
<input type="checkbox" name="guitar" [(ngModel)]="searchSettings.instruments.guitar">
<label>Guitar</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="bass">
<input type="checkbox" name="bass" [(ngModel)]="searchSettings.instruments.bass">
<label>Bass</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="rhythm">
<input type="checkbox" name="rhythm" [(ngModel)]="searchSettings.instruments.rhythm">
<label>Rhythm</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="keys">
<input type="checkbox" name="keys" [(ngModel)]="searchSettings.instruments.keys">
<label>Keys</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="drums">
<input type="checkbox" name="drums" [(ngModel)]="searchSettings.instruments.drums">
<label>Drums</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="guitarghl">
<input type="checkbox" name="guitarghl" [(ngModel)]="searchSettings.instruments.guitarghl">
<label>GHL Guitar</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="bassghl">
<input type="checkbox" name="bassghl" [(ngModel)]="searchSettings.instruments.bassghl">
<label>GHL Bass</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="vocals">
<input type="checkbox" name="vocals" [(ngModel)]="searchSettings.instruments.vocals">
<label>Vocals</label>
</div>
</div>
@@ -204,31 +204,31 @@
<div class="grouped fields">
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="expert">
<input type="checkbox" name="expert" [(ngModel)]="searchSettings.difficulties.expert">
<label>Expert</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="hard">
<input type="checkbox" name="hard" [(ngModel)]="searchSettings.difficulties.hard">
<label>Hard</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="medium">
<input type="checkbox" name="medium" [(ngModel)]="searchSettings.difficulties.medium">
<label>Medium</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="easy">
<input type="checkbox" name="easy" [(ngModel)]="searchSettings.difficulties.easy">
<label>Easy</label>
</div>
</div>
<h4 class="ui header">Difficulty range:</h4>
<div class="field">
<div class="ui labeled ticked range slider" id="diffSlider"></div>
<div #diffSlider class="ui labeled ticked range slider" id="diffSlider"></div>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core'
import { SearchService } from 'src/app/core/services/search.service'
import { getDefaultSearch, SongSearch } from 'src/electron/shared/interfaces/search.interface'
@Component({
selector: 'app-search-bar',
@@ -9,25 +10,51 @@ import { SearchService } from 'src/app/core/services/search.service'
export class SearchBarComponent implements 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
isError = false
showAdvanced = false
searchSettings = getDefaultSearch()
constructor(public searchService: SearchService) { }
ngAfterViewInit() {
$('.ui.dropdown').dropdown()
$('.ui.range.slider').slider({ min: 0, max: 6, start: 0, end: 6, step: 1 })
$(this.searchIcon.nativeElement).popup({
onShow: () => this.isError // Only show the popup if there is an error
})
this.searchService.onSearchErrorStateUpdate((isError) => {
this.isError = isError
})
$(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'
}
})
$(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
}
})
}
onSearch(query: string) {
this.searchService.newSearch(query)
this.searchSettings.query = query
this.searchSettings.limit = 50 + 1
this.searchSettings.offset = 0
this.searchService.newSearch(this.searchSettings)
}
onAdvancedSearchClick() {

View File

@@ -1,6 +1,6 @@
import { Injectable, EventEmitter } from '@angular/core'
import { ElectronService } from './electron.service'
import { SearchType, SongResult, SongSearch } from 'src/electron/shared/interfaces/search.interface'
import { SongResult, SongSearch } from 'src/electron/shared/interfaces/search.interface'
import { VersionResult } from 'src/electron/shared/interfaces/songDetails.interface'
@Injectable({
@@ -18,10 +18,10 @@ export class SearchService {
constructor(private electronService: ElectronService) { }
async newSearch(query: string) {
async newSearch(query: SongSearch) {
if (this.awaitingResults) { return }
this.awaitingResults = true
this.currentQuery = { query, type: SearchType.Any, offset: 0, length: 50 + 1 } // TODO: make length a setting
this.currentQuery = query
try {
this.results = this.trimLastChart(await this.electronService.invoke('song-search', this.currentQuery))
this.errorStateEmitter.emit(false)

View File

@@ -1,7 +1,8 @@
import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { SongSearch, SearchType, SongResult } from '../../shared/interfaces/search.interface'
import { SongSearch, SongResult } from '../../shared/interfaces/search.interface'
import * as needle from 'needle'
import { serverURL } from '../../shared/Paths'
/**
* Handles the 'song-search' event.
*/
@@ -15,29 +16,20 @@ class SearchHandler implements IPCInvokeHandler<'song-search'> {
return new Promise<SongResult[]>((resolve, reject) => {
needle.request(
'get',
serverURL + `/api/search/${this.getSearchType(search)}`, {
query: search.query,
limit: search.length,
offset: search.offset
}, (err, response) => {
serverURL + `/api/search`, search, (err, response) => {
if (err) {
reject(err.message)
} else {
resolve(response.body)
if (response.body.errors) {
console.log(response.body)
resolve([])
} else {
resolve(response.body)
}
}
})
})
}
/**
* @returns the search api type that corresponds with `search.type`.
*/
private getSearchType(search: SongSearch) {
switch (search.type) {
case SearchType.Any: return 'general'
default: return '<<<ERROR>>>' // TODO: add more search types
}
}
}
export const searchHandler = new SearchHandler()

View File

@@ -1,18 +1,76 @@
/**
* Represents a user's song search query.
*/
export interface SongSearch {
export interface SongSearch { // TODO: make limit a setting that's not always 51
query: string
type: SearchType
quantity: 'all' | 'any'
similarity: 'similar' | 'exact'
fields: SearchFields
tags: SearchTags
instruments: SearchInstruments
difficulties: SearchDifficulties
minDiff: number
maxDiff: number
limit: number
offset: number
length: number
}
/**
* The list of possible search categories.
*/
export enum SearchType {
'Any', 'Name', 'Artist', 'Album', 'Genre', 'Year', 'Charter'
export function getDefaultSearch(): SongSearch {
return {
query: '',
quantity: 'all',
similarity: 'similar',
fields: { name: true, artist: true, album: true, genre: true, year: true, charter: true, tag: true },
tags: { 'sections': false, 'star power': false, 'forcing': false, 'taps': false, 'lyrics': false,
'video': false, 'stems': false, 'solo sections': false, 'open notes': false },
instruments: { guitar: false, bass: false, rhythm: false, keys: false,
drums: false, guitarghl: false, bassghl: false, vocals: false },
difficulties: { expert: false, hard: false, medium: false, easy: false },
minDiff: 0,
maxDiff: 6,
limit: 50 + 1,
offset: 0
}
}
export interface SearchFields {
name: boolean
artist: boolean
album: boolean
genre: boolean
year: boolean
charter: boolean
tag: boolean
}
export interface SearchTags {
'sections': boolean // Tag inverted
'star power': boolean // Tag inverted
'forcing': boolean // Tag inverted
'taps': boolean
'lyrics': boolean
'video': boolean
'stems': boolean
'solo sections': boolean
'open notes': boolean
}
export interface SearchInstruments {
guitar: boolean
bass: boolean
rhythm: boolean
keys: boolean
drums: boolean
guitarghl: boolean
bassghl: boolean
vocals: boolean
}
export interface SearchDifficulties {
expert: boolean
hard: boolean
medium: boolean
easy: boolean
}
/**