diff --git a/src/i18n/README.md b/src/i18n/README.md index aa54ff70..ef06c08d 100644 --- a/src/i18n/README.md +++ b/src/i18n/README.md @@ -7,4 +7,13 @@ Some notes about Cider's i18n support. - The default language is used for messages that are not translated. - Try when possible to keep the messages the similar in length to the English ones. - Most of the strings in the content area are provided and translated by Apple themselves, and do not need to be translated. - - The language Apple Music uses are dependent on the storefront region. +- The language Apple Music uses are dependent on the storefront region. + + +## Localization Notices + +Several changes have been made to configuration options and will be listed below with the relevant locales that have +been modified, the ones not mentioned in the list need modifying. + +* `settings.option.experimental.closeButtonBehaviour`: Changed to `close_button_hide` - Should be "Close Button Should Hide the Application". `.quit`, `.minimizeTaskbar` and `.minimizeTray` have been removed. Translations done for en_US. +* `term.loadingPlaylist`: Added for `en_US` and `en_PISS`. \ No newline at end of file diff --git a/src/i18n/en_US.jsonc b/src/i18n/en_US.jsonc index 3454d43f..6f40f674 100644 --- a/src/i18n/en_US.jsonc +++ b/src/i18n/en_US.jsonc @@ -274,10 +274,7 @@ "settings.header.experimental": "Experimental", "settings.header.experimental.description": "Adjust the experimental settings for Cider.", "settings.option.experimental.compactUI": "Compact UI", // Toggle - "settings.option.experimental.closeButtonBehaviour": "Close Button Behavior", - "settings.option.experimental.closeButtonBehaviour.quit": "Quit Cider", - "settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Minimize to Taskbar", - "settings.option.experimental.closeButtonBehaviour.minimizeTray": "Minimize to Tray", + "settings.option.experimental.close_button_hide": "Close Button Should Hide the Application", // Refer to term.disabled & term.enabled // Spatialization Menu diff --git a/src/main/base/app.ts b/src/main/base/app.ts index 68e26b65..fb72d057 100644 --- a/src/main/base/app.ts +++ b/src/main/base/app.ts @@ -2,7 +2,7 @@ import * as electron from 'electron'; import * as path from 'path'; export class AppEvents { - private static protocols: any = [ + private protocols: string[] = [ "ame", "cider", "itms", @@ -10,20 +10,22 @@ export class AppEvents { "musics", "music" ] - private static plugin: any = null; - private static store: any = null; - private static win: any = null; + private plugin: any = undefined; + private store: any = undefined; + private win: any = undefined; + private tray: any = undefined; + private i18n: any = undefined; constructor(store: any) { - AppEvents.store = store - AppEvents.start(store); + this.store = store + this.start(store); } /** * Handles all actions that occur for the app on start (Mainly commandline arguments) * @returns {void} */ - private static start(store: any): void { + private start(store: any): void { console.info('[AppEvents] App started'); /********************************************************************************************************************** @@ -46,12 +48,12 @@ export class AppEvents { } // Expose GC - electron.app.commandLine.appendSwitch('js-flags','--expose_gc') + electron.app.commandLine.appendSwitch('js-flags', '--expose_gc') - if (process.platform === "win32") { - electron.app.setAppUserModelId("Cider") // For notification name + if (process.platform === "win32") { + electron.app.setAppUserModelId(electron.app.getName()) // For notification name } - + /*********************************************************************************************************************** * Commandline arguments **********************************************************************************************************************/ @@ -103,33 +105,39 @@ export class AppEvents { } public quit() { - console.log('App stopped'); + console.log('[AppEvents] App quit'); } public ready(plug: any) { - AppEvents.plugin = plug + this.plugin = plug console.log('[AppEvents] App ready'); } - public bwCreated(win: Electron.BrowserWindow) { - AppEvents.win = win + public bwCreated(win: Electron.BrowserWindow, i18n: any) { + this.win = win + this.i18n = i18n electron.app.on('open-url', (event, url) => { event.preventDefault() - if (AppEvents.protocols.some((protocol: string) => url.includes(protocol))) { - AppEvents.LinkHandler(url) + if (this.protocols.some((protocol: string) => url.includes(protocol))) { + this.LinkHandler(url) console.log(url) } }) - AppEvents.InstanceHandler() + this.InstanceHandler() + this.InitTray() } /*********************************************************************************************************************** * Private methods **********************************************************************************************************************/ - private static LinkHandler(arg: string) { + /** + * Handles links (URI) and protocols for the application + * @param arg + */ + private LinkHandler(arg: string) { if (!arg) return; // LastFM Auth URL @@ -137,10 +145,10 @@ export class AppEvents { let authURI = arg.split('/auth/')[1] if (authURI.startsWith('lastfm')) { // If we wanted more auth options const authKey = authURI.split('lastfm?token=')[1]; - AppEvents.store.set('lastfm.enabled', true); - AppEvents.store.set('lastfm.auth_token', authKey); - AppEvents.win.webContents.send('LastfmAuthenticated', authKey); - AppEvents.plugin.callPlugin('lastfm', 'authenticate', authKey); + this.store.set('lastfm.enabled', true); + this.store.set('lastfm.auth_token', authKey); + this.win.webContents.send('LastfmAuthenticated', authKey); + this.plugin.callPlugin('lastfm', 'authenticate', authKey); } } // Play @@ -156,7 +164,7 @@ export class AppEvents { for (const [key, value] of Object.entries(mediaType)) { if (playParam.includes(key)) { const id = playParam.split(key)[1] - AppEvents.win.webContents.send('play', value, id) + this.win.webContents.send('play', value, id) console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`) } } @@ -165,11 +173,14 @@ export class AppEvents { console.log(arg) let url = arg.split('//')[1] console.warn(`[LinkHandler] Attempting to load url: ${url}`); - AppEvents.win.webContents.send('play', 'url', url) + this.win.webContents.send('play', 'url', url) } } - private static InstanceHandler() { + /** + * Handles the creation of a new instance of the app + */ + private InstanceHandler() { // Detects of an existing instance is running (So if the lock has been achieved, no existing instance has been found) const gotTheLock = electron.app.requestSingleInstanceLock() @@ -185,17 +196,97 @@ export class AppEvents { console.log(arg) if (arg.includes("cider://")) { console.debug('[InstanceHandler] (second-instance) Link detected with ' + arg) - AppEvents.LinkHandler(arg) + this.LinkHandler(arg) } else if (arg.includes("--force-quit")) { console.warn('[InstanceHandler] (second-instance) Force Quit found. Quitting App.'); electron.app.quit() - } else if (AppEvents.win) { - if (AppEvents.win.isMinimized()) AppEvents.win.restore() - AppEvents.win.focus() + } else if (this.win) { + if (this.win.isMinimized()) this.win.restore() + this.win.focus() } }) }) } } + + /** + * Initializes the applications tray + */ + private InitTray() { + const icons = { + "win32": electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({ + width: 32, + height: 32 + }), + "linux": electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({ + width: 32, + height: 32 + }), + "darwin": electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({ + width: 20, + height: 20 + }), + } + console.log(this.i18n) + + this.tray = new electron.Tray(process.platform === 'win32' ? icons.win32 : (process.platform === 'darwin' ? icons.darwin : icons.linux)) + this.tray.setToolTip(electron.app.getName()) + this.setTray(false) + + this.tray.on('double-click', () => { + if (this.win) { + if (this.win.isVisible()) { + this.win.focus() + } else { + this.win.show() + } + } + }) + + this.win.on('show', () => { + this.setTray(true) + }) + + this.win.on('restore', () => { + this.setTray(true) + }) + + this.win.on('hide', () => { + this.setTray(false) + }) + + this.win.on('minimize', () => { + this.setTray(false) + }) + } + + /** + * Sets the tray context menu to a given state + * @param visible - BrowserWindow Visibility + */ + private setTray(visible: boolean = this.win.isVisible()) { + + const menu = electron.Menu.buildFromTemplate([ + { + label: (visible ? this.i18n['action.tray.minimize'] : `${this.i18n['action.tray.show']} ${electron.app.getName()}`), + click: () => { + if (this.win) { + if (visible) { + this.win.hide() + } else { + this.win.show() + } + } + } + }, + { + label: this.i18n['action.tray.quit'], + click: () => { + electron.app.quit() + } + } + ]) + this.tray.setContextMenu(menu) + } } diff --git a/src/main/base/store.ts b/src/main/base/store.ts index 55d00f53..c26f429a 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -6,7 +6,7 @@ export class ConfigStore { private defaults: any = { "general": { - "close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray + "close_button_hide": true, "open_on_startup": false, "discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music "discord_rpc_clear_on_pause": true, diff --git a/src/main/base/win.ts b/src/main/base/win.ts index 42859f56..1d6b1fc9 100644 --- a/src/main/base/win.ts +++ b/src/main/base/win.ts @@ -6,7 +6,7 @@ import * as express from "express"; import * as getPort from "get-port"; import * as yt from "youtube-search-without-api-key"; import * as fs from "fs"; -import { Stream } from "stream"; +import {Stream} from "stream"; import * as qrcode from "qrcode-terminal"; import * as os from "os"; import * as mm from 'music-metadata'; @@ -19,6 +19,7 @@ export class Win { private app: any | undefined = null; private store: any | undefined = null; private devMode: boolean = !electron.app.isPackaged; + public i18n: any = {}; constructor(app: electron.App, store: any) { this.app = app; @@ -76,7 +77,7 @@ export class Win { * Creates the browser window */ async createWindow(): Promise { - this.clientPort = await getPort({ port: 9000 }); + this.clientPort = await getPort({port: 9000}); this.verifyFiles(); // Load the previous state with fallback to defaults @@ -140,7 +141,7 @@ export class Win { */ private startWebServer(): void { const app = express(); - + app.use(express.static(path.join(this.paths.srcPath, "./renderer/"))); app.set("views", path.join(this.paths.srcPath, "./renderer/views")); app.set("view engine", "ejs"); @@ -157,7 +158,7 @@ export class Win { res.redirect("https://discord.gg/applemusic"); } }); - + app.get("/", (req, res) => { res.render("main", this.EnvironmentVariables); }); @@ -199,7 +200,7 @@ export class Win { remote.set("views", path.join(this.paths.srcPath, "./web-remote/views")); remote.set("view engine", "ejs"); getPort({port: 6942}).then((port) => { - this.remotePort = port; + this.remotePort = port; // Start Remote Discovery this.broadcastRemote() remote.listen(this.remotePort, () => { @@ -262,7 +263,7 @@ export class Win { if (itspod != null) details.requestHeaders["Cookie"] = `itspod=${itspod}`; } - callback({ requestHeaders: details.requestHeaders }); + callback({requestHeaders: details.requestHeaders}); } ); @@ -288,18 +289,29 @@ export class Win { event.returnValue = process.platform; }); + let i18nBase = fs.readFileSync(path.join(__dirname, "../../src/i18n/en_US.jsonc"), "utf8"); + i18nBase = jsonc.parse(i18nBase) + try { + let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${this.store.general.language}.jsonc`), "utf8"); + i18n = jsonc.parse(i18n) + this.i18n = Object.assign(i18nBase, i18n) + } catch (e) { + console.error(e); + } + electron.ipcMain.on("get-i18n", (event, key) => { let i18nBase = fs.readFileSync(path.join(__dirname, "../../src/i18n/en_US.jsonc"), "utf8"); i18nBase = jsonc.parse(i18nBase) try { let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${key}.jsonc`), "utf8"); i18n = jsonc.parse(i18n) - Object.assign(i18nBase, i18n) - }catch(e) { + this.i18n = Object.assign(i18nBase, i18n) + } catch (e) { console.error(e); event.returnValue = e; } - + + this.i18n = i18nBase; event.returnValue = i18nBase; }); @@ -330,11 +342,6 @@ export class Win { event.returnValue = this.devMode; }); - electron.ipcMain.on("close", () => { - // listen for close event - this.win.close(); - }); - electron.ipcMain.on("put-library-songs", (event, arg) => { fs.writeFileSync( path.join(this.paths.ciderCache, "library-songs.json"), @@ -415,8 +422,8 @@ export class Win { return await yt.search(u); }); - electron.ipcMain.handle("setVibrancy", (event, key, value) => { - this.win.setVibrancy(value); + electron.ipcMain.on("close", () => { + this.win.close(); }); electron.ipcMain.on("maximize", () => { @@ -429,7 +436,7 @@ export class Win { }); electron.ipcMain.on("unmaximize", () => { // listen for maximize event - this.win.unmaximize(); + this.win.unmaximize(); }); electron.ipcMain.on("minimize", () => { @@ -443,7 +450,7 @@ export class Win { }); electron.ipcMain.on("windowmin", (event, width, height) => { - this.win.setMinimumSize(width,height); + this.win.setMinimumSize(width, height); }) electron.ipcMain.on("windowontop", (event, ontop) => { @@ -451,7 +458,7 @@ export class Win { }); // Set scale - electron.ipcMain.on("windowresize", (event, width, height, lock = false) => { + electron.ipcMain.on("windowresize", (event, width, height, lock = false) => { this.win.setContentSize(width, height); this.win.setResizable(!lock); }); @@ -462,9 +469,9 @@ export class Win { }) //Fullscreen electron.ipcMain.on('detachDT', (event, _) => { - this.win.webContents.openDevTools({ mode: 'detach' }); + this.win.webContents.openDevTools({mode: 'detach'}); }) - + electron.ipcMain.on('play', (event, type, id) => { this.win.webContents.executeJavaScript(` @@ -497,7 +504,7 @@ export class Win { } //QR Code - electron.ipcMain.handle('showQR', async (event , _) => { + electron.ipcMain.handle('showQR', async (event, _) => { let url = `http://${getIp()}:${this.remotePort}`; electron.shell.openExternal(`https://cider.sh/pair-remote?url=${btoa(encodeURI(url))}`); /* @@ -510,11 +517,11 @@ export class Win { 'get url' fetch(url) .then(res => res.buffer()) - .then(async(buffer) => { + .then(async (buffer) => { try { const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a'); let SoundCheckTag = metadata.native.iTunes[1].value - console.log('sc',SoundCheckTag) + console.log('sc', SoundCheckTag) this.win.webContents.send('SoundCheckTag', SoundCheckTag) } catch (error) { console.error(error.message); @@ -564,6 +571,25 @@ export class Win { }); } + let isQuiting = false + + this.win.on("close", (event: Event) => { + if ((this.store.general.close_button_hide || process.platform === "darwin" )&& !isQuiting) { + event.preventDefault(); + this.win.hide(); + } else { + this.win.destroy(); + } + }) + + electron.app.on('before-quit', () => { + isQuiting = true + }); + + electron.app.on('window-all-closed', () => { + electron.app.quit() + }) + this.win.on("closed", () => { this.win = null; }); @@ -571,19 +597,20 @@ export class Win { // Set window Handler this.win.webContents.setWindowOpenHandler((x: any) => { if (x.url.includes("apple") || x.url.includes("localhost")) { - return { action: "allow" }; + return {action: "allow"}; } electron.shell.openExternal(x.url).catch(console.error); - return { action: "deny" }; + return {action: "deny"}; }); } + private async broadcastRemote() { function getIp() { - let ip :any = false; + let ip: any = false; let alias = 0; - const ifaces: any = os.networkInterfaces() ; + const ifaces: any = os.networkInterfaces(); for (var dev in ifaces) { - ifaces[dev].forEach( (details: any) => { + ifaces[dev].forEach((details: any) => { if (details.family === 'IPv4') { if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) { if (details.address.substring(0, 8) === '192.168.' || @@ -595,14 +622,15 @@ export class Win { } } } - }) ; + }); } return ip; } + const myString = `http://${getIp()}:${this.remotePort}`; let mdns = require('mdns-js'); const encoded = new Buffer(myString).toString('base64'); - var x = mdns.tcp('cider-remote'); + var x = mdns.tcp('cider-remote'); var txt_record = { "Ver": "131077", 'DvSv': '3689', @@ -613,7 +641,7 @@ export class Win { "CtlN": "Cider", "iV": "196623" } - let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, { name: encoded, txt: txt_record }); + let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, {name: encoded, txt: txt_record}); server2.start(); console.log('remote broadcasted') } diff --git a/src/main/index.ts b/src/main/index.ts index 59196a9f..e6557a4c 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -32,7 +32,7 @@ electron.app.on('ready', () => { electron.components.whenReady().then(async () => { win = await Cider.createWindow() - App.bwCreated(win); + App.bwCreated(win, Cider.i18n); /// please dont change this for plugins to get proper and fully initialized Win objects plug.callPlugins('onReady', win); win.on("ready-to-show", () => { diff --git a/src/main/plugins/minimizeToTray.ts b/src/main/plugins/minimizeToTray.ts deleted file mode 100644 index 0fe8df35..00000000 --- a/src/main/plugins/minimizeToTray.ts +++ /dev/null @@ -1,171 +0,0 @@ -import * as electron from 'electron'; -import * as path from 'path'; - - -export default class MinimizeToTray { - /** - * Private variables for interaction in plugins - */ - private _win: any; - private _app: any; - private _store: any; - private _tray: any; - private _forceQuit = false; - - /** - * Base Plugin Details (Eventually implemented into a GUI in settings) - */ - public name: string = 'Minimize to tray'; - public description: string = 'Allow Cider to minimize to tray'; - public version: string = '1.0.0'; - public author: string = 'vapormusic'; - - constructor(app: any, store: any) { - this._app = app; - this._store = store; - } - - private SetContextMenu(visibility : any) { - let self = this - if (visibility) { - this._tray.setContextMenu(electron.Menu.buildFromTemplate([ - // { - // label: 'Check for Updates', - // click: function () { - // app.ame.utils.checkForUpdates(true) - // } - // }, - { - label: 'Minimize to Tray', - click: function () { - if (typeof self._win.hide === 'function') { - self._win.hide(); - self.SetContextMenu(false); - } - } - }, - { - label: 'Quit', - click: function () { - self._forceQuit = true; self._app.quit(); - } - } - ])); - } else { - this._tray.setContextMenu(electron.Menu.buildFromTemplate([ - // { - // label: 'Check for Updates', - // click: function () { - // this._app.ame.utils.checkForUpdates(true) - // } - // }, - { - label: `Show ${electron.app.getName()}`, - click: function () { - if (typeof self._win.show === 'function') { - self._win.show(); - self.SetContextMenu(true); - } - } - }, - { - label: 'Quit', - click: function () { - self._forceQuit = true; self._app.quit(); - } - } - ])); - } - return true - - } - - /** - * Runs on app ready - */ - onReady(win: any): void { - this._win = win; - const winTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({ - width: 32, - height: 32 - }) - const macTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({ - width: 20, - height: 20 - }) - const linuxTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({ - width: 32, - height: 32 - }) - let trayIcon : any ; - if (process.platform === "win32") { - trayIcon = winTray - } else if (process.platform === "linux") { - trayIcon = linuxTray - } else if (process.platform === "darwin") { - trayIcon = macTray - } - - this._tray = new electron.Tray(trayIcon) - this._tray.setToolTip(this._app.getName()); - this.SetContextMenu(true); - - this._tray.on('double-click', () => { - if (typeof this._win.show === 'function') { - if (this._win.isVisible()) { - this._win.focus() - } else { - this._win.show() - } - } - }) - electron.ipcMain.handle("update-store-mtt", (event, value) => { - this._store.general["close_behavior"] = value; - }) - electron.ipcMain.on("win-close", (event, value) => { - console.log("tray", this._store.general["close_behavior"] ) - if (this._forceQuit || this._store.general["close_behavior"] == '0' ) { - this._app.quit(); - } else if (this._store.general["close_behavior"] == '1') { - this._win.minimize(); - } else { - this._win.hide(); - this.SetContextMenu(false); - } - }); - this._win.on("close", (e :any) => { - if (this._forceQuit || this._store.general["close_behavior"] == '0' ) { - this._app.quit(); - } else if (this._store.general["close_behavior"] == '1') { - e.preventDefault(); - this._win.minimize(); - } else { - e.preventDefault(); - this._win.hide(); - this.SetContextMenu(false); - } - }); - } - - /** - * Runs on app stop - */ - onBeforeQuit(): void { - - } - - /** - * Runs on playback State Change - * @param attributes Music Attributes (attributes.state = current state) - */ - onPlaybackStateDidChange(attributes: object): void { - } - - /** - * Runs on song change - * @param attributes Music Attributes - */ - onNowPlayingItemDidChange(attributes: object): void { - } - -} diff --git a/src/renderer/index.js b/src/renderer/index.js index 77e9077f..55cd45ea 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -3533,8 +3533,7 @@ const app = new Vue({ } }, closeWindow(){ - // window.close doesnt call the win "close" event for some reason - ipcRenderer.send('win-close'); + ipcRenderer.send('close'); }, checkForUpdate(){ ipcRenderer.send('check-for-update') diff --git a/src/renderer/index_old.html b/src/renderer/index_old.html deleted file mode 100644 index 927c9daf..00000000 --- a/src/renderer/index_old.html +++ /dev/null @@ -1,778 +0,0 @@ - - - - - - - - - - - - - - - - - Cider - - - - - - - - -
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
- -
-
- - - -
-
-
-
- - -
-
-
-
- -
-
- -
-
- -
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Apple Music -
- - - -
- Library -
- - - - -
- Playlists -
- -
- -
- - - - - -
-
- -
-
Updating your library...
-
{{ library.songs.meta.progress }} / {{ library.songs.meta.total }}
-
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
-
-
- - - - -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/renderer/views/pages/settings.ejs b/src/renderer/views/pages/settings.ejs index e5f89ebd..d9dd9444 100644 --- a/src/renderer/views/pages/settings.ejs +++ b/src/renderer/views/pages/settings.ejs @@ -598,20 +598,10 @@
- {{$root.getLz("settings.option.experimental.closeButtonBehaviour")}} + {{$root.getLz("settings.option.experimental.close_button_hide")}}
- +