From 49cba89a113a530ded7404fa9fb648db79716a0f Mon Sep 17 00:00:00 2001 From: Geomitron <22552797+Geomitron@users.noreply.github.com> Date: Thu, 13 Feb 2020 22:33:14 -0500 Subject: [PATCH] Initial settings tab --- src/app/app-routing.module.ts | 3 +- src/app/app.module.ts | 6 ++- .../search-bar/search-bar.component.html | 4 +- .../downloads-modal.component.ts | 2 +- .../settings/settings.component.html | 29 +++++++++++ .../settings/settings.component.scss | 10 ++++ .../components/settings/settings.component.ts | 52 +++++++++++++++++++ .../components/toolbar/toolbar.component.ts | 6 +-- src/app/core/services/electron.service.ts | 51 ++++++++++-------- src/app/core/services/settings.service.ts | 45 ++++++++++++++++ src/electron/ipc/InitSettingsHandler.ipc.ts | 7 ++- src/electron/ipc/OpenFolderHandler.ipc.ts | 10 ---- src/electron/ipc/SaveSettingsHandler.ipc.ts | 20 +++++++ src/electron/shared/IPCHandler.ts | 6 +-- 14 files changed, 204 insertions(+), 47 deletions(-) create mode 100644 src/app/components/settings/settings.component.html create mode 100644 src/app/components/settings/settings.component.scss create mode 100644 src/app/components/settings/settings.component.ts delete mode 100644 src/electron/ipc/OpenFolderHandler.ipc.ts create mode 100644 src/electron/ipc/SaveSettingsHandler.ipc.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 6d40c76..8e170c7 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -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'} ] diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f16e5e2..9539275 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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, diff --git a/src/app/components/browse/search-bar/search-bar.component.html b/src/app/components/browse/search-bar/search-bar.component.html index b5668f9..cadc882 100644 --- a/src/app/components/browse/search-bar/search-bar.component.html +++ b/src/app/components/browse/search-bar/search-bar.component.html @@ -19,7 +19,7 @@ Charter -
+
\ No newline at end of file diff --git a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts index d626629..9359dda 100644 --- a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts +++ b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts @@ -53,6 +53,6 @@ export class DownloadsModalComponent { } openFolder(filepath: string) { - this.electronService.sendIPC('open-folder', filepath) + this.electronService.showFolder(filepath) } } \ No newline at end of file diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html new file mode 100644 index 0000000..fb639bb --- /dev/null +++ b/src/app/components/settings/settings.component.html @@ -0,0 +1,29 @@ +

Paths

+
+
+ +
+ + + +
+
+
+ +

Cache

+
Current Cache Size:
{{cacheSize}}
+
+ + +

Theme

+ \ No newline at end of file diff --git a/src/app/components/settings/settings.component.scss b/src/app/components/settings/settings.component.scss new file mode 100644 index 0000000..b1ac3b1 --- /dev/null +++ b/src/app/components/settings/settings.component.scss @@ -0,0 +1,10 @@ +:host { + flex: 1; + padding: 2em; + overflow-y: scroll; +} + +.default-cursor { + cursor: default; + pointer-events: none; +} \ No newline at end of file diff --git a/src/app/components/settings/settings.component.ts b/src/app/components/settings/settings.component.ts new file mode 100644 index 0000000..f3a0420 --- /dev/null +++ b/src/app/components/settings/settings.component.ts @@ -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) + } +} \ No newline at end of file diff --git a/src/app/components/toolbar/toolbar.component.ts b/src/app/components/toolbar/toolbar.component.ts index d2f492e..68ac604 100644 --- a/src/app/components/toolbar/toolbar.component.ts +++ b/src/app/components/toolbar/toolbar.component.ts @@ -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() } } \ No newline at end of file diff --git a/src/app/core/services/electron.service.ts b/src/app/core/services/electron.service.ts index a1f3f31..1054fc0 100644 --- a/src/app/core/services/electron.service.ts +++ b/src/app/core/services/electron.service.ts @@ -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(event: E, data: IPCInvokeEvents[E]['input']) { - return this.ipcRenderer.invoke(event, data) as Promise + return this.electron.ipcRenderer.invoke(event, data) as Promise } /** @@ -52,7 +41,7 @@ export class ElectronService { * @param data The data object to send across IPC. */ sendIPC(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(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 + } } \ No newline at end of file diff --git a/src/app/core/services/settings.service.ts b/src/app/core/services/settings.service.ts index 399cd1b..3feea91 100644 --- a/src/app/core/services/settings.service.ts +++ b/src/app/core/services/settings.service.ts @@ -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() + } } \ No newline at end of file diff --git a/src/electron/ipc/InitSettingsHandler.ipc.ts b/src/electron/ipc/InitSettingsHandler.ipc.ts index 2a0dcee..59b517b 100644 --- a/src/electron/ipc/InitSettingsHandler.ipc.ts +++ b/src/electron/ipc/InitSettingsHandler.ipc.ts @@ -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) { diff --git a/src/electron/ipc/OpenFolderHandler.ipc.ts b/src/electron/ipc/OpenFolderHandler.ipc.ts deleted file mode 100644 index ee205e5..0000000 --- a/src/electron/ipc/OpenFolderHandler.ipc.ts +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/src/electron/ipc/SaveSettingsHandler.ipc.ts b/src/electron/ipc/SaveSettingsHandler.ipc.ts new file mode 100644 index 0000000..f09c0af --- /dev/null +++ b/src/electron/ipc/SaveSettingsHandler.ipc.ts @@ -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') + } +} \ No newline at end of file diff --git a/src/electron/shared/IPCHandler.ts b/src/electron/shared/IPCHandler.ts index ccc3965..d55ec7b 100644 --- a/src/electron/shared/IPCHandler.ts +++ b/src/electron/shared/IPCHandler.ts @@ -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 { export function getIPCEmitHandlers(): IPCEmitHandler[]{ return [ new DownloadHandler(), - new OpenFolderHandler() + new SaveSettingsHandler() ] } export type IPCEmitEvents = { 'download': Download 'download-updated': DownloadProgress - 'open-folder': string + 'update-settings': Settings } export interface IPCEmitHandler {