Merge branch 'ciderapp:develop' into develop

This commit is contained in:
GamingLiamStudios 2022-03-02 09:14:00 +11:00 committed by GitHub
commit 20036aeafd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 890 additions and 312 deletions

View file

@ -56,6 +56,11 @@ jobs:
command: yarn dist -w --x64 -p never command: yarn dist -w --x64 -p never
post-steps: post-steps:
- jira/notify - jira/notify
- run:
name: Generate Builds (Winget)
command: yarn winget -p never
post-steps:
- jira/notify
- run: - run:
name: Move Build Files name: Move Build Files
command: | command: |

View file

@ -92,6 +92,7 @@ jobs:
rm cider-yarn.lock || true rm cider-yarn.lock || true
xcodebuild -version xcodebuild -version
yarn install 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 cp resources/macPackager.js node_modules/app-builder-lib/out/macPackager.js
yarn dist:universalNotWorking -p never yarn dist:universalNotWorking -p never
# - name: Perform CodeQL Analysis # - name: Perform CodeQL Analysis

View file

@ -30,6 +30,7 @@
"dist:macarm": "yarn build && electron-builder --mac --arm64", "dist:macarm": "yarn build && electron-builder --mac --arm64",
"dist:universalNotWorking": "yarn build && electron-builder --mac --universal", "dist:universalNotWorking": "yarn build && electron-builder --mac --universal",
"dist:all": "yarn build && electron-builder -mwl", "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", "msft": "yarn build && electron-builder -c msft-package.json",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"circle:append-bid": "node resources/appendCommitToVersion" "circle:append-bid": "node resources/appendCommitToVersion"
@ -37,7 +38,7 @@
"dependencies": { "dependencies": {
"@sentry/electron": "^3.0.2", "@sentry/electron": "^3.0.2",
"@sentry/integrations": "^6.18.1", "@sentry/integrations": "^6.18.1",
"adm-zip": "^0.5.9", "adm-zip": "0.4.10",
"castv2-client": "^1.2.0", "castv2-client": "^1.2.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",

77
resources/verror-types Normal file
View file

@ -0,0 +1,77 @@
// Type definitions for verror 1.10
// Project: https://github.com/davepacheco/node-verror
// Definitions by: Sven Reglitzki <https://github.com/svi3c>, Maxime Toumi-M <https://github.com/max4t>
// 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<T extends Error>(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;

View file

@ -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.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": "Audio Spatialisation",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Spatialise audio and make audio more 3-dimensional (note: This is not Dolby Atmos)", "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." "spatial.notTurnedOn": "Audio Spatialisation is disabled. To use, please enable it first.",
"action.tray.minimize": "Minimise to Tray", "action.tray.minimize": "Minimise to Tray"
} }

View file

@ -2,7 +2,7 @@
"i18n.languageName": "English (US)", "i18n.languageName": "English (US)",
"i18n.languageNameEnglish": "English (US)", "i18n.languageNameEnglish": "English (US)",
"i18n.category": "main", "i18n.category": "main",
"i18n.authors": "@maikirakiwi @kyw504100", "i18n.authors": "@maikirakiwi @kyw504100 nosh118",
"app.name": "Cider", "app.name": "Cider",
"date.format": "${m} ${d}, ${y}", "date.format": "${m} ${d}, ${y}",
"dialog.cancel": "Cancel", "dialog.cancel": "Cancel",
@ -89,6 +89,8 @@
"term.time.added": "Added", "term.time.added": "Added",
"term.time.released": "Released", "term.time.released": "Released",
"term.time.updated": "Updated", "term.time.updated": "Updated",
"term.time.days": "days",
"term.time.day": "day",
"term.time.hours": "hours", "term.time.hours": "hours",
"term.time.hour": "hour", "term.time.hour": "hour",
"term.time.minutes": "minutes", "term.time.minutes": "minutes",
@ -115,6 +117,10 @@
"term.equalizer": "Equalizer", "term.equalizer": "Equalizer",
"term.reset": "Reset", "term.reset": "Reset",
"term.tracks": "tracks", "term.tracks": "tracks",
"term.track": {
"one" : "track",
"other" : "tracks"
},
"term.videos": "Videos", "term.videos": "Videos",
"term.menu": "Menu", "term.menu": "Menu",
"term.check": "Check", "term.check": "Check",
@ -142,7 +148,6 @@
"term.noVideos": "No videos found.", "term.noVideos": "No videos found.",
"term.plugin": "Plug-in", "term.plugin": "Plug-in",
"term.pluginMenu": "Plug-in Menu", "term.pluginMenu": "Plug-in Menu",
"term.pluginMenu.none": "No interactive plugins.",
"term.replay": "Replay", "term.replay": "Replay",
"term.uniqueAlbums": "Unique Albums", "term.uniqueAlbums": "Unique Albums",
"term.uniqueArtists": "Unique Artists", "term.uniqueArtists": "Unique Artists",
@ -158,7 +163,7 @@
"home.recentlyPlayed": "Recently Played", "home.recentlyPlayed": "Recently Played",
"home.recentlyAdded": "Recently Added", "home.recentlyAdded": "Recently Added",
"home.artistsFeed": "Your Artists Feed", "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.madeForYou": "Made For You",
"home.friendsListeningTo": "Friends Listening To", "home.friendsListeningTo": "Friends Listening To",
"home.followedArtists": "Followed Artists", "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.description": "Select the branch to update Cider to",
"settings.option.general.updateCider.branch.main": "Stable", "settings.option.general.updateCider.branch.main": "Stable",
"settings.option.general.updateCider.branch.develop": "Development", "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": "Audio",
"settings.header.audio.description": "Adjust the audio settings for Cider.", "settings.header.audio.description": "Adjust the audio settings for Cider.",
"settings.option.audio.volumeStep": "Volume Step", "settings.option.audio.volumeStep": "Volume Step",
@ -255,7 +264,7 @@
"settings.header.audio.quality.standard.description": "64 kbps", "settings.header.audio.quality.standard.description": "64 kbps",
"settings.option.audio.seamlessTransition": "Seamless Audio Transition", "settings.option.audio.seamlessTransition": "Seamless Audio Transition",
"settings.option.audio.enableAdvancedFunctionality": "Enable Advanced Functionality", "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": "Cider Audio Lab",
"settings.option.audio.audioLab.description": "An assortment of in-house developed audio effects for Cider.", "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.", "settings.warn.audioLab.withoutAF": "AudioContext (Advanced Functionality) is required to enable Cider Audio Laboratory.",

View file

@ -219,6 +219,7 @@ export class AppEvents {
app.quit() app.quit()
} else if (utils.getWindow()) { } else if (utils.getWindow()) {
if (utils.getWindow().isMinimized()) utils.getWindow().restore() if (utils.getWindow().isMinimized()) utils.getWindow().restore()
utils.getWindow().show()
utils.getWindow().focus() utils.getWindow().focus()
} }
}) })

View file

