Sidebar chart display

This commit is contained in:
Geomitron
2020-02-07 21:40:35 -05:00
parent a682fa52c6
commit 749a132b4a
15 changed files with 200 additions and 33 deletions

View File

@@ -8,7 +8,8 @@ import { BrowseComponent } from './components/browse/browse.component';
import { SearchBarComponent } from './components/browse/search-bar/search-bar.component'; import { SearchBarComponent } from './components/browse/search-bar/search-bar.component';
import { StatusBarComponent } from './components/browse/status-bar/status-bar.component'; import { StatusBarComponent } from './components/browse/status-bar/status-bar.component';
import { ResultTableComponent } from './components/browse/result-table/result-table.component'; import { ResultTableComponent } from './components/browse/result-table/result-table.component';
import { ChartSidebarComponent } from './components/browse/chart-sidebar/chart-sidebar.component' import { ChartSidebarComponent } from './components/browse/chart-sidebar/chart-sidebar.component';
import { ResultTableRowComponent } from './components/browse/result-table/result-table-row/result-table-row.component'
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -18,7 +19,8 @@ import { ChartSidebarComponent } from './components/browse/chart-sidebar/chart-s
SearchBarComponent, SearchBarComponent,
StatusBarComponent, StatusBarComponent,
ResultTableComponent, ResultTableComponent,
ChartSidebarComponent ChartSidebarComponent,
ResultTableRowComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@@ -2,10 +2,10 @@
<div class="ui celled two column grid"> <div class="ui celled two column grid">
<div id="table-row" class="row"> <div id="table-row" class="row">
<div id="table-column" class="column twelve wide"> <div id="table-column" class="column twelve wide">
<app-result-table #resultTable></app-result-table> <app-result-table #resultTable (rowClicked)="chartSidebar.onRowClicked($event)"></app-result-table>
</div> </div>
<div id="sidebar-column" class="column four wide"> <div id="sidebar-column" class="column four wide">
<app-chart-sidebar></app-chart-sidebar> <app-chart-sidebar #chartSidebar></app-chart-sidebar>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,5 +12,4 @@ export class BrowseComponent implements OnInit {
ngOnInit() { ngOnInit() {
console.log('Browse component loaded.') console.log('Browse component loaded.')
} }
} }

View File

@@ -2,20 +2,27 @@
<div class="ui placeholder"> <div class="ui placeholder">
<div class="square image"></div> <div class="square image"></div>
</div> </div>
<div class="ui fluid selection dropdown"> <div id="chartDropdown" class="ui fluid selection dropdown">
<input type="hidden" name="Chart"> <input type="hidden" name="Chart">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="default text">Chart</div> <div class="default text">Chart</div>
<div class="menu"> <div class="menu">
<div class="item" data-value="1">Chart 1</div> <div class="item" data-value="1">
<h4 class="ui header">Chart1</h4>
<div>row2</div>
</div>
<div class="item" data-value="0">Chart 2</div> <div class="item" data-value="0">Chart 2</div>
</div> </div>
</div> </div>
<div id="textPanel" class="content"> <div id="textPanel" class="content">
<span class="header">Funknitium-99</span> <span class="header">{{selectedVersion?.avTagName}}</span>
<div class="meta"> <div *ngIf="selectedVersion" class="description">
<span>Fearofdark</span> <div>{{charterPlural}} {{selectedVersion?.charters}}</div>
<div *ngIf="selectedVersion?.tags">Tags: {{selectedVersion.tags}}</div>
<div>Audio Length: {{getSongLength()}}</div>
<!-- TODO: add difficulty icons -->
</div> </div>
</div> </div>
<div class="ui positive buttons"> <div class="ui positive buttons">
<div class="ui button">Download Latest</div> <div class="ui button">Download Latest</div>

View File

