mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
Initial settings tab
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { BrowseComponent } from './components/browse/browse.component'
|
||||
import { SettingsComponent } from './components/settings/settings.component'
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'browse', component: BrowseComponent },
|
||||
{ path: 'library', component: BrowseComponent },
|
||||
{ path: 'settings', component: BrowseComponent },
|
||||
{ path: 'settings', component: SettingsComponent },
|
||||
{ path: 'about', component: BrowseComponent }, // TODO: replace these with the correct components
|
||||
{ path: '**', redirectTo: '/browse'}
|
||||
]
|
||||
|
||||
@@ -12,7 +12,8 @@ import { ChartSidebarComponent } from './components/browse/chart-sidebar/chart-s
|
||||
import { ResultTableRowComponent } from './components/browse/result-table/result-table-row/result-table-row.component';
|
||||
import { DownloadsModalComponent } from './components/browse/status-bar/downloads-modal/downloads-modal.component';
|
||||
import { ProgressBarDirective } from './core/directives/progress-bar.directive';
|
||||
import { CheckboxDirective } from './core/directives/checkbox.directive'
|
||||
import { CheckboxDirective } from './core/directives/checkbox.directive';
|
||||
import { SettingsComponent } from './components/settings/settings.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -26,7 +27,8 @@ import { CheckboxDirective } from './core/directives/checkbox.directive'
|
||||
ResultTableRowComponent,
|
||||
DownloadsModalComponent,
|
||||
ProgressBarDirective,
|
||||
CheckboxDirective
|
||||
CheckboxDirective,
|
||||
SettingsComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<a class="item">Charter</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item right">
|
||||
<!-- <div class="item right">
|
||||
<button class="ui positive disabled button">Bulk Download</button>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
@@ -53,6 +53,6 @@ export class DownloadsModalComponent {
|
||||
}
|
||||
|
||||
openFolder(filepath: string) {
|
||||
this.electronService.sendIPC('open-folder', filepath)
|
||||
this.electronService.showFolder(filepath)
|
||||
}
|
||||
}
|
||||
29
src/app/components/settings/settings.component.html
Normal file
29
src/app/components/settings/settings.component.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<h3 class="ui header">Paths</h3>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Chart library directory</label>
|
||||
<div class="ui action input">
|
||||
<input [value]="settingsService.libraryDirectory" class="default-cursor" 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 teal">Choose</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="ui header">Cache</h3>
|
||||
<div>Current Cache Size: <div class="ui label" style="margin-left: 1em;">{{cacheSize}}</div>
|
||||
</div>
|
||||
<button style="margin-top: 0.5em;" (click)="clearCache()" class="ui button">Clear Cache</button>
|
||||
|
||||
<h3 class="ui header">Theme</h3>
|
||||
<div #themeDropdown class="ui selection dropdown mr">
|
||||
<input type="hidden" name="sort" [value]="settingsService.theme">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Default</div>
|
||||
<div class="menu">
|
||||
<div class="item" [attr.data-value]="i" *ngFor="let theme of settingsService.builtinThemes; let i = index">{{theme}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
10
src/app/components/settings/settings.component.scss
Normal file
10
src/app/components/settings/settings.component.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
:host {
|
||||
flex: 1;
|
||||
padding: 2em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.default-cursor {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
52
src/app/components/settings/settings.component.ts
Normal file
52
src/app/components/settings/settings.component.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from '@angular/core'
|
||||
import { ElectronService } from 'src/app/core/services/electron.service'
|
||||
import { SettingsService } from 'src/app/core/services/settings.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrls: ['./settings.component.scss']
|
||||
})
|
||||
export class SettingsComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild('themeDropdown', { static: true }) themeDropdown: ElementRef
|
||||
|
||||
cacheSize = 'Calculating...'
|
||||
|
||||
constructor(private settingsService: SettingsService, private electronService: ElectronService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
const cacheSize = await this.settingsService.getCacheSize()
|
||||
this.cacheSize = Math.round(cacheSize / 1000000) + ' MB'
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
$(this.themeDropdown.nativeElement).dropdown({
|
||||
onChange: (_value: string, text: string) => {
|
||||
this.settingsService.theme = text
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async clearCache() {
|
||||
this.cacheSize = 'Please wait...'
|
||||
await this.settingsService.clearCache()
|
||||
this.cacheSize = 'Cleared!'
|
||||
}
|
||||
|
||||
async getLibraryDirectory() {
|
||||
const result = await this.electronService.showOpenDialog({
|
||||
title: 'Choose library folder',
|
||||
buttonLabel: 'This is where my charts are!',
|
||||
defaultPath: this.settingsService.libraryDirectory || '',
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
|
||||
if (result.canceled == false) {
|
||||
this.settingsService.libraryDirectory = result.filePaths[0]
|
||||
}
|
||||
}
|
||||
|
||||
async openLibraryDirectory() {
|
||||
this.electronService.openFolder(this.settingsService.libraryDirectory)
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,14 @@ export class ToolbarComponent {
|
||||
constructor(private electronService: ElectronService) { }
|
||||
|
||||
minimize() {
|
||||
this.electronService.remote.getCurrentWindow().minimize()
|
||||
this.electronService.currentWindow.minimize()
|
||||
}
|
||||
|
||||
maximize() {
|
||||
this.electronService.remote.getCurrentWindow().maximize()
|
||||
this.electronService.currentWindow.maximize()
|
||||
}
|
||||
|
||||
close() {
|
||||
this.electronService.remote.app.quit()
|
||||
this.electronService.quit()
|
||||
}
|
||||
}
|
||||
@@ -2,40 +2,29 @@ import { Injectable } from '@angular/core'
|
||||
|
||||
// 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.
|
||||
import { ipcRenderer, webFrame, remote } from 'electron'
|
||||
import * as childProcess from 'child_process'
|
||||
import * as fs from 'fs'
|
||||
import * as util from 'util'
|
||||
import * as electron from 'electron'
|
||||
import { IPCInvokeEvents, IPCEmitEvents } from '../../../electron/shared/IPCHandler'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ElectronService {
|
||||
ipcRenderer: typeof ipcRenderer
|
||||
webFrame: typeof webFrame
|
||||
remote: typeof remote
|
||||
childProcess: typeof childProcess
|
||||
fs: typeof fs
|
||||
util: typeof util
|
||||
electron: typeof electron
|
||||
|
||||
get isElectron() {
|
||||
return !!(window && window.process && window.process.type)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// Conditional imports
|
||||
if (this.isElectron) {
|
||||
this.ipcRenderer = window.require('electron').ipcRenderer
|
||||
this.webFrame = window.require('electron').webFrame
|
||||
this.remote = window.require('electron').remote
|
||||
|
||||
this.childProcess = window.require('child_process')
|
||||
this.fs = window.require('fs')
|
||||
this.util = window.require('util')
|
||||
this.electron = window.require('electron')
|
||||
}
|
||||
}
|
||||
|
||||
get currentWindow() {
|
||||
return this.electron.remote.getCurrentWindow()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an async function in the main process.
|
||||
* @param event The name of the IPC event to invoke.
|
||||
@@ -43,7 +32,7 @@ export class ElectronService {
|
||||
* @returns A promise that resolves to the output data.
|
||||
*/
|
||||
async invoke<E extends keyof IPCInvokeEvents>(event: E, data: IPCInvokeEvents[E]['input']) {
|
||||
return this.ipcRenderer.invoke(event, data) as Promise<IPCInvokeEvents[E]['output']>
|
||||
return this.electron.ipcRenderer.invoke(event, data) as Promise<IPCInvokeEvents[E]['output']>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +41,7 @@ export class ElectronService {
|
||||
* @param data The data object to send across IPC.
|
||||
*/
|
||||
sendIPC<E extends keyof IPCEmitEvents>(event: E, data: IPCEmitEvents[E]) {
|
||||
this.ipcRenderer.send(event, data)
|
||||
this.electron.ipcRenderer.send(event, data)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,8 +50,28 @@ export class ElectronService {
|
||||
* @param callback The data object to receive across IPC.
|
||||
*/
|
||||
receiveIPC<E extends keyof IPCEmitEvents>(event: E, callback: (result: IPCEmitEvents[E]) => void) {
|
||||
this.ipcRenderer.on(event, (_event, ...results) => {
|
||||
this.electron.ipcRenderer.on(event, (_event, ...results) => {
|
||||
callback(results[0])
|
||||
})
|
||||
}
|
||||
|
||||
quit() {
|
||||
this.electron.remote.app.quit()
|
||||
}
|
||||
|
||||
openFolder(filepath: string) {
|
||||
this.electron.shell.openItem(filepath)
|
||||
}
|
||||
|
||||
showFolder(filepath: string) {
|
||||
this.electron.shell.showItemInFolder(filepath)
|
||||
}
|
||||
|
||||
showOpenDialog(options: Electron.OpenDialogOptions) {
|
||||
return this.electron.remote.dialog.showOpenDialog(this.currentWindow, options)
|
||||
}
|
||||
|
||||
get defaultSession() {
|
||||
return this.electron.remote.session.defaultSession
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@ import { Settings } from 'src/electron/shared/Settings'
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SettingsService {
|
||||
readonly builtinThemes = ['Default', 'Dark']
|
||||
|
||||
private settings: Settings
|
||||
private currentThemeLink: HTMLLinkElement
|
||||
|
||||
constructor(private electronService: ElectronService) { }
|
||||
|
||||
@@ -17,4 +19,47 @@ export class SettingsService {
|
||||
}
|
||||
return this.settings
|
||||
}
|
||||
|
||||
saveSettings() {
|
||||
if (this.settings != undefined) {
|
||||
this.electronService.sendIPC('update-settings', this.settings)
|
||||
}
|
||||
}
|
||||
|
||||
changeTheme(theme: string) {
|
||||
if (this.currentThemeLink != undefined) this.currentThemeLink.remove()
|
||||
if (theme == 'Default') { return }
|
||||
|
||||
const link = document.createElement('link')
|
||||
link.type = 'text/css'
|
||||
link.rel = 'stylesheet'
|
||||
link.href = `assets/themes/${theme}.css`
|
||||
this.currentThemeLink = document.head.appendChild(link)
|
||||
}
|
||||
|
||||
async getCacheSize() {
|
||||
return this.electronService.defaultSession.getCacheSize()
|
||||
}
|
||||
|
||||
async clearCache() {
|
||||
this.saveSettings()
|
||||
return this.electronService.defaultSession.clearCache()
|
||||
}
|
||||
|
||||
get libraryDirectory() {
|
||||
return this.settings == undefined ? '' : this.settings.libraryPath
|
||||
}
|
||||
set libraryDirectory(newValue: string) {
|
||||
this.settings.libraryPath = newValue
|
||||
this.saveSettings()
|
||||
}
|
||||
|
||||
get theme() {
|
||||
return this.settings == undefined ? '' : this.settings.theme
|
||||
}
|
||||
set theme(newValue: string) {
|
||||
this.settings.theme = newValue
|
||||
this.changeTheme(newValue)
|
||||
this.saveSettings()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { exists as _exists, mkdir as _mkdir, readFile as _readFile, writeFile as _writeFile } from 'fs'
|
||||
import { exists as _exists, mkdir as _mkdir, readFile as _readFile } from 'fs'
|
||||
import { dataPath, tempPath, themesPath, settingsPath } from '../shared/Paths'
|
||||
import { promisify } from 'util'
|
||||
import { IPCInvokeHandler } from '../shared/IPCHandler'
|
||||
import { defaultSettings, Settings } from '../shared/Settings'
|
||||
import SaveSettingsHandler from './SaveSettingsHandler.ipc'
|
||||
|
||||
const exists = promisify(_exists)
|
||||
const mkdir = promisify(_mkdir)
|
||||
const readFile = promisify(_readFile)
|
||||
const writeFile = promisify(_writeFile)
|
||||
|
||||
export default class InitSettingsHandler implements IPCInvokeHandler<'init-settings'> {
|
||||
event: 'init-settings' = 'init-settings'
|
||||
@@ -38,8 +38,7 @@ export default class InitSettingsHandler implements IPCInvokeHandler<'init-setti
|
||||
if (await exists(settingsPath)) {
|
||||
return JSON.parse(await readFile(settingsPath, 'utf8'))
|
||||
} else {
|
||||
const newSettings = JSON.stringify(defaultSettings, undefined, 2)
|
||||
await writeFile(settingsPath, newSettings, 'utf8')
|
||||
await SaveSettingsHandler.saveSettings(defaultSettings)
|
||||
return defaultSettings
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { IPCEmitHandler } from '../shared/IPCHandler'
|
||||
import { shell } from 'electron'
|
||||
|
||||
export default class OpenFolderHandler implements IPCEmitHandler<'open-folder'> {
|
||||
event: 'open-folder' = 'open-folder'
|
||||
|
||||
async handler(filepath: string) {
|
||||
shell.showItemInFolder(filepath)
|
||||
}
|
||||
}
|
||||
20
src/electron/ipc/SaveSettingsHandler.ipc.ts
Normal file
20
src/electron/ipc/SaveSettingsHandler.ipc.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { writeFile as _writeFile } from 'fs'
|
||||
import { IPCEmitHandler } from '../shared/IPCHandler'
|
||||
import { Settings } from '../shared/Settings'
|
||||
import { promisify } from 'util'
|
||||
import { settingsPath } from '../shared/Paths'
|
||||
|
||||
const writeFile = promisify(_writeFile)
|
||||
|
||||
export default class SaveSettingsHandler implements IPCEmitHandler<'update-settings'> {
|
||||
event: 'update-settings' = 'update-settings'
|
||||
|
||||
handler(settings: Settings) {
|
||||
SaveSettingsHandler.saveSettings(settings)
|
||||
}
|
||||
|
||||
static async saveSettings(settings: Settings) {
|
||||
const settingsJSON = JSON.stringify(settings, undefined, 2)
|
||||
await writeFile(settingsPath, settingsJSON, 'utf8')
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { DownloadHandler } from '../ipc/download/DownloadHandler'
|
||||
import { Settings } from './Settings'
|
||||
import InitSettingsHandler from '../ipc/InitSettingsHandler.ipc'
|
||||
import BatchSongDetailsHandler from '../ipc/BatchSongDetailsHandler.ipc'
|
||||
import OpenFolderHandler from '../ipc/OpenFolderHandler.ipc'
|
||||
import SaveSettingsHandler from '../ipc/SaveSettingsHandler.ipc'
|
||||
|
||||
/**
|
||||
* To add a new IPC listener:
|
||||
@@ -59,14 +59,14 @@ export interface IPCInvokeHandler<E extends keyof IPCInvokeEvents> {
|
||||
export function getIPCEmitHandlers(): IPCEmitHandler<keyof IPCEmitEvents>[]{
|
||||
return [
|
||||
new DownloadHandler(),
|
||||
new OpenFolderHandler()
|
||||
new SaveSettingsHandler()
|
||||
]
|
||||
}
|
||||
|
||||
export type IPCEmitEvents = {
|
||||
'download': Download
|
||||
'download-updated': DownloadProgress
|
||||
'open-folder': string
|
||||
'update-settings': Settings
|
||||
}
|
||||
|
||||
export interface IPCEmitHandler<E extends keyof IPCEmitEvents> {
|
||||
|
||||
Reference in New Issue
Block a user