@ -1,17 +1,18 @@
import { join } from "path"; import {join} from "path";
import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell } from "electron"; import {app, BrowserWindow as bw, ipcMain, ShareMenu, shell} from "electron";
import * as windowStateKeeper from "electron-window-state"; import * as windowStateKeeper from "electron-window-state";
import * as express from "express"; import * as express from "express";
import * as getPort from "get-port"; import * as getPort from "get-port";
import { search } from "youtube-search-without-api-key"; import {search} from "youtube-search-without-api-key";
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync } from "fs"; import {existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync} from "fs";
import { Stream } from "stream"; import {Stream} from "stream";
import { networkInterfaces } from "os"; import {networkInterfaces} from "os";
import * as mm from 'music-metadata'; import * as mm from 'music-metadata';
import fetch from 'electron-fetch' import fetch from 'electron-fetch'
import { wsapi } from "./wsapi"; import {wsapi} from "./wsapi";
import { AppImageUpdater, NsisUpdater } from "electron-updater"; import {AppImageUpdater, NsisUpdater} from "electron-updater";
import { utils } from './utils'; import {utils} from './utils';
const fileWatcher = require('chokidar'); const fileWatcher = require('chokidar');
const AdmZip = require("adm-zip"); const AdmZip = require("adm-zip");
@ -219,7 +220,7 @@ export class BrowserWindow {
show: false, show: false,
// backgroundColor: "#1E1E1E", // backgroundColor: "#1E1E1E",
titleBarStyle: 'hidden', titleBarStyle: 'hidden',
trafficLightPosition: { x: 15, y: 20 }, trafficLightPosition: {x: 15, y: 20},
webPreferences: { webPreferences: {
experimentalFeatures: true, experimentalFeatures: true,
nodeIntegration: true, nodeIntegration: true,
@ -233,6 +234,7 @@ export class BrowserWindow {
preload: join(utils.getPath('srcPath'), "./preload/cider-preload.js"), preload: join(utils.getPath('srcPath'), "./preload/cider-preload.js"),
}, },
}; };
StartWatcher(path: string) { StartWatcher(path: string) {
var chokidar = require("chokidar"); var chokidar = require("chokidar");
@ -272,11 +274,12 @@ export class BrowserWindow {
// console.log('Raw event info:', event, path, details); // console.log('Raw event info:', event, path, details);
}); });
} }
/** /**
* Creates the browser window * Creates the browser window
*/ */
async createWindow(): Promise<Electron.BrowserWindow> { async createWindow(): Promise<Electron.BrowserWindow> {
this.clientPort = await getPort({ port: 9000 }); this.clientPort = await getPort({port: 9000});
BrowserWindow.verifyFiles(); BrowserWindow.verifyFiles();
this.StartWatcher(utils.getPath('themes')); this.StartWatcher(utils.getPath('themes'));
@ -497,7 +500,7 @@ export class BrowserWindow {
remote.use(express.static(join(utils.getPath('srcPath'), "./web-remote/"))) remote.use(express.static(join(utils.getPath('srcPath'), "./web-remote/")))
remote.set("views", join(utils.getPath('srcPath'), "./web-remote/views")); remote.set("views", join(utils.getPath('srcPath'), "./web-remote/views"));
remote.set("view engine", "ejs"); remote.set("view engine", "ejs");
getPort({ port: 6942 }).then((port) => { getPort({port: 6942}).then((port) => {
this.remotePort = port; this.remotePort = port;
// Start Remote Discovery // Start Remote Discovery
this.broadcastRemote() this.broadcastRemote()
@ -550,7 +553,7 @@ export class BrowserWindow {
if (itspod != null) if (itspod != null)
details.requestHeaders["Cookie"] = `itspod=${itspod}`; details.requestHeaders["Cookie"] = `itspod=${itspod}`;
} }
callback({ requestHeaders: details.requestHeaders }); callback({requestHeaders: details.requestHeaders});
} }
); );
@ -590,10 +593,33 @@ export class BrowserWindow {
let response = await fetch( let response = await fetch(
`${url}/archive/refs/heads/main.zip` `${url}/archive/refs/heads/main.zip`
); );
let zip = await response.buffer(); let repo = url.split("/").slice(-2).join("/");
let zipFile = new AdmZip(zip); let apiRepo = await fetch(
zipFile.extractAllTo(utils.getPath("themes"), true); `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) { } catch (e) {
returnVal.success = false; returnVal.success = false;
} }
@ -635,7 +661,8 @@ export class BrowserWindow {
description: themeJson.description || themeDescription, description: themeJson.description || themeDescription,
path: themePath, path: themePath,
file: theme, file: theme,
github_repo: themeJson.github_repo || "" github_repo: themeJson.github_repo || "",
commit: themeJson.commit || ""
}); });
} else { } else {
themeObjects.push({ themeObjects.push({
@ -643,7 +670,8 @@ export class BrowserWindow {
description: themeDescription, description: themeDescription,
path: themePath, path: themePath,
file: theme, 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) => { ipcMain.on("get-i18n", (event, key) => {
event.returnValue = utils.getLocale(key); event.returnValue = utils.getLocale(key);
}); });
@ -810,7 +853,7 @@ export class BrowserWindow {
}) })
//Fullscreen //Fullscreen
ipcMain.on('detachDT', (_event, _) => { 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) console.log('sc', SoundCheckTag)
BrowserWindow.win.webContents.send('SoundCheckTag', SoundCheckTag) BrowserWindow.win.webContents.send('SoundCheckTag', SoundCheckTag)
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
}); });
}); });
ipcMain.on('check-for-update', async (_event) => { ipcMain.on('check-for-update', async (_event) => {
@ -999,7 +1042,7 @@ export class BrowserWindow {
}) })
ipcMain.on('get-version', (_event) => { ipcMain.on('get-version', (_event) => {
if (app.isPackaged){ if (app.isPackaged) {
_event.returnValue = app.getVersion() _event.returnValue = app.getVersion()
} else { } else {
_event.returnValue = `Experimental running on Electron ${app.getVersion()}` _event.returnValue = `Experimental running on Electron ${app.getVersion()}`
@ -1068,10 +1111,10 @@ export class BrowserWindow {
// Set window Handler // Set window Handler
BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => { BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => {
if (x.url.includes("apple") || x.url.includes("localhost")) { if (x.url.includes("apple") || x.url.includes("localhost")) {
return { action: "allow" }; return {action: "allow"};
} }
shell.openExternal(x.url).catch(console.error); shell.openExternal(x.url).catch(console.error);
return { action: "deny" }; return {action: "deny"};
}); });
} }
@ -1120,7 +1163,7 @@ export class BrowserWindow {
"CtlN": "Cider", "CtlN": "Cider",
"iV": "196623" "iV": "196623"
}; };
let server2 = mdns.createAdvertisement(x, `${await getPort({ port: 3839 })}`, { let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, {
name: encoded, name: encoded,
txt: txt_record txt: txt_record
}); });

View file

@ -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;

View file

@ -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;

View file

@ -15,6 +15,7 @@ export class Store {
"update_branch": "main", "update_branch": "main",
"resumeOnStartupBehavior": "local", "resumeOnStartupBehavior": "local",
"privateEnabled": false, "privateEnabled": false,
"themeUpdateNotification": true
}, },
"home": { "home": {
"followedArtists": [], "followedArtists": [],
@ -96,7 +97,10 @@ export class Store {
"bg_artwork_rotation": false, "bg_artwork_rotation": false,
"hw_acceleration": "default", // default, webgpu, disabled "hw_acceleration": "default", // default, webgpu, disabled
"showuserinfo": true, "showuserinfo": true,
"miniplayer_top_toggle": true "miniplayer_top_toggle": true,
"directives": {
"windowLayout": "default"
}
}, },
"lyrics": { "lyrics": {
"enable_mxm": false, "enable_mxm": false,

View file

@ -158,6 +158,6 @@ export class utils {
log.transports.file.level = "debug" log.transports.file.level = "debug"
autoUpdater.logger = log autoUpdater.logger = log
await autoUpdater.checkForUpdate() await autoUpdater.checkForUpdatesAndNotify()
} }
} }

View file

@ -1,6 +1,7 @@
import * as electron from 'electron'; import * as electron from 'electron';
import * as os from 'os'; import * as os from 'os';
import {resolve} from 'path'; import {resolve} from 'path';
import * as CiderReceiver from '../base/castreceiver';
export default class ChromecastPlugin { 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) { private loadMedia(client: any, song: any, artist: any, album: any, albumart: any, cb?: any) {
// const u = 'http://' + this.getIp() + ':' + server.address().port + '/'; // const u = 'http://' + this.getIp() + ':' + server.address().port + '/';
const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver; // const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver;
client.launch(CiderReceiver, (err: any, player: any) => {
client.launch(DefaultMediaReceiver, (err: any, player: any) => {
if (err) { if (err) {
console.log(err); console.log(err);
return; return;
@ -178,6 +178,10 @@ export default class ChromecastPlugin {
client.stepInterval = status.volume.stepInterval; client.stepInterval = status.volume.stepInterval;
} }
}) })
// send websocket ip
player.sendIp("ws://"+this.getIp()+":26369");
}); });
} }
@ -273,6 +277,7 @@ export default class ChromecastPlugin {
return '' return ''
} }
/** /**
* Base Plugin Details (Eventually implemented into a GUI in settings) * Base Plugin Details (Eventually implemented into a GUI in settings)
*/ */

View file

@ -174,7 +174,11 @@ export default class MPRIS {
*/ */
onBeforeQuit(): void { onBeforeQuit(): void {
console.debug(`[Plugin][${this.name}] Stopped.`); console.debug(`[Plugin][${this.name}] Stopped.`);
this.clearState() try {
this.clearState()
}catch(e) {
console.error(e)
}
} }
/** /**

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1002 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M501,21C765.367,21 980,235.633 980,500C980,764.367 765.367,979 501,979C236.633,979 22,764.367 22,500C22,235.633 236.633,21 501,21ZM501,169C683.684,169 832,317.316 832,500C832,682.684 683.684,831 501,831C318.316,831 170,682.684 170,500C170,317.316 318.316,169 501,169Z" style="fill:rgb(255,38,84);"/>
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:rgb(255,38,84);"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -171,7 +171,7 @@ var CiderAudio = {
constructor() { constructor() {
super(); super();
this._bufferSize = 4096; this._bufferSize = 1024;
this._buffers = null; this._buffers = null;
this._initBuffer(); this._initBuffer();
} }

View file

@ -136,7 +136,7 @@ const app = new Vue({
sorting: "name", sorting: "name",
sortOrder: "asc", sortOrder: "asc",
listing: [], listing: [],
meta: {total: 0, progress: 0}, meta: { total: 0, progress: 0 },
search: "", search: "",
displayListing: [], displayListing: [],
downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library 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 sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page
sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page
listing: [], listing: [],
meta: {total: 0, progress: 0}, meta: { total: 0, progress: 0 },
search: "", search: "",
displayListing: [], displayListing: [],
downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library 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 sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page
sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page
listing: [], listing: [],
meta: {total: 0, progress: 0}, meta: { total: 0, progress: 0 },
search: "", search: "",
displayListing: [], displayListing: [],
downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library
@ -187,6 +187,7 @@ const app = new Vue({
playerReady: false, playerReady: false,
animateBackground: false, animateBackground: false,
currentArtUrl: '', currentArtUrl: '',
currentArtUrlRaw: '',
lyricon: false, lyricon: false,
currentTrackID: '', currentTrackID: '',
currentTrackIDBG: '', currentTrackIDBG: '',
@ -222,7 +223,7 @@ const app = new Vue({
"attributes": { "attributes": {
"name": "Cider User", "name": "Cider User",
"handle": "CiderUser", "handle": "CiderUser",
"artwork": {"url": "./assets/logocut.png"} "artwork": { "url": "./assets/logocut.png" }
} }
}, },
menuOpened: false, menuOpened: false,
@ -302,7 +303,7 @@ const app = new Vue({
}, },
methods: { methods: {
songLinkShare(amUrl) { 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 let self = this
httpRequest = new XMLHttpRequest(); httpRequest = new XMLHttpRequest();
httpRequest.open('GET', `https://api.song.link/v1-alpha.1/links?url=${amUrl}&userCountry=US`, true); 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() { async showSocialListeningTo() {
let contentIds = Object.keys(app.socialBadges.badgeMap) 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) { if (this.socialBadges.mediaItemDLState == 1 || this.socialBadges.mediaItemDLState == 2) {
return return
} }
@ -526,7 +527,7 @@ const app = new Vue({
self.selectedMediaItems[i].kind = "albums" 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 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) { 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) pl_items = pl_items.concat(ids)
} else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { } 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" self.selectedMediaItems[i].kind = "library-albums"
let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); 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) { 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) pl_items = pl_items.concat(ids)
} else { } else {
@ -566,7 +567,7 @@ const app = new Vue({
self.selectedMediaItems[i].kind = "albums" 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 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) { 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) pl_items = pl_items.concat(ids)
} else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { } 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" self.selectedMediaItems[i].kind = "library-albums"
let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); 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) { 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) pl_items = pl_items.concat(ids)
} else { } else {
@ -593,13 +594,13 @@ const app = new Vue({
this.modals.addToPlaylist = false this.modals.addToPlaylist = false
await app.mk.api.v3.music( await app.mk.api.v3.music(
`/v1/me/library/playlists/${playlist_id}/tracks`, {}, { `/v1/me/library/playlists/${playlist_id}/tracks`, {}, {
fetchOptions: { fetchOptions: {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
data: pl_items data: pl_items
}) })
}
} }
}
).then(() => { ).then(() => {
if (this.page == 'playlist_' + this.showingPlaylist.id) { if (this.page == 'playlist_' + this.showingPlaylist.id) {
this.getPlaylistFromID(this.showingPlaylist.id, true) this.getPlaylistFromID(this.showingPlaylist.id, true)
@ -654,7 +655,7 @@ const app = new Vue({
"attributes": { "attributes": {
"name": "Cider User", "name": "Cider User",
"handle": "CiderUser", "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; let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.setQueue({ app.mk.setQueue({
[truekind]: [lastItem.attributes.playParams.id], [truekind]: [lastItem.attributes.playParams.id],
parameters: {l: app.mklang} parameters: { l: app.mklang }
}) })
app.mk.mute() app.mk.mute()
setTimeout(() => { setTimeout(() => {
@ -729,7 +730,7 @@ const app = new Vue({
for (id of ids) { for (id of ids) {
if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) { if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) {
try { try {
app.mk.playLater({songs: [id]}) app.mk.playLater({ songs: [id] })
} catch (err) { } catch (err) {
} }
} }
@ -751,14 +752,14 @@ const app = new Vue({
} }
break; break;
case "history": 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) { if (history.data.data.length > 0) {
let lastItem = history.data.data[0] let lastItem = history.data.data[0]
let kind = lastItem.attributes.playParams.kind; let kind = lastItem.attributes.playParams.kind;
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.setQueue({ app.mk.setQueue({
[truekind]: [lastItem.attributes.playParams.id], [truekind]: [lastItem.attributes.playParams.id],
parameters: {l: app.mklang} parameters: { l: app.mklang }
}) })
app.mk.mute() app.mk.mute()
setTimeout(() => { 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 = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak)))
CiderAudio.audioNodes.gainNode.gain.value = replaygain.gain CiderAudio.audioNodes.gainNode.gain.value = replaygain.gain
} catch (e) { } catch (e) {
} }
}) })
ipcRenderer.on('play', function (_event, mode, id) { ipcRenderer.on('play', function (_event, mode, id) {
if (mode !== 'url') { 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() app.mk.play()
}) })
@ -903,6 +904,29 @@ const app = new Vue({
this.$forceUpdate() this.$forceUpdate()
}, 500) }, 500)
ipcRenderer.invoke("renderer-ready", true) 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) { async setTheme(theme = "", onlyPrefs = false) {
console.log(theme) console.log(theme)
@ -919,14 +943,14 @@ const app = new Vue({
try { try {
const infoResponse = await fetch("themes/" + app.cfg.visual.theme.replace("index.less", "theme.json")) const infoResponse = await fetch("themes/" + app.cfg.visual.theme.replace("index.less", "theme.json"))
this.chrome.appliedTheme.info = await infoResponse.json() this.chrome.appliedTheme.info = await infoResponse.json()
}catch(e){ } catch (e) {
e=null e = null
console.warn("failed to get theme.json") console.warn("failed to get theme.json")
this.chrome.appliedTheme.info = {} this.chrome.appliedTheme.info = {}
} }
if(!onlyPrefs) { if (!onlyPrefs) {
document.querySelector("#userTheme").href = `themes/${this.cfg.visual.theme}` document.querySelector("#userTheme").href = `themes/${this.cfg.visual.theme}`
document.querySelectorAll(`[id*='less']`).forEach(el => { document.querySelectorAll(`[id*='less']`).forEach(el => {
el.remove() el.remove()
@ -935,12 +959,15 @@ const app = new Vue({
} }
}, },
getThemeDirective(directive = "") { getThemeDirective(directive = "") {
if(typeof this.chrome.appliedTheme.info.directives != "object") { let directives = {}
return "" 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 return this.chrome.appliedTheme.info.directives[directive].value
} else { } else if(this.cfg.visual.directives[directive]) {
return this.cfg.visual.directives.windowLayout
}else{
return "" return ""
} }
}, },
@ -961,7 +988,7 @@ const app = new Vue({
classes.simplebg = true classes.simplebg = true
} }
if(this.getThemeDirective('windowLayout') == 'twopanel') { if (this.getThemeDirective('windowLayout') == 'twopanel') {
classes.twopanel = true classes.twopanel = true
} }
return classes return classes
@ -1043,12 +1070,12 @@ const app = new Vue({
this.newPlaylist() this.newPlaylist()
} }
}, },
{ {
name: app.getLz('term.createNewPlaylistFolder'), name: app.getLz('term.createNewPlaylistFolder'),
action: () => { action: () => {
this.newPlaylistFolder() this.newPlaylistFolder()
}
} }
}
] ]
} }
this.showMenuPanel(menu, event) this.showMenuPanel(menu, event)
@ -1057,13 +1084,13 @@ const app = new Vue({
let self = this let self = this
this.mk.api.v3.music( this.mk.api.v3.music(
`/v1/me/library/playlist-folders/${id}`, {}, { `/v1/me/library/playlist-folders/${id}`, {}, {
fetchOptions: { fetchOptions: {
method: "PATCH", method: "PATCH",
body: JSON.stringify({ body: JSON.stringify({
attributes: {name: name} attributes: { name: name }
}) })
}
} }
}
).then(res => { ).then(res => {
self.refreshPlaylists() self.refreshPlaylists()
}) })
@ -1072,13 +1099,13 @@ const app = new Vue({
let self = this let self = this
this.mk.api.v3.music( this.mk.api.v3.music(
`/v1/me/library/playlists/${id}`, {}, { `/v1/me/library/playlists/${id}`, {}, {
fetchOptions: { fetchOptions: {
method: "PATCH", method: "PATCH",
body: JSON.stringify({ body: JSON.stringify({
attributes: {name: name} attributes: { name: name }
}) })
}
} }
}
).then(res => { ).then(res => {
self.refreshPlaylists() self.refreshPlaylists()
}) })
@ -1103,9 +1130,9 @@ const app = new Vue({
fetchOptions: { fetchOptions: {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
"attributes": {"name": name}, "attributes": { "name": name },
"relationships": { "relationships": {
"tracks": {"data": tracks}, "tracks": { "data": tracks },
} }
}) })
} }
@ -1155,7 +1182,7 @@ const app = new Vue({
app.appRoute("collection-list") app.appRoute("collection-list")
}, },
async showArtistView(artist, title, view) { 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) console.log(response)
await this.showCollection(response, title, "artists") await this.showCollection(response, title, "artists")
}, },
@ -1273,7 +1300,7 @@ const app = new Vue({
"limit[artists:top-songs]": 20, "limit[artists:top-songs]": 20,
"art[url]": "f", "art[url]": "f",
l: this.mklang l: this.mklang
}, {includeResponseMeta: !0}) }, { includeResponseMeta: !0 })
console.log(artistData.data.data[0]) console.log(artistData.data.data[0])
this.artistPage.data = artistData.data.data[0] this.artistPage.data = artistData.data.data[0]
this.page = "artist-page" this.page = "artist-page"
@ -1371,7 +1398,7 @@ const app = new Vue({
if (format === 'long') { if (format === 'long') {
const longFormat = [] const longFormat = []
// Seconds // Seconds
if (datetime.getSeconds() !== 0) { if (datetime.getSeconds() !== 0) {
longFormat.push(`${datetime.getSeconds()} ${app.getLz('term.time.seconds')}`) longFormat.push(`${datetime.getSeconds()} ${app.getLz('term.time.seconds')}`)
@ -1379,17 +1406,17 @@ const app = new Vue({
// Minutes // Minutes
if (time >= 60) { 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 // Hours
if (time >= 3600) { 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 // Days
if (time >= 86400) { 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(', ') returnTime = longFormat.reverse().join(', ')
} }
@ -1431,7 +1458,7 @@ const app = new Vue({
kind: page, kind: page,
id: id, id: id,
attributes: { 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, let u = await app.mkapi(app.mk.nowPlayingItem.playParams.kind,
(app.mk.nowPlayingItem.songId == -1), (app.mk.nowPlayingItem.songId == -1),
(app.mk.nowPlayingItem.songId != -1) ? app.mk.nowPlayingItem.songId : app.mk.nowPlayingItem["id"], (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) app.searchAndNavigate(u.data.data[0], target)
} catch (e) { } catch (e) {
app.searchAndNavigate(app.mk.nowPlayingItem, target) app.searchAndNavigate(app.mk.nowPlayingItem, target)
@ -1640,7 +1667,7 @@ const app = new Vue({
} }
if (labelId != "") { if (labelId != "") {
app.showingPlaylist = [] 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; app.page = "recordLabel_" + labelId;
} }
@ -1678,75 +1705,75 @@ const app = new Vue({
console.log(kind, id, isLibrary) console.log(kind, id, isLibrary)
app.mk.stop().then(() => { app.mk.stop().then(() => {
if (kind.includes("artist")) { if (kind.includes("artist")) {
app.mk.setStationQueue({artist: 'a-' + id}).then(() => { app.mk.setStationQueue({ artist: 'a-' + id }).then(() => {
app.mk.play() app.mk.play()
}) })
} }
// else if (kind.includes("playlist") && (id.startsWith("p.") || id.startsWith("pl."))){ // else if (kind.includes("playlist") && (id.startsWith("p.") || id.startsWith("pl."))){
// /* Randomize array in-place using Durstenfeld shuffle algorithm */ // /* Randomize array in-place using Durstenfeld shuffle algorithm */
// function shuffleArray(array) { // function shuffleArray(array) {
// for (var i = array.length - 1; i > 0; i--) { // for (var i = array.length - 1; i > 0; i--) {
// var j = Math.floor(Math.random() * (i + 1)); // var j = Math.floor(Math.random() * (i + 1));
// var temp = array[i]; // var temp = array[i];
// array[i] = array[j]; // array[i] = array[j];
// array[j] = temp; // array[j] = temp;
// } // }
// } // }
// app.mk.clearQueue().then(function () { { // app.mk.clearQueue().then(function () { {
// app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () { // app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () {
// app.mk.play().then(function (){ // app.mk.play().then(function (){
// app.mk.clearQueue().then(function (){ // app.mk.clearQueue().then(function (){
// var playlistId = id // var playlistId = id
// const params = { // const params = {
// include: "tracks", // include: "tracks",
// platform: "web", // platform: "web",
// "include[library-playlists]": "catalog,tracks", // "include[library-playlists]": "catalog,tracks",
// "fields[playlists]": "curatorName,playlistType,name,artwork,url", // "fields[playlists]": "curatorName,playlistType,name,artwork,url",
// "include[library-songs]": "catalog,artists,albums", // "include[library-songs]": "catalog,artists,albums",
// "fields[catalog]": "artistUrl,albumUrl", // "fields[catalog]": "artistUrl,albumUrl",
// "fields[songs]": "artistUrl,albumUrl" // "fields[songs]": "artistUrl,albumUrl"
// } // }
// var playlistId = '' // var playlistId = ''
// try { // try {
// function getPlaylist(id, params, isLibrary){ // function getPlaylist(id, params, isLibrary){
// if (isLibrary){ // if (isLibrary){
// return app.mk.api.library.playlist(id, params) // return app.mk.api.library.playlist(id, params)
// } else { return app.mk.api.playlist(id, params)} // } else { return app.mk.api.playlist(id, params)}
// } // }
// getPlaylist(id, params, isLibrary).then(res => { // getPlaylist(id, params, isLibrary).then(res => {
// let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); // let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
// if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')}
// app.mk.queue.append(query) // app.mk.queue.append(query)
// if (!res.relationships.tracks.next) { // if (!res.relationships.tracks.next) {
// return // return
// } else { // } else {
// getPlaylistTracks(res.relationships.tracks.next) // getPlaylistTracks(res.relationships.tracks.next)
// } // }
// function getPlaylistTracks(next) { // function getPlaylistTracks(next) {
// app.apiCall(app.musicBaseUrl + next, res => { // app.apiCall(app.musicBaseUrl + next, res => {
// if (res.id != playlistId) { // if (res.id != playlistId) {
// return // return
// } // }
// let query = res.data.map(item => new MusicKit.MediaItem(item)) // let query = res.data.map(item => new MusicKit.MediaItem(item))
// if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')}
// app.mk.queue.append(query) // app.mk.queue.append(query)
// if (res.next) { // if (res.next) {
// getPlaylistTracks(res.next) // getPlaylistTracks(res.next)
// } // }
// }) // })
// } // }
// }) // })
// } catch (e) {} // } catch (e) {}
// }) // })
// }) // })
// }) // })
// } // }
// }) // })
// } // }
else { else {
app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '') app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '')
@ -1788,7 +1815,7 @@ const app = new Vue({
let self = this let self = this
let prefs = this.cfg.libraryPrefs.songs let prefs = this.cfg.libraryPrefs.songs
let albumAdded = self.library?.albums?.listing?.map(function (i) { 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() let startTime = new Date().getTime()
@ -1859,9 +1886,9 @@ const app = new Vue({
sortSongs() sortSongs()
} }
}, },
getAlbumSort() { getAlbumSort() {
this.library.albums.sortOrder[1] = this.cfg.libraryPrefs.albums.sortOrder; this.library.albums.sortOrder[1] = this.cfg.libraryPrefs.albums.sortOrder;
this.library.albums.sorting[1] = this.cfg.libraryPrefs.albums.sort; this.library.albums.sorting[1] = this.cfg.libraryPrefs.albums.sort;
}, },
// make a copy of searchLibrarySongs except use Albums instead of Songs // make a copy of searchLibrarySongs except use Albums instead of Songs
searchLibraryAlbums(index) { searchLibraryAlbums(index) {
@ -2233,7 +2260,7 @@ const app = new Vue({
} }
if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") {
console.log(`downloading next chunk - ${library.length console.log(`downloading next chunk - ${library.length
} albums so far`) } albums so far`)
downloadChunk() downloadChunk()
} else { } else {
self.library.albums.listing = library self.library.albums.listing = library
@ -2340,7 +2367,7 @@ const app = new Vue({
} }
if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") {
console.log(`downloading next chunk - ${library.length console.log(`downloading next chunk - ${library.length
} artists so far`) } artists so far`)
downloadChunk() downloadChunk()
} else { } else {
self.library.artists.listing = library self.library.artists.listing = library
@ -2357,20 +2384,20 @@ const app = new Vue({
getTotalTime() { getTotalTime() {
try { try {
if (app.showingPlaylist.relationships.tracks.data.length > 0) { 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); 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')}` 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 "" } else return ""
} catch (err) { } catch (err) {
return "" return ""
} }
}, },
async getLibrarySongs() { 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.listing = response.data.data
this.library.songs.meta = response.data.meta this.library.songs.meta = response.data.meta
}, },
async getLibraryAlbums() { 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.listing = response.data.data
this.library.albums.meta = response.data.meta this.library.albums.meta = response.data.meta
}, },
@ -2476,13 +2503,13 @@ const app = new Vue({
let self = this let self = this
this.mk.api.v3.music( this.mk.api.v3.music(
"/v1/me/library/playlist-folders/", {}, { "/v1/me/library/playlist-folders/", {}, {
fetchOptions: { fetchOptions: {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
attributes: {name: name} attributes: { name: name }
}) })
}
} }
}
).then((res) => { ).then((res) => {
let playlist = (res.data.data[0]) let playlist = (res.data.data[0])
self.playlists.listing.push({ self.playlists.listing.push({
@ -2841,15 +2868,15 @@ const app = new Vue({
line: "lrcInstrumental" line: "lrcInstrumental"
}); });
} }
preLrc.push({startTime: start, endTime: end, line: element.textContent}); preLrc.push({ startTime: start, endTime: end, line: element.textContent });
endTimes.push(end); endTimes.push(end);
} }
// first line dot // first line dot
if (preLrc.length > 0) 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 { } else {
for (element of lyricsLines) { 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; this.lyrics = preLrc;
@ -2917,17 +2944,17 @@ const app = new Vue({
console.log(id, truekind, isLibrary) console.log(id, truekind, isLibrary)
try { try {
if (truekind.includes("artist")) { if (truekind.includes("artist")) {
app.mk.setStationQueue({artist: 'a-' + id}).then(() => { app.mk.setStationQueue({ artist: 'a-' + id }).then(() => {
app.mk.play() app.mk.play()
}) })
} else if (truekind == "radioStations") { } else if (truekind == "radioStations") {
this.mk.setStationQueue({url: raurl}).then(function (queue) { this.mk.setStationQueue({ url: raurl }).then(function (queue) {
MusicKit.getInstance().play() MusicKit.getInstance().play()
}); });
} else { } else {
this.mk.setQueue({ this.mk.setQueue({
[truekind]: [id], [truekind]: [id],
parameters: {l: this.mklang} parameters: { l: this.mklang }
}).then(function (queue) { }).then(function (queue) {
MusicKit.getInstance().play() MusicKit.getInstance().play()
}) })
@ -2968,7 +2995,7 @@ const app = new Vue({
if (item) { if (item) {
app.mk.setQueue({ app.mk.setQueue({
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id,
parameters: {l: app.mklang} parameters: { l: app.mklang }
}).then(function () { }).then(function () {
app.mk.play().then(() => { app.mk.play().then(() => {
if (app.mk.shuffleMode == 1) { if (app.mk.shuffleMode == 1) {
@ -3025,7 +3052,7 @@ const app = new Vue({
for (let kind in itemsToPlay) { for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind] let ids = itemsToPlay[kind]
if (ids.length > 0) { 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] let ids = itemsToPlay[kind]
if (ids.length > 0) { if (ids.length > 0) {
if (app.mk.queue._itemIDs.length > 0) { if (app.mk.queue._itemIDs.length > 0) {
app.mk.playLater({[kind + "s"]: itemsToPlay[kind]}).then(function () { app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }).then(function () {
ind += 1; ind += 1;
console.log(ind, Object.keys(itemsToPlay).length) console.log(ind, Object.keys(itemsToPlay).length)
if (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.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id))
}
} }
}
) )
} else { } else {
app.mk.setQueue({[kind + "s"]: itemsToPlay[kind]}).then(function () { app.mk.setQueue({ [kind + "s"]: itemsToPlay[kind] }).then(function () {
ind += 1; ind += 1;
console.log(ind, Object.keys(itemsToPlay).length) console.log(ind, Object.keys(itemsToPlay).length)
if (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.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"))) { if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) {
app.mk.setQueue({ app.mk.setQueue({
[item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id,
parameters: {l: app.mklang} parameters: { l: app.mklang }
}).then(function () { }).then(function () {
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function () { app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function () {
if ((app.showingPlaylist && app.showingPlaylist.id == id)) { if ((app.showingPlaylist && app.showingPlaylist.id == id)) {
@ -3114,7 +3141,7 @@ const app = new Vue({
} else { } else {
this.mk.setQueue({ this.mk.setQueue({
[truekind]: [id], [truekind]: [id],
parameters: {l: this.mklang} parameters: { l: this.mklang }
}).then(function (queue) { }).then(function (queue) {
if (item && ((queue._itemIDs[childIndex] != item.id))) { if (item && ((queue._itemIDs[childIndex] != item.id))) {
childIndex = queue._itemIDs.indexOf(item.id) childIndex = queue._itemIDs.indexOf(item.id)
@ -3226,7 +3253,7 @@ const app = new Vue({
with: ["serverBubbles", "lyricSnippet"], with: ["serverBubbles", "lyricSnippet"],
"art[url]": "f", "art[url]": "f",
"art[social-profiles:url]": "c" "art[social-profiles:url]": "c"
}, {includeResponseMeta: !0}).then(function (results) { }, { includeResponseMeta: !0 }).then(function (results) {
results.data.results["meta"] = results.data.meta results.data.results["meta"] = results.data.meta
self.search.resultsSocial = results.data.results self.search.resultsSocial = results.data.results
}) })
@ -3246,7 +3273,7 @@ const app = new Vue({
return type.type == this return type.type == this
}, type) }, type)
if (index == -1) { if (index == -1) {
types.push({type: type, id: [id]}) types.push({ type: type, id: [id] })
} else { } else {
types[index].id.push(id) types[index].id.push(id)
} }
@ -3367,9 +3394,17 @@ const app = new Vue({
}, },
async getCurrentArtURL() { async getCurrentArtURL() {
try { 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.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 != '') { 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 { try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) { } 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}`); let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`);
data = data.data.data[0]; data = data.data.data[0];
if (data != null && data !== "" && data.attributes != null && data.attributes.artwork != null) { 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 ?? ''); ipcRenderer.send('updateRPCImage', this.currentArtUrl ?? '');
try { try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) { } catch (e) {
} }
} else { } else {
this.currentArtUrlRaw = ''
this.currentArtUrl = ''; this.currentArtUrl = '';
try { try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
@ -3430,10 +3467,10 @@ const app = new Vue({
}, },
quickPlay(query) { quickPlay(query) {
let self = this 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({ MusicKit.getInstance().setQueue({
song: data["songs"]['data'][0]["id"], song: data["songs"]['data'][0]["id"],
parameters: {l: app.mklang} parameters: { l: app.mklang }
}).then(function (queue) { }).then(function (queue) {
MusicKit.getInstance().play() MusicKit.getInstance().play()
setTimeout(() => { setTimeout(() => {
@ -3611,7 +3648,7 @@ const app = new Vue({
let data_type = this.mk.nowPlayingItem.playParams.kind let data_type = this.mk.nowPlayingItem.playParams.kind
let item_id = this.mk.nowPlayingItem.attributes.playParams.id ?? this.mk.nowPlayingItem.id let item_id = this.mk.nowPlayingItem.attributes.playParams.id ?? this.mk.nowPlayingItem.id
let isLibrary = this.mk.nowPlayingItem.attributes.playParams.isLibrary ?? false 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.selectedMediaItems = []
app.select_selectMediaItem(item_id, data_type, 0, '12344', isLibrary) app.select_selectMediaItem(item_id, data_type, 0, '12344', isLibrary)
let useMenu = "normal" let useMenu = "normal"
@ -3630,36 +3667,36 @@ const app = new Vue({
app.love(app.mk.nowPlayingItem) app.love(app.mk.nowPlayingItem)
} }
}, },
{ {
"icon": "./assets/feather/heart.svg", "icon": "./assets/feather/heart.svg",
"id": "unlove", "id": "unlove",
"active": true, "active": true,
"name": app.getLz('action.unlove'), "name": app.getLz('action.unlove'),
"hidden": true, "hidden": true,
"action": function () { "action": function () {
app.unlove(app.mk.nowPlayingItem) app.unlove(app.mk.nowPlayingItem)
} }
}, },
{ {
"icon": "./assets/feather/thumbs-down.svg", "icon": "./assets/feather/thumbs-down.svg",
"id": "dislike", "id": "dislike",
"name": app.getLz('action.dislike'), "name": app.getLz('action.dislike'),
"hidden": false, "hidden": false,
"disabled": true, "disabled": true,
"action": function () { "action": function () {
app.dislike(app.mk.nowPlayingItem) app.dislike(app.mk.nowPlayingItem)
} }
}, },
{ {
"icon": "./assets/feather/thumbs-down.svg", "icon": "./assets/feather/thumbs-down.svg",
"id": "undo_dislike", "id": "undo_dislike",
"name": app.getLz('action.undoDislike'), "name": app.getLz('action.undoDislike'),
"active": true, "active": true,
"hidden": true, "hidden": true,
"action": function () { "action": function () {
app.unlove(app.mk.nowPlayingItem) app.unlove(app.mk.nowPlayingItem)
} }
}, },
], ],
items: [ items: [
{ {
@ -3691,7 +3728,7 @@ const app = new Vue({
"icon": "./assets/feather/radio.svg", "icon": "./assets/feather/radio.svg",
"name": app.getLz('action.startRadio'), "name": app.getLz('action.startRadio'),
"action": function () { "action": function () {
app.mk.setStationQueue({song: app.mk.nowPlayingItem.id}).then(() => { app.mk.setStationQueue({ song: app.mk.nowPlayingItem.id }).then(() => {
app.mk.play() app.mk.play()
app.selectedMediaItems = [] app.selectedMediaItems = []
}) })
@ -4008,7 +4045,7 @@ const app = new Vue({
Vue.component('animated-number', { Vue.component('animated-number', {
template: "<div style='display: inline-block;'>{{ displayNumber }}</div>", template: "<div style='display: inline-block;'>{{ displayNumber }}</div>",
props: {'number': {default: 0}}, props: { 'number': { default: 0 } },
data() { data() {
return { return {

6
src/renderer/js/velocity.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,22 +1,177 @@
#app.twopanel { #app.twopanel {
--chromeHeight1: 42px; --chromeHeight1: 42px;
--chromeHeight2: 55px; --chromeHeight2: 76px;
--chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2)); --chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2));
.app-chrome {
.app-mainmenu { .app-chrome {
width: 88px; .app-mainmenu {
} margin-left: 10px;
height: var(--chromeHeight1); width: 88px;
&.chrome-bottom {
height: var(--chromeHeight2);
border-top: 1px solid rgba(0, 0, 0, 0.25);
.app-chrome--left {
padding-left: 32px;
}
}
} }
.app-sidebar-footer--controls { height: var(--chromeHeight1);
display: none!important;
&.chrome-bottom {
height: var(--chromeHeight2);
box-shadow: 0px -1px 0px rgba(0, 0, 0, 0.25);
} }
} }
.app-sidebar-footer--controls {
display: none !important;
}
.app-chrome.chrome-bottom {
.app-playback-controls .actions {
align-self: center;
}
.playback-button.play,
.playback-button.pause {
width: 42px;
height: 42px;
background-color: rgb(200 200 200 / 20%);
border-radius: 50%;
margin: 6px;
box-shadow: 0px 0px 0px 2px var(--keyColor);
}
.app-chrome--center {
display: flex;
flex-direction: column;
.app-chrome-playback-controls {
display: flex;
z-index: 1;
// margin-bottom: 12px;
}
.app-chrome-playback-duration {
position: relative;
width: 80%;
-webkit-app-region: no-drag;
height: 16px;
.song-progress {
@bgColor: transparent;
height: 16px;
position: absolute;
bottom: 4px;
left: 0px;
right: 4px;
background: @bgColor;
z-index: 0;
.song-duration {
display: flex;
}
.song-duration p {
font-weight: 400;
font-size: 10px;
height: 1.2em;
line-height: 1.3em;
overflow: hidden;
margin: 0 0 0 0.25em;
}
&:hover {
> 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;
}
}
}

View file

@ -1,3 +1,13 @@
.notyf__toast {
-webkit-app-region: no-drag;
cursor: pointer;
}
.notyf-info {
background: var(--keyColor);
}
.modal-fullscreen { .modal-fullscreen {
display: flex; display: flex;

View file

@ -3155,4 +3155,4 @@ body[platform='darwin'] {
@import url("less/macos.less"); @import url("less/macos.less");
@import url("less/linux.less"); @import url("less/linux.less");
@import url("less/compact.less"); @import url("less/compact.less");
@import url("less/directives.less"); @import url("less/directives.less");

View file

@ -0,0 +1 @@
//

View file

@ -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
}
}
}

View file

@ -1,31 +1,5 @@
<div class="app-chrome chrome-bottom" v-if="getThemeDirective('windowLayout') == 'twopanel'" :style="{'display': chrome.topChromeVisible ? '' : 'none'}"> <div class="app-chrome chrome-bottom" v-if="getThemeDirective('windowLayout') == 'twopanel'" :style="{'display': chrome.topChromeVisible ? '' : 'none'}">
<div class="app-chrome--left"> <div class="app-chrome--left">
<div class="app-chrome-item">
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0"
@click="mk.shuffleMode = 1"></button>
<button class="playback-button--small shuffle active" v-else
@click="mk.shuffleMode = 0"></button>
</div>
<div class="app-chrome-item">
<button class="playback-button previous" @click="prevButton()"></button>
</div>
<div class="app-chrome-item">
<button class="playback-button pause" @click="mk.pause()" v-if="mk.isPlaying"></button>
<button class="playback-button play" @click="mk.play()" v-else></button>
</div>
<div class="app-chrome-item">
<button class="playback-button next" @click="skipToNextItem()"></button>
</div>
<div class="app-chrome-item">
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0"
@click="mk.repeatMode = 1"></button>
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
v-else-if="mk.repeatMode == 1"></button>
<button class="playback-button--small repeat active" @click="mk.repeatMode = 0"
v-else-if="mk.repeatMode == 2"></button>
</div>
</div>
<div class="app-chrome--center">
<div class="app-chrome-item playback-controls"> <div class="app-chrome-item playback-controls">
<template v-if="mkReady()"> <template v-if="mkReady()">
<div class="app-playback-controls" @mouseover="chrome.progresshover = true" <div class="app-playback-controls" @mouseover="chrome.progresshover = true"
@ -59,21 +33,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="song-progress">
<div class="song-duration"
style="justify-content: space-between; height: 1px;"
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p>
<p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }}
</p>
</div>
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true"
@mouseup="mk.seekToTime($event.target.value);playerLCD.desiredDuration = 0;playerLCD.userInteraction = false"
:max="mk.currentPlaybackDuration" :value="getSongProgress()">
</div>
</div> </div>
<template v-if="mk.nowPlayingItem['attributes']['playParams']"> <template v-if="mk.nowPlayingItem['attributes']['playParams']">
<div class="actions"> <div class="actions">
@ -87,6 +46,50 @@
</template> </template>
</div> </div>
</div>
<div class="app-chrome--center">
<div class="app-chrome-playback-controls">
<div class="app-chrome-item">
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0"
@click="mk.shuffleMode = 1"></button>
<button class="playback-button--small shuffle active" v-else
@click="mk.shuffleMode = 0"></button>
</div>
<div class="app-chrome-item">
<button class="playback-button previous" @click="prevButton()"></button>
</div>
<div class="app-chrome-item">
<button class="playback-button pause" @click="mk.pause()" v-if="mk.isPlaying"></button>
<button class="playback-button play" @click="mk.play()" v-else></button>
</div>
<div class="app-chrome-item">
<button class="playback-button next" @click="skipToNextItem()"></button>
</div>
<div class="app-chrome-item">
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0"
@click="mk.repeatMode = 1"></button>
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
v-else-if="mk.repeatMode == 1"></button>
<button class="playback-button--small repeat active" @click="mk.repeatMode = 0"
v-else-if="mk.repeatMode == 2"></button>
</div>
</div>
<div class="app-chrome-playback-duration">
<div class="song-progress">
<div class="song-duration"
style="justify-content: space-between; height: 1px;">
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p>
<p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }}
</p>
</div>
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true"
@mouseup="mk.seekToTime($event.target.value);playerLCD.desiredDuration = 0;playerLCD.userInteraction = false"
:max="mk.currentPlaybackDuration" :value="getSongProgress()">
</div>
</div>
</div> </div>
<div class="app-chrome--right"> <div class="app-chrome--right">
<div class="app-chrome-item volume"> <div class="app-chrome-item volume">
@ -109,6 +112,11 @@
:class="{'active': drawer.panel == 'lyrics'}" :class="{'active': drawer.panel == 'lyrics'}"
@click="invokeDrawer('lyrics')"></button> @click="invokeDrawer('lyrics')"></button>
</template> </template>
<template v-else>
<button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"
@click="invokeDrawer('lyrics')"></button>
</template>
</div> </div>
</div> </div>

View file

@ -128,7 +128,11 @@
:class="{'active': drawer.panel == 'lyrics'}" :class="{'active': drawer.panel == 'lyrics'}"
@click="invokeDrawer('lyrics')"></button> @click="invokeDrawer('lyrics')"></button>
</template> </template>
<template v-else>
<button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"
@click="invokeDrawer('lyrics')"></button>
</template>
</div> </div>
</template> </template>
<div class="app-chrome-item full-height" id="window-controls-container" v-if="chrome.windowControlPosition == 'right'"> <div class="app-chrome-item full-height" id="window-controls-container" v-if="chrome.windowControlPosition == 'right'">

View file

@ -34,9 +34,31 @@
<script src="./js/bootbox.min.js"></script> <script src="./js/bootbox.min.js"></script>
<script src="./js/notyf.min.js"></script> <script src="./js/notyf.min.js"></script>
<script src="./js/showdown.min.js"></script> <script src="./js/showdown.min.js"></script>
<script src="./js/velocity.min.js"></script>
<style>
#LOADER {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #1E1E1E;
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
}
#LOADER > svg {
width: 128px;
}
</style>
</head> </head>
<body oncontextmenu="return false;" loading="1" platform="<%= env.platform %>"> <body oncontextmenu="return false;" loading="1" platform="<%= env.platform %>">
<div id="LOADER">
<%- include("../assets/cider-round.svg") %>
</div>
<div id="app" :class="getAppClasses()"> <div id="app" :class="getAppClasses()">
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div id="app-main" v-show="appMode == 'player'"> <div id="app-main" v-show="appMode == 'player'">
@ -47,13 +69,13 @@
</transition> </transition>
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'fullscreen'"> <div class="fullscreen-view-container" v-if="appMode == 'fullscreen'">
<fullscreen-view :image="currentArtUrl.replace('50x50', '600x600')" :time="lyriccurrenttime" <fullscreen-view :image="currentArtUrlRaw" :time="lyriccurrenttime"
:lyrics="lyrics" :richlyrics="richlyrics"></fullscreen-view> :lyrics="lyrics" :richlyrics="richlyrics"></fullscreen-view>
</div> </div>
</transition> </transition>
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'mini'"> <div class="fullscreen-view-container" v-if="appMode == 'mini'">
<mini-view :image="currentArtUrl.replace('50x50', '600x600')" :time="lyriccurrenttime" <mini-view :image="currentArtUrlRaw" :time="lyriccurrenttime"
:lyrics="lyrics" :richlyrics="richlyrics"></mini-view> :lyrics="lyrics" :richlyrics="richlyrics"></mini-view>
</div> </div>
</transition> </transition>
@ -64,6 +86,7 @@
<%- include(env.components[i]); %> <%- include(env.components[i]); %>
<% } %> <% } %>
<script type="text/x-template" <script type="text/x-template"
id="am-musiccovershelf"> id="am-musiccovershelf">
<h1>{{ component.attributes.title.stringForDisplay }}</h1> <h1>{{ component.attributes.title.stringForDisplay }}</h1>

View file

@ -143,6 +143,17 @@
</button> </button>
</div> </div>
</div> </div>
<div class="md-option-line">
<div class="md-option-segment">
Window Style
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" v-model="$root.cfg.visual.directives.windowLayout">
<option value="default">Cupertino</option>
<option value="twopanel">Redmond</option>
</select>
</div>
</div>
<div class="md-option-line"> <div class="md-option-line">
<div class="md-option-segment"> <div class="md-option-segment">
{{$root.getLz('settings.option.visual.windowBackgroundStyle')}} {{$root.getLz('settings.option.visual.windowBackgroundStyle')}}

View file

@ -6,12 +6,18 @@
<h1 class="header-text">{{$root.getLz('settings.header.visual.theme.github.page')}}</h1> <h1 class="header-text">{{$root.getLz('settings.header.visual.theme.github.page')}}</h1>
</div> </div>
<div class="col-auto flex-center"> <div class="col-auto flex-center">
<select class="md-select" @change="$root.setTheme($root.cfg.visual.theme)" v-model="$root.cfg.visual.theme"> <select class="md-select" @change="$root.setTheme($root.cfg.visual.theme)"
v-model="$root.cfg.visual.theme">
<option value="default.less">{{$root.getLz('settings.option.visual.theme.default')}}</option> <option value="default.less">{{$root.getLz('settings.option.visual.theme.default')}}</option>
<option value="dark.less">{{$root.getLz('settings.option.visual.theme.dark')}}</option> <option value="dark.less">{{$root.getLz('settings.option.visual.theme.dark')}}</option>
<option v-for="theme in themes" :value="theme.file">{{ theme.name }}</option> <option v-for="theme in themes" :value="theme.file">{{ theme.name }}</option>
</select> </select>
</div> </div>
<div class="col-auto flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="openThemesFolder()">
Open Themes Folder
</button>
</div>
<div class="col-auto nopadding flex-center"> <div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="installThemeURL()"> <button class="md-btn md-btn-small md-btn-block" @click="installThemeURL()">
{{$root.getLz('settings.option.visual.theme.github.download')}} {{$root.getLz('settings.option.visual.theme.github.download')}}
@ -28,12 +34,14 @@
<div class="row"> <div class="row">
<div class="col flex-center"> <div class="col flex-center">
<div> <div>
<h4 class="repo-name">{{ (repo.description != null) ? repo.description : repo.full_name }}</h4> <h4 class="repo-name">{{ (repo.description != null) ? repo.description :
repo.full_name }}</h4>
<div>⭐ {{ repo.stargazers_count }}</div> <div>⭐ {{ repo.stargazers_count }}</div>
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<span v-if="themesInstalled.includes(repo.full_name)" class="codicon codicon-cloud-download"></span> <span v-if="themesInstalled.includes(repo.full_name.toLowerCase())"
class="codicon codicon-cloud-download"></span>
</div> </div>
</div> </div>
</li> </li>
@ -47,7 +55,8 @@
<div> <div>
<h3 class="repo-preview-name">{{ openRepo.description }}</h3> <h3 class="repo-preview-name">{{ openRepo.description }}</h3>
<div> <div>
<div class="svg-icon inline" :style="{'--url': 'url(\'./assets/github.svg\')'}"></div> <div class="svg-icon inline"
:style="{'--url': 'url(\'./assets/github.svg\')'}"></div>
<a class="repo-url" target="_blank" :href="openRepo.html_url">{{ openRepo.full_name <a class="repo-url" target="_blank" :href="openRepo.html_url">{{ openRepo.full_name
}}</a></div> }}</a></div>
<div>⭐ {{ openRepo.stargazers_count }}</div> <div>⭐ {{ openRepo.stargazers_count }}</div>
@ -55,7 +64,7 @@
</div> </div>
<div class="col-auto nopadding flex-center"> <div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-primary" @click="installThemeRepo(openRepo)"> <button class="md-btn md-btn-primary" @click="installThemeRepo(openRepo)">
<span v-if="!themesInstalled.includes(openRepo.full_name)">{{$root.getLz('action.install')}}</span> <span v-if="!themesInstalled.includes(openRepo.full_name.toLowerCase())">{{$root.getLz('action.install')}}</span>
<span v-else>{{$root.getLz('action.update')}}</span> <span v-else>{{$root.getLz('action.update')}}</span>
</button> </button>
</div> </div>
@ -97,16 +106,19 @@
this.themes = ipcRenderer.sendSync("get-themes") this.themes = ipcRenderer.sendSync("get-themes")
this.getRepos(); this.getRepos();
this.getInstalledThemes(); this.getInstalledThemes();
app.checkForThemeUpdates()
}, },
methods: { methods: {
openThemesFolder() {
ipcRenderer.invoke("open-path", "themes")
},
getInstalledThemes() { getInstalledThemes() {
let self = this let self = this
const themes = ipcRenderer.sendSync("get-themes") const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank // for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => { themes.forEach(theme => {
if (theme.github_repo !== "") { if (theme.github_repo !== "" && typeof theme.commit != "") {
theme.github_repo = theme.github_repo.toLowerCase() self.themesInstalled.push(theme.github_repo.toLowerCase())
self.themesInstalled.push(theme.github_repo)
} }
}) })
}, },
@ -182,9 +194,6 @@
.then(response => response.text()) .then(response => response.text())
.then(result => { .then(result => {
let items = JSON.parse(result).items let items = JSON.parse(result).items
items.forEach(repo => {
repo.full_name = repo.full_name.toLowerCase()
})
self.repos = items self.repos = items
}) })
.catch(error => console.log('error', error)); .catch(error => console.log('error', error));

View file

@ -3,6 +3,7 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "esnext", "target": "esnext",
"module": "commonjs", "module": "commonjs",
"allowJs": true,
"noImplicitAny": true, "noImplicitAny": true,
"strict": true, "strict": true,
"sourceMap": true, "sourceMap": true,

41
winget.json Normal file
View file

@ -0,0 +1,41 @@
{
"electronVersion": "16.0.7",
"electronDownload": {
"version": "16.0.7+wvcus",
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
},
"appId": "cider",
"afterPack": "./resources/afterPack.js",
"afterSign": "./resources/notarize.js",
"protocols": [
{
"name": "Cider",
"schemes": [
"ame",
"cider",
"itms",
"itmss",
"musics",
"music"
]
}
],
"extends": null,
"files": [
"./build/**/*",
"./src/**/*",
"./resources/icons/**/*"
],
"nsis": {
"oneClick": true,
"perMachine": false,
"deleteAppDataOnUninstall": true,
"artifactName": "${productName}-Setup-winget-${version}.${ext}"
},
"win": {
"target": [
"nsis"
],
"icon": "resources/icons/icon.ico"
}
}