@@ -1,15 +1,86 @@
import { Component, AfterViewInit } from '@angular/core' import { Component } from '@angular/core'
import { SongResult } from 'src/electron/shared/interfaces/search.interface'
import { ElectronService } from 'src/app/core/services/electron.service'
import { VersionResult } from 'src/electron/shared/interfaces/songDetails.interface'
@Component({ @Component({
selector: 'app-chart-sidebar', selector: 'app-chart-sidebar',
templateUrl: './chart-sidebar.component.html', templateUrl: './chart-sidebar.component.html',
styleUrls: ['./chart-sidebar.component.scss'] styleUrls: ['./chart-sidebar.component.scss']
}) })
export class ChartSidebarComponent implements AfterViewInit { export class ChartSidebarComponent {
constructor() { } selectedVersion: VersionResult
charterPlural: string
ngAfterViewInit() {
$('.ui.dropdown').dropdown() private charts: { chartID: number, versions: VersionResult[] }[]
constructor(private electronService: ElectronService) { }
async onRowClicked(result: SongResult) {
const results = await this.electronService.invoke('song-details', result.id)
// Group results by chartID
this.charts = []
for (const result of results) {
const matchingChart = this.charts.find(chart => chart.chartID == result.chartID)
if (matchingChart != undefined) {
matchingChart.versions.push(result)
} else {
this.charts.push({ chartID: result.chartID, versions: [result] })
}
}
this.initChartDropdown()
}
/**
* Initializes the chart dropdown from <this.charts> (or removes it if there's only one chart)
*/
private initChartDropdown() {
const $chartDropdown = $('#chartDropdown')
if (this.charts.length < 2) {
$chartDropdown.hide()
this.switchChart(this.charts[0].chartID)
} else {
const values = this.charts.map(chart => {
const version = chart.versions[0]
return {
value: chart.chartID,
text: version.avTagName,
name: `${version.avTagName} <b>[${version.charters}]</b>`
}
})
$chartDropdown.dropdown('setup menu', { values })
$chartDropdown.dropdown('setting', 'onChange', (chartID: number) => this.switchChart(chartID))
$chartDropdown.dropdown('set selected', values[0].value)
$chartDropdown.show()
}
}
/**
* Updates the sidebar to display the metadata for the chart with <chartID>.
* @param chartID The ID of the chart to display.
*/
private switchChart(chartID: number) {
const chart = this.charts.find(chart => chart.chartID == chartID)
this.selectedVersion = chart.versions[0]
this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
//TODO init version dropdown
}
getSongLength() {
if (this.selectedVersion.song_length == 0) {
return 'Unknown'
}
let seconds = Math.round(this.selectedVersion.song_length / 1000)
let minutes = Math.floor(seconds / 60)
let hours = 0
while (minutes > 59) {
hours++
minutes -= 60
}
seconds = Math.floor(seconds % 60)
return `${hours == 0 ? '' : hours + ':'}${minutes == 0 ? '' : minutes + ':'}${seconds < 10 ? '0' + seconds : seconds}`
} }
} }

View File

@@ -0,0 +1,9 @@
<td>
<div class="ui checkbox">
<input type="checkbox">
</div>
</td>
<td>{{result.name}}</td>
<td>{{result.artist}}</td>
<td>{{result.album}}</td>
<td>{{result.genre}}</td>

View File

@@ -0,0 +1,3 @@
.ui.checkbox {
display: block;
}

View File

@@ -0,0 +1,17 @@
import { Component, AfterViewInit, Input } from '@angular/core'
import { SongResult } from 'src/electron/shared/interfaces/search.interface'
@Component({
selector: 'tr[app-result-table-row]',
templateUrl: './result-table-row.component.html',
styleUrls: ['./result-table-row.component.scss']
})
export class ResultTableRowComponent implements AfterViewInit {
@Input() result: SongResult
constructor() { }
ngAfterViewInit() {
$('.ui.checkbox').checkbox()
}
}

View File

@@ -17,16 +17,10 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let result of results"> <tr
<td> app-result-table-row
<div class="ui checkbox"> *ngFor="let result of results"
<input type="checkbox"> (click)="onRowClicked(result)"
</div> [result]="result"></tr>
</td>
<td>{{result.name}}</td>
<td>{{result.artist}}</td>
<td>{{result.album}}</td>
<td>{{result.genre}}</td>
</tr>
</tbody> </tbody>
</table> </table>

View File

