diff --git a/.circleci/config.yml b/.circleci/config.yml index 40c944a1..52126941 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -56,6 +56,11 @@ jobs: command: yarn dist -w --x64 -p never post-steps: - jira/notify + - run: + name: Generate Builds (Winget) + command: yarn winget -p never + post-steps: + - jira/notify - run: name: Move Build Files command: | diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 5f72d294..f1befd57 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -92,6 +92,7 @@ jobs: rm cider-yarn.lock || true xcodebuild -version yarn install + cp resources/verror-types node_modules/@types/verror/index.d.ts cp resources/macPackager.js node_modules/app-builder-lib/out/macPackager.js yarn dist:universalNotWorking -p never # - name: Perform CodeQL Analysis diff --git a/package.json b/package.json index 7716457a..ef2f2597 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dist:macarm": "yarn build && electron-builder --mac --arm64", "dist:universalNotWorking": "yarn build && electron-builder --mac --universal", "dist:all": "yarn build && electron-builder -mwl", + "winget": "yarn build && electron-builder --win -c winget.json", "msft": "yarn build && electron-builder -c msft-package.json", "postinstall": "electron-builder install-app-deps", "circle:append-bid": "node resources/appendCommitToVersion" @@ -37,7 +38,7 @@ "dependencies": { "@sentry/electron": "^3.0.2", "@sentry/integrations": "^6.18.1", - "adm-zip": "^0.5.9", + "adm-zip": "0.4.10", "castv2-client": "^1.2.0", "chokidar": "^3.5.3", "discord-rpc": "^4.0.1", diff --git a/resources/verror-types b/resources/verror-types new file mode 100644 index 00000000..07cfb21d --- /dev/null +++ b/resources/verror-types @@ -0,0 +1,77 @@ +// Type definitions for verror 1.10 +// Project: https://github.com/davepacheco/node-verror +// Definitions by: Sven Reglitzki , Maxime Toumi-M +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/* + * VError([cause], fmt[, arg...]): Like JavaScript's built-in Error class, but + * supports a "cause" argument (another error) and a printf-style message. The + * cause argument can be null or omitted entirely. + * + * Examples: + * + * CODE MESSAGE + * new VError('something bad happened') "something bad happened" + * new VError('missing file: "%s"', file) "missing file: "/etc/passwd" + * with file = '/etc/passwd' + * new VError(err, 'open failed') "open failed: file not found" + * with err.message = 'file not found' + */ +declare class VError extends Error { + static VError: typeof VError; + + static cause(err: Error): Error | null; + static info(err: Error): VError.Info; + static fullStack(err: Error): string; + static findCauseByName(err: Error, name: string): Error | null; + static hasCauseWithName(err: Error, name: string): boolean; + static errorFromList(errors: T[]): null | T | VError.MultiError; + static errorForEach(err: Error, func: (err: Error) => void): void; + + //@ts-ignore + cause(): Error | undefined; + constructor(options: VError.Options | Error, message: string, ...params: any[]); + constructor(message?: string, ...params: any[]); +} + +declare namespace VError { + interface Info { + [key: string]: any; + } + + interface Options { + cause?: Error | null | undefined; + name?: string | undefined; + strict?: boolean | undefined; + constructorOpt?(...args: any[]): void; + info?: Info | undefined; + } + + /* + * SError is like VError, but stricter about types. You cannot pass "null" or + * "undefined" as string arguments to the formatter. Since SError is only a + * different function, not really a different class, we don't set + * SError.prototype.name. + */ + class SError extends VError {} + + /* + * Represents a collection of errors for the purpose of consumers that generally + * only deal with one error. Callers can extract the individual errors + * contained in this object, but may also just treat it as a normal single + * error, in which case a summary message will be printed. + */ + class MultiError extends VError { + constructor(errors: Error[]); + errors(): Error[]; + } + + /* + * Like JavaScript's built-in Error class, but supports a "cause" argument which + * is wrapped, not "folded in" as with VError. Accepts a printf-style message. + * The cause argument can be null. + */ + class WError extends VError {} +} + +export = VError; \ No newline at end of file diff --git a/src/i18n/en_GB.json b/src/i18n/en_GB.json index d778c535..4ce16049 100644 --- a/src/i18n/en_GB.json +++ b/src/i18n/en_GB.json @@ -10,6 +10,6 @@ "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalises peak volume for individual tracks to create a more uniform listening experience.", "settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Audio Spatialisation", "settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Spatialise audio and make audio more 3-dimensional (note: This is not Dolby Atmos)", - "spatial.notTurnedOn": "Audio Spatialisation is disabled. To use, please enable it first." - "action.tray.minimize": "Minimise to Tray", + "spatial.notTurnedOn": "Audio Spatialisation is disabled. To use, please enable it first.", + "action.tray.minimize": "Minimise to Tray" } diff --git a/src/i18n/source/en_US.json b/src/i18n/source/en_US.json index 864ce43d..88683bf5 100644 --- a/src/i18n/source/en_US.json +++ b/src/i18n/source/en_US.json @@ -2,7 +2,7 @@ "i18n.languageName": "English (US)", "i18n.languageNameEnglish": "English (US)", "i18n.category": "main", - "i18n.authors": "@maikirakiwi @kyw504100", + "i18n.authors": "@maikirakiwi @kyw504100 nosh118", "app.name": "Cider", "date.format": "${m} ${d}, ${y}", "dialog.cancel": "Cancel", @@ -89,6 +89,8 @@ "term.time.added": "Added", "term.time.released": "Released", "term.time.updated": "Updated", + "term.time.days": "days", + "term.time.day": "day", "term.time.hours": "hours", "term.time.hour": "hour", "term.time.minutes": "minutes", @@ -115,6 +117,10 @@ "term.equalizer": "Equalizer", "term.reset": "Reset", "term.tracks": "tracks", + "term.track": { + "one" : "track", + "other" : "tracks" + }, "term.videos": "Videos", "term.menu": "Menu", "term.check": "Check", @@ -142,7 +148,6 @@ "term.noVideos": "No videos found.", "term.plugin": "Plug-in", "term.pluginMenu": "Plug-in Menu", - "term.pluginMenu.none": "No interactive plugins.", "term.replay": "Replay", "term.uniqueAlbums": "Unique Albums", "term.uniqueArtists": "Unique Artists", @@ -158,7 +163,7 @@ "home.recentlyPlayed": "Recently Played", "home.recentlyAdded": "Recently Added", "home.artistsFeed": "Your Artists Feed", - "home.artistsFeed.noArtist": "Follow some artists first and their latest releases will be here", + "home.artistsFeed.noArtist": "Follow some artists to see their latest releases", "home.madeForYou": "Made For You", "home.friendsListeningTo": "Friends Listening To", "home.followedArtists": "Followed Artists", @@ -240,6 +245,10 @@ "settings.option.general.updateCider.branch.description": "Select the branch to update Cider to", "settings.option.general.updateCider.branch.main": "Stable", "settings.option.general.updateCider.branch.develop": "Development", + "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", + "settings.notyf.updateCider.update-timeout": "Update timed out", "settings.header.audio": "Audio", "settings.header.audio.description": "Adjust the audio settings for Cider.", "settings.option.audio.volumeStep": "Volume Step", @@ -255,7 +264,7 @@ "settings.header.audio.quality.standard.description": "64 kbps", "settings.option.audio.seamlessTransition": "Seamless Audio Transition", "settings.option.audio.enableAdvancedFunctionality": "Enable Advanced Functionality", - "settings.option.audio.enableAdvancedFunctionality.description": "Enabling AudioContext functionality will allow for extended audio features like Audio Normalization , Equalizers and Visualizers, however on some systems this may cause stuttering in audio tracks.", + "settings.option.audio.enableAdvancedFunctionality.description": "Enabling AudioContext functionality will allow for extended audio features like Audio Normalization, Equalizers and Visualizers - however on some systems this may cause stuttering in audio tracks.", "settings.option.audio.audioLab": "Cider Audio Lab", "settings.option.audio.audioLab.description": "An assortment of in-house developed audio effects for Cider.", "settings.warn.audioLab.withoutAF": "AudioContext (Advanced Functionality) is required to enable Cider Audio Laboratory.", diff --git a/src/main/base/app.ts b/src/main/base/app.ts index 62cb923a..1856fe10 100644 --- a/src/main/base/app.ts +++ b/src/main/base/app.ts @@ -219,6 +219,7 @@ export class AppEvents { app.quit() } else if (utils.getWindow()) { if (utils.getWindow().isMinimized()) utils.getWindow().restore() + utils.getWindow().show() utils.getWindow().focus() } }) diff --git a/src/main/base/browserwindow.ts b/src/main/base/browserwindow.ts index 8c9c953d..bbd4a035 100644 --- a/src/main/base/browserwindow.ts +++ b/src/main/base/browserwindow.ts @@ -1,17 +1,18 @@ -import { join } from "path"; -import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell } from "electron"; +import {join} from "path"; +import {app, BrowserWindow as bw, ipcMain, ShareMenu, shell} from "electron"; import * as windowStateKeeper from "electron-window-state"; import * as express from "express"; import * as getPort from "get-port"; -import { search } from "youtube-search-without-api-key"; -import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync } from "fs"; -import { Stream } from "stream"; -import { networkInterfaces } from "os"; +import {search} from "youtube-search-without-api-key"; +import {existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync} from "fs"; +import {Stream} from "stream"; +import {networkInterfaces} from "os"; import * as mm from 'music-metadata'; import fetch from 'electron-fetch' -import { wsapi } from "./wsapi"; -import { AppImageUpdater, NsisUpdater } from "electron-updater"; -import { utils } from './utils'; +import {wsapi} from "./wsapi"; +import {AppImageUpdater, NsisUpdater} from "electron-updater"; +import {utils} from './utils'; + const fileWatcher = require('chokidar'); const AdmZip = require("adm-zip"); @@ -219,7 +220,7 @@ export class BrowserWindow { show: false, // backgroundColor: "#1E1E1E", titleBarStyle: 'hidden', - trafficLightPosition: { x: 15, y: 20 }, + trafficLightPosition: {x: 15, y: 20}, webPreferences: { experimentalFeatures: true, nodeIntegration: true, @@ -233,6 +234,7 @@ export class BrowserWindow { preload: join(utils.getPath('srcPath'), "./preload/cider-preload.js"), }, }; + StartWatcher(path: string) { var chokidar = require("chokidar"); @@ -272,11 +274,12 @@ export class BrowserWindow { // console.log('Raw event info:', event, path, details); }); } + /** * Creates the browser window */ async createWindow(): Promise { - this.clientPort = await getPort({ port: 9000 }); + this.clientPort = await getPort({port: 9000}); BrowserWindow.verifyFiles(); this.StartWatcher(utils.getPath('themes')); @@ -497,7 +500,7 @@ export class BrowserWindow { remote.use(express.static(join(utils.getPath('srcPath'), "./web-remote/"))) remote.set("views", join(utils.getPath('srcPath'), "./web-remote/views")); remote.set("view engine", "ejs"); - getPort({ port: 6942 }).then((port) => { + getPort({port: 6942}).then((port) => { this.remotePort = port; // Start Remote Discovery this.broadcastRemote() @@ -550,7 +553,7 @@ export class BrowserWindow { if (itspod != null) details.requestHeaders["Cookie"] = `itspod=${itspod}`; } - callback({ requestHeaders: details.requestHeaders }); + callback({requestHeaders: details.requestHeaders}); } ); @@ -590,10 +593,33 @@ export class BrowserWindow { let response = await fetch( `${url}/archive/refs/heads/main.zip` ); - let zip = await response.buffer(); - let zipFile = new AdmZip(zip); - zipFile.extractAllTo(utils.getPath("themes"), true); - + let repo = url.split("/").slice(-2).join("/"); + let apiRepo = await fetch( + `https://api.github.com/repos/${repo}` + ).then((res) => res.json()); + console.debug(`REPO ID: ${apiRepo.id}`); + // extract the files from the first folder in the zip response + let zip = new AdmZip(await response.buffer()); + let entry = zip.getEntries()[0]; + if (!existsSync(join(utils.getPath("themes"), "gh_" + apiRepo.id))) { + mkdirSync(join(utils.getPath("themes"), "gh_" + apiRepo.id)); + } + console.log(join(utils.getPath("themes"), "gh_" + apiRepo.id)) + zip.extractEntryTo(entry, join(utils.getPath("themes"), "gh_" + apiRepo.id), false, true); + let commit = await fetch( + `https://api.github.com/repos/${repo}/commits` + ).then((res) => res.json()); + console.debug(`COMMIT SHA: ${commit[0].sha}`); + let theme = JSON.parse( + readFileSync(join(utils.getPath("themes"), "gh_" + apiRepo.id, "theme.json"), "utf8") + ); + theme.id = apiRepo.id + theme.commit = commit[0].sha; + writeFileSync( + join(utils.getPath("themes"), "gh_" + apiRepo.id, "theme.json"), + JSON.stringify(theme, null, 4), + "utf8" + ); } catch (e) { returnVal.success = false; } @@ -635,7 +661,8 @@ export class BrowserWindow { description: themeJson.description || themeDescription, path: themePath, file: theme, - github_repo: themeJson.github_repo || "" + github_repo: themeJson.github_repo || "", + commit: themeJson.commit || "" }); } else { themeObjects.push({ @@ -643,7 +670,8 @@ export class BrowserWindow { description: themeDescription, path: themePath, file: theme, - github_repo: "" + github_repo: "", + commit: "" }); } } @@ -654,6 +682,21 @@ export class BrowserWindow { } }); + ipcMain.handle("open-path", async (event, path) => { + switch(path) { + default: + case "plugins": + shell.openPath(utils.getPath("plugins")); + break; + case "userdata": + shell.openPath(app.getPath("userData")); + break; + case "themes": + shell.openPath(utils.getPath("themes")); + break; + } + }); + ipcMain.on("get-i18n", (event, key) => { event.returnValue = utils.getLocale(key); }); @@ -810,7 +853,7 @@ export class BrowserWindow { }) //Fullscreen ipcMain.on('detachDT', (_event, _) => { - BrowserWindow.win.webContents.openDevTools({ mode: 'detach' }); + BrowserWindow.win.webContents.openDevTools({mode: 'detach'}); }) @@ -968,8 +1011,8 @@ export class BrowserWindow { console.log('sc', SoundCheckTag) BrowserWindow.win.webContents.send('SoundCheckTag', SoundCheckTag) }).catch(err => { - console.log(err) - }); + console.log(err) + }); }); ipcMain.on('check-for-update', async (_event) => { @@ -999,7 +1042,7 @@ export class BrowserWindow { }) ipcMain.on('get-version', (_event) => { - if (app.isPackaged){ + if (app.isPackaged) { _event.returnValue = app.getVersion() } else { _event.returnValue = `Experimental running on Electron ${app.getVersion()}` @@ -1068,10 +1111,10 @@ export class BrowserWindow { // Set window Handler BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => { if (x.url.includes("apple") || x.url.includes("localhost")) { - return { action: "allow" }; + return {action: "allow"}; } shell.openExternal(x.url).catch(console.error); - return { action: "deny" }; + return {action: "deny"}; }); } @@ -1120,7 +1163,7 @@ export class BrowserWindow { "CtlN": "Cider", "iV": "196623" }; - let server2 = mdns.createAdvertisement(x, `${await getPort({ port: 3839 })}`, { + let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, { name: encoded, txt: txt_record }); diff --git a/src/main/base/castcontroller.js b/src/main/base/castcontroller.js new file mode 100644 index 00000000..d6f3319d --- /dev/null +++ b/src/main/base/castcontroller.js @@ -0,0 +1,24 @@ +var util = require('util'); +var castv2Cli = require('castv2-client'); +var RequestResponseController = castv2Cli.RequestResponseController; + +function CiderCastController(client, sourceId, destinationId) { + RequestResponseController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.ciderapp.customdata'); + this.once('close', onclose); + var self = this; + function onclose() { + self.stop(); + } +} + +util.inherits(CiderCastController, RequestResponseController); + +CiderCastController.prototype.sendIp = function(ip) { + // TODO: Implement Callback + let data = { + ip : ip + } + this.request(data); +}; + +module.exports = CiderCastController; \ No newline at end of file diff --git a/src/main/base/castreceiver.js b/src/main/base/castreceiver.js new file mode 100644 index 00000000..134699c0 --- /dev/null +++ b/src/main/base/castreceiver.js @@ -0,0 +1,77 @@ +//@ts-nocheck +var util = require('util'); +// var debug = require('debug')('castv2-client'); +var Application = require('castv2-client').Application; +var MediaController = require('castv2-client').MediaController; +var CiderCastController = require('./castcontroller'); + +function CiderReceiver(client, session) { + Application.apply(this, arguments); + + this.media = this.createController(MediaController); + this.mediaReceiver = this.createController(CiderCastController); + + this.media.on('status', onstatus); + + var self = this; + + function onstatus(status) { + self.emit('status', status); + } + +} +// FE96A351 +// 27E1334F +CiderReceiver.APP_ID = 'FE96A351'; + +util.inherits(CiderReceiver, Application); + +CiderReceiver.prototype.getStatus = function(callback) { + this.media.getStatus.apply(this.media, arguments); +}; + +CiderReceiver.prototype.load = function(media, options, callback) { + this.media.load.apply(this.media, arguments); +}; + +CiderReceiver.prototype.play = function(callback) { + this.media.play.apply(this.media, arguments); +}; + +CiderReceiver.prototype.pause = function(callback) { + this.media.pause.apply(this.media, arguments); +}; + +CiderReceiver.prototype.stop = function(callback) { + this.media.stop.apply(this.media, arguments); +}; + +CiderReceiver.prototype.seek = function(currentTime, callback) { + this.media.seek.apply(this.media, arguments); +}; + +CiderReceiver.prototype.queueLoad = function(items, options, callback) { + this.media.queueLoad.apply(this.media, arguments); +}; + +CiderReceiver.prototype.queueInsert = function(items, options, callback) { + this.media.queueInsert.apply(this.media, arguments); +}; + +CiderReceiver.prototype.queueRemove = function(itemIds, options, callback) { + this.media.queueRemove.apply(this.media, arguments); +}; + +CiderReceiver.prototype.queueReorder = function(itemIds, options, callback) { + this.media.queueReorder.apply(this.media, arguments); +}; + +CiderReceiver.prototype.queueUpdate = function(items, callback) { + this.media.queueUpdate.apply(this.media, arguments); +}; + +CiderReceiver.prototype.sendIp = function(opts){ + this.mediaReceiver.sendIp.apply(this.mediaReceiver, arguments); +}; + +module.exports = CiderReceiver; diff --git a/src/main/base/store.ts b/src/main/base/store.ts index e733c8d6..17c1c4c2 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -15,6 +15,7 @@ export class Store { "update_branch": "main", "resumeOnStartupBehavior": "local", "privateEnabled": false, + "themeUpdateNotification": true }, "home": { "followedArtists": [], @@ -96,7 +97,10 @@ export class Store { "bg_artwork_rotation": false, "hw_acceleration": "default", // default, webgpu, disabled "showuserinfo": true, - "miniplayer_top_toggle": true + "miniplayer_top_toggle": true, + "directives": { + "windowLayout": "default" + } }, "lyrics": { "enable_mxm": false, diff --git a/src/main/base/utils.ts b/src/main/base/utils.ts index 25216580..98dc3a65 100644 --- a/src/main/base/utils.ts +++ b/src/main/base/utils.ts @@ -158,6 +158,6 @@ export class utils { log.transports.file.level = "debug" autoUpdater.logger = log - await autoUpdater.checkForUpdate() + await autoUpdater.checkForUpdatesAndNotify() } -} \ No newline at end of file +} diff --git a/src/main/plugins/chromecast.ts b/src/main/plugins/chromecast.ts index e99b6017..34e48c48 100644 --- a/src/main/plugins/chromecast.ts +++ b/src/main/plugins/chromecast.ts @@ -1,6 +1,7 @@ import * as electron from 'electron'; import * as os from 'os'; import {resolve} from 'path'; +import * as CiderReceiver from '../base/castreceiver'; export default class ChromecastPlugin { @@ -133,9 +134,8 @@ export default class ChromecastPlugin { private loadMedia(client: any, song: any, artist: any, album: any, albumart: any, cb?: any) { // const u = 'http://' + this.getIp() + ':' + server.address().port + '/'; - const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver; - - client.launch(DefaultMediaReceiver, (err: any, player: any) => { + // const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver; + client.launch(CiderReceiver, (err: any, player: any) => { if (err) { console.log(err); return; @@ -178,6 +178,10 @@ export default class ChromecastPlugin { client.stepInterval = status.volume.stepInterval; } }) + + // send websocket ip + + player.sendIp("ws://"+this.getIp()+":26369"); }); } @@ -273,6 +277,7 @@ export default class ChromecastPlugin { return '' } + /** * Base Plugin Details (Eventually implemented into a GUI in settings) */ diff --git a/src/main/plugins/mpris.ts b/src/main/plugins/mpris.ts index 31504a09..a29b3524 100644 --- a/src/main/plugins/mpris.ts +++ b/src/main/plugins/mpris.ts @@ -174,7 +174,11 @@ export default class MPRIS { */ onBeforeQuit(): void { console.debug(`[Plugin][${this.name}] Stopped.`); - this.clearState() + try { + this.clearState() + }catch(e) { + console.error(e) + } } /** diff --git a/src/renderer/assets/cider-round.svg b/src/renderer/assets/cider-round.svg new file mode 100644 index 00000000..f27dc45d --- /dev/null +++ b/src/renderer/assets/cider-round.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/renderer/audio/audio.js b/src/renderer/audio/audio.js index e5b0de5d..bb604e45 100644 --- a/src/renderer/audio/audio.js +++ b/src/renderer/audio/audio.js @@ -171,7 +171,7 @@ var CiderAudio = { constructor() { super(); - this._bufferSize = 4096; + this._bufferSize = 1024; this._buffers = null; this._initBuffer(); } diff --git a/src/renderer/index.js b/src/renderer/index.js index e0057fb7..e8e8d57d 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -136,7 +136,7 @@ const app = new Vue({ sorting: "name", sortOrder: "asc", listing: [], - meta: {total: 0, progress: 0}, + meta: { total: 0, progress: 0 }, search: "", displayListing: [], downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library @@ -152,7 +152,7 @@ const app = new Vue({ sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page listing: [], - meta: {total: 0, progress: 0}, + meta: { total: 0, progress: 0 }, search: "", displayListing: [], downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library @@ -168,7 +168,7 @@ const app = new Vue({ sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page listing: [], - meta: {total: 0, progress: 0}, + meta: { total: 0, progress: 0 }, search: "", displayListing: [], downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library @@ -187,6 +187,7 @@ const app = new Vue({ playerReady: false, animateBackground: false, currentArtUrl: '', + currentArtUrlRaw: '', lyricon: false, currentTrackID: '', currentTrackIDBG: '', @@ -222,7 +223,7 @@ const app = new Vue({ "attributes": { "name": "Cider User", "handle": "CiderUser", - "artwork": {"url": "./assets/logocut.png"} + "artwork": { "url": "./assets/logocut.png" } } }, menuOpened: false, @@ -302,7 +303,7 @@ const app = new Vue({ }, methods: { songLinkShare(amUrl) { - notyf.open({type: "info", message: app.getLz('term.song.link.generate')}) + notyf.open({ type: "info", className: "notyf-info", message: app.getLz('term.song.link.generate') }) let self = this httpRequest = new XMLHttpRequest(); httpRequest.open('GET', `https://api.song.link/v1-alpha.1/links?url=${amUrl}&userCountry=US`, true); @@ -397,7 +398,7 @@ const app = new Vue({ }, async showSocialListeningTo() { let contentIds = Object.keys(app.socialBadges.badgeMap) - app.showCollection({data: this.socialBadges.mediaItems}, "Friends Listening To", "albums") + app.showCollection({ data: this.socialBadges.mediaItems }, "Friends Listening To", "albums") if (this.socialBadges.mediaItemDLState == 1 || this.socialBadges.mediaItemDLState == 2) { return } @@ -526,7 +527,7 @@ const app = new Vue({ self.selectedMediaItems[i].kind = "albums" let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { - return {id: i.id, type: i.type} + return { id: i.id, type: i.type } }) pl_items = pl_items.concat(ids) } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { @@ -539,7 +540,7 @@ const app = new Vue({ self.selectedMediaItems[i].kind = "library-albums" let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { - return {id: i.id, type: i.type} + return { id: i.id, type: i.type } }) pl_items = pl_items.concat(ids) } else { @@ -566,7 +567,7 @@ const app = new Vue({ self.selectedMediaItems[i].kind = "albums" let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { - return {id: i.id, type: i.type} + return { id: i.id, type: i.type } }) pl_items = pl_items.concat(ids) } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { @@ -579,7 +580,7 @@ const app = new Vue({ self.selectedMediaItems[i].kind = "library-albums" let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { - return {id: i.id, type: i.type} + return { id: i.id, type: i.type } }) pl_items = pl_items.concat(ids) } else { @@ -593,13 +594,13 @@ const app = new Vue({ this.modals.addToPlaylist = false await app.mk.api.v3.music( `/v1/me/library/playlists/${playlist_id}/tracks`, {}, { - fetchOptions: { - method: "POST", - body: JSON.stringify({ - data: pl_items - }) - } + fetchOptions: { + method: "POST", + body: JSON.stringify({ + data: pl_items + }) } + } ).then(() => { if (this.page == 'playlist_' + this.showingPlaylist.id) { this.getPlaylistFromID(this.showingPlaylist.id, true) @@ -654,7 +655,7 @@ const app = new Vue({ "attributes": { "name": "Cider User", "handle": "CiderUser", - "artwork": {"url": "./assets/logocut.png"} + "artwork": { "url": "./assets/logocut.png" } } } } @@ -710,7 +711,7 @@ const app = new Vue({ let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; app.mk.setQueue({ [truekind]: [lastItem.attributes.playParams.id], - parameters: {l: app.mklang} + parameters: { l: app.mklang } }) app.mk.mute() setTimeout(() => { @@ -729,7 +730,7 @@ const app = new Vue({ for (id of ids) { if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) { try { - app.mk.playLater({songs: [id]}) + app.mk.playLater({ songs: [id] }) } catch (err) { } } @@ -751,14 +752,14 @@ const app = new Vue({ } break; case "history": - let history = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, {l: app.mklang}) + let history = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, { l: app.mklang }) if (history.data.data.length > 0) { let lastItem = history.data.data[0] let kind = lastItem.attributes.playParams.kind; let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; app.mk.setQueue({ [truekind]: [lastItem.attributes.playParams.id], - parameters: {l: app.mklang} + parameters: { l: app.mklang } }) app.mk.mute() setTimeout(() => { @@ -790,12 +791,12 @@ const app = new Vue({ //CiderAudio.audioNodes.gainNode.gain.value = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak))) CiderAudio.audioNodes.gainNode.gain.value = replaygain.gain } catch (e) { - } + } }) ipcRenderer.on('play', function (_event, mode, id) { if (mode !== 'url') { - self.mk.setQueue({[mode]: id, parameters: {l: self.mklang}}).then(() => { + self.mk.setQueue({ [mode]: id, parameters: { l: self.mklang } }).then(() => { app.mk.play() }) @@ -903,6 +904,29 @@ const app = new Vue({ this.$forceUpdate() }, 500) ipcRenderer.invoke("renderer-ready", true) + document.querySelector("#LOADER").remove() + if(this.cfg.general.themeUpdateNotification) { + this.checkForThemeUpdates() + } + }, + async checkForThemeUpdates() { + let self = this + const themes = ipcRenderer.sendSync("get-themes") + await asyncForEach(themes, async (theme) => { + if (theme.commit != "") { + await fetch(`https://api.github.com/repos/${theme.github_repo}/commits`) + .then(res => res.json()) + .then(res => { + if (res[0].sha != theme.commit) { + const notify = notyf.open({ className: "notyf-info", type: "info", message: `[Themes] ${theme.name} has an update available.` }) + notify.on("click", ()=>{ + app.appRoute("themes-github") + notyf.dismiss(notify) + }) + } + }) + } + }) }, async setTheme(theme = "", onlyPrefs = false) { console.log(theme) @@ -919,14 +943,14 @@ const app = new Vue({ try { const infoResponse = await fetch("themes/" + app.cfg.visual.theme.replace("index.less", "theme.json")) this.chrome.appliedTheme.info = await infoResponse.json() - }catch(e){ - e=null + } catch (e) { + e = null console.warn("failed to get theme.json") this.chrome.appliedTheme.info = {} } - if(!onlyPrefs) { + if (!onlyPrefs) { document.querySelector("#userTheme").href = `themes/${this.cfg.visual.theme}` document.querySelectorAll(`[id*='less']`).forEach(el => { el.remove() @@ -935,12 +959,15 @@ const app = new Vue({ } }, getThemeDirective(directive = "") { - if(typeof this.chrome.appliedTheme.info.directives != "object") { - return "" + let directives = {} + if (typeof this.chrome.appliedTheme.info.directives == "object") { + directives = this.chrome.appliedTheme.info.directives } - if(this.chrome.appliedTheme.info.directives[directive]) { + if (directives[directive]) { return this.chrome.appliedTheme.info.directives[directive].value - } else { + } else if(this.cfg.visual.directives[directive]) { + return this.cfg.visual.directives.windowLayout + }else{ return "" } }, @@ -961,7 +988,7 @@ const app = new Vue({ classes.simplebg = true } - if(this.getThemeDirective('windowLayout') == 'twopanel') { + if (this.getThemeDirective('windowLayout') == 'twopanel') { classes.twopanel = true } return classes @@ -1043,12 +1070,12 @@ const app = new Vue({ this.newPlaylist() } }, - { - name: app.getLz('term.createNewPlaylistFolder'), - action: () => { - this.newPlaylistFolder() - } + { + name: app.getLz('term.createNewPlaylistFolder'), + action: () => { + this.newPlaylistFolder() } + } ] } this.showMenuPanel(menu, event) @@ -1057,13 +1084,13 @@ const app = new Vue({ let self = this this.mk.api.v3.music( `/v1/me/library/playlist-folders/${id}`, {}, { - fetchOptions: { - method: "PATCH", - body: JSON.stringify({ - attributes: {name: name} - }) - } + fetchOptions: { + method: "PATCH", + body: JSON.stringify({ + attributes: { name: name } + }) } + } ).then(res => { self.refreshPlaylists() }) @@ -1072,13 +1099,13 @@ const app = new Vue({ let self = this this.mk.api.v3.music( `/v1/me/library/playlists/${id}`, {}, { - fetchOptions: { - method: "PATCH", - body: JSON.stringify({ - attributes: {name: name} - }) - } + fetchOptions: { + method: "PATCH", + body: JSON.stringify({ + attributes: { name: name } + }) } + } ).then(res => { self.refreshPlaylists() }) @@ -1103,9 +1130,9 @@ const app = new Vue({ fetchOptions: { method: "POST", body: JSON.stringify({ - "attributes": {"name": name}, + "attributes": { "name": name }, "relationships": { - "tracks": {"data": tracks}, + "tracks": { "data": tracks }, } }) } @@ -1155,7 +1182,7 @@ const app = new Vue({ app.appRoute("collection-list") }, async showArtistView(artist, title, view) { - let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}?l=${this.mklang}`, {}, {includeResponseMeta: !0})).data + let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}?l=${this.mklang}`, {}, { includeResponseMeta: !0 })).data console.log(response) await this.showCollection(response, title, "artists") }, @@ -1273,7 +1300,7 @@ const app = new Vue({ "limit[artists:top-songs]": 20, "art[url]": "f", l: this.mklang - }, {includeResponseMeta: !0}) + }, { includeResponseMeta: !0 }) console.log(artistData.data.data[0]) this.artistPage.data = artistData.data.data[0] this.page = "artist-page" @@ -1371,7 +1398,7 @@ const app = new Vue({ if (format === 'long') { const longFormat = [] - + // Seconds if (datetime.getSeconds() !== 0) { longFormat.push(`${datetime.getSeconds()} ${app.getLz('term.time.seconds')}`) @@ -1379,17 +1406,17 @@ const app = new Vue({ // Minutes if (time >= 60) { - longFormat.push(`${datetime.getMinutes()} ${app.getLz('term.time.minute', options = {count: datetime.getMinutes()})}`) + longFormat.push(`${datetime.getMinutes()} ${app.getLz('term.time.minute', options = { count: datetime.getMinutes() })}`) } // Hours if (time >= 3600) { - longFormat.push(`${datetime.getHours()} ${app.getLz('term.time.hour', options = {count: datetime.getHours()})}`) + longFormat.push(`${datetime.getHours()} ${app.getLz('term.time.hour', options = { count: datetime.getHours() })}`) } // Days if (time >= 86400) { - longFormat.push(`${day} ${app.getLz('term.time.day', options = {count: day})}`) + longFormat.push(`${day} ${app.getLz('term.time.day', options = { count: day })}`) } returnTime = longFormat.reverse().join(', ') } @@ -1431,7 +1458,7 @@ const app = new Vue({ kind: page, id: id, attributes: { - playParams: {kind: page, id: id, isLibrary: isLibrary} + playParams: { kind: page, id: id, isLibrary: isLibrary } } }) }, @@ -1536,7 +1563,7 @@ const app = new Vue({ let u = await app.mkapi(app.mk.nowPlayingItem.playParams.kind, (app.mk.nowPlayingItem.songId == -1), (app.mk.nowPlayingItem.songId != -1) ? app.mk.nowPlayingItem.songId : app.mk.nowPlayingItem["id"], - {"include[songs]": "albums,artists", l: app.mklang}); + { "include[songs]": "albums,artists", l: app.mklang }); app.searchAndNavigate(u.data.data[0], target) } catch (e) { app.searchAndNavigate(app.mk.nowPlayingItem, target) @@ -1640,7 +1667,7 @@ const app = new Vue({ } if (labelId != "") { app.showingPlaylist = [] - await app.getTypeFromID("recordLabel", labelId, false, {views: 'top-releases,latest-releases,top-artists'}); + await app.getTypeFromID("recordLabel", labelId, false, { views: 'top-releases,latest-releases,top-artists' }); app.page = "recordLabel_" + labelId; } @@ -1678,75 +1705,75 @@ const app = new Vue({ console.log(kind, id, isLibrary) app.mk.stop().then(() => { if (kind.includes("artist")) { - app.mk.setStationQueue({artist: 'a-' + id}).then(() => { + app.mk.setStationQueue({ artist: 'a-' + id }).then(() => { app.mk.play() }) } - // else if (kind.includes("playlist") && (id.startsWith("p.") || id.startsWith("pl."))){ - // /* Randomize array in-place using Durstenfeld shuffle algorithm */ - // function shuffleArray(array) { - // for (var i = array.length - 1; i > 0; i--) { - // var j = Math.floor(Math.random() * (i + 1)); - // var temp = array[i]; - // array[i] = array[j]; - // array[j] = temp; - // } - // } - // app.mk.clearQueue().then(function () { { - // app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () { - // app.mk.play().then(function (){ - // app.mk.clearQueue().then(function (){ - // var playlistId = id - // const params = { - // include: "tracks", - // platform: "web", - // "include[library-playlists]": "catalog,tracks", - // "fields[playlists]": "curatorName,playlistType,name,artwork,url", - // "include[library-songs]": "catalog,artists,albums", - // "fields[catalog]": "artistUrl,albumUrl", - // "fields[songs]": "artistUrl,albumUrl" - // } - // var playlistId = '' + // else if (kind.includes("playlist") && (id.startsWith("p.") || id.startsWith("pl."))){ + // /* Randomize array in-place using Durstenfeld shuffle algorithm */ + // function shuffleArray(array) { + // for (var i = array.length - 1; i > 0; i--) { + // var j = Math.floor(Math.random() * (i + 1)); + // var temp = array[i]; + // array[i] = array[j]; + // array[j] = temp; + // } + // } + // app.mk.clearQueue().then(function () { { + // app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () { + // app.mk.play().then(function (){ + // app.mk.clearQueue().then(function (){ + // var playlistId = id + // const params = { + // include: "tracks", + // platform: "web", + // "include[library-playlists]": "catalog,tracks", + // "fields[playlists]": "curatorName,playlistType,name,artwork,url", + // "include[library-songs]": "catalog,artists,albums", + // "fields[catalog]": "artistUrl,albumUrl", + // "fields[songs]": "artistUrl,albumUrl" + // } + // var playlistId = '' - // try { - // function getPlaylist(id, params, isLibrary){ - // if (isLibrary){ - // return app.mk.api.library.playlist(id, params) - // } else { return app.mk.api.playlist(id, params)} - // } - // getPlaylist(id, params, isLibrary).then(res => { - // let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); - // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} - // app.mk.queue.append(query) - // if (!res.relationships.tracks.next) { - // return - // } else { - // getPlaylistTracks(res.relationships.tracks.next) - // } + // try { + // function getPlaylist(id, params, isLibrary){ + // if (isLibrary){ + // return app.mk.api.library.playlist(id, params) + // } else { return app.mk.api.playlist(id, params)} + // } + // getPlaylist(id, params, isLibrary).then(res => { + // let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); + // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} + // app.mk.queue.append(query) + // if (!res.relationships.tracks.next) { + // return + // } else { + // getPlaylistTracks(res.relationships.tracks.next) + // } - // function getPlaylistTracks(next) { - // app.apiCall(app.musicBaseUrl + next, res => { - // if (res.id != playlistId) { - // return - // } - // let query = res.data.map(item => new MusicKit.MediaItem(item)) - // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} - // app.mk.queue.append(query) + // function getPlaylistTracks(next) { + // app.apiCall(app.musicBaseUrl + next, res => { + // if (res.id != playlistId) { + // return + // } + // let query = res.data.map(item => new MusicKit.MediaItem(item)) + // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} + // app.mk.queue.append(query) - // if (res.next) { - // getPlaylistTracks(res.next) - // } - // }) - // } - // }) - // } catch (e) {} + // if (res.next) { + // getPlaylistTracks(res.next) + // } + // }) + // } + // }) + // } catch (e) {} - // }) - // }) - // }) - // } - // }) + // }) + // }) + // }) + // } + // }) // } else { app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '') @@ -1788,7 +1815,7 @@ const app = new Vue({ let self = this let prefs = this.cfg.libraryPrefs.songs let albumAdded = self.library?.albums?.listing?.map(function (i) { - return {[i.id]: i.attributes?.dateAdded} + return { [i.id]: i.attributes?.dateAdded } }) let startTime = new Date().getTime() @@ -1859,9 +1886,9 @@ const app = new Vue({ sortSongs() } }, - getAlbumSort() { - this.library.albums.sortOrder[1] = this.cfg.libraryPrefs.albums.sortOrder; - this.library.albums.sorting[1] = this.cfg.libraryPrefs.albums.sort; + getAlbumSort() { + this.library.albums.sortOrder[1] = this.cfg.libraryPrefs.albums.sortOrder; + this.library.albums.sorting[1] = this.cfg.libraryPrefs.albums.sort; }, // make a copy of searchLibrarySongs except use Albums instead of Songs searchLibraryAlbums(index) { @@ -2233,7 +2260,7 @@ const app = new Vue({ } if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { console.log(`downloading next chunk - ${library.length - } albums so far`) + } albums so far`) downloadChunk() } else { self.library.albums.listing = library @@ -2340,7 +2367,7 @@ const app = new Vue({ } if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { console.log(`downloading next chunk - ${library.length - } artists so far`) + } artists so far`) downloadChunk() } else { self.library.artists.listing = library @@ -2357,20 +2384,20 @@ const app = new Vue({ getTotalTime() { try { if (app.showingPlaylist.relationships.tracks.data.length > 0) { - const timeInSeconds = Math.round([].concat(...app.showingPlaylist.relationships.tracks.data).reduce((a, {attributes: {durationInMillis}}) => a + durationInMillis, 0) / 1000); - return `${app.showingPlaylist.relationships.tracks.data.length} ${app.getLz('term.track', options = {count: app.showingPlaylist.relationships.tracks.data.length})}, ${this.convertTime(timeInSeconds, 'long')}` + const timeInSeconds = Math.round([].concat(...app.showingPlaylist.relationships.tracks.data).reduce((a, { attributes: { durationInMillis } }) => a + durationInMillis, 0) / 1000); + return `${app.showingPlaylist.relationships.tracks.data.length} ${app.getLz('term.track', options = { count: app.showingPlaylist.relationships.tracks.data.length })}, ${this.convertTime(timeInSeconds, 'long')}` } else return "" } catch (err) { return "" } }, async getLibrarySongs() { - let response = await this.mkapi("songs", true, "", {limit: 100, l: this.mklang}, {includeResponseMeta: !0}) + let response = await this.mkapi("songs", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }) this.library.songs.listing = response.data.data this.library.songs.meta = response.data.meta }, async getLibraryAlbums() { - let response = await this.mkapi("albums", true, "", {limit: 100, l: this.mklang}, {includeResponseMeta: !0}) + let response = await this.mkapi("albums", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }) this.library.albums.listing = response.data.data this.library.albums.meta = response.data.meta }, @@ -2476,13 +2503,13 @@ const app = new Vue({ let self = this this.mk.api.v3.music( "/v1/me/library/playlist-folders/", {}, { - fetchOptions: { - method: "POST", - body: JSON.stringify({ - attributes: {name: name} - }) - } + fetchOptions: { + method: "POST", + body: JSON.stringify({ + attributes: { name: name } + }) } + } ).then((res) => { let playlist = (res.data.data[0]) self.playlists.listing.push({ @@ -2841,15 +2868,15 @@ const app = new Vue({ line: "lrcInstrumental" }); } - preLrc.push({startTime: start, endTime: end, line: element.textContent}); + preLrc.push({ startTime: start, endTime: end, line: element.textContent }); endTimes.push(end); } // first line dot if (preLrc.length > 0) - preLrc.unshift({startTime: 0, endTime: preLrc[0].startTime, line: "lrcInstrumental"}); + preLrc.unshift({ startTime: 0, endTime: preLrc[0].startTime, line: "lrcInstrumental" }); } else { for (element of lyricsLines) { - preLrc.push({startTime: 9999999, endTime: 9999999, line: element.textContent}); + preLrc.push({ startTime: 9999999, endTime: 9999999, line: element.textContent }); } } this.lyrics = preLrc; @@ -2917,17 +2944,17 @@ const app = new Vue({ console.log(id, truekind, isLibrary) try { if (truekind.includes("artist")) { - app.mk.setStationQueue({artist: 'a-' + id}).then(() => { + app.mk.setStationQueue({ artist: 'a-' + id }).then(() => { app.mk.play() }) } else if (truekind == "radioStations") { - this.mk.setStationQueue({url: raurl}).then(function (queue) { + this.mk.setStationQueue({ url: raurl }).then(function (queue) { MusicKit.getInstance().play() }); } else { this.mk.setQueue({ [truekind]: [id], - parameters: {l: this.mklang} + parameters: { l: this.mklang } }).then(function (queue) { MusicKit.getInstance().play() }) @@ -2968,7 +2995,7 @@ const app = new Vue({ if (item) { app.mk.setQueue({ [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, - parameters: {l: app.mklang} + parameters: { l: app.mklang } }).then(function () { app.mk.play().then(() => { if (app.mk.shuffleMode == 1) { @@ -3025,7 +3052,7 @@ const app = new Vue({ for (let kind in itemsToPlay) { let ids = itemsToPlay[kind] if (ids.length > 0) { - app.mk.playLater({[kind + "s"]: itemsToPlay[kind]}) + app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }) } } }) @@ -3046,22 +3073,22 @@ const app = new Vue({ let ids = itemsToPlay[kind] if (ids.length > 0) { if (app.mk.queue._itemIDs.length > 0) { - app.mk.playLater({[kind + "s"]: itemsToPlay[kind]}).then(function () { - ind += 1; - console.log(ind, Object.keys(itemsToPlay).length) - if (ind >= Object.keys(itemsToPlay).length) { - app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) - } + app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }).then(function () { + ind += 1; + console.log(ind, Object.keys(itemsToPlay).length) + if (ind >= Object.keys(itemsToPlay).length) { + app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) } + } ) } else { - app.mk.setQueue({[kind + "s"]: itemsToPlay[kind]}).then(function () { - ind += 1; - console.log(ind, Object.keys(itemsToPlay).length) - if (ind >= Object.keys(itemsToPlay).length) { - app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) - } + app.mk.setQueue({ [kind + "s"]: itemsToPlay[kind] }).then(function () { + ind += 1; + console.log(ind, Object.keys(itemsToPlay).length) + if (ind >= Object.keys(itemsToPlay).length) { + app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) } + } ) } } @@ -3074,7 +3101,7 @@ const app = new Vue({ if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) { app.mk.setQueue({ [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, - parameters: {l: app.mklang} + parameters: { l: app.mklang } }).then(function () { app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function () { if ((app.showingPlaylist && app.showingPlaylist.id == id)) { @@ -3114,7 +3141,7 @@ const app = new Vue({ } else { this.mk.setQueue({ [truekind]: [id], - parameters: {l: this.mklang} + parameters: { l: this.mklang } }).then(function (queue) { if (item && ((queue._itemIDs[childIndex] != item.id))) { childIndex = queue._itemIDs.indexOf(item.id) @@ -3226,7 +3253,7 @@ const app = new Vue({ with: ["serverBubbles", "lyricSnippet"], "art[url]": "f", "art[social-profiles:url]": "c" - }, {includeResponseMeta: !0}).then(function (results) { + }, { includeResponseMeta: !0 }).then(function (results) { results.data.results["meta"] = results.data.meta self.search.resultsSocial = results.data.results }) @@ -3246,7 +3273,7 @@ const app = new Vue({ return type.type == this }, type) if (index == -1) { - types.push({type: type, id: [id]}) + types.push({ type: type, id: [id] }) } else { types[index].id.push(id) } @@ -3367,9 +3394,17 @@ const app = new Vue({ }, async getCurrentArtURL() { try { + let artworkSize = 50 + if (app.getThemeDirective("lcdArtworkSize") != "") { + artworkSize = app.getThemeDirective("lcdArtworkSize") + }else if(this.cfg.visual.directives.windowLayout == "twopanel") { + artworkSize = 70 + } this.currentArtUrl = ''; + this.currentArtUrlRaw = ''; if (app.mk.nowPlayingItem != null && app.mk.nowPlayingItem.attributes != null && app.mk.nowPlayingItem.attributes.artwork != null && app.mk.nowPlayingItem.attributes.artwork.url != null && app.mk.nowPlayingItem.attributes.artwork.url != '') { - this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50); + this.currentArtUrlRaw = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '') + this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize); try { document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); } catch (e) { @@ -3378,13 +3413,15 @@ const app = new Vue({ let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`); data = data.data.data[0]; if (data != null && data !== "" && data.attributes != null && data.attributes.artwork != null) { - this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', 50).replace('{h}', 50); + this.currentArtUrlRaw = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '') + this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize); ipcRenderer.send('updateRPCImage', this.currentArtUrl ?? ''); try { document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); } catch (e) { } } else { + this.currentArtUrlRaw = '' this.currentArtUrl = ''; try { document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); @@ -3430,10 +3467,10 @@ const app = new Vue({ }, quickPlay(query) { let self = this - MusicKit.getInstance().api.search(query, {limit: 2, types: 'songs'}).then(function (data) { + MusicKit.getInstance().api.search(query, { limit: 2, types: 'songs' }).then(function (data) { MusicKit.getInstance().setQueue({ song: data["songs"]['data'][0]["id"], - parameters: {l: app.mklang} + parameters: { l: app.mklang } }).then(function (queue) { MusicKit.getInstance().play() setTimeout(() => { @@ -3611,7 +3648,7 @@ const app = new Vue({ let data_type = this.mk.nowPlayingItem.playParams.kind let item_id = this.mk.nowPlayingItem.attributes.playParams.id ?? this.mk.nowPlayingItem.id let isLibrary = this.mk.nowPlayingItem.attributes.playParams.isLibrary ?? false - let params = {"fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "t": "1"} + let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "t": "1" } app.selectedMediaItems = [] app.select_selectMediaItem(item_id, data_type, 0, '12344', isLibrary) let useMenu = "normal" @@ -3630,36 +3667,36 @@ const app = new Vue({ app.love(app.mk.nowPlayingItem) } }, - { - "icon": "./assets/feather/heart.svg", - "id": "unlove", - "active": true, - "name": app.getLz('action.unlove'), - "hidden": true, - "action": function () { - app.unlove(app.mk.nowPlayingItem) - } - }, - { - "icon": "./assets/feather/thumbs-down.svg", - "id": "dislike", - "name": app.getLz('action.dislike'), - "hidden": false, - "disabled": true, - "action": function () { - app.dislike(app.mk.nowPlayingItem) - } - }, - { - "icon": "./assets/feather/thumbs-down.svg", - "id": "undo_dislike", - "name": app.getLz('action.undoDislike'), - "active": true, - "hidden": true, - "action": function () { - app.unlove(app.mk.nowPlayingItem) - } - }, + { + "icon": "./assets/feather/heart.svg", + "id": "unlove", + "active": true, + "name": app.getLz('action.unlove'), + "hidden": true, + "action": function () { + app.unlove(app.mk.nowPlayingItem) + } + }, + { + "icon": "./assets/feather/thumbs-down.svg", + "id": "dislike", + "name": app.getLz('action.dislike'), + "hidden": false, + "disabled": true, + "action": function () { + app.dislike(app.mk.nowPlayingItem) + } + }, + { + "icon": "./assets/feather/thumbs-down.svg", + "id": "undo_dislike", + "name": app.getLz('action.undoDislike'), + "active": true, + "hidden": true, + "action": function () { + app.unlove(app.mk.nowPlayingItem) + } + }, ], items: [ { @@ -3691,7 +3728,7 @@ const app = new Vue({ "icon": "./assets/feather/radio.svg", "name": app.getLz('action.startRadio'), "action": function () { - app.mk.setStationQueue({song: app.mk.nowPlayingItem.id}).then(() => { + app.mk.setStationQueue({ song: app.mk.nowPlayingItem.id }).then(() => { app.mk.play() app.selectedMediaItems = [] }) @@ -4008,7 +4045,7 @@ const app = new Vue({ Vue.component('animated-number', { template: "
{{ displayNumber }}
", - props: {'number': {default: 0}}, + props: { 'number': { default: 0 } }, data() { return { diff --git a/src/renderer/js/velocity.min.js b/src/renderer/js/velocity.min.js new file mode 100644 index 00000000..ab1fce1c --- /dev/null +++ b/src/renderer/js/velocity.min.js @@ -0,0 +1,6 @@ +/** + * velocity-animate (C) 2014-2017 Julian Shapiro. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + */ + !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Velocity=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n=t?function(t,n){for(var r=0;r0?i=a:t=a}while(Math.abs(o)>n&&++l=0)return n;null==e||t||console.error("VelocityJS: Trying to set 'duration' to an invalid value:",e)}function W(e,t,n){if(u(e))return S[e];if(o(e))return e;if(Array.isArray(e)){if(1===e.length)return r=e[0],C[r]||(C[r]=function(e,t,n){return 0===e?t:1===e?n:t+Math.round(e*r)*(1/r)*(n-t)});if(2===e.length)return function e(t,n,r){var i={x:-1,v:0,tension:parseFloat(t)||500,friction:parseFloat(n)||20},o=[0],a=null!=r,l=0,s=void 0,u=void 0;for(s=a?(l=e(i.tension,i.friction))/r*.016:.016;u=j(u||i,s),o.push(1+u.x),l+=16,Math.abs(u.x)>1e-4&&Math.abs(u.v)>1e-4;);return a?function(e,t,n){return 0===e?t:1===e?n:t+o[Math.floor(e*(o.length-1))]*(n-t)}:l}(e[0],e[1],t);if(4===e.length)return q.apply(null,e)||!1}var r;null==e||n||console.error("VelocityJS: Trying to set 'easing' to an invalid value:",e)}function $(e){if(!1===e)return 0;var t=parseInt(e,10);if(!isNaN(t)&&t>=0)return Math.min(t,60);null!=e&&console.warn("VelocityJS: Trying to set 'fpsLimit' to an invalid value:",e)}function G(e){switch(e){case!1:return 0;case!0:return!0;default:var t=parseInt(e,10);if(!isNaN(t)&&t>=0)return t}null!=e&&console.warn("VelocityJS: Trying to set 'loop' to an invalid value:",e)}function Q(e,t){if(!1===e||u(e))return e;null==e||t||console.warn("VelocityJS: Trying to set 'queue' to an invalid value:",e)}function D(e){switch(e){case!1:return 0;case!0:return!0;default:var t=parseInt(e,10);if(!isNaN(t)&&t>=0)return t}null!=e&&console.warn("VelocityJS: Trying to set 'repeat' to an invalid value:",e)}function U(e){if(l(e))return e;null!=e&&console.error("VelocityJS: Trying to set 'speed' to an invalid value:",e)}function Z(e){if(i(e))return e;null!=e&&console.error("VelocityJS: Trying to set 'sync' to an invalid value:",e)}var Y=void 0,X=void 0,K=void 0,ee=void 0,te=void 0,ne=void 0,re=void 0,ie=void 0,oe=void 0,ae=void 0,le=void 0,se=void 0,ue=void 0,ce=void 0,fe=void 0,de=void 0,ve=function(){function e(){t(this,e)}return n(e,null,[{key:"reset",value:function(){Y=!0,X=void 0,K=void 0,ee=0,te=w,ne=W("swing",w),re=60,ie=0,ae=980/60,le=!0,se=!0,ue="",ce=0,fe=1,de=!0}},{key:"cache",get:function(){return Y},set:function(e){void 0!==(e=z(e))&&(Y=e)}},{key:"begin",get:function(){return X},set:function(e){void 0!==(e=F(e))&&(X=e)}},{key:"complete",get:function(){return K},set:function(e){void 0!==(e=H(e))&&(K=e)}},{key:"delay",get:function(){return ee},set:function(e){void 0!==(e=R(e))&&(ee=e)}},{key:"duration",get:function(){return te},set:function(e){void 0!==(e=B(e))&&(te=e)}},{key:"easing",get:function(){return ne},set:function(e){void 0!==(e=W(e,te))&&(ne=e)}},{key:"fpsLimit",get:function(){return re},set:function(e){void 0!==(e=$(e))&&(re=e,ae=980/e)}},{key:"loop",get:function(){return ie},set:function(e){void 0!==(e=G(e))&&(ie=e)}},{key:"mobileHA",get:function(){return oe},set:function(e){i(e)&&(oe=e)}},{key:"minFrameTime",get:function(){return ae}},{key:"promise",get:function(){return le},set:function(e){void 0!==(e=function(e){if(i(e))return e;null!=e&&console.warn("VelocityJS: Trying to set 'promise' to an invalid value:",e)}(e))&&(le=e)}},{key:"promiseRejectEmpty",get:function(){return se},set:function(e){void 0!==(e=function(e){if(i(e))return e;null!=e&&console.warn("VelocityJS: Trying to set 'promiseRejectEmpty' to an invalid value:",e)}(e))&&(se=e)}},{key:"queue",get:function(){return ue},set:function(e){void 0!==(e=Q(e))&&(ue=e)}},{key:"repeat",get:function(){return ce},set:function(e){void 0!==(e=D(e))&&(ce=e)}},{key:"repeatAgain",get:function(){return ce}},{key:"speed",get:function(){return fe},set:function(e){void 0!==(e=U(e))&&(fe=e)}},{key:"sync",get:function(){return de},set:function(e){void 0!==(e=Z(e))&&(de=e)}}]),e}();Object.freeze(ve),ve.reset();var pe=[],ye={},ge=new Set,he=[],me=new Map,we="velocityData";function be(e){var t=e[we];if(t)return t;for(var n=e.ownerDocument.defaultView,r=0,i=0;i=0&&pe[r].hasOwnProperty(n)}function Ne(e,t){for(var n=be(e),r=void 0,i=he.length-1,o=n.types;!r&&i>=0;i--)o&1<=2&&console.info('Set "'+t+'": "'+n+'"',e))}function Le(e){if(e.indexOf("calc(")>=0){for(var t=e.split(/([\(\)])/),n=0,r=0;r=2&&console.info('Get "'+t+'": "'+o+'"',e),o}var De=/^#([A-f\d]{3}){1,2}$/i,Ue={function:function(e,t,n,r,i,o){return e.call(t,r,n.length,i)},number:function(e,t,n,r,i,o){return String(e)+function(e){for(var t in ye)if(ye[t].includes(e))return t;return""}(o.fn)},string:function(e,t,n,r,i,o){return Be(e)},undefined:function(e,t,n,r,i,o){return Be(Qe(t,i,o.fn)||"")}};function Ze(t,n){var r=t.tweens=Object.create(null),i=t.elements,a=t.element,s=i.indexOf(a),c=be(a),f=p(t.queue,t.options.queue),d=p(t.options.duration,ve.duration);for(var v in n)if(n.hasOwnProperty(v)){var y=Ie(v),g=Ne(a,y),h=n[v];if(!g&&"tween"!==y){Ut.debug&&console.log('Skipping "'+v+'" due to a lack of browser support.');continue}if(null==h){Ut.debug&&console.log('Skipping "'+v+'" due to no value supplied.');continue}var m=r[y]={},w=void 0,b=void 0;if(m.fn=g,o(h)&&(h=h.call(a,s,i.length,i)),Array.isArray(h)){var x=h[1],k=h[2];w=h[0],u(x)&&(/^[\d-]/.test(x)||De.test(x))||o(x)||l(x)?b=x:u(x)&&S[x]||Array.isArray(x)?(m.easing=W(x,d),b=k):b=x||k}else w=h;m.end=Ue[void 0===w?"undefined":e(w)](w,a,i,s,y,m),null==b&&!1!==f&&void 0!==c.queueList[f]||(m.start=Ue[void 0===b?"undefined":e(b)](b,a,i,s,y,m),et(y,m,d))}}var Ye=/((?:[+\-*/]=)?(?:[+-]?\d*\.\d+|[+-]?\d+)[a-z%]*|(?:.(?!$|[+-]?\d|[+\-*/]=[+-]?\d))+.|.)/g,Xe=/^([+\-*/]=)?([+-]?\d*\.\d+|[+-]?\d+)(.*)$/;function Ke(e,t){for(var n=e.length,r=[],i=[],o=void 0,a=0;a1}for(var l=[],s=l.pattern=[],c=function(e){if(u(s[s.length-1]))s[s.length-1]+=e;else if(e){s.push(e);for(var t=0;t1)){for(var r="display"===t,i="visibility"===t,a=0;a=0?H++:R.indexOf("rgb")>=0&&(H=1):H&&(H<4?s[F]=!0:H=0)}return l}function et(e,t,n,r){var i=t.start,o=t.end;if(u(o)&&u(i)){var a=Ke([i,o],e);if(!a&&r){var l=i.match(/\d\.?\d*/g)||["0"],s=l.length,c=0;a=Ke([o.replace(/\d+\.?\d*/g,function(){return l[c++%s]}),o],e)}if(a)switch(Ut.debug&&console.log("Velocity: Sequence found:",a),a[0].percent=0,a[1].percent=1,t.sequence=a,t.easing){case S["at-start"]:case S.during:case S["at-end"]:a[0].easing=a[1].easing=t.easing}}}function tt(e){if(ke.firstNew===e&&(ke.firstNew=e._next),!(1&e._flags)){var t=e.element,n=e.tweens;p(e.options.duration,ve.duration);for(var r in n){var i=n[r];if(null==i.start){var o=Qe(e.element,r);u(o)?(i.start=Be(o),et(r,i,0,!0)):Array.isArray(o)||console.warn("bad type",i,r,o)}Ut.debug&&console.log('tweensContainer "'+r+'": '+JSON.stringify(i),t)}e._flags|=1}}function nt(e){var t=e.begin||e.options.begin;if(t)try{var n=e.elements;t.call(n,n,e)}catch(e){setTimeout(function(){throw e},1)}}function rt(e){var t=e.progress||e.options.progress;if(t)try{var n=e.elements,r=e.percentComplete,i=e.options,o=e.tween;t.call(n,n,r,Math.max(0,e.timeStart+(null!=e.duration?e.duration:null!=i.duration?i.duration:ve.duration)-vt),void 0!==o?o:String(100*r),e)}catch(e){setTimeout(function(){throw e},1)}}function it(){var e=!0,t=!1,n=void 0;try{for(var r,i=lt[Symbol.iterator]();!(e=(r=i.next()).done);e=!0){rt(r.value)}}catch(e){t=!0,n=e}finally{try{!e&&i.return&&i.return()}finally{if(t)throw n}}lt.clear();var o=!0,a=!1,l=void 0;try{for(var s,u=at[Symbol.iterator]();!(o=(s=u.next()).done);o=!0){Me(s.value)}}catch(e){a=!0,l=e}finally{try{!o&&u.return&&u.return()}finally{if(a)throw l}}at.clear()}var ot=1e3/60,at=new Set,lt=new Set,st=function(){var e=window.performance||{};if("function"!=typeof e.now){var t=e.timing&&e.timing.navigationStart?e.timing.navigationStart:y();e.now=function(){return y()-t}}return e}(),ut=function(e){return setTimeout(e,Math.max(0,ot-(st.now()-vt)))},ct=window.requestAnimationFrame||ut,ft=void 0,dt=void 0,vt=0;try{(dt=new Worker(URL.createObjectURL(new Blob(["("+function(){var e=this,t=void 0;this.onmessage=function(n){switch(n.data){case!0:t||(t=setInterval(function(){e.postMessage(!0)},1e3/30));break;case!1:t&&(clearInterval(t),t=0);break;default:e.postMessage(n.data)}}}+")()"])))).onmessage=function(e){!0===e.data?pt():it()},ke.isMobile||void 0===document.hidden||document.addEventListener("visibilitychange",function(){dt.postMessage(ke.isTicking&&document.hidden)})}catch(e){}function pt(e){if(!ft){if(ft=!0,!1!==e){var t=st.now(),n=vt?t-vt:ot,r=ve.speed,i=ve.easing,o=ve.duration,a=void 0,l=void 0;if(n>=ve.minFrameTime||!vt){for(vt=t;ke.firstNew;)tt(ke.firstNew);for(a=ke.first;a&&a!==ke.firstNew;a=a._next){var s=a.element,u=be(s);if(s.parentNode&&u){var c=a.options,f=a._flags,d=a.timeStart;if(!d){var v=null!=a.queue?a.queue:c.queue;d=t-n,!1!==v&&(d=Math.max(d,u.lastFinishList[v]||0)),a.timeStart=d}16&f?a.timeStart+=n:2&f||(a._flags|=2,c._ready++)}else _e(a)}for(a=ke.first;a&&a!==ke.firstNew;a=l){var p=a._flags;if(l=a._next,2&p&&!(16&p)){var y=a.options;if(32&p&&y._readyt)continue;a.timeStart=h+=m/(m>0?g:1)}a._flags|=4,0==y._started++&&(y._first=a,y.begin&&(nt(a),y.begin=void 0))}1!==g&&(a.timeStart=h+=Math.min(n,t-h)*(1-g));var w=null!=a.easing?a.easing:null!=y.easing?y.easing:i,b=a.ellapsedTime=t-h,S=null!=a.duration?a.duration:null!=y.duration?y.duration:o,x=a.percentComplete=Ut.mock?1:Math.min(b/S,1),O=a.tweens,E=64&p;for(var _ in(a.progress||y._first===a&&y.progress)&<.add(a),1===x&&at.add(a),O){var T=O[_],M=T.sequence,V=M.pattern,q="",N=0;if(V){for(var A=(T.easing||w)(x,0,1,_),L=0,J=0;J=0?r.replace(/^.*\./,""):void 0)&&Q(e[0]),a=ve.queue;if(c(t)&&t.velocity.animations){var l=!0,s=!1,u=void 0;try{for(var f,d=t.velocity.animations[Symbol.iterator]();!(l=(f=d.next()).done);l=!0){ht(f.value,o,a,i)}}catch(e){s=!0,u=e}finally{try{!l&&d.return&&d.return()}finally{if(s)throw u}}}else for(var v=ke.first;v;)t&&!t.includes(v.element)||ht(v,o,a,i),v=v._next;n&&(c(t)&&t.velocity.animations&&t.then?t.then(n._resolver):n._resolver(t))}function wt(t,n,r,i){var o=t[0],a=t[1];if(!o)return console.warn("VelocityJS: Cannot access a non-existant property!"),null;if(void 0===a&&!s(o)){if(Array.isArray(o)){if(1===n.length){var f={},d=!0,v=!1,p=void 0;try{for(var y,g=o[Symbol.iterator]();!(d=(y=g.next()).done);d=!0){var h=y.value;f[h]=Be(Qe(n[0],h))}}catch(e){v=!0,p=e}finally{try{!d&&g.return&&g.return()}finally{if(v)throw p}}return f}var m=[],w=!0,b=!1,S=void 0;try{for(var x,k=n[Symbol.iterator]();!(w=(x=k.next()).done);w=!0){var O=x.value,E={},_=!0,T=!1,M=void 0;try{for(var V,q=o[Symbol.iterator]();!(_=(V=q.next()).done);_=!0){var N=V.value;E[N]=Be(Qe(O,N))}}catch(e){T=!0,M=e}finally{try{!_&&q.return&&q.return()}finally{if(T)throw M}}m.push(E)}}catch(e){b=!0,S=e}finally{try{!w&&k.return&&k.return()}finally{if(b)throw S}}return m}if(1===n.length)return Be(Qe(n[0],o));var A=[],L=!0,J=!1,I=void 0;try{for(var j,C=n[Symbol.iterator]();!(L=(j=C.next()).done);L=!0){var P=j.value;A.push(Be(Qe(P,o)))}}catch(e){J=!0,I=e}finally{try{!L&&C.return&&C.return()}finally{if(J)throw I}}return A}var z=[];if(s(o)){for(var F in o)if(o.hasOwnProperty(F)){var H=!0,R=!1,B=void 0;try{for(var W,$=n[Symbol.iterator]();!(H=(W=$.next()).done);H=!0){var G=W.value,Q=o[F];u(Q)||l(Q)?Ae(G,F,o[F]):(z.push('Cannot set a property "'+F+'" to an unknown type: '+(void 0===Q?"undefined":e(Q))),console.warn('VelocityJS: Cannot set a property "'+F+'" to an unknown type:',Q))}}catch(e){R=!0,B=e}finally{try{!H&&$.return&&$.return()}finally{if(R)throw B}}}}else if(u(a)||l(a)){var D=!0,U=!1,Z=void 0;try{for(var Y,X=n[Symbol.iterator]();!(D=(Y=X.next()).done);D=!0){Ae(Y.value,o,String(a))}}catch(e){U=!0,Z=e}finally{try{!D&&X.return&&X.return()}finally{if(U)throw Z}}}else z.push('Cannot set a property "'+o+'" to an unknown type: '+(void 0===a?"undefined":e(a))),console.warn('VelocityJS: Cannot set a property "'+o+'" to an unknown type:',a);r&&(z.length?r._rejecter(z.join(", ")):c(n)&&n.velocity.animations&&n.then?n.then(r._resolver):r._resolver(n))}function bt(e,t,n){tt(e),void 0!==t&&t!==p(e.queue,e.options.queue,n)||(e._flags|=8,Me(e))}m(["option",function(e,t,n,r){var i=e[0],o=r.indexOf(".")>=0?r.replace(/^.*\./,""):void 0,a="false"!==o&&Q(o,!0),l=void 0,s=e[1];if(!i)return console.warn("VelocityJS: Cannot access a non-existant key!"),null;if(c(t)&&t.velocity.animations)l=t.velocity.animations;else{l=[];for(var u=ke.first;u;u=u._next)t.indexOf(u.element)>=0&&p(u.queue,u.options.queue)===a&&l.push(u);if(t.length>1&&l.length>1){for(var f=1,d=l[0].options;f, "tween", percentComplete, property, end | [end, , ], ) => value\nVelocity(, "tween", percentComplete, {property: end | [end, , ], ...}, ) => {property: value, ...}'),null;t=[document.body],o=!0}var a=e[0],c={elements:t,element:t[0],queue:!1,options:{duration:1e3},tweens:null},f={},d=e[1],v=void 0,y=void 0,g=e[2],h=0;if(u(e[1])?Te&&Te[e[1]]?(y=Te[e[1]],d={},g=e[2]):(v=!0,d=r({},e[1],e[2]),g=e[3]):Array.isArray(e[1])&&(v=!0,d={tween:e[1]},g=e[2]),!l(a)||a<0||a>1)throw new Error("VelocityJS: Must tween a percentage from 0 to 1!");if(!s(d))throw new Error("VelocityJS: Cannot tween an invalid property!");if(o)for(var m in d)if(d.hasOwnProperty(m)&&(!Array.isArray(d[m])||d[m].length<2))throw new Error("VelocityJS: When not supplying an element you must force-feed values: "+m);var b=W(p(g,ve.easing),w);for(var S in y?tn(c,y):Ze(c,d),c.tweens){var x=c.tweens[S],O=x.sequence,E=O.pattern,_="",T=0;if(h++,E){for(var M=(x.easing||b)(a,0,1,S),V=0,q=0;q4;e--){var t=document.createElement("div");if(t.innerHTML="\x3c!--[if IE "+e+"]>100)console.warn("VelocityJS: Trying to use an invalid value as a percentage (0 <= n <= 100):",r,x);else if(isNaN(x))console.warn("VelocityJS: Trying to use an invalid number as a percentage:",r,v,S);else for(var k in o[String(x)]||(o[String(x)]=[]),o[String(x)].push(v),i[v])c.includes(k)||c.push(k)}}catch(e){g=!0,h=e}finally{try{!y&&b.return&&b.return()}finally{if(g)throw h}}}}var O=Object.keys(o).sort(function(e,t){var n=parseFloat(e),r=parseFloat(t);return n>r?1:n1&&(u(ae[1])||Array.isArray(ae[1]))&&(Q[D].easing=W(ae[1],f.duration||w)),Q[D++].percent=parseFloat(ee)/100)}}catch(e){ne=!0,re=e}finally{try{!te&&oe.return&&oe.return()}finally{if(ne)throw re}}}}catch(e){Z=!0,Y=e}finally{try{!U&&K.return&&K.return()}finally{if(Z)throw Y}}f.tweens[A]=Q}}}}catch(e){_=!0,T=e}finally{try{!E&&V.return&&V.return()}finally{if(_)throw T}}}else console.warn("VelocityJS: Trying to set 'registerSequence' sequence to an invalid value:",r,i);else console.warn("VelocityJS: Trying to set 'registerSequence' name to an invalid value:",r)}}],!0);var nn=void 0;try{nn=Promise}catch(e){}var rn=", if that is deliberate then pass `promiseRejectEmpty:false` as an option";function on(e,t){v(t,"promise",e),v(t,"then",e.then.bind(e)),v(t,"catch",e.catch.bind(e)),e.finally&&v(t,"finally",e.finally.bind(e))}function an(){var e,t=ve,n=arguments.length<=0?void 0:arguments[0],r=s(n)&&(n.p||s(n.properties)&&!n.properties.names||u(n.properties)),y=0,g=void 0,m=void 0,w=void 0,b=void 0,S=void 0,x=void 0,k=void 0;(a(this)?g=[this]:f(this)?(g=d(this),c(this)&&(b=this.velocity.animations)):r?(g=d(n.elements||n.e),y++):a(n)?(g=d([n]),y++):f(n)&&(g=d(n),y++),g&&(v(g,"velocity",an.bind(g)),b&&v(g.velocity,"animations",b)),r)?m=p(n.properties,n.p):(e=y++,m=arguments.length<=e?void 0:arguments[e]);var O="reverse"===m,E=!O&&u(m),_=E&&Te[m],T=r?p(n.options,n.o):arguments.length<=y?void 0:arguments[y];if(s(T)&&(w=T),nn&&p(w&&w.promise,t.promise)&&(S=new nn(function(e,t){k=t,x=function(t){c(t)&&t.promise?(delete t.then,delete t.catch,delete t.finally,e(t),on(t.promise,t)):e(t)}}),g&&on(S,g)),S){var M=w&&w.promiseRejectEmpty,V=p(M,t.promiseRejectEmpty);g||E?m||(V?k("Velocity: No properties supplied"+(i(M)?"":rn)+". Aborting."):x()):V?k("Velocity: No elements supplied"+(i(M)?"":rn)+". Aborting."):x()}if(!g&&!E||!m)return S;if(E){for(var q=[],N=S&&{_promise:S,_resolver:x,_rejecter:k};y4;e--){var t=document.createElement("div");if(t.innerHTML="\x3c!--[if IE "+e+"]> input[type=range] { + &::-webkit-slider-thumb { + opacity: 1; + transform: scale(1); + z-index: 1; + } + } + } + + input[type=range] { + appearance: none; + width: 100%; + height: 4px; + background-color: rgb(200 200 200 / 10%); + border-radius: 2px; + + &::-webkit-slider-thumb { + opacity: 0; + transform: scale(0.5); + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + border-radius: 100%; + background: var(--keyColor); + cursor: default; + transition: opacity .10s var(--appleEase), transform .10s var(--appleEase); + } + } + } + } + + } + + .app-chrome--left { + width: 30%; + justify-content: flex-start; + align-items: flex-start; + + .playback-controls { + .artwork { + width: var(--chromeHeight2); + height: var(--chromeHeight2); + margin: 0px 6px 0px 0px; + box-shadow: unset; + border: 0px; + + .mediaitem-artwork, + img { + border-radius: 0px; + box-shadow: unset; + border: 0px; + } + } + + .playback-info { + align-items: flex-start; + margin: 6px; + + .song-name { + text-align: left; + font-size: 15px; + font-weight: initial; + width: 100%; + -webkit-mask-image: linear-gradient(-90deg, transparent 0%, transparent 10%, black 20%); + } + + .song-artist-album { + width: 100%; + -webkit-mask-image: linear-gradient(-90deg, transparent 0%, transparent 10%, black 20%); + } + + .audio-type { + margin: 0px; + } + + .song-artist-album-content { + text-align: left; + font-size: 12px; + } + } + + + width: 100%; + height: 100%; + max-width: 100%; + border: 0px; + } + + flex: 0 0 auto; + } + + .app-chrome--right { + width: 30%; + flex: 0 0 auto; + } + } +} diff --git a/src/renderer/less/helpers.less b/src/renderer/less/helpers.less index d437c18f..712d498c 100644 --- a/src/renderer/less/helpers.less +++ b/src/renderer/less/helpers.less @@ -1,3 +1,13 @@ +.notyf__toast { + -webkit-app-region: no-drag; + cursor: pointer; +} + +.notyf-info { + background: var(--keyColor); +} + + .modal-fullscreen { display: flex; diff --git a/src/renderer/style.less b/src/renderer/style.less index 15206741..ead9279b 100644 --- a/src/renderer/style.less +++ b/src/renderer/style.less @@ -3155,4 +3155,4 @@ body[platform='darwin'] { @import url("less/macos.less"); @import url("less/linux.less"); @import url("less/compact.less"); -@import url("less/directives.less"); +@import url("less/directives.less"); \ No newline at end of file diff --git a/src/renderer/themes/groovy/index.less b/src/renderer/themes/groovy/index.less new file mode 100644 index 00000000..ab0c0141 --- /dev/null +++ b/src/renderer/themes/groovy/index.less @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/src/renderer/themes/groovy/theme.json b/src/renderer/themes/groovy/theme.json new file mode 100644 index 00000000..db9f5469 --- /dev/null +++ b/src/renderer/themes/groovy/theme.json @@ -0,0 +1,15 @@ +{ + "name": "Groovy", + "description": "Inspired by Groove Music and Media Player found on Windows", + "version": "1.0.0", + "author": "ciderapp", + "github_repo": "ciderapp/Groovy", + "directives": { + "windowLayout": { + "value": "twopanel" + }, + "lcdArtworkSize": { + "value": 70 + } + } +} \ No newline at end of file diff --git a/src/renderer/views/app/chrome-bottom.ejs b/src/renderer/views/app/chrome-bottom.ejs index 2009389c..012abd1b 100644 --- a/src/renderer/views/app/chrome-bottom.ejs +++ b/src/renderer/views/app/chrome-bottom.ejs @@ -1,31 +1,5 @@
-
- - -
-
- -
-
- - -
-
- -
-
- - - -
-
-
+
diff --git a/src/renderer/views/app/chrome-top.ejs b/src/renderer/views/app/chrome-top.ejs index 84ed0270..83e763be 100644 --- a/src/renderer/views/app/chrome-top.ejs +++ b/src/renderer/views/app/chrome-top.ejs @@ -128,7 +128,11 @@ :class="{'active': drawer.panel == 'lyrics'}" @click="invokeDrawer('lyrics')"> - +
diff --git a/src/renderer/views/main.ejs b/src/renderer/views/main.ejs index 97064e4c..45d67546 100644 --- a/src/renderer/views/main.ejs +++ b/src/renderer/views/main.ejs @@ -34,9 +34,31 @@ + + +
+ <%- include("../assets/cider-round.svg") %> +
@@ -47,13 +69,13 @@
-
-
@@ -64,6 +86,7 @@ <%- include(env.components[i]); %> <% } %> +