mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-09 05:09:39 +00:00
Advanced Search TS
This commit is contained in:
@@ -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]
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user