mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
Update formatting
This commit is contained in:
16
package.json
16
package.json
@@ -55,6 +55,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^17.0.3",
|
"@angular-devkit/build-angular": "^17.0.3",
|
||||||
"@angular-eslint/builder": "17.1.0",
|
"@angular-eslint/builder": "17.1.0",
|
||||||
|
"@angular-eslint/eslint-plugin": "^17.1.0",
|
||||||
|
"@angular-eslint/eslint-plugin-template": "^17.1.0",
|
||||||
|
"@angular-eslint/schematics": "^17.1.0",
|
||||||
|
"@angular-eslint/template-parser": "^17.1.0",
|
||||||
"@angular/cli": "^17.0.3",
|
"@angular/cli": "^17.0.3",
|
||||||
"@angular/compiler-cli": "^17.0.4",
|
"@angular/compiler-cli": "^17.0.4",
|
||||||
"@angular/language-service": "^17.0.4",
|
"@angular/language-service": "^17.0.4",
|
||||||
@@ -65,10 +69,22 @@
|
|||||||
"@types/randombytes": "^2.0.0",
|
"@types/randombytes": "^2.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||||
"@typescript-eslint/parser": "^6.12.0",
|
"@typescript-eslint/parser": "^6.12.0",
|
||||||
|
"concurrently": "^8.2.2",
|
||||||
"electron": "^27.1.2",
|
"electron": "^27.1.2",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.54.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-import": "^2.29.0",
|
||||||
|
"eslint-plugin-jsdoc": "^46.9.0",
|
||||||
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
|
"postcss": "^8.4.31",
|
||||||
|
"prettier": "^3.1.0",
|
||||||
|
"prettier-eslint": "^16.1.2",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"tailwindcss": "^3.3.5",
|
||||||
|
"tsx": "^4.4.0",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1576
pnpm-lock.yaml
generated
1576
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { Routes, RouterModule, RouteReuseStrategy } from '@angular/router'
|
import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router'
|
||||||
|
|
||||||
import { BrowseComponent } from './components/browse/browse.component'
|
import { BrowseComponent } from './components/browse/browse.component'
|
||||||
import { SettingsComponent } from './components/settings/settings.component'
|
import { SettingsComponent } from './components/settings/settings.component'
|
||||||
import { TabPersistStrategy } from './core/tab-persist.strategy'
|
import { TabPersistStrategy } from './core/tab-persist.strategy'
|
||||||
@@ -10,7 +11,7 @@ const routes: Routes = [
|
|||||||
{ path: 'library', redirectTo: '/browse' },
|
{ path: 'library', redirectTo: '/browse' },
|
||||||
{ path: 'settings', component: SettingsComponent, data: { shouldReuse: true } },
|
{ path: 'settings', component: SettingsComponent, data: { shouldReuse: true } },
|
||||||
{ path: 'about', redirectTo: '/browse' },
|
{ path: 'about', redirectTo: '/browse' },
|
||||||
{ path: '**', redirectTo: '/browse' }
|
{ path: '**', redirectTo: '/browse' },
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -18,6 +19,6 @@ const routes: Routes = [
|
|||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RouteReuseStrategy, useClass: TabPersistStrategy },
|
{ provide: RouteReuseStrategy, useClass: TabPersistStrategy },
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule { }
|
export class AppRoutingModule { }
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div *ngIf="settingsLoaded" style="display: flex; flex-direction: column; height: 100%;">
|
<div *ngIf="settingsLoaded" style="display: flex; flex-direction: column; height: 100%">
|
||||||
<app-toolbar></app-toolbar>
|
<app-toolbar></app-toolbar>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
|
|
||||||
import { SettingsService } from './core/services/settings.service'
|
import { SettingsService } from './core/services/settings.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styles: []
|
styles: [],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser'
|
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module'
|
import { AppRoutingModule } from './app-routing.module'
|
||||||
import { AppComponent } from './app.component'
|
import { AppComponent } from './app.component'
|
||||||
import { ToolbarComponent } from './components/toolbar/toolbar.component'
|
|
||||||
import { BrowseComponent } from './components/browse/browse.component'
|
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'
|
import { ResultTableRowComponent } from './components/browse/result-table/result-table-row/result-table-row.component'
|
||||||
|
import { ResultTableComponent } from './components/browse/result-table/result-table.component'
|
||||||
|
import { SearchBarComponent } from './components/browse/search-bar/search-bar.component'
|
||||||
import { DownloadsModalComponent } from './components/browse/status-bar/downloads-modal/downloads-modal.component'
|
import { DownloadsModalComponent } from './components/browse/status-bar/downloads-modal/downloads-modal.component'
|
||||||
import { ProgressBarDirective } from './core/directives/progress-bar.directive'
|
import { StatusBarComponent } from './components/browse/status-bar/status-bar.component'
|
||||||
import { CheckboxDirective } from './core/directives/checkbox.directive'
|
|
||||||
import { SettingsComponent } from './components/settings/settings.component'
|
import { SettingsComponent } from './components/settings/settings.component'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { ToolbarComponent } from './components/toolbar/toolbar.component'
|
||||||
|
import { CheckboxDirective } from './core/directives/checkbox.directive'
|
||||||
|
import { ProgressBarDirective } from './core/directives/progress-bar.directive'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -29,13 +29,13 @@ import { FormsModule } from '@angular/forms'
|
|||||||
DownloadsModalComponent,
|
DownloadsModalComponent,
|
||||||
ProgressBarDirective,
|
ProgressBarDirective,
|
||||||
CheckboxDirective,
|
CheckboxDirective,
|
||||||
SettingsComponent
|
SettingsComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule
|
FormsModule,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
@@ -2,10 +2,7 @@
|
|||||||
<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
|
<app-result-table #resultTable (rowClicked)="chartSidebar.onRowClicked($event)"></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 #chartSidebar></app-chart-sidebar>
|
<app-chart-sidebar #chartSidebar></app-chart-sidebar>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Component, ViewChild, AfterViewInit } from '@angular/core'
|
import { AfterViewInit, Component, ViewChild } from '@angular/core'
|
||||||
import { ChartSidebarComponent } from './chart-sidebar/chart-sidebar.component'
|
|
||||||
import { StatusBarComponent } from './status-bar/status-bar.component'
|
|
||||||
import { ResultTableComponent } from './result-table/result-table.component'
|
|
||||||
import { SearchService } from 'src/app/core/services/search.service'
|
import { SearchService } from 'src/app/core/services/search.service'
|
||||||
|
import { ChartSidebarComponent } from './chart-sidebar/chart-sidebar.component'
|
||||||
|
import { ResultTableComponent } from './result-table/result-table.component'
|
||||||
|
import { StatusBarComponent } from './status-bar/status-bar.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-browse',
|
selector: 'app-browse',
|
||||||
templateUrl: './browse.component.html',
|
templateUrl: './browse.component.html',
|
||||||
styleUrls: ['./browse.component.scss']
|
styleUrls: ['./browse.component.scss'],
|
||||||
})
|
})
|
||||||
export class BrowseComponent implements AfterViewInit {
|
export class BrowseComponent implements AfterViewInit {
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
<div id="sidebarCard" *ngIf="selectedVersion" class="ui fluid card">
|
<div id="sidebarCard" *ngIf="selectedVersion" class="ui fluid card">
|
||||||
<div class="ui placeholder" [ngClass]="{'placeholder': albumArtSrc == '', 'inverted': settingsService.theme == 'Dark'}">
|
<div class="ui placeholder" [ngClass]="{ placeholder: albumArtSrc === '', inverted: settingsService.theme === 'Dark' }">
|
||||||
<img *ngIf="albumArtSrc != null" class="ui square image" [src]="albumArtSrc">
|
<img *ngIf="albumArtSrc !== null" class="ui square image" [src]="albumArtSrc" />
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="charts.length > 1" id="chartDropdown" class="ui fluid right labeled scrolling icon dropdown button">
|
<div *ngIf="charts.length > 1" id="chartDropdown" class="ui fluid right labeled scrolling icon dropdown button">
|
||||||
<input type="hidden" name="Chart">
|
<input type="hidden" name="Chart" />
|
||||||
<i id="chartDropdownIcon" class="dropdown icon"></i>
|
<i id="chartDropdownIcon" class="dropdown icon"></i>
|
||||||
<div class="default text"></div>
|
<div class="default text"></div>
|
||||||
<div id="chartDropdownMenu" class="menu">
|
<div id="chartDropdownMenu" class="menu"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="textPanel" class="content">
|
<div id="textPanel" class="content">
|
||||||
<span class="header">{{selectedVersion.chartName}}</span>
|
<span class="header">{{ selectedVersion.chartName }}</span>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<div *ngIf="songResult.album == null"><b>Album:</b> {{selectedVersion.album}} ({{selectedVersion.year}})</div>
|
<div *ngIf="songResult.album === null"><b>Album:</b> {{ selectedVersion.album }} ({{ selectedVersion.year }})</div>
|
||||||
<div *ngIf="songResult.album != null"><b>Year:</b> {{selectedVersion.year}}</div>
|
<div *ngIf="songResult.album !== null"><b>Year:</b> {{ selectedVersion.year }}</div>
|
||||||
<div *ngIf="songResult.genre == null"><b>Genre:</b> {{selectedVersion.genre}}</div>
|
<div *ngIf="songResult.genre === null"><b>Genre:</b> {{ selectedVersion.genre }}</div>
|
||||||
<div><b>{{charterPlural}}</b> {{selectedVersion.charters}}</div>
|
<div>
|
||||||
<div *ngIf="selectedVersion.tags"><b>Tags:</b> {{selectedVersion.tags}}</div>
|
<b>{{ charterPlural }}</b> {{ selectedVersion.charters }}
|
||||||
<div><b>Audio Length:</b> {{songLength}}</div>
|
</div>
|
||||||
|
<div *ngIf="selectedVersion.tags"><b>Tags:</b> {{ selectedVersion.tags }}</div>
|
||||||
|
<div><b>Audio Length:</b> {{ songLength }}</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="ui horizontal list">
|
<div class="ui horizontal list">
|
||||||
<div *ngFor="let difficulty of difficultiesList" class="item">
|
<div *ngFor="let difficulty of difficultiesList" class="item">
|
||||||
<img class="ui avatar image" src="assets/images/instruments/{{difficulty.instrument}}">
|
<img class="ui avatar image" src="assets/images/instruments/{{ difficulty.instrument }}" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">Diff: {{difficulty.diffNumber}}</div>
|
<div class="header">Diff: {{ difficulty.diffNumber }}</div>
|
||||||
{{difficulty.chartedDifficulties}}
|
{{ difficulty.chartedDifficulties }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="sourceLinks">
|
<div id="sourceLinks">
|
||||||
<a id="sourceLink" (click)="onSourceLinkClicked()">{{selectedVersion.driveData.source.sourceName}}</a>
|
<a id="sourceLink" (click)="onSourceLinkClicked()">{{ selectedVersion.driveData.source.sourceName }}</a>
|
||||||
<button *ngIf="shownFolderButton()" id="folderButton" class="mini ui icon button" (click)="onFolderButtonClicked()">
|
<button *ngIf="shownFolderButton()" id="folderButton" class="mini ui icon button" (click)="onFolderButtonClicked()">
|
||||||
<i class="folder open outline icon"></i>
|
<i class="folder open outline icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="downloadButtons" class="ui positive buttons">
|
<div id="downloadButtons" class="ui positive buttons">
|
||||||
<div id="downloadButton" class="ui button" (click)="onDownloadClicked()">{{downloadButtonText}}</div>
|
<div id="downloadButton" class="ui button" (click)="onDownloadClicked()">{{ downloadButtonText }}</div>
|
||||||
<div *ngIf="getSelectedChartVersions().length > 1" id="versionDropdown" class="ui floating dropdown icon button">
|
<div *ngIf="getSelectedChartVersions().length > 1" id="versionDropdown" class="ui floating dropdown icon button">
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Component, OnInit } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'
|
||||||
|
|
||||||
|
import { SearchService } from 'src/app/core/services/search.service'
|
||||||
|
import { SettingsService } from 'src/app/core/services/settings.service'
|
||||||
|
import { groupBy } from 'src/electron/shared/UtilFunctions'
|
||||||
import { SongResult } from '../../../../electron/shared/interfaces/search.interface'
|
import { SongResult } from '../../../../electron/shared/interfaces/search.interface'
|
||||||
import { ElectronService } from '../../../core/services/electron.service'
|
import { ChartedDifficulty, getInstrumentIcon, Instrument, VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
|
||||||
import { VersionResult, getInstrumentIcon, Instrument, ChartedDifficulty } 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'
|
import { ElectronService } from '../../../core/services/electron.service'
|
||||||
import { SearchService } from 'src/app/core/services/search.service'
|
|
||||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'
|
|
||||||
import { SettingsService } from 'src/app/core/services/settings.service'
|
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
instrument: string
|
instrument: string
|
||||||
@@ -18,7 +19,7 @@ interface Difficulty {
|
|||||||
@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 OnInit {
|
export class ChartSidebarComponent implements OnInit {
|
||||||
|
|
||||||
@@ -26,6 +27,12 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
selectedVersion: VersionResult
|
selectedVersion: VersionResult
|
||||||
charts: VersionResult[][]
|
charts: VersionResult[][]
|
||||||
|
|
||||||
|
albumArtSrc: SafeUrl = ''
|
||||||
|
charterPlural: string
|
||||||
|
songLength: string
|
||||||
|
difficultiesList: Difficulty[]
|
||||||
|
downloadButtonText: string
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private electronService: ElectronService,
|
private electronService: ElectronService,
|
||||||
private albumArtService: AlbumArtService,
|
private albumArtService: AlbumArtService,
|
||||||
@@ -72,7 +79,6 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
albumArtSrc: SafeUrl = ''
|
|
||||||
/**
|
/**
|
||||||
* Updates the sidebar to display the album art.
|
* Updates the sidebar to display the album art.
|
||||||
*/
|
*/
|
||||||
@@ -93,7 +99,7 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
return {
|
return {
|
||||||
value: version.chartID,
|
value: version.chartID,
|
||||||
text: version.chartName,
|
text: version.chartName,
|
||||||
name: `${version.chartName} <b>[${version.charters}]</b>`
|
name: `${version.chartName} <b>[${version.charters}]</b>`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const $chartDropdown = $('#chartDropdown')
|
const $chartDropdown = $('#chartDropdown')
|
||||||
@@ -123,7 +129,6 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
charterPlural: string
|
|
||||||
/**
|
/**
|
||||||
* Chooses to display 'Charter:' or 'Charters:'.
|
* Chooses to display 'Charter:' or 'Charters:'.
|
||||||
*/
|
*/
|
||||||
@@ -131,7 +136,6 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
|
this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
|
||||||
}
|
}
|
||||||
|
|
||||||
songLength: string
|
|
||||||
/**
|
/**
|
||||||
* Converts `this.selectedVersion.chartMetadata.length` into a readable duration.
|
* Converts `this.selectedVersion.chartMetadata.length` into a readable duration.
|
||||||
*/
|
*/
|
||||||
@@ -148,7 +152,6 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
this.songLength = `${hours == 0 ? '' : hours + ':'}${minutes == 0 ? '' : minutes + ':'}${seconds < 10 ? '0' + seconds : seconds}`
|
this.songLength = `${hours == 0 ? '' : hours + ':'}${minutes == 0 ? '' : minutes + ':'}${seconds < 10 ? '0' + seconds : seconds}`
|
||||||
}
|
}
|
||||||
|
|
||||||
difficultiesList: Difficulty[]
|
|
||||||
/**
|
/**
|
||||||
* Updates `dfficultiesList` with the difficulty information for the selected version.
|
* Updates `dfficultiesList` with the difficulty information for the selected version.
|
||||||
*/
|
*/
|
||||||
@@ -160,7 +163,7 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
this.difficultiesList.push({
|
this.difficultiesList.push({
|
||||||
instrument: getInstrumentIcon(instrument),
|
instrument: getInstrumentIcon(instrument),
|
||||||
diffNumber: this.getDiffNumber(instrument),
|
diffNumber: this.getDiffNumber(instrument),
|
||||||
chartedDifficulties: this.getChartedDifficultiesText(instrument)
|
chartedDifficulties: this.getChartedDifficultiesText(instrument),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +192,6 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
return difficultyNames.join(', ')
|
return difficultyNames.join(', ')
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadButtonText: string
|
|
||||||
/**
|
/**
|
||||||
* Chooses the text to display on the download button.
|
* Chooses the text to display on the download button.
|
||||||
*/
|
*/
|
||||||
@@ -219,7 +221,7 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
const values = versions.map(version => ({
|
const values = versions.map(version => ({
|
||||||
value: version.versionID,
|
value: version.versionID,
|
||||||
text: 'Uploaded ' + this.getLastModifiedText(version.lastModified),
|
text: 'Uploaded ' + this.getLastModifiedText(version.lastModified),
|
||||||
name: 'Uploaded ' + this.getLastModifiedText(version.lastModified)
|
name: 'Uploaded ' + this.getLastModifiedText(version.lastModified),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
$versionDropdown.dropdown('setup menu', { values })
|
$versionDropdown.dropdown('setup menu', { values })
|
||||||
@@ -280,7 +282,7 @@ export class ChartSidebarComponent implements OnInit {
|
|||||||
chartName: this.selectedVersion.chartName,
|
chartName: this.selectedVersion.chartName,
|
||||||
artist: this.songResult.artist,
|
artist: this.songResult.artist,
|
||||||
charter: this.selectedVersion.charters,
|
charter: this.selectedVersion.charters,
|
||||||
driveData: this.selectedVersion.driveData
|
driveData: this.selectedVersion.driveData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
<td>
|
<td>
|
||||||
<div #checkbox class="ui checkbox" (click)="$event.stopPropagation()">
|
<div #checkbox class="ui checkbox" (click)="$event.stopPropagation()">
|
||||||
<input type="checkbox">
|
<input type="checkbox" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><span id="chartCount" *ngIf="result.chartCount > 1">{{result.chartCount}}</span>{{result.name}}</td>
|
<td>
|
||||||
<td>{{result.artist}}</td>
|
<span id="chartCount" *ngIf="result.chartCount > 1">{{ result.chartCount }}</span
|
||||||
<td>{{result.album || 'Various'}}</td>
|
>{{ result.name }}
|
||||||
<td>{{result.genre || 'Various'}}</td>
|
</td>
|
||||||
|
<td>{{ result.artist }}</td>
|
||||||
|
<td>{{ result.album || 'Various' }}</td>
|
||||||
|
<td>{{ result.genre || 'Various' }}</td>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Component, AfterViewInit, Input, ViewChild, ElementRef } from '@angular/core'
|
import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'
|
||||||
|
|
||||||
import { SongResult } from '../../../../../electron/shared/interfaces/search.interface'
|
import { SongResult } from '../../../../../electron/shared/interfaces/search.interface'
|
||||||
import { SelectionService } from '../../../../core/services/selection.service'
|
import { SelectionService } from '../../../../core/services/selection.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tr[app-result-table-row]',
|
selector: 'tr[app-result-table-row]',
|
||||||
templateUrl: './result-table-row.component.html',
|
templateUrl: './result-table-row.component.html',
|
||||||
styleUrls: ['./result-table-row.component.scss']
|
styleUrls: ['./result-table-row.component.scss'],
|
||||||
})
|
})
|
||||||
export class ResultTableRowComponent implements AfterViewInit {
|
export class ResultTableRowComponent implements AfterViewInit {
|
||||||
@Input() result: SongResult
|
@Input() result: SongResult
|
||||||
@@ -19,7 +20,7 @@ export class ResultTableRowComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.selectionService.onSelectionChanged(this.songID, (isChecked) => {
|
this.selectionService.onSelectionChanged(this.songID, isChecked => {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
$(this.checkbox.nativeElement).checkbox('check')
|
$(this.checkbox.nativeElement).checkbox('check')
|
||||||
} else {
|
} else {
|
||||||
@@ -33,7 +34,7 @@ export class ResultTableRowComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
onUnchecked: () => {
|
onUnchecked: () => {
|
||||||
this.selectionService.deselectSong(this.songID)
|
this.selectionService.deselectSong(this.songID)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
<table id="resultTable" class="ui stackable selectable single sortable fixed line striped compact small table" [class.inverted]="settingsService.theme == 'Dark'">
|
<table
|
||||||
|
id="resultTable"
|
||||||
|
class="ui stackable selectable single sortable fixed line striped compact small table"
|
||||||
|
[class.inverted]="settingsService.theme === 'Dark'">
|
||||||
<!-- TODO: maybe have some of these tags customizable? E.g. small/large/compact/padded -->
|
<!-- TODO: maybe have some of these tags customizable? E.g. small/large/compact/padded -->
|
||||||
<!-- TODO: learn semantic themes in order to change the $mobileBreakpoint global variable (better search table adjustment) -->
|
<!-- TODO: learn semantic themes in order to change the $mobileBreakpoint global variable (better search table adjustment) -->
|
||||||
<thead>
|
<thead>
|
||||||
@@ -6,13 +9,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="collapsing" id="checkboxColumn">
|
<th class="collapsing" id="checkboxColumn">
|
||||||
<div class="ui checkbox" id="checkbox" #checkboxColumn appCheckbox (checked)="checkAll($event)">
|
<div class="ui checkbox" id="checkbox" #checkboxColumn appCheckbox (checked)="checkAll($event)">
|
||||||
<input type="checkbox">
|
<input type="checkbox" />
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th class="four wide" [class.sorted]="sortColumn == 'name'" [ngClass]="sortDirection" (click)="onColClicked('name')">Name</th>
|
<th class="four wide" [class.sorted]="sortColumn === 'name'" [ngClass]="sortDirection" (click)="onColClicked('name')">Name</th>
|
||||||
<th class="four wide" [class.sorted]="sortColumn == 'artist'" [ngClass]="sortDirection" (click)="onColClicked('artist')">Artist</th>
|
<th class="four wide" [class.sorted]="sortColumn === 'artist'" [ngClass]="sortDirection" (click)="onColClicked('artist')">Artist</th>
|
||||||
<th class="four wide" [class.sorted]="sortColumn == 'album'" [ngClass]="sortDirection" (click)="onColClicked('album')">Album</th>
|
<th class="four wide" [class.sorted]="sortColumn === 'album'" [ngClass]="sortDirection" (click)="onColClicked('album')">Album</th>
|
||||||
<th class="four wide" [class.sorted]="sortColumn == 'genre'" [ngClass]="sortDirection" (click)="onColClicked('genre')">Genre</th>
|
<th class="four wide" [class.sorted]="sortColumn === 'genre'" [ngClass]="sortDirection" (click)="onColClicked('genre')">Genre</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -21,7 +24,7 @@
|
|||||||
#tableRow
|
#tableRow
|
||||||
*ngFor="let result of results"
|
*ngFor="let result of results"
|
||||||
(click)="onRowClicked(result)"
|
(click)="onRowClicked(result)"
|
||||||
[class.active]="activeRowID == result.id"
|
[class.active]="activeRowID === result.id"
|
||||||
[result]="result"></tr>
|
[result]="result"></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
import { Component, Output, EventEmitter, ViewChildren, QueryList, ViewChild, OnInit } from '@angular/core'
|
import { Component, EventEmitter, OnInit, Output, QueryList, ViewChild, ViewChildren } from '@angular/core'
|
||||||
|
|
||||||
|
import Comparators from 'comparators'
|
||||||
|
|
||||||
|
import { SettingsService } from 'src/app/core/services/settings.service'
|
||||||
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 { CheckboxDirective } from '../../../core/directives/checkbox.directive'
|
import { CheckboxDirective } from '../../../core/directives/checkbox.directive'
|
||||||
import { SearchService } from '../../../core/services/search.service'
|
import { SearchService } from '../../../core/services/search.service'
|
||||||
import { SelectionService } from '../../../core/services/selection.service'
|
import { SelectionService } from '../../../core/services/selection.service'
|
||||||
import { SettingsService } from 'src/app/core/services/settings.service'
|
import { ResultTableRowComponent } from './result-table-row/result-table-row.component'
|
||||||
import Comparators from 'comparators'
|
|
||||||
|
|
||||||
@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 OnInit {
|
export class ResultTableComponent implements OnInit {
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ export class ResultTableComponent implements OnInit {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.selectionService.onSelectAllChanged((selected) => {
|
this.selectionService.onSelectAllChanged(selected => {
|
||||||
this.checkboxColumn.check(selected)
|
this.checkboxColumn.check(selected)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
<div id="searchMenu" class="ui bottom attached borderless menu">
|
<div id="searchMenu" class="ui bottom attached borderless menu">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="ui icon input" [class.loading]="isLoading()">
|
<div class="ui icon input" [class.loading]="isLoading()">
|
||||||
<input #searchBox type="text" placeholder=" Search..." (keyup.enter)="onSearch(searchBox.value)">
|
<input #searchBox type="text" placeholder=" Search..." (keyup.enter)="onSearch(searchBox.value)" />
|
||||||
<i #searchIcon id="searchIcon" class="link icon" [ngClass]="isError ? 'red exclamation triangle' : 'search'"
|
<i
|
||||||
data-content="Failed to connect to the Bridge database" data-position="bottom right"></i>
|
#searchIcon
|
||||||
|
id="searchIcon"
|
||||||
|
class="link icon"
|
||||||
|
[ngClass]="isError ? 'red exclamation triangle' : 'search'"
|
||||||
|
data-content="Failed to connect to the Bridge database"
|
||||||
|
data-position="bottom right"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="quantityDropdownItem" [class.hidden]="!showAdvanced" class="item">
|
<div id="quantityDropdownItem" [class.hidden]="!showAdvanced" class="item">
|
||||||
<div #quantityDropdown class="ui compact selection dropdown">
|
<div #quantityDropdown class="ui compact selection dropdown">
|
||||||
<input name="quantityMatch" type="hidden">
|
<input name="quantityMatch" type="hidden" />
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="text"><b>all</b> words match</div>
|
<div class="text"><b>all</b> words match</div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
@@ -21,7 +26,7 @@
|
|||||||
|
|
||||||
<div id="similarityDropdownItem" [class.hidden]="!showAdvanced" class="item">
|
<div id="similarityDropdownItem" [class.hidden]="!showAdvanced" class="item">
|
||||||
<div #similarityDropdown class="ui compact selection dropdown">
|
<div #similarityDropdown class="ui compact selection dropdown">
|
||||||
<input name="similarityMatch" type="hidden" value="similar">
|
<input name="similarityMatch" type="hidden" value="similar" />
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="text"><b>similar</b> words match</div>
|
<div class="text"><b>similar</b> words match</div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
@@ -45,43 +50,43 @@
|
|||||||
<h5 class="ui dividing header">Search for</h5>
|
<h5 class="ui dividing header">Search for</h5>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="name" [(ngModel)]="searchSettings.fields.name">
|
<input type="checkbox" name="name" [(ngModel)]="searchSettings.fields.name" />
|
||||||
<label>Name</label>
|
<label>Name</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="artist" [(ngModel)]="searchSettings.fields.artist">
|
<input type="checkbox" name="artist" [(ngModel)]="searchSettings.fields.artist" />
|
||||||
<label>Artist</label>
|
<label>Artist</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="album" [(ngModel)]="searchSettings.fields.album">
|
<input type="checkbox" name="album" [(ngModel)]="searchSettings.fields.album" />
|
||||||
<label>Album</label>
|
<label>Album</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="genre" [(ngModel)]="searchSettings.fields.genre">
|
<input type="checkbox" name="genre" [(ngModel)]="searchSettings.fields.genre" />
|
||||||
<label>Genre</label>
|
<label>Genre</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="year" [(ngModel)]="searchSettings.fields.year">
|
<input type="checkbox" name="year" [(ngModel)]="searchSettings.fields.year" />
|
||||||
<label>Year</label>
|
<label>Year</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="charter" [(ngModel)]="searchSettings.fields.charter">
|
<input type="checkbox" name="charter" [(ngModel)]="searchSettings.fields.charter" />
|
||||||
<label>Charter</label>
|
<label>Charter</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="tag" [(ngModel)]="searchSettings.fields.tag">
|
<input type="checkbox" name="tag" [(ngModel)]="searchSettings.fields.tag" />
|
||||||
<label>Tag</label>
|
<label>Tag</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,55 +97,55 @@
|
|||||||
<h5 class="ui dividing header">Must include</h5>
|
<h5 class="ui dividing header">Must include</h5>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="sections" [(ngModel)]="searchSettings.tags.sections">
|
<input type="checkbox" name="sections" [(ngModel)]="searchSettings.tags.sections" />
|
||||||
<label>Sections</label>
|
<label>Sections</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="starpower" [(ngModel)]="searchSettings.tags['star power']">
|
<input type="checkbox" name="starpower" [(ngModel)]="searchSettings.tags['star power']" />
|
||||||
<label>Star Power</label>
|
<label>Star Power</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="forcing" [(ngModel)]="searchSettings.tags.forcing">
|
<input type="checkbox" name="forcing" [(ngModel)]="searchSettings.tags.forcing" />
|
||||||
<label>Forcing</label>
|
<label>Forcing</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="taps" [(ngModel)]="searchSettings.tags.taps">
|
<input type="checkbox" name="taps" [(ngModel)]="searchSettings.tags.taps" />
|
||||||
<label>Taps</label>
|
<label>Taps</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="lyrics" [(ngModel)]="searchSettings.tags.lyrics">
|
<input type="checkbox" name="lyrics" [(ngModel)]="searchSettings.tags.lyrics" />
|
||||||
<label>Lyrics</label>
|
<label>Lyrics</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="videobackground" [(ngModel)]="searchSettings.tags.video">
|
<input type="checkbox" name="videobackground" [(ngModel)]="searchSettings.tags.video" />
|
||||||
<label>Video Background</label>
|
<label>Video Background</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="stems" [(ngModel)]="searchSettings.tags.stems">
|
<input type="checkbox" name="stems" [(ngModel)]="searchSettings.tags.stems" />
|
||||||
<label>Audio Stems</label>
|
<label>Audio Stems</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="solosections" [(ngModel)]="searchSettings.tags['solo sections']">
|
<input type="checkbox" name="solosections" [(ngModel)]="searchSettings.tags['solo sections']" />
|
||||||
<label>Solo Sections</label>
|
<label>Solo Sections</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="opennotes" [(ngModel)]="searchSettings.tags['open notes']">
|
<input type="checkbox" name="opennotes" [(ngModel)]="searchSettings.tags['open notes']" />
|
||||||
<label>Open Notes</label>
|
<label>Open Notes</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,49 +156,49 @@
|
|||||||
<h5 class="ui dividing header">Instruments</h5>
|
<h5 class="ui dividing header">Instruments</h5>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="guitar" [(ngModel)]="searchSettings.instruments.guitar">
|
<input type="checkbox" name="guitar" [(ngModel)]="searchSettings.instruments.guitar" />
|
||||||
<label>Guitar</label>
|
<label>Guitar</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="bass" [(ngModel)]="searchSettings.instruments.bass">
|
<input type="checkbox" name="bass" [(ngModel)]="searchSettings.instruments.bass" />
|
||||||
<label>Bass</label>
|
<label>Bass</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="rhythm" [(ngModel)]="searchSettings.instruments.rhythm">
|
<input type="checkbox" name="rhythm" [(ngModel)]="searchSettings.instruments.rhythm" />
|
||||||
<label>Rhythm</label>
|
<label>Rhythm</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="keys" [(ngModel)]="searchSettings.instruments.keys">
|
<input type="checkbox" name="keys" [(ngModel)]="searchSettings.instruments.keys" />
|
||||||
<label>Keys</label>
|
<label>Keys</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="drums" [(ngModel)]="searchSettings.instruments.drums">
|
<input type="checkbox" name="drums" [(ngModel)]="searchSettings.instruments.drums" />
|
||||||
<label>Drums</label>
|
<label>Drums</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="guitarghl" [(ngModel)]="searchSettings.instruments.guitarghl">
|
<input type="checkbox" name="guitarghl" [(ngModel)]="searchSettings.instruments.guitarghl" />
|
||||||
<label>GHL Guitar</label>
|
<label>GHL Guitar</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="bassghl" [(ngModel)]="searchSettings.instruments.bassghl">
|
<input type="checkbox" name="bassghl" [(ngModel)]="searchSettings.instruments.bassghl" />
|
||||||
<label>GHL Bass</label>
|
<label>GHL Bass</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="vocals" [(ngModel)]="searchSettings.instruments.vocals">
|
<input type="checkbox" name="vocals" [(ngModel)]="searchSettings.instruments.vocals" />
|
||||||
<label>Vocals</label>
|
<label>Vocals</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,25 +209,25 @@
|
|||||||
<div class="grouped fields">
|
<div class="grouped fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="expert" [(ngModel)]="searchSettings.difficulties.expert">
|
<input type="checkbox" name="expert" [(ngModel)]="searchSettings.difficulties.expert" />
|
||||||
<label>Expert</label>
|
<label>Expert</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="hard" [(ngModel)]="searchSettings.difficulties.hard">
|
<input type="checkbox" name="hard" [(ngModel)]="searchSettings.difficulties.hard" />
|
||||||
<label>Hard</label>
|
<label>Hard</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="medium" [(ngModel)]="searchSettings.difficulties.medium">
|
<input type="checkbox" name="medium" [(ngModel)]="searchSettings.difficulties.medium" />
|
||||||
<label>Medium</label>
|
<label>Medium</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="easy" [(ngModel)]="searchSettings.difficulties.easy">
|
<input type="checkbox" name="easy" [(ngModel)]="searchSettings.difficulties.easy" />
|
||||||
<label>Easy</label>
|
<label>Easy</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core'
|
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
|
||||||
|
|
||||||
import { SearchService } from 'src/app/core/services/search.service'
|
import { SearchService } from 'src/app/core/services/search.service'
|
||||||
import { getDefaultSearch, SongSearch } from 'src/electron/shared/interfaces/search.interface'
|
import { getDefaultSearch } from 'src/electron/shared/interfaces/search.interface'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-search-bar',
|
selector: 'app-search-bar',
|
||||||
templateUrl: './search-bar.component.html',
|
templateUrl: './search-bar.component.html',
|
||||||
styleUrls: ['./search-bar.component.scss']
|
styleUrls: ['./search-bar.component.scss'],
|
||||||
})
|
})
|
||||||
export class SearchBarComponent implements AfterViewInit {
|
export class SearchBarComponent implements AfterViewInit {
|
||||||
|
|
||||||
@@ -23,20 +24,20 @@ export class SearchBarComponent implements AfterViewInit {
|
|||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
$(this.searchIcon.nativeElement).popup({
|
$(this.searchIcon.nativeElement).popup({
|
||||||
onShow: () => this.isError // Only show the popup if there is an error
|
onShow: () => this.isError, // Only show the popup if there is an error
|
||||||
})
|
})
|
||||||
this.searchService.onSearchErrorStateUpdate((isError) => {
|
this.searchService.onSearchErrorStateUpdate(isError => {
|
||||||
this.isError = isError
|
this.isError = isError
|
||||||
})
|
})
|
||||||
$(this.quantityDropdown.nativeElement).dropdown({
|
$(this.quantityDropdown.nativeElement).dropdown({
|
||||||
onChange: (value: string) => {
|
onChange: (value: string) => {
|
||||||
this.searchSettings.quantity = value as 'all' | 'any'
|
this.searchSettings.quantity = value as 'all' | 'any'
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
$(this.similarityDropdown.nativeElement).dropdown({
|
$(this.similarityDropdown.nativeElement).dropdown({
|
||||||
onChange: (value: string) => {
|
onChange: (value: string) => {
|
||||||
this.searchSettings.similarity = value as 'similar' | 'exact'
|
this.searchSettings.similarity = value as 'similar' | 'exact'
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ export class SearchBarComponent implements AfterViewInit {
|
|||||||
onChange: (_length: number, min: number, max: number) => {
|
onChange: (_length: number, min: number, max: number) => {
|
||||||
this.searchSettings.minDiff = min
|
this.searchSettings.minDiff = min
|
||||||
this.searchSettings.maxDiff = max
|
this.searchSettings.maxDiff = max
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}, 50)
|
}, 50)
|
||||||
this.sliderInitialized = true
|
this.sliderInitialized = true
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
<div *ngIf="downloads.length > 0" class="ui segments">
|
<div *ngIf="downloads.length > 0" class="ui segments">
|
||||||
<div *ngFor="let download of downloads; trackBy:trackByVersionID" class="ui segment" [style.background-color]="getBackgroundColor(download)">
|
<div *ngFor="let download of downloads; trackBy: trackByVersionID" class="ui segment" [style.background-color]="getBackgroundColor(download)">
|
||||||
|
|
||||||
<h3 id="downloadTitle">
|
<h3 id="downloadTitle">
|
||||||
<span style="flex-grow: 1;">{{download.title}}</span>
|
<span style="flex-grow: 1">{{ download.title }}</span>
|
||||||
<i class="inside right close icon" (click)="cancelDownload(download.versionID)"></i>
|
<i class="inside right close icon" (click)="cancelDownload(download.versionID)"></i>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div id="downloadText">
|
<div id="downloadText">
|
||||||
<span style="flex-grow: 1;">{{download.header}}</span>
|
<span style="flex-grow: 1">{{ download.header }}</span>
|
||||||
<span *ngIf="!download.isLink" class="description">{{download.description}}</span>
|
<span *ngIf="!download.isLink" class="description">{{ download.description }}</span>
|
||||||
<span *ngIf="download.isLink" class="description">
|
<span *ngIf="download.isLink" class="description">
|
||||||
<a (click)="openFolder(download.description)">{{download.description}}</a>
|
<a (click)="openFolder(download.description)">{{ download.description }}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="downloadProgressDiv">
|
<div id="downloadProgressDiv">
|
||||||
<div id="downloadProgressBar" appProgressBar [percent]="download.percent" class="ui small progress">
|
<div id="downloadProgressBar" appProgressBar [percent]="download.percent" class="ui small progress">
|
||||||
<div class="bar" [style.height]="download.type == 'error' ? '-webkit-fill-available' : undefined">
|
<div class="bar" [style.height]="download.type === 'error' ? '-webkit-fill-available' : undefined">
|
||||||
<div class="progress"></div>
|
<div class="progress"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button *ngIf="download.type == 'error'" class="ui right attached labeled compact icon button"
|
<button *ngIf="download.type === 'error'" class="ui right attached labeled compact icon button" (click)="retryDownload(download.versionID)">
|
||||||
(click)="retryDownload(download.versionID)">
|
|
||||||
<i class="redo icon"></i>
|
<i class="redo icon"></i>
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component, ChangeDetectorRef } from '@angular/core'
|
import { ChangeDetectorRef, Component } from '@angular/core'
|
||||||
|
|
||||||
import { DownloadProgress } from '../../../../../electron/shared/interfaces/download.interface'
|
import { DownloadProgress } from '../../../../../electron/shared/interfaces/download.interface'
|
||||||
import { DownloadService } from '../../../../core/services/download.service'
|
import { DownloadService } from '../../../../core/services/download.service'
|
||||||
import { ElectronService } from '../../../../core/services/electron.service'
|
import { ElectronService } from '../../../../core/services/electron.service'
|
||||||
@@ -6,14 +7,14 @@ import { ElectronService } from '../../../../core/services/electron.service'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-downloads-modal',
|
selector: 'app-downloads-modal',
|
||||||
templateUrl: './downloads-modal.component.html',
|
templateUrl: './downloads-modal.component.html',
|
||||||
styleUrls: ['./downloads-modal.component.scss']
|
styleUrls: ['./downloads-modal.component.scss'],
|
||||||
})
|
})
|
||||||
export class DownloadsModalComponent {
|
export class DownloadsModalComponent {
|
||||||
|
|
||||||
downloads: DownloadProgress[] = []
|
downloads: DownloadProgress[] = []
|
||||||
|
|
||||||
constructor(private electronService: ElectronService, private downloadService: DownloadService, ref: ChangeDetectorRef) {
|
constructor(private electronService: ElectronService, private downloadService: DownloadService, ref: ChangeDetectorRef) {
|
||||||
electronService.receiveIPC('queue-updated', (order) => {
|
electronService.receiveIPC('queue-updated', order => {
|
||||||
this.downloads.sort((a, b) => order.indexOf(a.versionID) - order.indexOf(b.versionID))
|
this.downloads.sort((a, b) => order.indexOf(a.versionID) - order.indexOf(b.versionID))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div id="bottomMenu" class="ui bottom borderless menu">
|
<div id="bottomMenu" class="ui bottom borderless menu">
|
||||||
<div *ngIf="resultCount > 0" class="item">{{resultCount}}{{allResultsVisible ? '' : '+'}} Result{{resultCount == 1 ? '' : 's'}}</div>
|
<div *ngIf="resultCount > 0" class="item">{{ resultCount }}{{ allResultsVisible ? '' : '+' }} Result{{ resultCount === 1 ? '' : 's' }}</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<button *ngIf="selectedResults.length > 1" (click)="downloadSelected()" class="ui positive button">
|
<button *ngIf="selectedResults.length > 1" (click)="downloadSelected()" class="ui positive button">
|
||||||
Download {{selectedResults.length}} Results
|
Download {{ selectedResults.length }} Results
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<a *ngIf="downloading" class="item right" (click)="showDownloads()">
|
<a *ngIf="downloading" class="item right" (click)="showDownloads()">
|
||||||
@@ -18,7 +18,9 @@
|
|||||||
<div class="scrolling content">
|
<div class="scrolling content">
|
||||||
<div class="ui segments">
|
<div class="ui segments">
|
||||||
<div class="ui segment" *ngFor="let chartGroup of chartGroups">
|
<div class="ui segment" *ngFor="let chartGroup of chartGroups">
|
||||||
<p *ngFor="let chart of chartGroup">{{chart.chartName}} <b>[{{chart.charters}}]</b></p>
|
<p *ngFor="let chart of chartGroup">
|
||||||
|
{{ chart.chartName }} <b>[{{ chart.charters }}]</b>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Component, ChangeDetectorRef } from '@angular/core'
|
import { ChangeDetectorRef, Component } from '@angular/core'
|
||||||
|
|
||||||
|
import { VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
|
||||||
|
import { groupBy } from '../../../../electron/shared/UtilFunctions'
|
||||||
import { DownloadService } from '../../../core/services/download.service'
|
import { DownloadService } from '../../../core/services/download.service'
|
||||||
import { ElectronService } from '../../../core/services/electron.service'
|
import { ElectronService } from '../../../core/services/electron.service'
|
||||||
import { groupBy } from '../../../../electron/shared/UtilFunctions'
|
|
||||||
import { VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
|
|
||||||
import { SearchService } from '../../../core/services/search.service'
|
import { SearchService } from '../../../core/services/search.service'
|
||||||
import { SelectionService } from '../../../core/services/selection.service'
|
import { SelectionService } from '../../../core/services/selection.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-status-bar',
|
selector: 'app-status-bar',
|
||||||
templateUrl: './status-bar.component.html',
|
templateUrl: './status-bar.component.html',
|
||||||
styleUrls: ['./status-bar.component.scss']
|
styleUrls: ['./status-bar.component.scss'],
|
||||||
})
|
})
|
||||||
export class StatusBarComponent {
|
export class StatusBarComponent {
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ export class StatusBarComponent {
|
|||||||
chartName: downloadVersion.chartName,
|
chartName: downloadVersion.chartName,
|
||||||
artist: downloadSong.artist,
|
artist: downloadSong.artist,
|
||||||
charter: downloadVersion.charters,
|
charter: downloadVersion.charters,
|
||||||
driveData: downloadVersion.driveData
|
driveData: downloadVersion.driveData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -96,7 +97,7 @@ export class StatusBarComponent {
|
|||||||
chartName: downloadVersion.chartName,
|
chartName: downloadVersion.chartName,
|
||||||
artist: downloadSong.artist,
|
artist: downloadSong.artist,
|
||||||
charter: downloadVersion.charters,
|
charter: downloadVersion.charters,
|
||||||
driveData: downloadVersion.driveData
|
driveData: downloadVersion.driveData,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,30 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Chart library directory</label>
|
<label>Chart library directory</label>
|
||||||
<div class="ui action input">
|
<div class="ui action input">
|
||||||
<input [value]="settingsService.libraryDirectory || 'No folder selected'" class="default-cursor" readonly
|
<input
|
||||||
type="text" placeholder="No directory selected!">
|
[value]="settingsService.libraryDirectory || 'No folder selected'"
|
||||||
<button *ngIf="settingsService.libraryDirectory != undefined" (click)="openLibraryDirectory()"
|
class="default-cursor"
|
||||||
class="ui button">Open Folder</button>
|
readonly
|
||||||
|
type="text"
|
||||||
|
placeholder="No directory selected!" />
|
||||||
|
<button *ngIf="settingsService.libraryDirectory !== undefined" (click)="openLibraryDirectory()" class="ui button">Open Folder</button>
|
||||||
<button (click)="getLibraryDirectory()" class="ui button positive">Choose</button>
|
<button (click)="getLibraryDirectory()" class="ui button positive">Choose</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="ui header">Cache</h3>
|
<h3 class="ui header">Cache</h3>
|
||||||
<div>Current Cache Size: <div class="ui label" style="margin-left: 1em;">{{cacheSize}}</div>
|
<div>
|
||||||
|
Current Cache Size:
|
||||||
|
<div class="ui label" style="margin-left: 1em">{{ cacheSize }}</div>
|
||||||
</div>
|
</div>
|
||||||
<button style="margin-top: 0.5em;" (click)="clearCache()" class="ui button">Clear Cache</button>
|
<button style="margin-top: 0.5em" (click)="clearCache()" class="ui button">Clear Cache</button>
|
||||||
|
|
||||||
<h3 class="ui header">Downloads</h3>
|
<h3 class="ui header">Downloads</h3>
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div appCheckbox #videoCheckbox class="ui checkbox" (checked)="downloadVideos($event)">
|
<div appCheckbox #videoCheckbox class="ui checkbox" (checked)="downloadVideos($event)">
|
||||||
<input type="checkbox">
|
<input type="checkbox" />
|
||||||
<label>Download video backgrounds</label>
|
<label>Download video backgrounds</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,39 +34,38 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Google rate limit delay</label>
|
<label>Google rate limit delay</label>
|
||||||
<div id="rateLimitInput" class="ui right labeled input">
|
<div id="rateLimitInput" class="ui right labeled input">
|
||||||
<input type="number" [value]="settingsService.rateLimitDelay" (input)="changeRateLimit($event)">
|
<input type="number" [value]="settingsService.rateLimitDelay" (input)="changeRateLimit($event)" />
|
||||||
<div class="ui basic label">
|
<div class="ui basic label">sec</div>
|
||||||
sec
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="settingsService.rateLimitDelay < 30" class="ui warning message">
|
<div *ngIf="settingsService.rateLimitDelay < 30" class="ui warning message">
|
||||||
<i class="exclamation circle icon"></i>
|
<i class="exclamation circle icon"></i>
|
||||||
<b>Warning:</b> downloading files from Google with a delay less than about 30 seconds will eventually cause Google to
|
<b>Warning:</b> downloading files from Google with a delay less than about 30 seconds will eventually cause Google to refuse download requests from
|
||||||
refuse download requests from this program for a few hours. This limitation will be removed in a future update.
|
this program for a few hours. This limitation will be removed in a future update.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="ui header">Theme</h3>
|
<h3 class="ui header">Theme</h3>
|
||||||
<div #themeDropdown class="ui selection dropdown mr">
|
<div #themeDropdown class="ui selection dropdown mr">
|
||||||
<input type="hidden" name="sort" [value]="settingsService.theme">
|
<input type="hidden" name="sort" [value]="settingsService.theme" />
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="default text">{{settingsService.theme}}</div>
|
<div class="default text">{{ settingsService.theme }}</div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="item" [attr.data-value]="i" *ngFor="let theme of settingsService.builtinThemes; let i = index">{{theme}}</div>
|
<div class="item" [attr.data-value]="i" *ngFor="let theme of settingsService.builtinThemes; let i = index">{{ theme }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
<button *ngIf="updateAvailable" class="ui labeled icon positive button" (click)="downloadUpdate()">
|
<button *ngIf="updateAvailable" class="ui labeled icon positive button" (click)="downloadUpdate()">
|
||||||
<i class="left alternate icon" [ngClass]="(updateDownloaded ? 'sync' : 'cloud download')"></i>{{downloadUpdateText}}</button>
|
<i class="left alternate icon" [ngClass]="updateDownloaded ? 'sync' : 'cloud download'"></i>{{ downloadUpdateText }}
|
||||||
|
</button>
|
||||||
<button *ngIf="updateAvailable === null" class="ui labeled yellow icon button" [class.disabled]="updateRetrying" (click)="retryUpdate()">
|
<button *ngIf="updateAvailable === null" class="ui labeled yellow icon button" [class.disabled]="updateRetrying" (click)="retryUpdate()">
|
||||||
<i class="left alternate sync alternate icon" [class.loading]="updateRetrying"></i>{{retryUpdateText}}</button>
|
<i class="left alternate sync alternate icon" [class.loading]="updateRetrying"></i>{{ retryUpdateText }}
|
||||||
<button id="versionNumberButton" class="ui basic disabled button">{{currentVersion}}</button>
|
</button>
|
||||||
|
<button id="versionNumberButton" class="ui basic disabled button">{{ currentVersion }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="ui basic icon button" data-tooltip="Toggle developer tools" data-position="top right"
|
<button class="ui basic icon button" data-tooltip="Toggle developer tools" data-position="top right" (click)="toggleDevTools()">
|
||||||
(click)="toggleDevTools()">
|
|
||||||
<i class="cog icon"></i>
|
<i class="cog icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component, OnInit, AfterViewInit, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core'
|
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'
|
||||||
|
|
||||||
import { CheckboxDirective } from 'src/app/core/directives/checkbox.directive'
|
import { CheckboxDirective } from 'src/app/core/directives/checkbox.directive'
|
||||||
import { ElectronService } from 'src/app/core/services/electron.service'
|
import { ElectronService } from 'src/app/core/services/electron.service'
|
||||||
import { SettingsService } from 'src/app/core/services/settings.service'
|
import { SettingsService } from 'src/app/core/services/settings.service'
|
||||||
@@ -6,7 +7,7 @@ import { SettingsService } from 'src/app/core/services/settings.service'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
templateUrl: './settings.component.html',
|
templateUrl: './settings.component.html',
|
||||||
styleUrls: ['./settings.component.scss']
|
styleUrls: ['./settings.component.scss'],
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit, AfterViewInit {
|
export class SettingsComponent implements OnInit, AfterViewInit {
|
||||||
@ViewChild('themeDropdown', { static: true }) themeDropdown: ElementRef
|
@ViewChild('themeDropdown', { static: true }) themeDropdown: ElementRef
|
||||||
@@ -29,7 +30,7 @@ export class SettingsComponent implements OnInit, AfterViewInit {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.electronService.receiveIPC('update-available', (result) => {
|
this.electronService.receiveIPC('update-available', result => {
|
||||||
this.updateAvailable = result != null
|
this.updateAvailable = result != null
|
||||||
this.updateRetrying = false
|
this.updateRetrying = false
|
||||||
if (this.updateAvailable) {
|
if (this.updateAvailable) {
|
||||||
@@ -61,7 +62,7 @@ export class SettingsComponent implements OnInit, AfterViewInit {
|
|||||||
$(this.themeDropdown.nativeElement).dropdown({
|
$(this.themeDropdown.nativeElement).dropdown({
|
||||||
onChange: (_value: string, text: string) => {
|
onChange: (_value: string, text: string) => {
|
||||||
this.settingsService.theme = text
|
this.settingsService.theme = text
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
this.videoCheckbox.check(this.settingsService.downloadVideos)
|
this.videoCheckbox.check(this.settingsService.downloadVideos)
|
||||||
@@ -82,7 +83,7 @@ export class SettingsComponent implements OnInit, AfterViewInit {
|
|||||||
title: 'Choose library folder',
|
title: 'Choose library folder',
|
||||||
buttonLabel: 'This is where my charts are!',
|
buttonLabel: 'This is where my charts are!',
|
||||||
defaultPath: this.settingsService.libraryDirectory || '',
|
defaultPath: this.settingsService.libraryDirectory || '',
|
||||||
properties: ['openDirectory']
|
properties: ['openDirectory'],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.canceled == false) {
|
if (result.canceled == false) {
|
||||||
@@ -106,7 +107,7 @@ export class SettingsComponent implements OnInit, AfterViewInit {
|
|||||||
this.updateDownloading = true
|
this.updateDownloading = true
|
||||||
this.electronService.sendIPC('download-update', undefined)
|
this.electronService.sendIPC('download-update', undefined)
|
||||||
this.downloadUpdateText = 'Downloading... (0%)'
|
this.downloadUpdateText = 'Downloading... (0%)'
|
||||||
this.electronService.receiveIPC('update-progress', (result) => {
|
this.electronService.receiveIPC('update-progress', result => {
|
||||||
this.downloadUpdateText = `Downloading... (${result.percent.toFixed(0)}%)`
|
this.downloadUpdateText = `Downloading... (${result.percent.toFixed(0)}%)`
|
||||||
this.ref.detectChanges()
|
this.ref.detectChanges()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, OnInit, ChangeDetectorRef } from '@angular/core'
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'
|
||||||
|
|
||||||
import { ElectronService } from '../../core/services/electron.service'
|
import { ElectronService } from '../../core/services/electron.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-toolbar',
|
selector: 'app-toolbar',
|
||||||
templateUrl: './toolbar.component.html',
|
templateUrl: './toolbar.component.html',
|
||||||
styleUrls: ['./toolbar.component.scss']
|
styleUrls: ['./toolbar.component.scss'],
|
||||||
})
|
})
|
||||||
export class ToolbarComponent implements OnInit {
|
export class ToolbarComponent implements OnInit {
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ export class ToolbarComponent implements OnInit {
|
|||||||
this.ref.detectChanges()
|
this.ref.detectChanges()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.electronService.receiveIPC('update-available', (result) => {
|
this.electronService.receiveIPC('update-available', result => {
|
||||||
this.updateAvailable = result != null
|
this.updateAvailable = result != null
|
||||||
this.ref.detectChanges()
|
this.ref.detectChanges()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Directive, ElementRef, Output, EventEmitter, AfterViewInit } from '@angular/core'
|
import { AfterViewInit, Directive, ElementRef, EventEmitter, Output } from '@angular/core'
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appCheckbox]'
|
selector: '[appCheckbox]',
|
||||||
})
|
})
|
||||||
export class CheckboxDirective implements AfterViewInit {
|
export class CheckboxDirective implements AfterViewInit {
|
||||||
@Output() checked = new EventEmitter<boolean>()
|
@Output() checked = new EventEmitter<boolean>()
|
||||||
@@ -19,7 +19,7 @@ export class CheckboxDirective implements AfterViewInit {
|
|||||||
onUnchecked: () => {
|
onUnchecked: () => {
|
||||||
this.checked.emit(false)
|
this.checked.emit(false)
|
||||||
this._isChecked = false
|
this._isChecked = false
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Directive, ElementRef, Input } from '@angular/core'
|
import { Directive, ElementRef, Input } from '@angular/core'
|
||||||
|
|
||||||
import * as _ from 'underscore'
|
import * as _ from 'underscore'
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appProgressBar]'
|
selector: '[appProgressBar]',
|
||||||
})
|
})
|
||||||
export class ProgressBarDirective {
|
export class ProgressBarDirective {
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AlbumArtService {
|
export class AlbumArtService {
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Injectable, EventEmitter } from '@angular/core'
|
import { EventEmitter, Injectable } from '@angular/core'
|
||||||
|
|
||||||
|
import { DownloadProgress, NewDownload } from '../../../electron/shared/interfaces/download.interface'
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { NewDownload, DownloadProgress } from '../../../electron/shared/interfaces/download.interface'
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class DownloadService {
|
export class DownloadService {
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import { Injectable } from '@angular/core'
|
|||||||
// If you import a module but never use any of the imported values other than as TypeScript types,
|
// If you import a module but never use any of the imported values other than as TypeScript types,
|
||||||
// the resulting javascript file will look as if you never imported the module at all.
|
// the resulting javascript file will look as if you never imported the module at all.
|
||||||
import * as electron from 'electron'
|
import * as electron from 'electron'
|
||||||
import { IPCInvokeEvents, IPCEmitEvents } from '../../../electron/shared/IPCHandler'
|
|
||||||
|
import { IPCEmitEvents, IPCInvokeEvents } from '../../../electron/shared/IPCHandler'
|
||||||
|
|
||||||
const { app, getCurrentWindow, dialog, session } = window.require('@electron/remote')
|
const { app, getCurrentWindow, dialog, session } = window.require('@electron/remote')
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ElectronService {
|
export class ElectronService {
|
||||||
electron: typeof electron
|
electron: typeof electron
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Injectable, EventEmitter } from '@angular/core'
|
import { EventEmitter, Injectable } from '@angular/core'
|
||||||
import { ElectronService } from './electron.service'
|
|
||||||
import { 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'
|
import { VersionResult } from 'src/electron/shared/interfaces/songDetails.interface'
|
||||||
|
import { ElectronService } from './electron.service'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SearchService {
|
export class SearchService {
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Injectable, EventEmitter } from '@angular/core'
|
import { EventEmitter, Injectable } from '@angular/core'
|
||||||
|
|
||||||
import { SongResult } from '../../../electron/shared/interfaces/search.interface'
|
import { SongResult } from '../../../electron/shared/interfaces/search.interface'
|
||||||
import { SearchService } from './search.service'
|
import { SearchService } from './search.service'
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ interface SelectionEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SelectionService {
|
export class SelectionService {
|
||||||
|
|
||||||
@@ -23,14 +24,14 @@ export class SelectionService {
|
|||||||
private selections: { [songID: number]: boolean | undefined } = {}
|
private selections: { [songID: number]: boolean | undefined } = {}
|
||||||
|
|
||||||
constructor(searchService: SearchService) {
|
constructor(searchService: SearchService) {
|
||||||
searchService.onSearchChanged((results) => {
|
searchService.onSearchChanged(results => {
|
||||||
this.searchResults = results
|
this.searchResults = results
|
||||||
if (this.allSelected) {
|
if (this.allSelected) {
|
||||||
this.selectAll() // Select newly added rows if allSelected
|
this.selectAll() // Select newly added rows if allSelected
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
searchService.onNewSearch((results) => {
|
searchService.onNewSearch(results => {
|
||||||
this.searchResults = results
|
this.searchResults = results
|
||||||
this.selectionChangedCallbacks = {}
|
this.selectionChangedCallbacks = {}
|
||||||
this.selections = {}
|
this.selections = {}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ElectronService } from './electron.service'
|
|
||||||
import { Settings } from 'src/electron/shared/Settings'
|
import { Settings } from 'src/electron/shared/Settings'
|
||||||
|
import { ElectronService } from './electron.service'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SettingsService {
|
export class SettingsService {
|
||||||
readonly builtinThemes = ['Default', 'Dark']
|
readonly builtinThemes = ['Default', 'Dark']
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router'
|
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This makes each route with the 'reuse' data flag persist when not in focus.
|
* This makes each route with the 'reuse' data flag persist when not in focus.
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { Dirent, readdir as _readdir } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { rimraf } from 'rimraf'
|
||||||
|
import { inspect, promisify } from 'util'
|
||||||
|
|
||||||
|
import { devLog } from '../shared/ElectronUtilFunctions'
|
||||||
import { IPCInvokeHandler } from '../shared/IPCHandler'
|
import { IPCInvokeHandler } from '../shared/IPCHandler'
|
||||||
import { tempPath } from '../shared/Paths'
|
import { tempPath } from '../shared/Paths'
|
||||||
import { rimraf } from 'rimraf'
|
|
||||||
import { Dirent, readdir as _readdir } from 'fs'
|
|
||||||
import { inspect, promisify } from 'util'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { devLog } from '../shared/ElectronUtilFunctions'
|
|
||||||
|
|
||||||
const readdir = promisify(_readdir)
|
const readdir = promisify(_readdir)
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ const readdir = promisify(_readdir)
|
|||||||
* Handles the 'clear-cache' event.
|
* Handles the 'clear-cache' event.
|
||||||
*/
|
*/
|
||||||
class ClearCacheHandler implements IPCInvokeHandler<'clear-cache'> {
|
class ClearCacheHandler implements IPCInvokeHandler<'clear-cache'> {
|
||||||
event: 'clear-cache' = 'clear-cache'
|
event = 'clear-cache' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all the files under `tempPath`
|
* Deletes all the files under `tempPath`
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { IPCEmitHandler } from '../shared/IPCHandler'
|
|
||||||
import { shell } from 'electron'
|
import { shell } from 'electron'
|
||||||
|
|
||||||
|
import { IPCEmitHandler } from '../shared/IPCHandler'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the 'open-url' event.
|
* Handles the 'open-url' event.
|
||||||
*/
|
*/
|
||||||
class OpenURLHandler implements IPCEmitHandler<'open-url'> {
|
class OpenURLHandler implements IPCEmitHandler<'open-url'> {
|
||||||
event: 'open-url' = 'open-url'
|
event = 'open-url' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens `url` in the default browser.
|
* Opens `url` in the default browser.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { dataPath, tempPath, themesPath, settingsPath } from '../shared/Paths'
|
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { IPCInvokeHandler, IPCEmitHandler } from '../shared/IPCHandler'
|
|
||||||
|
import { IPCEmitHandler, IPCInvokeHandler } from '../shared/IPCHandler'
|
||||||
|
import { dataPath, settingsPath, tempPath, themesPath } from '../shared/Paths'
|
||||||
import { defaultSettings, Settings } from '../shared/Settings'
|
import { defaultSettings, Settings } from '../shared/Settings'
|
||||||
|
|
||||||
const exists = promisify(fs.exists)
|
const exists = promisify(fs.exists)
|
||||||
@@ -11,11 +12,34 @@ const writeFile = promisify(fs.writeFile)
|
|||||||
|
|
||||||
let settings: Settings
|
let settings: Settings
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the 'set-settings' event.
|
||||||
|
*/
|
||||||
|
class SetSettingsHandler implements IPCEmitHandler<'set-settings'> {
|
||||||
|
event = 'set-settings' as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates Bridge's settings object to `newSettings` and saves them to Bridge's data directories.
|
||||||
|
*/
|
||||||
|
handler(newSettings: Settings) {
|
||||||
|
settings = newSettings
|
||||||
|
SetSettingsHandler.saveSettings(settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves `settings` to Bridge's data directories.
|
||||||
|
*/
|
||||||
|
static async saveSettings(settings: Settings) {
|
||||||
|
const settingsJSON = JSON.stringify(settings, undefined, 2)
|
||||||
|
await writeFile(settingsPath, settingsJSON, 'utf8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the 'get-settings' event.
|
* Handles the 'get-settings' event.
|
||||||
*/
|
*/
|
||||||
class GetSettingsHandler implements IPCInvokeHandler<'get-settings'> {
|
class GetSettingsHandler implements IPCInvokeHandler<'get-settings'> {
|
||||||
event: 'get-settings' = 'get-settings'
|
event = 'get-settings' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the current settings oject, or default settings if they couldn't be loaded.
|
* @returns the current settings oject, or default settings if they couldn't be loaded.
|
||||||
@@ -65,29 +89,6 @@ class GetSettingsHandler implements IPCInvokeHandler<'get-settings'> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the 'set-settings' event.
|
|
||||||
*/
|
|
||||||
class SetSettingsHandler implements IPCEmitHandler<'set-settings'> {
|
|
||||||
event: 'set-settings' = 'set-settings'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates Bridge's settings object to `newSettings` and saves them to Bridge's data directories.
|
|
||||||
*/
|
|
||||||
handler(newSettings: Settings) {
|
|
||||||
settings = newSettings
|
|
||||||
SetSettingsHandler.saveSettings(settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves `settings` to Bridge's data directories.
|
|
||||||
*/
|
|
||||||
static async saveSettings(settings: Settings) {
|
|
||||||
const settingsJSON = JSON.stringify(settings, undefined, 2)
|
|
||||||
await writeFile(settingsPath, settingsJSON, 'utf8')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSettingsHandler = new GetSettingsHandler()
|
export const getSettingsHandler = new GetSettingsHandler()
|
||||||
export const setSettingsHandler = new SetSettingsHandler()
|
export const setSettingsHandler = new SetSettingsHandler()
|
||||||
export function getSettings() { return getSettingsHandler.getSettings() }
|
export function getSettings() { return getSettingsHandler.getSettings() }
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IPCEmitHandler, IPCInvokeHandler } from '../shared/IPCHandler'
|
|
||||||
import { autoUpdater, UpdateInfo } from 'electron-updater'
|
import { autoUpdater, UpdateInfo } from 'electron-updater'
|
||||||
|
|
||||||
import { emitIPCEvent } from '../main'
|
import { emitIPCEvent } from '../main'
|
||||||
|
import { IPCEmitHandler, IPCInvokeHandler } from '../shared/IPCHandler'
|
||||||
|
|
||||||
export interface UpdateProgress {
|
export interface UpdateProgress {
|
||||||
bytesPerSecond: number
|
bytesPerSecond: number
|
||||||
@@ -15,7 +16,13 @@ let updateAvailable = false
|
|||||||
* Checks for updates when the program is launched.
|
* Checks for updates when the program is launched.
|
||||||
*/
|
*/
|
||||||
class UpdateChecker implements IPCEmitHandler<'retry-update'> {
|
class UpdateChecker implements IPCEmitHandler<'retry-update'> {
|
||||||
event: 'retry-update' = 'retry-update'
|
event = 'retry-update' as const
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
autoUpdater.autoDownload = false
|
||||||
|
autoUpdater.logger = null
|
||||||
|
this.registerUpdaterListeners()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for an update.
|
* Check for an update.
|
||||||
@@ -24,12 +31,6 @@ class UpdateChecker implements IPCEmitHandler<'retry-update'> {
|
|||||||
this.checkForUpdates()
|
this.checkForUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
|
||||||
autoUpdater.autoDownload = false
|
|
||||||
autoUpdater.logger = null
|
|
||||||
this.registerUpdaterListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
checkForUpdates() {
|
checkForUpdates() {
|
||||||
autoUpdater.checkForUpdates().catch(reason => {
|
autoUpdater.checkForUpdates().catch(reason => {
|
||||||
updateAvailable = null
|
updateAvailable = null
|
||||||
@@ -61,7 +62,7 @@ export const updateChecker = new UpdateChecker()
|
|||||||
* Handles the 'get-update-available' event.
|
* Handles the 'get-update-available' event.
|
||||||
*/
|
*/
|
||||||
class GetUpdateAvailableHandler implements IPCInvokeHandler<'get-update-available'> {
|
class GetUpdateAvailableHandler implements IPCInvokeHandler<'get-update-available'> {
|
||||||
event: 'get-update-available' = 'get-update-available'
|
event = 'get-update-available' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns `true` if an update is available.
|
* @returns `true` if an update is available.
|
||||||
@@ -77,7 +78,7 @@ export const getUpdateAvailableHandler = new GetUpdateAvailableHandler()
|
|||||||
* Handles the 'get-current-version' event.
|
* Handles the 'get-current-version' event.
|
||||||
*/
|
*/
|
||||||
class GetCurrentVersionHandler implements IPCInvokeHandler<'get-current-version'> {
|
class GetCurrentVersionHandler implements IPCInvokeHandler<'get-current-version'> {
|
||||||
event: 'get-current-version' = 'get-current-version'
|
event = 'get-current-version' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the current version of Bridge.
|
* @returns the current version of Bridge.
|
||||||
@@ -93,7 +94,7 @@ export const getCurrentVersionHandler = new GetCurrentVersionHandler()
|
|||||||
* Handles the 'download-update' event.
|
* Handles the 'download-update' event.
|
||||||
*/
|
*/
|
||||||
class DownloadUpdateHandler implements IPCEmitHandler<'download-update'> {
|
class DownloadUpdateHandler implements IPCEmitHandler<'download-update'> {
|
||||||
event: 'download-update' = 'download-update'
|
event = 'download-update' as const
|
||||||
downloading = false
|
downloading = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +122,7 @@ export const downloadUpdateHandler = new DownloadUpdateHandler()
|
|||||||
* Handles the 'quit-and-install' event.
|
* Handles the 'quit-and-install' event.
|
||||||
*/
|
*/
|
||||||
class QuitAndInstallHandler implements IPCEmitHandler<'quit-and-install'> {
|
class QuitAndInstallHandler implements IPCEmitHandler<'quit-and-install'> {
|
||||||
event: 'quit-and-install' = 'quit-and-install'
|
event = 'quit-and-install' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immediately closes the application and installs the update.
|
* Immediately closes the application and installs the update.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
|
||||||
import { AlbumArtResult } from '../../shared/interfaces/songDetails.interface'
|
import { AlbumArtResult } from '../../shared/interfaces/songDetails.interface'
|
||||||
|
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
||||||
import { serverURL } from '../../shared/Paths'
|
import { serverURL } from '../../shared/Paths'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the 'album-art' event.
|
* Handles the 'album-art' event.
|
||||||
*/
|
*/
|
||||||
class AlbumArtHandler implements IPCInvokeHandler<'album-art'> {
|
class AlbumArtHandler implements IPCInvokeHandler<'album-art'> {
|
||||||
event: 'album-art' = 'album-art'
|
event = 'album-art' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns an `AlbumArtResult` object containing the album art for the song with `songID`.
|
* @returns an `AlbumArtResult` object containing the album art for the song with `songID`.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
|
||||||
import { VersionResult } from '../../shared/interfaces/songDetails.interface'
|
import { VersionResult } from '../../shared/interfaces/songDetails.interface'
|
||||||
|
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
||||||
import { serverURL } from '../../shared/Paths'
|
import { serverURL } from '../../shared/Paths'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the 'batch-song-details' event.
|
* Handles the 'batch-song-details' event.
|
||||||
*/
|
*/
|
||||||
class BatchSongDetailsHandler implements IPCInvokeHandler<'batch-song-details'> {
|
class BatchSongDetailsHandler implements IPCInvokeHandler<'batch-song-details'> {
|
||||||
event: 'batch-song-details' = 'batch-song-details'
|
event = 'batch-song-details' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns an array of all the chart versions with a songID found in `songIDs`.
|
* @returns an array of all the chart versions with a songID found in `songIDs`.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
|
||||||
import { SongResult, SongSearch } from '../../shared/interfaces/search.interface'
|
import { SongResult, SongSearch } from '../../shared/interfaces/search.interface'
|
||||||
|
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
||||||
import { serverURL } from '../../shared/Paths'
|
import { serverURL } from '../../shared/Paths'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the 'song-search' event.
|
* Handles the 'song-search' event.
|
||||||
*/
|
*/
|
||||||
class SearchHandler implements IPCInvokeHandler<'song-search'> {
|
class SearchHandler implements IPCInvokeHandler<'song-search'> {
|
||||||
event: 'song-search' = 'song-search'
|
event = 'song-search' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the top 50 songs that match `search`.
|
* @returns the top 50 songs that match `search`.
|
||||||
@@ -15,9 +15,10 @@ class SearchHandler implements IPCInvokeHandler<'song-search'> {
|
|||||||
const response = await fetch(`https://${serverURL}/api/search`, {
|
const response = await fetch(`https://${serverURL}/api/search`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(search)
|
body: JSON.stringify(search),
|
||||||
})
|
})
|
||||||
|
|
||||||
return await response.json()
|
return await response.json()
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
|
||||||
import { VersionResult } from '../../shared/interfaces/songDetails.interface'
|
import { VersionResult } from '../../shared/interfaces/songDetails.interface'
|
||||||
|
import { IPCInvokeHandler } from '../../shared/IPCHandler'
|
||||||
import { serverURL } from '../../shared/Paths'
|
import { serverURL } from '../../shared/Paths'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the 'song-details' event.
|
* Handles the 'song-details' event.
|
||||||
*/
|
*/
|
||||||
class SongDetailsHandler implements IPCInvokeHandler<'song-details'> {
|
class SongDetailsHandler implements IPCInvokeHandler<'song-details'> {
|
||||||
event: 'song-details' = 'song-details'
|
event = 'song-details' as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the chart versions with `songID`.
|
* @returns the chart versions with `songID`.
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import { FileDownloader, getDownloader } from './FileDownloader'
|
|
||||||
import { join, parse } from 'path'
|
import { join, parse } from 'path'
|
||||||
import { FileExtractor } from './FileExtractor'
|
|
||||||
import { sanitizeFilename, interpolate } from '../../shared/UtilFunctions'
|
|
||||||
import { emitIPCEvent } from '../../main'
|
|
||||||
import { ProgressType, NewDownload } from 'src/electron/shared/interfaces/download.interface'
|
|
||||||
import { DriveFile } from 'src/electron/shared/interfaces/songDetails.interface'
|
|
||||||
import { FileTransfer } from './FileTransfer'
|
|
||||||
import { rimraf } from 'rimraf'
|
import { rimraf } from 'rimraf'
|
||||||
import { FilesystemChecker } from './FilesystemChecker'
|
|
||||||
import { getSettings } from '../SettingsHandler.ipc'
|
|
||||||
import { hasVideoExtension } from '../../shared/ElectronUtilFunctions'
|
|
||||||
|
|
||||||
type EventCallback = {
|
import { NewDownload, ProgressType } from 'src/electron/shared/interfaces/download.interface'
|
||||||
|
import { DriveFile } from 'src/electron/shared/interfaces/songDetails.interface'
|
||||||
|
import { emitIPCEvent } from '../../main'
|
||||||
|
import { hasVideoExtension } from '../../shared/ElectronUtilFunctions'
|
||||||
|
import { interpolate, sanitizeFilename } from '../../shared/UtilFunctions'
|
||||||
|
import { getSettings } from '../SettingsHandler.ipc'
|
||||||
|
import { FileDownloader, getDownloader } from './FileDownloader'
|
||||||
|
import { FileExtractor } from './FileExtractor'
|
||||||
|
import { FilesystemChecker } from './FilesystemChecker'
|
||||||
|
import { FileTransfer } from './FileTransfer'
|
||||||
|
|
||||||
|
interface EventCallback {
|
||||||
/** Note: this will not be the last event if `retry()` is called. */
|
/** Note: this will not be the last event if `retry()` is called. */
|
||||||
'error': () => void
|
'error': () => void
|
||||||
'complete': () => void
|
'complete': () => void
|
||||||
}
|
}
|
||||||
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
|
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
|
||||||
|
|
||||||
export type DownloadError = { header: string; body: string; isLink?: boolean }
|
export interface DownloadError { header: string; body: string; isLink?: boolean }
|
||||||
|
|
||||||
export class ChartDownload {
|
export class ChartDownload {
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ export class ChartDownload {
|
|||||||
description: description,
|
description: description,
|
||||||
percent: this.percent,
|
percent: this.percent,
|
||||||
type: type,
|
type: type,
|
||||||
isLink
|
isLink,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ export class ChartDownload {
|
|||||||
checker.on('error', this.handleError.bind(this))
|
checker.on('error', this.handleError.bind(this))
|
||||||
|
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>(resolve => {
|
||||||
checker.on('complete', (tempPath) => {
|
checker.on('complete', tempPath => {
|
||||||
this.tempPath = tempPath
|
this.tempPath = tempPath
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
@@ -202,7 +203,8 @@ export class ChartDownload {
|
|||||||
|
|
||||||
downloader.on('waitProgress', (remainingSeconds: number, totalSeconds: number) => {
|
downloader.on('waitProgress', (remainingSeconds: number, totalSeconds: number) => {
|
||||||
downloadStartPoint = this.individualFileProgressPortion / 2
|
downloadStartPoint = this.individualFileProgressPortion / 2
|
||||||
this.percent = this._allFilesProgress + interpolate(remainingSeconds, totalSeconds, 0, 0, this.individualFileProgressPortion / 2)
|
this.percent =
|
||||||
|
this._allFilesProgress + interpolate(remainingSeconds, totalSeconds, 0, 0, this.individualFileProgressPortion / 2)
|
||||||
this.updateGUI(downloadHeader, `Waiting for Google rate limit... (${remainingSeconds}s)`, 'good')
|
this.updateGUI(downloadHeader, `Waiting for Google rate limit... (${remainingSeconds}s)`, 'good')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -237,7 +239,7 @@ export class ChartDownload {
|
|||||||
private addExtractorEventListeners(extractor: FileExtractor) {
|
private addExtractorEventListeners(extractor: FileExtractor) {
|
||||||
let archive = ''
|
let archive = ''
|
||||||
|
|
||||||
extractor.on('start', (filename) => {
|
extractor.on('start', filename => {
|
||||||
archive = filename
|
archive = filename
|
||||||
this.updateGUI(`[${archive}]`, 'Extracting...', 'good')
|
this.updateGUI(`[${archive}]`, 'Extracting...', 'good')
|
||||||
})
|
})
|
||||||
@@ -264,7 +266,7 @@ export class ChartDownload {
|
|||||||
private addTransferEventListeners(transfer: FileTransfer) {
|
private addTransferEventListeners(transfer: FileTransfer) {
|
||||||
let destinationFolder: string
|
let destinationFolder: string
|
||||||
|
|
||||||
transfer.on('start', (_destinationFolder) => {
|
transfer.on('start', _destinationFolder => {
|
||||||
destinationFolder = _destinationFolder
|
destinationFolder = _destinationFolder
|
||||||
this.updateGUI('Moving files to library folder...', destinationFolder, 'good', true)
|
this.updateGUI('Moving files to library folder...', destinationFolder, 'good', true)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { IPCEmitHandler } from '../../shared/IPCHandler'
|
|
||||||
import { Download } from '../../shared/interfaces/download.interface'
|
import { Download } from '../../shared/interfaces/download.interface'
|
||||||
|
import { IPCEmitHandler } from '../../shared/IPCHandler'
|
||||||
import { ChartDownload } from './ChartDownload'
|
import { ChartDownload } from './ChartDownload'
|
||||||
import { DownloadQueue } from './DownloadQueue'
|
import { DownloadQueue } from './DownloadQueue'
|
||||||
|
|
||||||
class DownloadHandler implements IPCEmitHandler<'download'> {
|
class DownloadHandler implements IPCEmitHandler<'download'> {
|
||||||
event: 'download' = 'download'
|
event = 'download' as const
|
||||||
|
|
||||||
downloadQueue: DownloadQueue = new DownloadQueue()
|
downloadQueue: DownloadQueue = new DownloadQueue()
|
||||||
currentDownload: ChartDownload = undefined
|
currentDownload: ChartDownload = undefined
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Comparators from 'comparators'
|
import Comparators from 'comparators'
|
||||||
import { ChartDownload } from './ChartDownload'
|
|
||||||
import { emitIPCEvent } from '../../main'
|
import { emitIPCEvent } from '../../main'
|
||||||
|
import { ChartDownload } from './ChartDownload'
|
||||||
|
|
||||||
export class DownloadQueue {
|
export class DownloadQueue {
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import { AnyFunction } from '../../shared/UtilFunctions'
|
import Bottleneck from 'bottleneck'
|
||||||
import { devLog } from '../../shared/ElectronUtilFunctions'
|
|
||||||
import { createWriteStream, writeFile as _writeFile } from 'fs'
|
import { createWriteStream, writeFile as _writeFile } from 'fs'
|
||||||
|
import { google } from 'googleapis'
|
||||||
import * as needle from 'needle'
|
import * as needle from 'needle'
|
||||||
|
import { join } from 'path'
|
||||||
import { Readable } from 'stream'
|
import { Readable } from 'stream'
|
||||||
|
import { inspect, promisify } from 'util'
|
||||||
|
|
||||||
|
import { devLog } from '../../shared/ElectronUtilFunctions'
|
||||||
|
import { tempPath } from '../../shared/Paths'
|
||||||
|
import { AnyFunction } from '../../shared/UtilFunctions'
|
||||||
|
import { DownloadError } from './ChartDownload'
|
||||||
// TODO: replace needle with got (for cancel() method) (if before-headers event is possible?)
|
// TODO: replace needle with got (for cancel() method) (if before-headers event is possible?)
|
||||||
import { googleTimer } from './GoogleTimer'
|
import { googleTimer } from './GoogleTimer'
|
||||||
import { DownloadError } from './ChartDownload'
|
|
||||||
import { google } from 'googleapis'
|
|
||||||
import Bottleneck from 'bottleneck'
|
|
||||||
import { inspect, promisify } from 'util'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { tempPath } from '../../shared/Paths'
|
|
||||||
const drive = google.drive('v3')
|
const drive = google.drive('v3')
|
||||||
const limiter = new Bottleneck({
|
const limiter = new Bottleneck({
|
||||||
minTime: 200 // Wait 200 ms between API requests
|
minTime: 200, // Wait 200 ms between API requests
|
||||||
})
|
})
|
||||||
|
|
||||||
const RETRY_MAX = 2
|
const RETRY_MAX = 2
|
||||||
const writeFile = promisify(_writeFile)
|
const writeFile = promisify(_writeFile)
|
||||||
|
|
||||||
type EventCallback = {
|
interface EventCallback {
|
||||||
'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
|
'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
|
||||||
/** Note: this event can be called multiple times if the connection times out or a large file is downloaded */
|
/** Note: this event can be called multiple times if the connection times out or a large file is downloaded */
|
||||||
'requestSent': () => void
|
'requestSent': () => void
|
||||||
@@ -36,7 +38,7 @@ const downloadErrors = {
|
|||||||
connectionError: (err: Error) => { return { header: 'Connection Error', body: `${err.name}: ${err.message}` } },
|
connectionError: (err: Error) => { return { header: 'Connection Error', body: `${err.name}: ${err.message}` } },
|
||||||
responseError: (statusCode: string) => { return { header: 'Connection failed', body: `Server returned status code: ${statusCode}` } },
|
responseError: (statusCode: string) => { return { header: 'Connection failed', body: `Server returned status code: ${statusCode}` } },
|
||||||
htmlError: (path: string) => { return { header: 'Download server returned HTML instead of a file.', body: path, isLink: true } },
|
htmlError: (path: string) => { return { header: 'Download server returned HTML instead of a file.', body: path, isLink: true } },
|
||||||
linkError: (url: string) => { return { header: 'Invalid link', body: `The download link is not formatted correctly: ${url}` } }
|
linkError: (url: string) => { return { header: 'Invalid link', body: `The download link is not formatted correctly: ${url}` } },
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,9 +102,9 @@ class APIFileDownloader {
|
|||||||
try {
|
try {
|
||||||
this.downloadStream = (await drive.files.get({
|
this.downloadStream = (await drive.files.get({
|
||||||
fileId: this.fileID,
|
fileId: this.fileID,
|
||||||
alt: 'media'
|
alt: 'media',
|
||||||
}, {
|
}, {
|
||||||
responseType: 'stream'
|
responseType: 'stream',
|
||||||
})).data
|
})).data
|
||||||
|
|
||||||
if (this.wasCanceled) { return }
|
if (this.wasCanceled) { return }
|
||||||
@@ -243,10 +245,10 @@ class SlowFileDownloader {
|
|||||||
'open_timeout': 5000,
|
'open_timeout': 5000,
|
||||||
'headers': Object.assign({
|
'headers': Object.assign({
|
||||||
'Referer': this.url,
|
'Referer': this.url,
|
||||||
'Accept': '*/*'
|
'Accept': '*/*',
|
||||||
},
|
},
|
||||||
(cookieHeader ? { 'Cookie': cookieHeader } : undefined)
|
(cookieHeader ? { 'Cookie': cookieHeader } : undefined)
|
||||||
)
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.req.on('timeout', this.cancelable((type: string) => {
|
this.req.on('timeout', this.cancelable((type: string) => {
|
||||||
@@ -299,8 +301,8 @@ class SlowFileDownloader {
|
|||||||
const NID = /NID=([^;]*);/.exec(cookieHeader)[1].replace('=', '%')
|
const NID = /NID=([^;]*);/.exec(cookieHeader)[1].replace('=', '%')
|
||||||
const newHeader = `download_warning_${warningCode}=${confirmToken}; NID=${NID}`
|
const newHeader = `download_warning_${warningCode}=${confirmToken}; NID=${NID}`
|
||||||
this.requestDownload(newHeader)
|
this.requestDownload(newHeader)
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
this.saveHTMLError(virusScanHTML).then((path) => {
|
this.saveHTMLError(virusScanHTML).then(path => {
|
||||||
this.failDownload(downloadErrors.htmlError(path))
|
this.failDownload(downloadErrors.htmlError(path))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -316,7 +318,7 @@ class SlowFileDownloader {
|
|||||||
this.callbacks.downloadProgress(0)
|
this.callbacks.downloadProgress(0)
|
||||||
let downloadedSize = 0
|
let downloadedSize = 0
|
||||||
this.req.pipe(createWriteStream(this.fullPath))
|
this.req.pipe(createWriteStream(this.fullPath))
|
||||||
this.req.on('data', this.cancelable((data) => {
|
this.req.on('data', this.cancelable(data => {
|
||||||
downloadedSize += data.length
|
downloadedSize += data.length
|
||||||
this.callbacks.downloadProgress(downloadedSize)
|
this.callbacks.downloadProgress(downloadedSize)
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { readdir, unlink, mkdir as _mkdir } from 'fs'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { join, extname } from 'path'
|
|
||||||
import { AnyFunction } from '../../shared/UtilFunctions'
|
|
||||||
import { devLog } from '../../shared/ElectronUtilFunctions'
|
|
||||||
import * as node7z from 'node-7z'
|
|
||||||
import * as zipBin from '7zip-bin'
|
import * as zipBin from '7zip-bin'
|
||||||
|
import { mkdir as _mkdir, readdir, unlink } from 'fs'
|
||||||
|
import * as node7z from 'node-7z'
|
||||||
import * as unrarjs from 'node-unrar-js' // TODO find better rar library that has async extraction
|
import * as unrarjs from 'node-unrar-js' // TODO find better rar library that has async extraction
|
||||||
import { FailReason } from 'node-unrar-js/dist/js/extractor'
|
import { FailReason } from 'node-unrar-js/dist/js/extractor'
|
||||||
|
import { extname, join } from 'path'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
import { devLog } from '../../shared/ElectronUtilFunctions'
|
||||||
|
import { AnyFunction } from '../../shared/UtilFunctions'
|
||||||
import { DownloadError } from './ChartDownload'
|
import { DownloadError } from './ChartDownload'
|
||||||
|
|
||||||
const mkdir = promisify(_mkdir)
|
const mkdir = promisify(_mkdir)
|
||||||
|
|
||||||
type EventCallback = {
|
interface EventCallback {
|
||||||
'start': (filename: string) => void
|
'start': (filename: string) => void
|
||||||
'extractProgress': (percent: number, fileCount: number) => void
|
'extractProgress': (percent: number, fileCount: number) => void
|
||||||
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
|
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
|
||||||
@@ -23,11 +24,11 @@ const extractErrors = {
|
|||||||
readError: (err: NodeJS.ErrnoException) => { return { header: `Failed to read file (${err.code})`, body: `${err.name}: ${err.message}` } },
|
readError: (err: NodeJS.ErrnoException) => { return { header: `Failed to read file (${err.code})`, body: `${err.name}: ${err.message}` } },
|
||||||
emptyError: () => { return { header: 'Failed to extract archive', body: 'File archive was downloaded but could not be found' } },
|
emptyError: () => { return { header: 'Failed to extract archive', body: 'File archive was downloaded but could not be found' } },
|
||||||
rarmkdirError: (err: NodeJS.ErrnoException, sourceFile: string) => {
|
rarmkdirError: (err: NodeJS.ErrnoException, sourceFile: string) => {
|
||||||
return { header: `Extracting archive failed. (${err.code})`, body: `${err.name}: ${err.message} (${sourceFile})`}
|
return { header: `Extracting archive failed. (${err.code})`, body: `${err.name}: ${err.message} (${sourceFile})` }
|
||||||
},
|
},
|
||||||
rarextractError: (result: { reason: FailReason; msg: string }, sourceFile: string) => {
|
rarextractError: (result: { reason: FailReason; msg: string }, sourceFile: string) => {
|
||||||
return { header: `Extracting archive failed: ${result.reason}`, body: `${result.msg} (${sourceFile})`}
|
return { header: `Extracting archive failed: ${result.reason}`, body: `${result.msg} (${sourceFile})` }
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FileExtractor {
|
export class FileExtractor {
|
||||||
@@ -135,7 +136,7 @@ export class FileExtractor {
|
|||||||
* Tries to delete the archive at `fullPath`.
|
* Tries to delete the archive at `fullPath`.
|
||||||
*/
|
*/
|
||||||
private deleteArchive(fullPath: string) {
|
private deleteArchive(fullPath: string) {
|
||||||
unlink(fullPath, this.cancelable((err) => {
|
unlink(fullPath, this.cancelable(err => {
|
||||||
if (err && err.code != 'ENOENT') {
|
if (err && err.code != 'ENOENT') {
|
||||||
devLog(`Warning: failed to delete archive at [${fullPath}]`)
|
devLog(`Warning: failed to delete archive at [${fullPath}]`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Dirent, readdir as _readdir } from 'fs'
|
import { Dirent, readdir as _readdir } from 'fs'
|
||||||
import { promisify } from 'util'
|
|
||||||
import { getSettings } from '../SettingsHandler.ipc'
|
|
||||||
import * as mv from 'mv'
|
import * as mv from 'mv'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { rimraf } from 'rimraf'
|
import { rimraf } from 'rimraf'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
import { getSettings } from '../SettingsHandler.ipc'
|
||||||
import { DownloadError } from './ChartDownload'
|
import { DownloadError } from './ChartDownload'
|
||||||
|
|
||||||
const readdir = promisify(_readdir)
|
const readdir = promisify(_readdir)
|
||||||
|
|
||||||
type EventCallback = {
|
interface EventCallback {
|
||||||
'start': (destinationFolder: string) => void
|
'start': (destinationFolder: string) => void
|
||||||
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
|
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
|
||||||
'complete': () => void
|
'complete': () => void
|
||||||
@@ -19,7 +20,7 @@ const transferErrors = {
|
|||||||
readError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to read file.'),
|
readError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to read file.'),
|
||||||
deleteError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete file.'),
|
deleteError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete file.'),
|
||||||
rimrafError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete folder.'),
|
rimrafError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete folder.'),
|
||||||
mvError: (err: NodeJS.ErrnoException) => fsError(err, `Failed to move folder to library.${err.code == 'EPERM' ? ' (does the chart already exist?)' : ''}`)
|
mvError: (err: NodeJS.ErrnoException) => fsError(err, `Failed to move folder to library.${err.code == 'EPERM' ? ' (does the chart already exist?)' : ''}`),
|
||||||
}
|
}
|
||||||
|
|
||||||
function fsError(err: NodeJS.ErrnoException, description: string) {
|
function fsError(err: NodeJS.ErrnoException, description: string) {
|
||||||
@@ -89,7 +90,7 @@ export class FileTransfer {
|
|||||||
* Moves the downloaded chart to the library path.
|
* Moves the downloaded chart to the library path.
|
||||||
*/
|
*/
|
||||||
private moveFolder() {
|
private moveFolder() {
|
||||||
mv(this.nestedSourceFolder, this.destinationFolder, { mkdirp: true }, (err) => {
|
mv(this.nestedSourceFolder, this.destinationFolder, { mkdirp: true }, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.callbacks.error(transferErrors.mvError(err), () => this.moveFolder())
|
this.callbacks.error(transferErrors.mvError(err), () => this.moveFolder())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { DownloadError } from './ChartDownload'
|
|
||||||
import { tempPath } from '../../shared/Paths'
|
|
||||||
import { AnyFunction } from '../../shared/UtilFunctions'
|
|
||||||
import { devLog } from '../../shared/ElectronUtilFunctions'
|
|
||||||
import { randomBytes as _randomBytes } from 'crypto'
|
import { randomBytes as _randomBytes } from 'crypto'
|
||||||
import { mkdir, access, constants } from 'fs'
|
import { access, constants, mkdir } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
import { devLog } from '../../shared/ElectronUtilFunctions'
|
||||||
|
import { tempPath } from '../../shared/Paths'
|
||||||
|
import { AnyFunction } from '../../shared/UtilFunctions'
|
||||||
import { getSettings } from '../SettingsHandler.ipc'
|
import { getSettings } from '../SettingsHandler.ipc'
|
||||||
|
import { DownloadError } from './ChartDownload'
|
||||||
|
|
||||||
const randomBytes = promisify(_randomBytes)
|
const randomBytes = promisify(_randomBytes)
|
||||||
|
|
||||||
type EventCallback = {
|
interface EventCallback {
|
||||||
'start': () => void
|
'start': () => void
|
||||||
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
|
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
|
||||||
'complete': (tempPath: string) => void
|
'complete': (tempPath: string) => void
|
||||||
@@ -23,7 +24,7 @@ const filesystemErrors = {
|
|||||||
destinationFolderExists: (destinationPath: string) => {
|
destinationFolderExists: (destinationPath: string) => {
|
||||||
return { header: 'This chart already exists in your library folder.', body: destinationPath, isLink: true }
|
return { header: 'This chart already exists in your library folder.', body: destinationPath, isLink: true }
|
||||||
},
|
},
|
||||||
mkdirError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to create temporary folder.')
|
mkdirError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to create temporary folder.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
function fsError(err: NodeJS.ErrnoException, description: string) {
|
function fsError(err: NodeJS.ErrnoException, description: string) {
|
||||||
@@ -58,7 +59,7 @@ export class FilesystemChecker {
|
|||||||
if (getSettings().libraryPath == undefined) {
|
if (getSettings().libraryPath == undefined) {
|
||||||
this.callbacks.error(filesystemErrors.libraryFolder(), () => this.beginCheck())
|
this.callbacks.error(filesystemErrors.libraryFolder(), () => this.beginCheck())
|
||||||
} else {
|
} else {
|
||||||
access(getSettings().libraryPath, constants.W_OK, this.cancelable((err) => {
|
access(getSettings().libraryPath, constants.W_OK, this.cancelable(err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.callbacks.error(filesystemErrors.libraryAccess(err), () => this.beginCheck())
|
this.callbacks.error(filesystemErrors.libraryAccess(err), () => this.beginCheck())
|
||||||
} else {
|
} else {
|
||||||
@@ -73,7 +74,7 @@ export class FilesystemChecker {
|
|||||||
*/
|
*/
|
||||||
private checkDestinationFolder() {
|
private checkDestinationFolder() {
|
||||||
const destinationPath = join(getSettings().libraryPath, this.destinationFolderName)
|
const destinationPath = join(getSettings().libraryPath, this.destinationFolderName)
|
||||||
access(destinationPath, constants.F_OK, this.cancelable((err) => {
|
access(destinationPath, constants.F_OK, this.cancelable(err => {
|
||||||
if (err) { // File does not exist
|
if (err) { // File does not exist
|
||||||
this.createDownloadFolder()
|
this.createDownloadFolder()
|
||||||
} else {
|
} else {
|
||||||
@@ -88,7 +89,7 @@ export class FilesystemChecker {
|
|||||||
private async createDownloadFolder(retryCount = 0) {
|
private async createDownloadFolder(retryCount = 0) {
|
||||||
const tempChartPath = join(tempPath, `chart_${(await randomBytes(5)).toString('hex')}`)
|
const tempChartPath = join(tempPath, `chart_${(await randomBytes(5)).toString('hex')}`)
|
||||||
|
|
||||||
mkdir(tempChartPath, this.cancelable((err) => {
|
mkdir(tempChartPath, this.cancelable(err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (retryCount < 5) {
|
if (retryCount < 5) {
|
||||||
devLog(`Error creating folder [${tempChartPath}], retrying with a different folder...`)
|
devLog(`Error creating folder [${tempChartPath}], retrying with a different folder...`)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getSettings } from '../SettingsHandler.ipc'
|
import { getSettings } from '../SettingsHandler.ipc'
|
||||||
|
|
||||||
type EventCallback = {
|
interface EventCallback {
|
||||||
'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
|
'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
|
||||||
'complete': () => void
|
'complete': () => void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { app, BrowserWindow, ipcMain } from 'electron'
|
import { app, BrowserWindow, ipcMain } from 'electron'
|
||||||
import { updateChecker } from './ipc/UpdateHandler.ipc'
|
|
||||||
import * as windowStateKeeper from 'electron-window-state'
|
import * as windowStateKeeper from 'electron-window-state'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as url from 'url'
|
import * as url from 'url'
|
||||||
require('electron-unhandled')({ showDialog: true })
|
|
||||||
|
|
||||||
|
|
||||||
// IPC Handlers
|
|
||||||
import { getIPCInvokeHandlers, getIPCEmitHandlers, IPCEmitEvents } from './shared/IPCHandler'
|
|
||||||
import { getSettingsHandler } from './ipc/SettingsHandler.ipc'
|
import { getSettingsHandler } from './ipc/SettingsHandler.ipc'
|
||||||
|
import { updateChecker } from './ipc/UpdateHandler.ipc'
|
||||||
|
// IPC Handlers
|
||||||
|
import { getIPCEmitHandlers, getIPCInvokeHandlers, IPCEmitEvents } from './shared/IPCHandler'
|
||||||
import { dataPath } from './shared/Paths'
|
import { dataPath } from './shared/Paths'
|
||||||
|
|
||||||
|
require('electron-unhandled')({ showDialog: true })
|
||||||
|
|
||||||
export let mainWindow: BrowserWindow
|
export let mainWindow: BrowserWindow
|
||||||
const args = process.argv.slice(1)
|
const args = process.argv.slice(1)
|
||||||
const isDevBuild = args.some(val => val == '--dev')
|
const isDevBuild = args.some(val => val == '--dev')
|
||||||
@@ -72,7 +72,7 @@ function createBridgeWindow() {
|
|||||||
const windowState = windowStateKeeper({
|
const windowState = windowStateKeeper({
|
||||||
defaultWidth: 1000,
|
defaultWidth: 1000,
|
||||||
defaultHeight: 800,
|
defaultHeight: 800,
|
||||||
path: dataPath
|
path: dataPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create the browser window
|
// Create the browser window
|
||||||
@@ -118,15 +118,15 @@ function createBrowserWindow(windowState: windowStateKeeper.State) {
|
|||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
allowRunningInsecureContent: (isDevBuild) ? true : false,
|
allowRunningInsecureContent: (isDevBuild) ? true : false,
|
||||||
textAreasAreResizable: false,
|
textAreasAreResizable: false,
|
||||||
contextIsolation: false
|
contextIsolation: false,
|
||||||
},
|
},
|
||||||
simpleFullscreen: true,
|
simpleFullscreen: true,
|
||||||
fullscreenable: false,
|
fullscreenable: false,
|
||||||
backgroundColor: '#121212'
|
backgroundColor: '#121212',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform == 'linux' && !isDevBuild) {
|
if (process.platform == 'linux' && !isDevBuild) {
|
||||||
options = Object.assign(options, { icon: path.join(__dirname, '..', 'assets', 'images', 'system', 'icons', 'png', '48x48.png' ) })
|
options = Object.assign(options, { icon: path.join(__dirname, '..', 'assets', 'images', 'system', 'icons', 'png', '48x48.png') })
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BrowserWindow(options)
|
return new BrowserWindow(options)
|
||||||
@@ -139,7 +139,7 @@ function getLoadUrl() {
|
|||||||
return url.format({
|
return url.format({
|
||||||
protocol: isDevBuild ? 'http:' : 'file:',
|
protocol: isDevBuild ? 'http:' : 'file:',
|
||||||
pathname: isDevBuild ? '//localhost:4200/' : path.join(__dirname, '..', 'index.html'),
|
pathname: isDevBuild ? '//localhost:4200/' : path.join(__dirname, '..', 'index.html'),
|
||||||
slashes: true
|
slashes: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { basename, parse } from 'path'
|
import { basename, parse } from 'path'
|
||||||
|
|
||||||
import { getSettingsHandler } from '../ipc/SettingsHandler.ipc'
|
import { getSettingsHandler } from '../ipc/SettingsHandler.ipc'
|
||||||
import { emitIPCEvent } from '../main'
|
import { emitIPCEvent } from '../main'
|
||||||
import { lower } from './UtilFunctions'
|
import { lower } from './UtilFunctions'
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { SongSearch, SongResult } from './interfaces/search.interface'
|
import { UpdateInfo } from 'electron-updater'
|
||||||
import { VersionResult, AlbumArtResult } from './interfaces/songDetails.interface'
|
|
||||||
|
import { albumArtHandler } from '../ipc/browse/AlbumArtHandler.ipc'
|
||||||
|
import { batchSongDetailsHandler } from '../ipc/browse/BatchSongDetailsHandler.ipc'
|
||||||
import { searchHandler } from '../ipc/browse/SearchHandler.ipc'
|
import { searchHandler } from '../ipc/browse/SearchHandler.ipc'
|
||||||
import { songDetailsHandler } from '../ipc/browse/SongDetailsHandler.ipc'
|
import { songDetailsHandler } from '../ipc/browse/SongDetailsHandler.ipc'
|
||||||
import { albumArtHandler } from '../ipc/browse/AlbumArtHandler.ipc'
|
|
||||||
import { Download, DownloadProgress } from './interfaces/download.interface'
|
|
||||||
import { downloadHandler } from '../ipc/download/DownloadHandler'
|
|
||||||
import { Settings } from './Settings'
|
|
||||||
import { batchSongDetailsHandler } from '../ipc/browse/BatchSongDetailsHandler.ipc'
|
|
||||||
import { getSettingsHandler, setSettingsHandler } from '../ipc/SettingsHandler.ipc'
|
|
||||||
import { clearCacheHandler } from '../ipc/CacheHandler.ipc'
|
import { clearCacheHandler } from '../ipc/CacheHandler.ipc'
|
||||||
import { updateChecker, UpdateProgress, getCurrentVersionHandler, downloadUpdateHandler, quitAndInstallHandler, getUpdateAvailableHandler } from '../ipc/UpdateHandler.ipc'
|
import { downloadHandler } from '../ipc/download/DownloadHandler'
|
||||||
import { UpdateInfo } from 'electron-updater'
|
|
||||||
import { openURLHandler } from '../ipc/OpenURLHandler.ipc'
|
import { openURLHandler } from '../ipc/OpenURLHandler.ipc'
|
||||||
|
import { getSettingsHandler, setSettingsHandler } from '../ipc/SettingsHandler.ipc'
|
||||||
|
import { downloadUpdateHandler, getCurrentVersionHandler, getUpdateAvailableHandler, quitAndInstallHandler, updateChecker, UpdateProgress } from '../ipc/UpdateHandler.ipc'
|
||||||
|
import { Download, DownloadProgress } from './interfaces/download.interface'
|
||||||
|
import { SongResult, SongSearch } from './interfaces/search.interface'
|
||||||
|
import { AlbumArtResult, VersionResult } from './interfaces/songDetails.interface'
|
||||||
|
import { Settings } from './Settings'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To add a new IPC listener:
|
* To add a new IPC listener:
|
||||||
@@ -37,7 +38,7 @@ export function getIPCInvokeHandlers(): IPCInvokeHandler<keyof IPCInvokeEvents>[
|
|||||||
/**
|
/**
|
||||||
* The list of possible async IPC events that return values, mapped to their input and output types.
|
* The list of possible async IPC events that return values, mapped to their input and output types.
|
||||||
*/
|
*/
|
||||||
export type IPCInvokeEvents = {
|
export interface IPCInvokeEvents {
|
||||||
'get-settings': {
|
'get-settings': {
|
||||||
input: undefined
|
input: undefined
|
||||||
output: Settings
|
output: Settings
|
||||||
@@ -88,14 +89,14 @@ export function getIPCEmitHandlers(): IPCEmitHandler<keyof IPCEmitEvents>[] {
|
|||||||
downloadUpdateHandler,
|
downloadUpdateHandler,
|
||||||
updateChecker,
|
updateChecker,
|
||||||
quitAndInstallHandler,
|
quitAndInstallHandler,
|
||||||
openURLHandler
|
openURLHandler,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of possible async IPC events that don't return values, mapped to their input types.
|
* The list of possible async IPC events that don't return values, mapped to their input types.
|
||||||
*/
|
*/
|
||||||
export type IPCEmitEvents = {
|
export interface IPCEmitEvents {
|
||||||
'log': any[]
|
'log': any[]
|
||||||
|
|
||||||
'download': Download
|
'download': Download
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { join } from 'path'
|
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
// Data paths
|
// Data paths
|
||||||
export const dataPath = join(app.getPath('userData'), 'bridge_data')
|
export const dataPath = join(app.getPath('userData'), 'bridge_data')
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ export const defaultSettings: Settings = {
|
|||||||
rateLimitDelay: 31,
|
rateLimitDelay: 31,
|
||||||
downloadVideos: true,
|
downloadVideos: true,
|
||||||
theme: 'Default',
|
theme: 'Default',
|
||||||
libraryPath: undefined
|
libraryPath: undefined,
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as randomBytes from 'randombytes'
|
import * as randomBytes from 'randombytes'
|
||||||
|
|
||||||
const sanitize = require('sanitize-filename')
|
const sanitize = require('sanitize-filename')
|
||||||
|
|
||||||
// WARNING: do not import anything related to Electron; the code will not compile correctly.
|
// WARNING: do not import anything related to Electron; the code will not compile correctly.
|
||||||
@@ -24,7 +25,7 @@ export function sanitizeFilename(filename: string): string {
|
|||||||
case '*': return '⁎'
|
case '*': return '⁎'
|
||||||
default: return '_'
|
default: return '_'
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
return (newFilename == '' ? randomBytes(5).toString('hex') : newFilename)
|
return (newFilename == '' ? randomBytes(5).toString('hex') : newFilename)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,19 @@ export function getDefaultSearch(): SongSearch {
|
|||||||
quantity: 'all',
|
quantity: 'all',
|
||||||
similarity: 'similar',
|
similarity: 'similar',
|
||||||
fields: { name: true, artist: true, album: true, genre: true, year: true, charter: true, tag: true },
|
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,
|
tags: {
|
||||||
'video': false, 'stems': false, 'solo sections': false, 'open notes': false },
|
'sections': false, 'star power': false, 'forcing': false, 'taps': false, 'lyrics': false,
|
||||||
instruments: { guitar: false, bass: false, rhythm: false, keys: false,
|
'video': false, 'stems': false, 'solo sections': false, 'open notes': false
|
||||||
drums: false, guitarghl: false, bassghl: false, vocals: 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 },
|
difficulties: { expert: false, hard: false, medium: false, easy: false },
|
||||||
minDiff: 0,
|
minDiff: 0,
|
||||||
maxDiff: 6,
|
maxDiff: 6,
|
||||||
limit: 50 + 1,
|
limit: 50 + 1,
|
||||||
offset: 0
|
offset: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
declare module 'node-7z'
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: true
|
production: true,
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// The list of file replacements can be found in `angular.json`.
|
// The list of file replacements can be found in `angular.json`.
|
||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false
|
production: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<title>Bridge</title>
|
<title>Bridge</title>
|
||||||
<base href="./">
|
<base href="./" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -47,7 +47,6 @@
|
|||||||
*/
|
*/
|
||||||
import 'zone.js' // Included with Angular CLI.
|
import 'zone.js' // Included with Angular CLI.
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
* APPLICATION IMPORTS
|
* APPLICATION IMPORTS
|
||||||
*/
|
*/
|
||||||
1
src/typings.d.ts
vendored
1
src/typings.d.ts
vendored
@@ -5,7 +5,6 @@ interface NodeModule {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
// declare let window: Window
|
// declare let window: Window
|
||||||
declare let $: any
|
declare let $: any
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"suppressImplicitAnyIndexErrors": true,
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
@@ -19,5 +18,8 @@
|
|||||||
],
|
],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"useDefineForClassFields": false
|
"useDefineForClassFields": false
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictTemplates": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user