mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-07-08 19:15:09 +00:00
Version dropdown and Album Art
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -95,6 +95,14 @@
|
|||||||
"tslib": "1.10.0",
|
"tslib": "1.10.0",
|
||||||
"typescript": "3.5.3",
|
"typescript": "3.5.3",
|
||||||
"webpack-sources": "1.4.3"
|
"webpack-sources": "1.4.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"typescript": {
|
||||||
|
"version": "3.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
|
||||||
|
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular-devkit/build-webpack": {
|
"@angular-devkit/build-webpack": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="ui fluid card">
|
<div *ngIf="selectedVersion" class="ui fluid card">
|
||||||
<div class="ui placeholder">
|
<div class="ui placeholder" [ngClass]="{'placeholder': !albumArtBuffer}">
|
||||||
<div class="square image"></div>
|
<img class="ui square image" [src]="getAlbumArtSrc()">
|
||||||
</div>
|
</div>
|
||||||
<div id="chartDropdown" class="ui fluid selection dropdown">
|
<div id="chartDropdown" class="ui fluid selection dropdown">
|
||||||
<input type="hidden" name="Chart">
|
<input type="hidden" name="Chart">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="textPanel" class="content">
|
<div id="textPanel" class="content">
|
||||||
<span class="header">{{selectedVersion?.avTagName}}</span>
|
<span class="header">{{selectedVersion?.avTagName}}</span>
|
||||||
<div *ngIf="selectedVersion" class="description">
|
<div class="description">
|
||||||
<div>{{charterPlural}} {{selectedVersion?.charters}}</div>
|
<div>{{charterPlural}} {{selectedVersion?.charters}}</div>
|
||||||
<div *ngIf="selectedVersion?.tags">Tags: {{selectedVersion.tags}}</div>
|
<div *ngIf="selectedVersion?.tags">Tags: {{selectedVersion.tags}}</div>
|
||||||
<div>Audio Length: {{getSongLength()}}</div>
|
<div>Audio Length: {{getSongLength()}}</div>
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ui positive buttons">
|
<div class="ui positive buttons">
|
||||||
<div class="ui button">Download Latest</div>
|
<div class="ui button" (click)="onDownloadClicked()">Download Latest</div>
|
||||||
<div id="versionButton" class="ui floating dropdown icon button">
|
<div id="versionDropdown" class="ui floating dropdown icon button">
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="item">Version 1</div>
|
<div class="item">Version 1</div>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#versionButton {
|
#versionDropdown {
|
||||||
max-width: 30px;
|
max-width: min-content;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import { Component } from '@angular/core'
|
|||||||
import { SongResult } from 'src/electron/shared/interfaces/search.interface'
|
import { SongResult } from 'src/electron/shared/interfaces/search.interface'
|
||||||
import { ElectronService } from 'src/app/core/services/electron.service'
|
import { ElectronService } from 'src/app/core/services/electron.service'
|
||||||
import { VersionResult } from 'src/electron/shared/interfaces/songDetails.interface'
|
import { VersionResult } from 'src/electron/shared/interfaces/songDetails.interface'
|
||||||
|
import { AlbumArtService } from 'src/app/core/services/album-art.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-chart-sidebar',
|
selector: 'app-chart-sidebar',
|
||||||
@@ -12,12 +13,14 @@ export class ChartSidebarComponent {
|
|||||||
|
|
||||||
selectedVersion: VersionResult
|
selectedVersion: VersionResult
|
||||||
charterPlural: string
|
charterPlural: string
|
||||||
|
albumArtBuffer: Buffer
|
||||||
|
|
||||||
private charts: { chartID: number, versions: VersionResult[] }[]
|
private charts: { chartID: number, versions: VersionResult[] }[]
|
||||||
|
|
||||||
constructor(private electronService: ElectronService) { }
|
constructor(private electronService: ElectronService, private albumArtService: AlbumArtService) { }
|
||||||
|
|
||||||
async onRowClicked(result: SongResult) {
|
async onRowClicked(result: SongResult) {
|
||||||
|
const albumArt = this.albumArtService.getImage(result.id)
|
||||||
const results = await this.electronService.invoke('song-details', result.id)
|
const results = await this.electronService.invoke('song-details', result.id)
|
||||||
|
|
||||||
// Group results by chartID
|
// Group results by chartID
|
||||||
@@ -30,8 +33,17 @@ export class ChartSidebarComponent {
|
|||||||
this.charts.push({ chartID: result.chartID, versions: [result] })
|
this.charts.push({ chartID: result.chartID, versions: [result] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initChartDropdown()
|
this.initChartDropdown()
|
||||||
|
|
||||||
|
this.albumArtBuffer = await albumArt
|
||||||
|
}
|
||||||
|
|
||||||
|
getAlbumArtSrc() {
|
||||||
|
if (this.albumArtBuffer) {
|
||||||
|
return 'data:image/jpg;base64,' + this.albumArtBuffer.toString('base64')
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,9 +78,12 @@ export class ChartSidebarComponent {
|
|||||||
const chart = this.charts.find(chart => chart.chartID == chartID)
|
const chart = this.charts.find(chart => chart.chartID == chartID)
|
||||||
this.selectedVersion = chart.versions[0]
|
this.selectedVersion = chart.versions[0]
|
||||||
this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
|
this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
|
||||||
//TODO init version dropdown
|
this.initVersionDropdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts <this.selectedVersion.song_length> into a readable duration.
|
||||||
|
*/
|
||||||
getSongLength() {
|
getSongLength() {
|
||||||
if (this.selectedVersion.song_length == 0) {
|
if (this.selectedVersion.song_length == 0) {
|
||||||
return 'Unknown'
|
return 'Unknown'
|
||||||
@@ -83,4 +98,42 @@ export class ChartSidebarComponent {
|
|||||||
seconds = Math.floor(seconds % 60)
|
seconds = Math.floor(seconds % 60)
|
||||||
return `${hours == 0 ? '' : hours + ':'}${minutes == 0 ? '' : minutes + ':'}${seconds < 10 ? '0' + seconds : seconds}`
|
return `${hours == 0 ? '' : hours + ':'}${minutes == 0 ? '' : minutes + ':'}${seconds < 10 ? '0' + seconds : seconds}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the version dropdown from <this.selectedVersion> (or removes it if there's only one version)
|
||||||
|
*/
|
||||||
|
private initVersionDropdown() {
|
||||||
|
const $versionDropdown = $('#versionDropdown')
|
||||||
|
const versions = this.charts.find(chart => chart.chartID == this.selectedVersion.chartID).versions
|
||||||
|
if (versions.length < 2) {
|
||||||
|
$versionDropdown.hide()
|
||||||
|
} else {
|
||||||
|
const values = versions.map(version => {
|
||||||
|
return {
|
||||||
|
value: version.versionID,
|
||||||
|
text: this.getLastModified(version.lastModified),
|
||||||
|
name: this.getLastModified(version.lastModified)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$versionDropdown.dropdown('setup menu', { values })
|
||||||
|
$versionDropdown.dropdown('setting', 'onChange', (versionID: number) => {
|
||||||
|
console.log(`Selected version: ${versionID}`)
|
||||||
|
return this.selectedVersion = versions.find(version => version.versionID = versionID)
|
||||||
|
})
|
||||||
|
$versionDropdown.dropdown('set selected', values[0].value)
|
||||||
|
$versionDropdown.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the <lastModified> value to a user-readable format.
|
||||||
|
* @param lastModified The UNIX timestamp for the lastModified date.
|
||||||
|
*/
|
||||||
|
private getLastModified(lastModified: number) {
|
||||||
|
return new Date(lastModified).toLocaleDateString()
|
||||||
|
}
|
||||||
|
|
||||||
|
onDownloadClicked() {
|
||||||
|
console.log(this.selectedVersion.downloadLink)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
25
src/app/core/services/album-art.service.ts
Normal file
25
src/app/core/services/album-art.service.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { ElectronService } from './electron.service'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AlbumArtService {
|
||||||
|
|
||||||
|
constructor(private electronService: ElectronService) { }
|
||||||
|
|
||||||
|
private imageCache: { [songID: number]: Buffer } = {}
|
||||||
|
|
||||||
|
async getImage(songID: number): Promise<Buffer | null> {
|
||||||
|
if (this.imageCache[songID] == undefined) {
|
||||||
|
const albumArtResult = await this.electronService.invoke('album-art', songID)
|
||||||
|
if (albumArtResult) {
|
||||||
|
this.imageCache[songID] = albumArtResult.art
|
||||||
|
} else {
|
||||||
|
this.imageCache[songID] = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.imageCache[songID]
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/electron/ipc/AlbumArtHandler.ipc.ts
Normal file
22
src/electron/ipc/AlbumArtHandler.ipc.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IPCHandler } from '../shared/IPCHandler'
|
||||||
|
import Database from '../shared/Database'
|
||||||
|
import { AlbumArtResult } from '../shared/interfaces/songDetails.interface'
|
||||||
|
|
||||||
|
export default class AlbumArtHandler implements IPCHandler<'album-art'> {
|
||||||
|
event: 'album-art' = 'album-art'
|
||||||
|
// TODO: add method documentation
|
||||||
|
|
||||||
|
async handler(songID: number) {
|
||||||
|
const db = await Database.getInstance()
|
||||||
|
|
||||||
|
return db.sendQuery(this.getAlbumArtQuery(songID), 1) as Promise<AlbumArtResult>
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAlbumArtQuery(songID: number) {
|
||||||
|
return `
|
||||||
|
SELECT art
|
||||||
|
FROM AlbumArt
|
||||||
|
WHERE songID = ${songID};
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ export default class Database {
|
|||||||
static async getInstance() {
|
static async getInstance() {
|
||||||
if (this.database == undefined) {
|
if (this.database == undefined) {
|
||||||
this.database = new Database()
|
this.database = new Database()
|
||||||
|
await this.database.initDatabaseConnection()
|
||||||
}
|
}
|
||||||
await this.database.initDatabaseConnection()
|
|
||||||
return this.database
|
return this.database
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ export default class Database {
|
|||||||
* @returns one of the responses as type <ResponseType[]>, or an empty array if the query fails.
|
* @returns one of the responses as type <ResponseType[]>, or an empty array if the query fails.
|
||||||
*/
|
*/
|
||||||
async sendQuery<ResponseType>(query: string, queryStatement?: number) {
|
async sendQuery<ResponseType>(query: string, queryStatement?: number) {
|
||||||
return new Promise<ResponseType[]>(resolve => {
|
return new Promise<ResponseType[] | ResponseType>(resolve => {
|
||||||
this.conn.query(query, (err, results) => {
|
this.conn.query(query, (err, results) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
failQuery(query, err)
|
failQuery(query, err)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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 { VersionResult, AlbumArtResult } from './interfaces/songDetails.interface'
|
||||||
|
import SearchHandler from '../ipc/SearchHandler.ipc'
|
||||||
import SongDetailsHandler from '../ipc/SongDetailsHandler.ipc'
|
import SongDetailsHandler from '../ipc/SongDetailsHandler.ipc'
|
||||||
|
import AlbumArtHandler from '../ipc/AlbumArtHandler.ipc'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To add a new IPC listener:
|
* To add a new IPC listener:
|
||||||
@@ -14,7 +15,8 @@ import SongDetailsHandler from '../ipc/SongDetailsHandler.ipc'
|
|||||||
export function getIPCHandlers(): IPCHandler<keyof IPCEvents>[] {
|
export function getIPCHandlers(): IPCHandler<keyof IPCEvents>[] {
|
||||||
return [
|
return [
|
||||||
new SearchHandler(),
|
new SearchHandler(),
|
||||||
new SongDetailsHandler()
|
new SongDetailsHandler(),
|
||||||
|
new AlbumArtHandler()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +25,10 @@ export type IPCEvents = {
|
|||||||
input: SongSearch
|
input: SongSearch
|
||||||
output: SongResult[]
|
output: SongResult[]
|
||||||
}
|
}
|
||||||
|
['album-art']: {
|
||||||
|
input: SongResult['id']
|
||||||
|
output: AlbumArtResult
|
||||||
|
}
|
||||||
['song-details']: {
|
['song-details']: {
|
||||||
input: SongResult['id']
|
input: SongResult['id']
|
||||||
output: VersionResult[]
|
output: VersionResult[]
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
export interface AlbumArtResult {
|
||||||
|
art: Buffer
|
||||||
|
}
|
||||||
|
|
||||||
export interface VersionResult {
|
export interface VersionResult {
|
||||||
versionID: number
|
versionID: number
|
||||||
chartID: number
|
chartID: number
|
||||||
|
|||||||
Reference in New Issue
Block a user