diff --git a/package.json b/package.json index 99046ccd..29b2da45 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,11 @@ "@sentry/electron": "^3.0.7", "@sentry/integrations": "^6.19.6", "adm-zip": "0.4.10", + "airtunes2": "git+https://github.com/vapormusic/node_airtunes2.git", "castv2-client": "^1.2.0", "chokidar": "^3.5.3", "discord-rpc": "^4.0.1", + "dns-js": "git+https://github.com/ciderapp/node-dns-js.git", "ejs": "^3.1.6", "electron-fetch": "^1.7.4", "electron-log": "^4.4.6", @@ -52,7 +54,6 @@ "get-port": "^5.1.1", "jsonc": "^2.0.0", "lastfmapi": "^0.1.1", - "dns-js": "git+https://github.com/ciderapp/node-dns-js.git", "mdns-js": "git+https://github.com/ciderapp/node-mdns-js.git", "mpris-service": "^2.1.2", "music-metadata": "^7.12.3", @@ -70,11 +71,11 @@ "youtube-search-without-api-key": "^1.0.7" }, "devDependencies": { + "@types/adm-zip": "^0.5.0", "@types/discord-rpc": "4.0.2", "@types/express": "^4.17.13", "@types/qrcode-terminal": "^0.12.0", "@types/ws": "^8.5.3", - "@types/adm-zip": "^0.5.0", "electron": "git+https://github.com/castlabs/electron-releases.git", "electron-builder": "^23.0.3", "electron-builder-notarize-pkg": "^1.2.0", @@ -107,9 +108,9 @@ } ], "build": { - "electronVersion": "18.0.3", + "electronVersion": "18.0.4", "electronDownload": { - "version": "18.0.3+wvcus", + "version": "18.0.4+wvcus", "mirror": "https://github.com/castlabs/electron-releases/releases/download/v" }, "appId": "cider", diff --git a/src/i18n/README.md b/src/i18n/README.md index c86103ee..eba2e4b7 100644 --- a/src/i18n/README.md +++ b/src/i18n/README.md @@ -262,3 +262,8 @@ Update 16/04/2022 9:30 UTC * `settings.header.connect`: Added for `en_US` +Update 22/04/2022 13:00 UTC + +* `settings.option.general.keybindings`: Added for `en_US` +* `settings.option.general.keybindings.open`: Added for `en_US` + diff --git a/src/i18n/en_US.json b/src/i18n/en_US.json index 3b92951b..198cada2 100644 --- a/src/i18n/en_US.json +++ b/src/i18n/en_US.json @@ -279,6 +279,8 @@ "settings.option.general.updateCider.branch.develop": "Development", "settings.option.general.customizeSidebar": "Customize Sidebar Items", "settings.option.general.customizeSidebar.customize": "Customize", + "settings.option.general.keybindings": "Keybindings", + "settings.option.general.keybindings.open": "Open", "settings.notyf.updateCider.update-not-available": "No update available", "settings.notyf.updateCider.update-downloaded": "Update has been downloaded, restart to apply", "settings.notyf.updateCider.update-error": "Error updating Cider", @@ -422,7 +424,7 @@ "settings.option.advanced.playlistTrackMapping": "Playlist Track Mapping", "settings.option.advanced.playlistTrackMapping.description": "Enables deep scanning of playlists to determine which tracks are in which playlists. Playlist cache build times can increase significantly.", "settings.option.visual.transparent": "Transparent frame", - "settings.option.visual.transparent.description": "Transparent frame (needs Theme Support , requires relaunch)", + "settings.option.visual.transparent.description": "Transparent frame (needs Theme Support, requires relaunch)", "settings.header.advanced": "Advanced", "settings.header.connect": "Sync", "settings.option.connect.link_account": "Enable Sync with Cider Connect", diff --git a/src/i18n/hu_HU.json b/src/i18n/hu_HU.json index 1c42b123..5ebe3a07 100644 --- a/src/i18n/hu_HU.json +++ b/src/i18n/hu_HU.json @@ -279,6 +279,8 @@ "settings.option.general.updateCider.branch.develop": "Fejlesztői", "settings.option.general.customizeSidebar": "Oldalsáv elemeinek testreszabása", "settings.option.general.customizeSidebar.customize": "Testreszabás", + "settings.option.general.keybindings": "Billentyűparancsok", + "settings.option.general.keybindings.open": "Megnyitás", "settings.notyf.updateCider.update-not-available": "Nem található frissítés", "settings.notyf.updateCider.update-downloaded": "A frissítés le lett töltve, a telepítéshez indítsa újra az alkalmazást", "settings.notyf.updateCider.update-error": "Hiba történt a frissítés közben", diff --git a/src/i18n/source/en_US.json b/src/i18n/source/en_US.json index 2d5f4bf5..e22e8e6f 100644 --- a/src/i18n/source/en_US.json +++ b/src/i18n/source/en_US.json @@ -279,6 +279,8 @@ "settings.option.general.updateCider.branch.develop": "Development", "settings.option.general.customizeSidebar": "Customize Sidebar Items", "settings.option.general.customizeSidebar.customize": "Customize", + "settings.option.general.keybindings": "Keybindings", + "settings.option.general.keybindings.open": "Open", "settings.notyf.updateCider.update-not-available": "No update available", "settings.notyf.updateCider.update-downloaded": "Update has been downloaded, restart to apply", "settings.notyf.updateCider.update-error": "Error updating Cider", @@ -422,7 +424,7 @@ "settings.option.advanced.playlistTrackMapping": "Playlist Track Mapping", "settings.option.advanced.playlistTrackMapping.description": "Enables deep scanning of playlists to determine which tracks are in which playlists. Playlist cache build times can increase significantly.", "settings.option.visual.transparent": "Transparent frame", - "settings.option.visual.transparent.description": "Transparent frame (needs Theme Support , requires relaunch)", + "settings.option.visual.transparent.description": "Transparent frame (needs Theme Support, requires relaunch)", "settings.header.advanced": "Advanced", "settings.header.connect": "Connect", "spatial.notTurnedOn": "Audio Spatialization is disabled. To use, please enable it first.", diff --git a/src/main/base/browserwindow.ts b/src/main/base/browserwindow.ts index 371aaed4..0b08af18 100644 --- a/src/main/base/browserwindow.ts +++ b/src/main/base/browserwindow.ts @@ -528,6 +528,7 @@ export class BrowserWindow { app.get("/connect/set-cc-user/:data", (req, res) => { //utils.getStoreValue('connectUser', JSON.parse()) // [Connect] Save user in store utils.setStoreValue('connectUser', JSON.parse(req.params.data)) + utils.getWindow().reload() res.redirect(`https://connect.cidercollective.dev/linked.html`) }); // [Connect] Set auth URL in store for `shell.openExternal` @@ -1202,6 +1203,13 @@ export class BrowserWindow { ipcMain.on('cc-auth', (_event) => { shell.openExternal(String(utils.getStoreValue('cc_authURL'))); }); + + ipcMain.on('cc-logout', (_event) => { + utils.setStoreValue('connectUser', { + auth: null + }); + utils.getWindow().reload(); + }); /* ********************************************************************************************* * Window Events * **********************************************************************************************/ diff --git a/src/main/base/store.ts b/src/main/base/store.ts index e06ec8f6..3436d807 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -154,8 +154,12 @@ export class Store { "advanced": { "AudioContext": false, "experiments": [], - "playlistTrackMapping": true - } + "playlistTrackMapping": true, + "ffmpegLocation": "" + }, + "connectUser": { + "auth": null, + }, } private migrations: any = { '>=1.4.3': (store: ElectronStore) => { diff --git a/src/main/plugins/menubar.ts b/src/main/plugins/menubar.ts index 33caae52..b2e7caba 100644 --- a/src/main/plugins/menubar.ts +++ b/src/main/plugins/menubar.ts @@ -121,7 +121,7 @@ export default class Thumbar { }, { label: 'Plug-in Menu', - accelerator: 'CommandOrControl++Shift+P', + accelerator: 'CommandOrControl+Shift+P', click: () => this._win.webContents.executeJavaScript(`app.modals.pluginMenu = true`) } diff --git a/src/main/plugins/raop.ts b/src/main/plugins/raop.ts new file mode 100644 index 00000000..8c58c488 --- /dev/null +++ b/src/main/plugins/raop.ts @@ -0,0 +1,344 @@ +import * as electron from 'electron'; +import * as os from 'os'; +import * as fs from 'fs'; +import { join, resolve } from 'path'; +import * as CiderReceiver from '../base/castreceiver'; +import fetch from 'electron-fetch'; +import {Stream} from "stream"; +import {spawn} from 'child_process'; +import {Worker} from 'worker_threads'; +import { Blob } from 'buffer'; + + +export default class RAOP { + + /** + * Private variables for interaction in plugins + */ + private _utils: any; + private _win: any; + private _app: any; + private _store: any; + private _cacheAttr: any; + + private ipairplay: any = ""; + private portairplay: any = ""; + private u = require('airtunes2'); + private airtunes: any; + private device: any; + private mdns = require('mdns-js'); + private ok: any = 1; + private devices: any = []; + private castDevices: any = []; + private i: any = false; + private audioStream: any = new Stream.PassThrough(); + private ffmpeg: any = null; + private worker: any = null; + + + private processNode = ` + import {parentPort, workerData} from "worker_threads"; + function getAudioConv (buffers) { + + function interleave16(leftChannel, rightChannel) { + var length = leftChannel.length + rightChannel.length; + var result = new Int16Array(length); + + var inputIndex = 0; + + for (var index = 0; index < length;) { + result[index++] = leftChannel[inputIndex]; + result[index++] = rightChannel[inputIndex]; + inputIndex++; + } + return result; + } + + + + function convert(n) { + var v = n < 0 ? n * 32768 : n * 32767; // convert in range [-32768, 32767] + return Math.max(-32768, Math.min(32768, v)); // clamp + } + + function bitratechange(e) { + var t = e.length; + let sampleRate = 96.0; + let outputSampleRate = 44.1; + var s = 0, + o = sampleRate / outputSampleRate, + u = Math.ceil(t * outputSampleRate / sampleRate), + a = new Int16Array(u); + for (let i = 0; i < u; i++) { + a[i] = e[Math.floor(s)]; + s += o; + } + + return a; + } + + let newaudio = buffers; + + let pcmData = new Int8Array(interleave16(bitratechange(Int16Array.from(newaudio[0], x => convert(x))), bitratechange(Int16Array.from(newaudio[1], x => convert(x)))).buffer); + return pcmData; + } + parentPort.on("message", data => { + parentPort.postMessage({buffer: data.buffer, outbuffer: getAudioConv(data.buffer)}); + }); + +`; + + private ondeviceup(name: any, host: any, port: any, addresses: any) { + if (this.castDevices.findIndex((item: any) => item.name === host && item.port === port && item.addresses === addresses) === -1) { + this.castDevices.push({ + name: host, + host: addresses ? addresses[0] : '', + port: port, + addresses: addresses + }); + if (this.devices.indexOf(host) === -1) { + this.devices.push(host); + } + if (name) { + this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err)); + console.log("deviceFound", host, name); + } + } else { + this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err)); + console.log("deviceFound (added)", host, name); + } + } + + /** + * Base Plugin Details (Eventually implemented into a GUI in settings) + */ + public name: string = 'RAOP'; + public description: string = 'RAOP Plugin'; + public version: string = '0.0.1'; + public author: string = 'vapormusic / Cider Collective'; + + /** + * Runs on plugin load (Currently run on application start) + */ + constructor(utils: { getStore: () => any; getApp: () => any; }) { + this._utils = utils; + console.debug(`[Plugin][${this.name}] Loading Complete.`); + this._app = utils.getApp(); + + } + + /** + * Runs on app ready + */ + onReady(win: any): void { + this._win = win; + + electron.ipcMain.on('getKnownAirplayDevices', (event) => { + event.returnValue = this.castDevices + }); + + electron.ipcMain.on("getAirplayDevice", (event, data) => { + this.castDevices = []; + console.log("scan for airplay devices"); + + const browser = this.mdns.createBrowser(this.mdns.tcp('raop')); + browser.on('ready', browser.discover); + + browser.on('update', (service: any) => { + if (service.addresses && service.fullname && service.fullname.includes('_raop._tcp')) { + this._win.webContents.executeJavaScript(`console.log( + "${service.name} ${service.host}:${service.port} ${service.addresses}" + )`);} + this.ondeviceup(service.name, service.host, service.port, service.addresses); + }); + + }); + + + + electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL) => { + + if (ipv4 != this.ipairplay || ipport != this.portairplay) { + if (this.airtunes == null) { this.airtunes = new this.u()} + this.ipairplay = ipv4; + this.portairplay = ipport; + this.device = this.airtunes.add(ipv4, { + port: ipport, + volume: 60, + password: sepassword, + }); + this.device.on('status', (status: any) => { + console.log('device status', status); + if (status == "ready"){ + this._win.webContents.setAudioMuted(true); + this._win.webContents.executeJavaScript(`CiderAudio.sendAudio()`).catch((err: any) => console.error(err)); + } + if (status == 'stopped') { + this.airtunes.stopAll(() => { + console.log('end'); + }); + this.airtunes = null; + this.device = null; + this.ipairplay = ''; + this.portairplay = ''; + this.ok = 1; + + } else { + setTimeout(() => { + if (this.ok == 1) { + console.log(this.device.key, title ?? '', artist ?? '', album ?? ''); + this.airtunes.setTrackInfo(this.device.key, title ?? '', artist?? '', album?? ''); + this.uploadImageAirplay(artworkURL); + console.log('done'); + this.ok == 2 + } + }, 1000); + } + + + }); + + } + + + + + }); + + electron.ipcMain.on('writeWAV', (event, leftbuffer, rightbuffer) => { + if (this.airtunes != null) { + if (this.worker == null) { + try{ + const toDataUrl = (js: any) => new URL(`data:text/javascript,${encodeURIComponent(js)}`); + // let blob = new Blob([this.processNode], { type: 'application/javascript' }); + //Create new worker + this.worker = new Worker(toDataUrl(this.processNode)); + + //Listen for a message from worker + this.worker.on("message", (result: any) => { + // fs.writeFile(join(electron.app.getPath('userData'), 'buffer.raw'), Buffer.from(Int8Array.from(result.outbuffer)),{flag: 'a+'}, function (err) { + // if (err) throw err; + // console.log('It\'s saved!'); + // }); + this.airtunes.circularBuffer.write(Buffer.from(Int8Array.from(result.outbuffer))); + }); + + this.worker.on("error", (error: any) => { + console.log("bruh",error); + }); + this.worker.postMessage({buffer: [leftbuffer, rightbuffer]}); + } catch (e){console.log(e)} + + + // this.ffmpeg != null ? this.ffmpeg.kill() : null; + // this.ffmpeg = spawn(this._utils.getStoreValue("advanced.ffmpegLocation"), [ + // '-f', 's16le', // PCM 16bits, little-endian + // '-ar', '48000', + // '-ac', "2", + // '-err_detect','ignore_err', + // '-i', "http://localhost:9000/audio.wav", + // '-acodec', 'pcm_s16le', + // '-f', 's16le', // PCM 16bits, little-endian + // '-ar', '44100', // Sampling rate + // '-ac', "2", // Stereo + // 'pipe:1' // Output on stdout + // ]); + + // // pipe data to AirTunes + // this.ffmpeg.stdout.pipe(this.airtunes); + // this.i = true; + } else { + this.worker.postMessage({buffer: [leftbuffer, rightbuffer]}); + } + } + + }); + + + + electron.ipcMain.on('disconnectAirplay', (event) => { + this._win.webContents.setAudioMuted(false); + this.airtunes.stopAll(function () { + console.log('end'); + }); + this.airtunes = null; + this.device = null; + this.ipairplay = ''; + this.portairplay = ''; + this.ok = 1; + this.i = false; + }); + + electron.ipcMain.on('updateAirplayInfo', (event, title, artist, album, artworkURL) => { + if (this.airtunes && this.device) { + console.log(this.device.key, title, artist, album); + this.airtunes.setTrackInfo(this.device.key, title, artist, album); + this.uploadImageAirplay(artworkURL) + } + }); + + electron.ipcMain.on('updateRPCImage', (_event, imageurl) => { + this.uploadImageAirplay(imageurl) + }) + + + + } + + private uploadImageAirplay = (url: any) => { + try { + if (url != null && url != '') { + //console.log(join(this._app.getPath('userData'), 'temp.png'), url); + fetch(url) + .then(res => res.buffer()) + .then((buffer) => { + this.airtunes.setArtwork(this.device.key, buffer, "image/png"); + }).catch(err => { + console.log(err) + }); + } + } catch (e) { console.log(e) } + } + + /** + * Runs on app stop + */ + onBeforeQuit(): void { + + } + + // /** + // * Runs on song change + // * @param attributes Music Attributes + // */ + // onNowPlayingItemDidChange(attributes: any): void { + // if (this.airtunes && this.device) { + // let title = attributes.name ? attributes.name : ''; + // let artist = attributes.artistName ? attributes.artistName : ''; + // let album = attributes.albumName ? attributes.albumName : ''; + // let artworkURL = attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024') ?? null; + // console.log(this.device.key, title, artist, album); + // this.airtunes.setTrackInfo(this.device.key, title, artist, album); + // if (artworkURL) + // this.uploadImageAirplay(artworkURL) + // } + // } + + /** + * Runs on playback State Change + * @param attributes Music Attributes (attributes.status = current state) + */ + onPlaybackStateDidChange(attributes: any): void { + if (this.airtunes && this.device) { + let title = attributes?.name ?? ''; + let artist = attributes?.artistName ?? ''; + let album = attributes?.albumName ?? ''; + let artworkURL = attributes?.artwork?.url ?? null; + console.log(this.device.key, title, artist, album); + this.airtunes.setTrackInfo(this.device.key, title, artist, album); + if (artworkURL != null){} + this.uploadImageAirplay(artworkURL.replace('{w}', '1024').replace('{h}', '1024')) + } + } + +} diff --git a/src/renderer/assets/check.svg b/src/renderer/assets/check.svg new file mode 100644 index 00000000..1c209899 --- /dev/null +++ b/src/renderer/assets/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/index.js b/src/renderer/index.js index 879b017a..8df442f8 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -290,5 +290,5 @@ let screenHeight = screen.height; window.onerror = function (error) { console.log(error) - bootbox.alert("Error occured: " + error) + bootbox.alert("Error occurred: " + error) }; diff --git a/src/renderer/less/pages.less b/src/renderer/less/pages.less index 38d9e51b..66c68f19 100644 --- a/src/renderer/less/pages.less +++ b/src/renderer/less/pages.less @@ -1149,6 +1149,10 @@ font-size : 2em; } + .settings-option-body-webview { + height: 100%; + width: 100%; + } .settings-option-body { margin: 16px; } diff --git a/src/renderer/main/events.js b/src/renderer/main/events.js index 63631db6..00726985 100644 --- a/src/renderer/main/events.js +++ b/src/renderer/main/events.js @@ -85,6 +85,12 @@ const Events = { } catch (e) { } } + // Prevent Scrolling on spacebar + if (event.keyCode === 32 && event.target === document.body) { + event.preventDefault() + app.SpacePause() + + } }); // Hang Timer diff --git a/src/renderer/style.css b/src/renderer/style.css index 42702b6e..33e4b4db 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -12826,6 +12826,19 @@ body.no-gpu .drawertransition-leave-to { body.no-gpu .lyric-line:hover::after { display: none; } +.keybindings-border { + padding-left: 15px; + padding-right: 15px; + border-style: solid; + border-radius: 5px; + border-color: #CBCBCB; +} +.keybinding-text { + width: 95px; + display: flex; + justify-content: center; + align-items: center; +} .qrimg { width: -webkit-fill-available; max-block-size: -webkit-fill-available; diff --git a/src/renderer/style.less b/src/renderer/style.less index 80934d6f..133469dd 100644 --- a/src/renderer/style.less +++ b/src/renderer/style.less @@ -1228,6 +1228,18 @@ body[platform="darwin"] .app-chrome .app-chrome-item > .window-controls > div.cl margin-bottom: 15px; } } +// Add Music Video Icons to Songs that are Music Videos +div[data-type="library-music-videos"] .info-rect .title::before, +div[data-type="musicVideo"] .info-rect .title::before { + content: ""; + background-image: url(./assets/feather/video.svg); + background-size: contain; + filter:invert(0.6); + display: inline-block; + width: 20px; + height: 20px; + margin-bottom: -4px; +} .app-chrome .app-chrome-item > .app-playback-controls .song-duration p { font-weight: 400; @@ -3123,6 +3135,21 @@ body.no-gpu { } } +.keybindings-border { + padding-left: 15px; + padding-right: 15px; + border-style: solid; + border-radius: 5px; + border-color: #CBCBCB; +} + +.keybinding-text { + width: 95px; + display: flex; + justify-content: center; + align-items: center; +} + .qrimg { width: -webkit-fill-available; max-block-size: -webkit-fill-available; diff --git a/src/renderer/views/components/castmenu.ejs b/src/renderer/views/components/castmenu.ejs index 2cf42e54..ad3bd209 100644 --- a/src/renderer/views/components/castmenu.ejs +++ b/src/renderer/views/components/castmenu.ejs @@ -33,11 +33,27 @@ -
{{$root.getLz('action.cast.airplay')}}
-
+
{{$root.getLz('action.cast.airplay')}}
+
- {{$root.getLz('action.cast.airplay.underdevelopment')}} + {{true ? 'Homepods only for now! (NO PASSWORD PLEASE!)' : 'Please add FFmpeg location in Settings -> Advanced'}} + +
@@ -84,8 +100,10 @@ let self = this; this.scanning = true; ipcRenderer.send('getChromeCastDevices', ''); + ipcRenderer.send("getAirplayDevice","") setTimeout(() => { self.devices.cast = ipcRenderer.sendSync("getKnownCastDevices"); + self.devices.airplay = ipcRenderer.sendSync("getKnownAirplayDevices"); self.scanning = false; }, 2000); console.log(this.devices); @@ -96,8 +114,13 @@ this.activeCasts.push(device); ipcRenderer.send('performGCCast', device, "Cider", "Playing ...", "Test build", ''); }, + setAirPlayCast(device) { + this.activeCasts.push(device); + ipcRenderer.send("performAirplayPCM",device.host,device.port,null,"","","","") + }, stopCasting() { CiderAudio.stopAudio(); + ipcRenderer.send('disconnectAirplay', ''); ipcRenderer.send('stopGCast', ''); this.activeCasts = []; // vm.$forceUpdate(); diff --git a/src/renderer/views/pages/settings.ejs b/src/renderer/views/pages/settings.ejs index 0910eb61..71cba409 100644 --- a/src/renderer/views/pages/settings.ejs +++ b/src/renderer/views/pages/settings.ejs @@ -123,6 +123,68 @@
+
+
+ {{$root.getLz('settings.option.general.keybindings')}} +
+
+ +
+ +
+
+
+ Toggle Private Session +
+
+

{{ getCommandOrControl() }} + P

+
+
+
+
+ Web Remote +
+
+

{{ getCommandOrControl() }} + Shift + W

+
+
+
+
+ Audio Settings +
+
+

{{ getCommandOrControl() }} + Shift + A

+
+
+
+
+ Plugin Menu +
+
+

{{ getCommandOrControl() }} + Shift + P

+
+
+
+
+ Cast to Devices +
+
+

{{ getCommandOrControl() }} + Shift + C

+
+
+
+
+ Open Developer Tools +
+
+

{{ getCommandOrControl() }} + {{ getOptionOrShift() }} + I

+
+
+
+
+
@@ -853,6 +915,17 @@
+
+
+ FFmpeg location
+ Restart needed to work. Required for AirPlay. (For example: C:\ffmpeg-4.4-essentials_build\bin\ffmpeg.exe)
+ You can look at the internet on how to install it. +
+
+ +
+
+
{{$root.getLz('settings.option.visual.plugin.github.explore')}} @@ -976,6 +1049,7 @@
+
@@ -983,7 +1057,7 @@ {{$root.getLz('settings.header.connect')}}
-
+
{{$root.getLz('settings.option.connect.link_account')}} {{$root.getLz('settings.option.connect.link_account.description')}} @@ -996,9 +1070,55 @@
+
+
+
+ {{$root.getLz('settings.option.connect.link_account')}} + {{$root.getLz('settings.option.connect.link_account.description')}} +
+
+
+ +
+
+
+ {{app.cfg.connectUser.username}} + +
+ +
+
+ Sync Settings +
+
+ +
+
+ +
+
+ Sync Themes +
+
+ +
+
+ +
+
+ Sync Plugins +
+
+ +
+
+ +
-
@@ -1124,6 +1244,12 @@ } }, methods: { + getCommandOrControl() { + return app.platform == "darwin" ? "⌘" : "Ctrl"; + }, + getOptionOrShift() { + return app.platform == "darwin" ? "⌥" : "Shift"; + }, windowBgStyleChange() { this.$root.getNowPlayingArtworkBG(undefined, true) if (this.$root.cfg.visual.window_background_style == "mica") { @@ -1240,6 +1366,9 @@ authCC() { ipcRenderer.send('cc-auth') }, + logoutCC() { + ipcRenderer.send('cc-logout') + }, } }) \ No newline at end of file diff --git a/src/renderer/views/svg/check.svg b/src/renderer/views/svg/check.svg new file mode 100644 index 00000000..1c209899 --- /dev/null +++ b/src/renderer/views/svg/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/winget.json b/winget.json index 4468a361..351ef190 100644 --- a/winget.json +++ b/winget.json @@ -1,7 +1,7 @@ { - "electronVersion": "16.0.07", + "electronVersion": "18.0.4", "electronDownload": { - "version": "16.0.7+wvcus", + "version": "18.0.4+wvcus", "mirror": "https://github.com/castlabs/electron-releases/releases/download/v" }, "appId": "cider",