@@ -1,4 +1,4 @@
import { Component, AfterViewInit, Input } from '@angular/core' import { Component, AfterViewInit, Input, Output, EventEmitter } from '@angular/core'
import { SongResult } from 'src/electron/shared/interfaces/search.interface' import { SongResult } from 'src/electron/shared/interfaces/search.interface'
@Component({ @Component({
@@ -9,9 +9,15 @@ import { SongResult } from 'src/electron/shared/interfaces/search.interface'
export class ResultTableComponent implements AfterViewInit { export class ResultTableComponent implements AfterViewInit {
@Input() results: SongResult[] @Input() results: SongResult[]
@Output() rowClicked = new EventEmitter<SongResult>()
constructor() { } constructor() { }
ngAfterViewInit() { ngAfterViewInit() {
$('.ui.checkbox').checkbox() $('.ui.checkbox').checkbox()
} }
onRowClicked(result: SongResult) {
this.rowClicked.emit(result)
}
} }

View File

@@ -0,0 +1,22 @@
import { IPCHandler } from '../shared/IPCHandler'
import Database from '../shared/Database'
import { VersionResult } from '../shared/interfaces/songDetails.interface'
export default class SongDetailsHandler implements IPCHandler<'song-details'> {
event: 'song-details' = 'song-details'
// TODO: add method documentation
async handler(songID: number) {
const db = await Database.getInstance()
return db.sendQuery(this.getVersionQuery(songID)) as Promise<VersionResult[]>
}
private getVersionQuery(songID: number) {
return `
SELECT *
FROM VersionMetaFull
WHERE songID = ${songID};
`
}
}

View File

@@ -4,6 +4,7 @@ import * as url from 'url'
// IPC Handlers // IPC Handlers
import { getIPCHandlers } from './shared/IPCHandler' import { getIPCHandlers } from './shared/IPCHandler'
import Database from './shared/Database'
let mainWindow: BrowserWindow let mainWindow: BrowserWindow
const args = process.argv.slice(1) const args = process.argv.slice(1)
@@ -70,6 +71,7 @@ function createBridgeWindow() {
} }
mainWindow.on('closed', () => { mainWindow.on('closed', () => {
Database.closeConnection()
mainWindow = null // Dereference mainWindow when the window is closed mainWindow = null // Dereference mainWindow when the window is closed
}) })
} }

View File

@@ -49,6 +49,12 @@ export default class Database {
}) })
} }
static closeConnection() {
if (this.database != undefined) {
this.database.conn.destroy()
}
}
/** /**
* Sends <query> to the database. * Sends <query> to the database.
* @param query The query string to be sent. * @param query The query string to be sent.

View File

@@ -1,5 +1,7 @@
import SearchHandler from '../ipc/SearchHandler.ipc' import SearchHandler from '../ipc/SearchHandler.ipc'
import { SongSearch, SongResult } from './interfaces/search.interface' import { SongSearch, SongResult } from './interfaces/search.interface'
import { VersionResult } from './interfaces/songDetails.interface'
import SongDetailsHandler from '../ipc/SongDetailsHandler.ipc'
/** /**
* To add a new IPC listener: * To add a new IPC listener:
@@ -11,7 +13,8 @@ import { SongSearch, SongResult } from './interfaces/search.interface'
export function getIPCHandlers(): IPCHandler<keyof IPCEvents>[] { export function getIPCHandlers(): IPCHandler<keyof IPCEvents>[] {
return [ return [
new SearchHandler() new SearchHandler(),
new SongDetailsHandler()
] ]
} }
@@ -20,9 +23,9 @@ export type IPCEvents = {
input: SongSearch input: SongSearch
output: SongResult[] output: SongResult[]
} }
['test-event-B']: { ['song-details']: {
input: number input: SongResult['id']
output: number output: VersionResult[]
} }
} }

View File

@@ -0,0 +1,26 @@
export interface VersionResult {
versionID: number
chartID: number
songID: number
latestVersionID: number
latestSetlistVersionID: number
icon: string
name: string
avTagName: string
charters: string
charterIDs: string
tags: string | null
downloadLink: string
lastModified: number
song_length: number
diff_band: number
diff_guitar: number
diff_rhythm: number
diff_bass: number
diff_drums: number
diff_keys: number
diff_guitarghl: number
diff_bassghl: number
songDataIncorrect: boolean
isUnusualAvTagName: boolean
}