mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
Download Selected button, Various small improvements
This commit is contained in:
@@ -14,4 +14,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-status-bar #statusBar></app-status-bar>
|
<app-status-bar #statusBar (deselectSongs)="resultTable.onSongsDeselected($event)"></app-status-bar>
|
||||||
@@ -19,7 +19,10 @@ export class BrowseComponent {
|
|||||||
|
|
||||||
onResultsUpdated(results: SongResult[]) {
|
onResultsUpdated(results: SongResult[]) {
|
||||||
this.resultTable.results = results
|
this.resultTable.results = results
|
||||||
|
this.resultTable.onNewSearch()
|
||||||
|
this.resultTable.checkAll
|
||||||
this.chartSidebar.selectedVersion = undefined
|
this.chartSidebar.selectedVersion = undefined
|
||||||
this.statusBar.resultCount = results.length
|
this.statusBar.resultCount = results.length
|
||||||
|
this.statusBar.selectedResults = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,15 +17,15 @@
|
|||||||
<div id="textPanel" class="content">
|
<div id="textPanel" class="content">
|
||||||
<span class="header">{{selectedVersion?.avTagName}}</span>
|
<span class="header">{{selectedVersion?.avTagName}}</span>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<div>{{charterPlural}} {{selectedVersion?.charters}}</div>
|
<div><b>{{charterPlural}}</b> {{selectedVersion?.charters}}</div>
|
||||||
<div *ngIf="selectedVersion?.tags">Tags: {{selectedVersion.tags}}</div>
|
<div *ngIf="selectedVersion?.tags"><b>Tags:</b> {{selectedVersion.tags}}</div>
|
||||||
<div>Audio Length: {{getSongLength()}}</div>
|
<div><b>Audio Length:</b> {{getSongLength()}}</div>
|
||||||
<!-- TODO: add difficulty icons -->
|
<!-- TODO: add difficulty icons -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ui positive buttons">
|
<div class="ui positive buttons">
|
||||||
<div class="ui button" (click)="onDownloadClicked()">Download Latest</div>
|
<div class="ui button" (click)="onDownloadClicked()">Download{{getVersions().length > 1 ? ' Latest' : ''}}</div>
|
||||||
<div *ngIf="getVersions().length > 1" id="versionDropdown" class="ui floating dropdown icon button">
|
<div *ngIf="getVersions().length > 1" id="versionDropdown" class="ui floating dropdown icon button">
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ElectronService } from '../../../core/services/electron.service'
|
|||||||
import { VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
|
import { VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
|
||||||
import { AlbumArtService } from '../../../core/services/album-art.service'
|
import { AlbumArtService } from '../../../core/services/album-art.service'
|
||||||
import { DownloadService } from '../../../core/services/download.service'
|
import { DownloadService } from '../../../core/services/download.service'
|
||||||
|
import { groupBy } from 'src/electron/shared/UtilFunctions'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-chart-sidebar',
|
selector: 'app-chart-sidebar',
|
||||||
@@ -17,7 +18,7 @@ export class ChartSidebarComponent {
|
|||||||
charterPlural: string
|
charterPlural: string
|
||||||
albumArtBuffer: Buffer
|
albumArtBuffer: Buffer
|
||||||
|
|
||||||
charts: { chartID: number, versions: VersionResult[] }[]
|
charts: VersionResult[][]
|
||||||
|
|
||||||
constructor(private electronService: ElectronService, private albumArtService: AlbumArtService, private downloadService: DownloadService) { }
|
constructor(private electronService: ElectronService, private albumArtService: AlbumArtService, private downloadService: DownloadService) { }
|
||||||
|
|
||||||
@@ -26,16 +27,7 @@ export class ChartSidebarComponent {
|
|||||||
const albumArt = this.albumArtService.getImage(result.id)
|
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
|
this.charts = groupBy(results, '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()
|
this.initChartDropdown()
|
||||||
|
|
||||||
this.albumArtBuffer = await albumArt
|
this.albumArtBuffer = await albumArt
|
||||||
@@ -53,12 +45,12 @@ export class ChartSidebarComponent {
|
|||||||
* Initializes the chart dropdown from <this.charts> (or removes it if there's only one chart)
|
* Initializes the chart dropdown from <this.charts> (or removes it if there's only one chart)
|
||||||
*/
|
*/
|
||||||
private async initChartDropdown() {
|
private async initChartDropdown() {
|
||||||
this.switchChart(this.charts[0].chartID)
|
this.switchChart(this.charts[0][0].chartID)
|
||||||
await new Promise<void>(resolve => setTimeout(() => resolve(), 0)) // Wait for *ngIf to update DOM
|
await new Promise<void>(resolve => setTimeout(() => resolve(), 0)) // Wait for *ngIf to update DOM
|
||||||
const values = this.charts.map(chart => {
|
const values = this.charts.map(chart => {
|
||||||
const version = chart.versions[0]
|
const version = chart[0]
|
||||||
return {
|
return {
|
||||||
value: chart.chartID,
|
value: version.chartID,
|
||||||
text: version.avTagName,
|
text: version.avTagName,
|
||||||
name: `${version.avTagName} <b>[${version.charters}]</b>`
|
name: `${version.avTagName} <b>[${version.charters}]</b>`
|
||||||
}
|
}
|
||||||
@@ -74,8 +66,8 @@ export class ChartSidebarComponent {
|
|||||||
* @param chartID The ID of the chart to display.
|
* @param chartID The ID of the chart to display.
|
||||||
*/
|
*/
|
||||||
private switchChart(chartID: number) {
|
private switchChart(chartID: number) {
|
||||||
const chart = this.charts.find(chart => chart.chartID == chartID)
|
const chart = this.charts.find(chart => chart[0].chartID == chartID)
|
||||||
this.selectedVersion = chart.versions[0]
|
this.selectedVersion = chart[0]
|
||||||
this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
|
this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
|
||||||
this.initVersionDropdown()
|
this.initVersionDropdown()
|
||||||
}
|
}
|
||||||
@@ -88,6 +80,7 @@ export class ChartSidebarComponent {
|
|||||||
return 'Unknown'
|
return 'Unknown'
|
||||||
}
|
}
|
||||||
let seconds = Math.round(this.selectedVersion.song_length / 1000)
|
let seconds = Math.round(this.selectedVersion.song_length / 1000)
|
||||||
|
if (seconds < 60) { return `${seconds} second${seconds == 1 ? '' : 's'}` }
|
||||||
let minutes = Math.floor(seconds / 60)
|
let minutes = Math.floor(seconds / 60)
|
||||||
let hours = 0
|
let hours = 0
|
||||||
while (minutes > 59) {
|
while (minutes > 59) {
|
||||||
@@ -138,6 +131,6 @@ export class ChartSidebarComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getVersions() {
|
getVersions() {
|
||||||
return this.charts.find(chart => chart.chartID == this.selectedVersion.chartID).versions
|
return this.charts.find(chart => chart[0].chartID == this.selectedVersion.chartID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,10 @@ export class ResultTableRowComponent implements AfterViewInit {
|
|||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
|
get songID() {
|
||||||
|
return this.result.id
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
$(this.checkbox.nativeElement).checkbox({
|
$(this.checkbox.nativeElement).checkbox({
|
||||||
onChecked: () => {
|
onChecked: () => {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<!-- NOTE: it would be nice to make this header sticky, but Fomantic-UI doesn't currently support that -->
|
<!-- NOTE: it would be nice to make this header sticky, but Fomantic-UI doesn't currently support that -->
|
||||||
<tr>
|
<tr>
|
||||||
<th appCheckbox (checked)="checkAll($event)" id="checkboxColumn" class="collapsing">
|
<th class="collapsing" id="checkboxColumn">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox" id="checkbox" #checkboxColumn appCheckbox (checked)="checkAll($event)">
|
||||||
<input type="checkbox">
|
<input type="checkbox">
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
import { Component, AfterViewInit, Input, Output, EventEmitter, ViewChildren, QueryList } from '@angular/core'
|
import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, ViewChild } from '@angular/core'
|
||||||
import { SongResult } from '../../../../electron/shared/interfaces/search.interface'
|
import { SongResult } from '../../../../electron/shared/interfaces/search.interface'
|
||||||
import { ResultTableRowComponent } from './result-table-row/result-table-row.component'
|
import { ResultTableRowComponent } from './result-table-row/result-table-row.component'
|
||||||
|
import { CheckboxDirective } from 'src/app/core/directives/checkbox.directive'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-result-table',
|
selector: 'app-result-table',
|
||||||
templateUrl: './result-table.component.html',
|
templateUrl: './result-table.component.html',
|
||||||
styleUrls: ['./result-table.component.scss']
|
styleUrls: ['./result-table.component.scss']
|
||||||
})
|
})
|
||||||
export class ResultTableComponent implements AfterViewInit {
|
export class ResultTableComponent {
|
||||||
@Input() results: SongResult[]
|
@Input() results: SongResult[]
|
||||||
|
|
||||||
@Output() rowClicked = new EventEmitter<SongResult>()
|
@Output() rowClicked = new EventEmitter<SongResult>()
|
||||||
@Output() songChecked = new EventEmitter<SongResult>()
|
@Output() songChecked = new EventEmitter<SongResult>()
|
||||||
@Output() songUnchecked = new EventEmitter<SongResult>()
|
@Output() songUnchecked = new EventEmitter<SongResult>()
|
||||||
|
|
||||||
|
@ViewChild(CheckboxDirective, { static: true }) checkboxColumn: CheckboxDirective
|
||||||
@ViewChildren('tableRow') tableRows: QueryList<ResultTableRowComponent>
|
@ViewChildren('tableRow') tableRows: QueryList<ResultTableRowComponent>
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngAfterViewInit() {
|
|
||||||
$('.ui.checkbox').checkbox()
|
|
||||||
}
|
|
||||||
|
|
||||||
onRowClicked(result: SongResult) {
|
onRowClicked(result: SongResult) {
|
||||||
this.rowClicked.emit(result)
|
this.rowClicked.emit(result)
|
||||||
@@ -37,4 +35,12 @@ export class ResultTableComponent implements AfterViewInit {
|
|||||||
checkAll(isChecked: boolean) {
|
checkAll(isChecked: boolean) {
|
||||||
this.tableRows.forEach(row => row.check(isChecked))
|
this.tableRows.forEach(row => row.check(isChecked))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSongsDeselected(songs: SongResult['id'][]) {
|
||||||
|
this.tableRows.forEach(row => row.check(!songs.includes(row.songID)))
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewSearch() {
|
||||||
|
this.checkboxColumn.check(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<div class="ui bottom attached borderless menu">
|
<div class="ui bottom attached borderless menu">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<!-- TODO: refactor this html into multiple sub-components -->
|
|
||||||
<!-- TODO: add advanced search -->
|
<!-- TODO: add advanced search -->
|
||||||
<div class="ui icon input">
|
<div class="ui icon input">
|
||||||
<input #searchBox type="text" placeholder=" Search..." (keyup.enter)="onSearch(searchBox.value)">
|
<input #searchBox type="text" placeholder=" Search..." (keyup.enter)="onSearch(searchBox.value)">
|
||||||
|
|||||||
@@ -11,6 +11,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div id="selectedModal" class="ui modal">
|
||||||
|
<div class="header">Some selected songs have more than one chart!</div>
|
||||||
|
<div class="scrolling content">
|
||||||
|
<div class="ui segments">
|
||||||
|
<div class="ui segment" *ngFor="let chartGroup of chartGroups">
|
||||||
|
<p *ngFor="let chart of chartGroup">{{chart.avTagName}} <b>[{{chart.charters}}]</b></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="ui approve button" (click)="downloadAllCharts()">Download all charts for each song</div>
|
||||||
|
<div class="ui cancel button" (click)="deselectSongsWithMultipleCharts()">Deselect these songs</div>
|
||||||
|
<div class="ui cancel button">Cancel</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="downloadsModal" class="ui modal">
|
<div id="downloadsModal" class="ui modal">
|
||||||
<i class="inside close icon"></i>
|
<i class="inside close icon"></i>
|
||||||
<div class="header">Downloads</div>
|
<div class="header">Downloads</div>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Component, ChangeDetectorRef } from '@angular/core'
|
import { Component, ChangeDetectorRef, Output, EventEmitter } from '@angular/core'
|
||||||
import { SongResult } from 'src/electron/shared/interfaces/search.interface'
|
import { SongResult } from 'src/electron/shared/interfaces/search.interface'
|
||||||
import { DownloadService } from 'src/app/core/services/download.service'
|
import { DownloadService } from 'src/app/core/services/download.service'
|
||||||
|
import { ElectronService } from 'src/app/core/services/electron.service'
|
||||||
|
import { groupBy } from 'src/electron/shared/UtilFunctions'
|
||||||
|
import { VersionResult } from 'src/electron/shared/interfaces/songDetails.interface'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-status-bar',
|
selector: 'app-status-bar',
|
||||||
@@ -9,12 +12,16 @@ import { DownloadService } from 'src/app/core/services/download.service'
|
|||||||
})
|
})
|
||||||
export class StatusBarComponent {
|
export class StatusBarComponent {
|
||||||
|
|
||||||
|
@Output() deselectSongs = new EventEmitter<SongResult['id'][]>()
|
||||||
|
|
||||||
resultCount = 0
|
resultCount = 0
|
||||||
downloading = false
|
downloading = false
|
||||||
percent = 0
|
percent = 0
|
||||||
selectedResults: SongResult[] = []
|
selectedResults: SongResult[] = []
|
||||||
|
batchResults: VersionResult[]
|
||||||
|
chartGroups: VersionResult[][]
|
||||||
|
|
||||||
constructor(downloadService: DownloadService, ref: ChangeDetectorRef) {
|
constructor(private electronService: ElectronService, private downloadService: DownloadService, ref: ChangeDetectorRef) {
|
||||||
downloadService.onDownloadUpdated(() => {
|
downloadService.onDownloadUpdated(() => {
|
||||||
this.downloading = downloadService.downloadCount > 0
|
this.downloading = downloadService.downloadCount > 0
|
||||||
this.percent = downloadService.totalPercent
|
this.percent = downloadService.totalPercent
|
||||||
@@ -36,9 +43,50 @@ export class StatusBarComponent {
|
|||||||
this.selectedResults = this.selectedResults.filter(oldResult => oldResult.id != result.id)
|
this.selectedResults = this.selectedResults.filter(oldResult => oldResult.id != result.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadSelected() {
|
async downloadSelected() {
|
||||||
// TODO send query to get versions; for any with more than one chart, show modal for confirmation:
|
this.chartGroups = []
|
||||||
// "some selected songs have more than one chart: ___" [download all charts for each song] [deselect these songs] [X]
|
this.batchResults = await this.electronService.invoke('batch-song-details', this.selectedResults.map(result => result.id))
|
||||||
console.log(this.selectedResults)
|
const versionGroups = groupBy(this.batchResults, 'songID')
|
||||||
|
for (const versionGroup of versionGroups) {
|
||||||
|
if (versionGroup.findIndex(version => version.chartID != versionGroup[0].chartID) != -1) {
|
||||||
|
// Must have multiple charts of this song
|
||||||
|
this.chartGroups.push(versionGroup.filter(version => version.versionID == version.latestVersionID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chartGroups.length == 0) {
|
||||||
|
for (const versions of versionGroups) {
|
||||||
|
const downloadVersion = versions.find(version => version.versionID == version.latestVersionID)
|
||||||
|
const downloadSong = this.selectedResults.find(song => song.id == downloadVersion.songID)
|
||||||
|
this.downloadService.addDownload(
|
||||||
|
downloadVersion.versionID, {
|
||||||
|
avTagName: downloadVersion.avTagName,
|
||||||
|
artist: downloadSong.artist,
|
||||||
|
charter: downloadVersion.charters,
|
||||||
|
links: JSON.parse(downloadVersion.downloadLink)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$('#selectedModal').modal('show')
|
||||||
|
//[download all charts for each song] [deselect these songs] [X]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadAllCharts() {
|
||||||
|
for (const version of this.batchResults) {
|
||||||
|
if (version.versionID != version.latestVersionID) { continue }
|
||||||
|
const downloadSong = this.selectedResults.find(song => song.id == version.songID)
|
||||||
|
this.downloadService.addDownload(
|
||||||
|
version.versionID, {
|
||||||
|
avTagName: version.avTagName,
|
||||||
|
artist: downloadSong.artist,
|
||||||
|
charter: version.charters,
|
||||||
|
links: JSON.parse(version.downloadLink)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectSongsWithMultipleCharts() {
|
||||||
|
this.deselectSongs.emit(this.chartGroups.map(group => group[0].songID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,29 @@
|
|||||||
import { Directive, ElementRef, Output, EventEmitter } from '@angular/core'
|
import { Directive, ElementRef, Output, EventEmitter, AfterViewInit } from '@angular/core'
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appCheckbox]'
|
selector: '[appCheckbox]'
|
||||||
})
|
})
|
||||||
export class CheckboxDirective {
|
export class CheckboxDirective implements AfterViewInit {
|
||||||
@Output() checked = new EventEmitter<boolean>()
|
@Output() checked = new EventEmitter<boolean>()
|
||||||
|
|
||||||
private _checked = false
|
constructor(private checkbox: ElementRef) { }
|
||||||
|
|
||||||
constructor(element: ElementRef) {
|
ngAfterViewInit() {
|
||||||
$(element.nativeElement).checkbox({
|
$(this.checkbox.nativeElement).checkbox({
|
||||||
onChange: () => {
|
onChecked: () => {
|
||||||
this._checked = !this._checked
|
this.checked.emit(true)
|
||||||
this.checked.emit(this._checked)
|
},
|
||||||
|
onUnchecked: () => {
|
||||||
|
this.checked.emit(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async check(isChecked: boolean) {
|
||||||
|
if (isChecked) {
|
||||||
|
$(this.checkbox.nativeElement).checkbox('check')
|
||||||
|
} else {
|
||||||
|
$(this.checkbox.nativeElement).checkbox('uncheck')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,15 @@ export class DownloadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDownloadUpdated(callback: (download: DownloadProgress) => void) {
|
onDownloadUpdated(callback: (download: DownloadProgress) => void) {
|
||||||
this.downloadUpdatedEmitter.subscribe(_.throttle(callback, 30))
|
const debouncedCallback = _.throttle(callback, 30)
|
||||||
|
this.downloadUpdatedEmitter.subscribe((download: DownloadProgress) => {
|
||||||
|
if (this.downloads.findIndex(oldDownload => oldDownload.versionID == download.versionID) == -1) {
|
||||||
|
// If this is a new download item, don't call debouncedCallback; it may miss adding new versions to the list
|
||||||
|
callback(download)
|
||||||
|
} else {
|
||||||
|
debouncedCallback(download)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelDownload(versionID: number) {
|
cancelDownload(versionID: number) {
|
||||||
|
|||||||
22
src/electron/ipc/BatchSongDetailsHandler.ipc.ts
Normal file
22
src/electron/ipc/BatchSongDetailsHandler.ipc.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IPCInvokeHandler } from '../shared/IPCHandler'
|
||||||
|
import Database from '../shared/Database'
|
||||||
|
import { VersionResult } from '../shared/interfaces/songDetails.interface'
|
||||||
|
|
||||||
|
export default class BatchSongDetailsHandler implements IPCInvokeHandler<'batch-song-details'> {
|
||||||
|
event: 'batch-song-details' = 'batch-song-details'
|
||||||
|
// TODO: add method documentation
|
||||||
|
|
||||||
|
async handler(songIDs: number[]) {
|
||||||
|
const db = await Database.getInstance()
|
||||||
|
|
||||||
|
return db.sendQuery(this.getVersionQuery(songIDs)) as Promise<VersionResult[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
private getVersionQuery(songIDs: number[]) {
|
||||||
|
return `
|
||||||
|
SELECT *
|
||||||
|
FROM VersionMetaFull
|
||||||
|
WHERE songID IN (${songIDs.join(',')});
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/electron/shared/ElectronUtilFunctions.ts
Normal file
11
src/electron/shared/ElectronUtilFunctions.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import InitSettingsHandler from '../ipc/InitSettingsHandler.ipc'
|
||||||
|
import { basename } from 'path'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param absoluteFilepath The absolute filepath to a folder
|
||||||
|
* @returns The relative filepath from the scanned folder to <absoluteFilepath>
|
||||||
|
*/
|
||||||
|
export async function getRelativeFilepath(absoluteFilepath: string) {
|
||||||
|
const settings = await InitSettingsHandler.getSettings()
|
||||||
|
return basename(settings.libraryPath) + absoluteFilepath.substring(settings.libraryPath.length)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { red } from 'cli-color'
|
import { red } from 'cli-color'
|
||||||
import { getRelativeFilepath } from './UtilFunctions'
|
import { getRelativeFilepath } from './ElectronUtilFunctions'
|
||||||
|
|
||||||
// TODO: add better error reporting (through the UI)
|
// TODO: add better error reporting (through the UI)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Download, NewDownload, DownloadProgress } from './interfaces/download.i
|
|||||||
import { DownloadHandler } from '../ipc/download/DownloadHandler'
|
import { DownloadHandler } from '../ipc/download/DownloadHandler'
|
||||||
import { Settings } from './Settings'
|
import { Settings } from './Settings'
|
||||||
import InitSettingsHandler from '../ipc/InitSettingsHandler.ipc'
|
import InitSettingsHandler from '../ipc/InitSettingsHandler.ipc'
|
||||||
|
import BatchSongDetailsHandler from '../ipc/BatchSongDetailsHandler.ipc'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To add a new IPC listener:
|
* To add a new IPC listener:
|
||||||
@@ -21,6 +22,7 @@ export function getIPCInvokeHandlers(): IPCInvokeHandler<keyof IPCInvokeEvents>[
|
|||||||
new InitSettingsHandler(),
|
new InitSettingsHandler(),
|
||||||
new SearchHandler(),
|
new SearchHandler(),
|
||||||
new SongDetailsHandler(),
|
new SongDetailsHandler(),
|
||||||
|
new BatchSongDetailsHandler(),
|
||||||
new AlbumArtHandler()
|
new AlbumArtHandler()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,10 @@ export type IPCInvokeEvents = {
|
|||||||
input: SongResult['id']
|
input: SongResult['id']
|
||||||
output: VersionResult[]
|
output: VersionResult[]
|
||||||
}
|
}
|
||||||
|
'batch-song-details': {
|
||||||
|
input: number[]
|
||||||
|
output: VersionResult[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPCInvokeHandler<E extends keyof IPCInvokeEvents> {
|
export interface IPCInvokeHandler<E extends keyof IPCInvokeEvents> {
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
let sanitize = require('sanitize-filename')
|
let sanitize = require('sanitize-filename')
|
||||||
|
|
||||||
/**
|
|
||||||
* @param absoluteFilepath The absolute filepath to a folder
|
|
||||||
* @returns The relative filepath from the scanned folder to <absoluteFilepath>
|
|
||||||
*/
|
|
||||||
export function getRelativeFilepath(absoluteFilepath: string) {
|
|
||||||
// TODO: figure out how these functions should use <settings> (like an async initialization script that
|
|
||||||
// loads everything and connects to the database, etc...)
|
|
||||||
// return basename(scanSettings.songsFolderPath) + absoluteFilepath.substring(scanSettings.songsFolderPath.length)
|
|
||||||
return absoluteFilepath
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns A random UUID
|
* @returns A random UUID
|
||||||
*/
|
*/
|
||||||
@@ -53,4 +42,23 @@ export function sanitizeFilename(filename: string): string {
|
|||||||
*/
|
*/
|
||||||
export function interpolate(val: number, fromA: number, fromB: number, toA: number, toB: number) {
|
export function interpolate(val: number, fromA: number, fromB: number, toA: number, toB: number) {
|
||||||
return ((val - fromA) / (fromB - fromA)) * (toB - toA) + toA
|
return ((val - fromA) / (fromB - fromA)) * (toB - toA) + toA
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits <objectList> into multiple arrays, grouping by matching <key> values.
|
||||||
|
* @param objectList A list of objects.
|
||||||
|
* @param key A key from the list of objects.
|
||||||
|
*/
|
||||||
|
export function groupBy<T>(objectList: T[], key: keyof T) {
|
||||||
|
const results: T[][] = []
|
||||||
|
for (const object of objectList) {
|
||||||
|
const matchingGroup = results.find(result => result[0][key] == object[key])
|
||||||
|
if (matchingGroup != undefined) {
|
||||||
|
matchingGroup.push(object)
|
||||||
|
} else {
|
||||||
|
results.push([object])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user