mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-09 05:09:39 +00:00
Sidebar chart display
This commit is contained in:
@@ -8,7 +8,8 @@ import { BrowseComponent } from './components/browse/browse.component';
|
||||
import { SearchBarComponent } from './components/browse/search-bar/search-bar.component';
|
||||
import { StatusBarComponent } from './components/browse/status-bar/status-bar.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({
|
||||
declarations: [
|
||||
@@ -18,7 +19,8 @@ import { ChartSidebarComponent } from './components/browse/chart-sidebar/chart-s
|
||||
SearchBarComponent,
|
||||
StatusBarComponent,
|
||||
ResultTableComponent,
|
||||
ChartSidebarComponent
|
||||
ChartSidebarComponent,
|
||||
ResultTableRowComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="ui celled two column grid">
|
||||
<div id="table-row" class="row">
|
||||
<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 id="sidebar-column" class="column four wide">
|
||||
<app-chart-sidebar></app-chart-sidebar>
|
||||
<app-chart-sidebar #chartSidebar></app-chart-sidebar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,5 +12,4 @@ export class BrowseComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
console.log('Browse component loaded.')
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,20 +2,27 @@
|
||||
<div class="ui placeholder">
|
||||
<div class="square image"></div>
|
||||
</div>
|
||||
<div class="ui fluid selection dropdown">
|
||||
<div id="chartDropdown" class="ui fluid selection dropdown">
|
||||
<input type="hidden" name="Chart">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Chart</div>
|
||||
<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>
|
||||
</div>
|
||||
<div id="textPanel" class="content">
|
||||
<span class="header">Funknitium-99</span>
|
||||
<div class="meta">
|
||||
<span>Fearofdark</span>
|
||||
<span class="header">{{selectedVersion?.avTagName}}</span>
|
||||
<div *ngIf="selectedVersion" class="description">
|
||||
<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 class="ui positive buttons">
|
||||
<div class="ui button">Download Latest</div>
|
||||
|
||||
@@ -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({
|
||||
selector: 'app-chart-sidebar',
|
||||
templateUrl: './chart-sidebar.component.html',
|
||||
styleUrls: ['./chart-sidebar.component.scss']
|
||||
})
|
||||
export class ChartSidebarComponent implements AfterViewInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngAfterViewInit() {
|
||||
$('.ui.dropdown').dropdown()
|
||||
export class ChartSidebarComponent {
|
||||
|
||||
selectedVersion: VersionResult
|
||||
charterPlural: string
|
||||
|
||||
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}`
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
.ui.checkbox {
|
||||
display: block;
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let result of results">
|
||||
<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>
|
||||
</tr>
|
||||
<tr
|
||||
app-result-table-row
|
||||
*ngFor="let result of results"
|
||||
(click)="onRowClicked(result)"
|
||||
[result]="result"></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -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'
|
||||
|
||||
@Component({
|
||||
@@ -9,9 +9,15 @@ import { SongResult } from 'src/electron/shared/interfaces/search.interface'
|
||||
export class ResultTableComponent implements AfterViewInit {
|
||||
@Input() results: SongResult[]
|
||||
|
||||
@Output() rowClicked = new EventEmitter<SongResult>()
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngAfterViewInit() {
|
||||
$('.ui.checkbox').checkbox()
|
||||
}
|
||||
|
||||
onRowClicked(result: SongResult) {
|
||||
this.rowClicked.emit(result)
|
||||
}
|
||||
}
|
||||
22
src/electron/ipc/SongDetailsHandler.ipc.ts
Normal file
22
src/electron/ipc/SongDetailsHandler.ipc.ts
Normal 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};
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import * as url from 'url'
|
||||
|
||||
// IPC Handlers
|
||||
import { getIPCHandlers } from './shared/IPCHandler'
|
||||
import Database from './shared/Database'
|
||||
|
||||
let mainWindow: BrowserWindow
|
||||
const args = process.argv.slice(1)
|
||||
@@ -70,6 +71,7 @@ function createBridgeWindow() {
|
||||
}
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
Database.closeConnection()
|
||||
mainWindow = null // Dereference mainWindow when the window is closed
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,6 +49,12 @@ export default class Database {
|
||||
})
|
||||
}
|
||||
|
||||
static closeConnection() {
|
||||
if (this.database != undefined) {
|
||||
this.database.conn.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends <query> to the database.
|
||||
* @param query The query string to be sent.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import SearchHandler from '../ipc/SearchHandler.ipc'
|
||||
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:
|
||||
@@ -11,7 +13,8 @@ import { SongSearch, SongResult } from './interfaces/search.interface'
|
||||
|
||||
export function getIPCHandlers(): IPCHandler<keyof IPCEvents>[] {
|
||||
return [
|
||||
new SearchHandler()
|
||||
new SearchHandler(),
|
||||
new SongDetailsHandler()
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,9 +23,9 @@ export type IPCEvents = {
|
||||
input: SongSearch
|
||||
output: SongResult[]
|
||||
}
|
||||
['test-event-B']: {
|
||||
input: number
|
||||
output: number
|
||||
['song-details']: {
|
||||
input: SongResult['id']
|
||||
output: VersionResult[]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
src/electron/shared/interfaces/songDetails.interface.ts
Normal file
26
src/electron/shared/interfaces/songDetails.interface.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user