Merge branch 'develop'

This commit is contained in:
Core 2022-01-31 23:27:30 +00:00
commit ea3d67b8b4
No known key found for this signature in database
GPG key ID: FE9BF1B547F8F3C6
10 changed files with 197 additions and 1032 deletions

View file

@ -7,4 +7,13 @@ Some notes about Cider's i18n support.
- The default language is used for messages that are not translated.
- Try when possible to keep the messages the similar in length to the English ones.
- Most of the strings in the content area are provided and translated by Apple themselves, and do not need to be translated.
- The language Apple Music uses are dependent on the storefront region.
- The language Apple Music uses are dependent on the storefront region.
## Localization Notices
Several changes have been made to configuration options and will be listed below with the relevant locales that have
been modified, the ones not mentioned in the list need modifying.
* `settings.option.experimental.closeButtonBehaviour`: Changed to `close_button_hide` - Should be "Close Button Should Hide the Application". `.quit`, `.minimizeTaskbar` and `.minimizeTray` have been removed. Translations done for en_US.
* `term.loadingPlaylist`: Added for `en_US` and `en_PISS`.

View file

@ -274,10 +274,7 @@
"settings.header.experimental": "Experimental",
"settings.header.experimental.description": "Adjust the experimental settings for Cider.",
"settings.option.experimental.compactUI": "Compact UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Close Button Behavior",
"settings.option.experimental.closeButtonBehaviour.quit": "Quit Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Minimize to Taskbar",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Minimize to Tray",
"settings.option.experimental.close_button_hide": "Close Button Should Hide the Application",
// Refer to term.disabled & term.enabled
// Spatialization Menu

View file

@ -2,7 +2,7 @@ import * as electron from 'electron';
import * as path from 'path';
export class AppEvents {
private static protocols: any = [
private protocols: string[] = [
"ame",
"cider",
"itms",
@ -10,20 +10,22 @@ export class AppEvents {
"musics",
"music"
]
private static plugin: any = null;
private static store: any = null;
private static win: any = null;
private plugin: any = undefined;
private store: any = undefined;
private win: any = undefined;
private tray: any = undefined;
private i18n: any = undefined;
constructor(store: any) {
AppEvents.store = store
AppEvents.start(store);
this.store = store
this.start(store);
}
/**
* Handles all actions that occur for the app on start (Mainly commandline arguments)
* @returns {void}
*/
private static start(store: any): void {
private start(store: any): void {
console.info('[AppEvents] App started');
/**********************************************************************************************************************
@ -46,12 +48,12 @@ export class AppEvents {
}
// Expose GC
electron.app.commandLine.appendSwitch('js-flags','--expose_gc')
electron.app.commandLine.appendSwitch('js-flags', '--expose_gc')
if (process.platform === "win32") {
electron.app.setAppUserModelId("Cider") // For notification name
if (process.platform === "win32") {
electron.app.setAppUserModelId(electron.app.getName()) // For notification name
}
/***********************************************************************************************************************
* Commandline arguments
**********************************************************************************************************************/
@ -103,33 +105,39 @@ export class AppEvents {
}
public quit() {
console.log('App stopped');
console.log('[AppEvents] App quit');
}
public ready(plug: any) {
AppEvents.plugin = plug
this.plugin = plug
console.log('[AppEvents] App ready');
}
public bwCreated(win: Electron.BrowserWindow) {
AppEvents.win = win
public bwCreated(win: Electron.BrowserWindow, i18n: any) {
this.win = win
this.i18n = i18n
electron.app.on('open-url', (event, url) => {
event.preventDefault()
if (AppEvents.protocols.some((protocol: string) => url.includes(protocol))) {
AppEvents.LinkHandler(url)
if (this.protocols.some((protocol: string) => url.includes(protocol))) {
this.LinkHandler(url)
console.log(url)
}
})
AppEvents.InstanceHandler()
this.InstanceHandler()
this.InitTray()
}
/***********************************************************************************************************************
* Private methods
**********************************************************************************************************************/
private static LinkHandler(arg: string) {
/**
* Handles links (URI) and protocols for the application
* @param arg
*/
private LinkHandler(arg: string) {
if (!arg) return;
// LastFM Auth URL
@ -137,10 +145,10 @@ export class AppEvents {
let authURI = arg.split('/auth/')[1]
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1];
AppEvents.store.set('lastfm.enabled', true);
AppEvents.store.set('lastfm.auth_token', authKey);
AppEvents.win.webContents.send('LastfmAuthenticated', authKey);
AppEvents.plugin.callPlugin('lastfm', 'authenticate', authKey);
this.store.set('lastfm.enabled', true);
this.store.set('lastfm.auth_token', authKey);
this.win.webContents.send('LastfmAuthenticated', authKey);
this.plugin.callPlugin('lastfm', 'authenticate', authKey);
}
}
// Play
@ -156,7 +164,7 @@ export class AppEvents {
for (const [key, value] of Object.entries(mediaType)) {
if (playParam.includes(key)) {
const id = playParam.split(key)[1]
AppEvents.win.webContents.send('play', value, id)
this.win.webContents.send('play', value, id)
console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`)
}
}
@ -165,11 +173,14 @@ export class AppEvents {
console.log(arg)
let url = arg.split('//')[1]
console.warn(`[LinkHandler] Attempting to load url: ${url}`);
AppEvents.win.webContents.send('play', 'url', url)
this.win.webContents.send('play', 'url', url)
}
}
private static InstanceHandler() {
/**
* Handles the creation of a new instance of the app
*/
private InstanceHandler() {
// Detects of an existing instance is running (So if the lock has been achieved, no existing instance has been found)
const gotTheLock = electron.app.requestSingleInstanceLock()
@ -185,17 +196,97 @@ export class AppEvents {
console.log(arg)
if (arg.includes("cider://")) {
console.debug('[InstanceHandler] (second-instance) Link detected with ' + arg)
AppEvents.LinkHandler(arg)
this.LinkHandler(arg)
} else if (arg.includes("--force-quit")) {
console.warn('[InstanceHandler] (second-instance) Force Quit found. Quitting App.');
electron.app.quit()
} else if (AppEvents.win) {
if (AppEvents.win.isMinimized()) AppEvents.win.restore()
AppEvents.win.focus()
} else if (this.win) {
if (this.win.isMinimized()) this.win.restore()
this.win.focus()
}
})
})
}
}
/**
* Initializes the applications tray
*/
private InitTray() {
const icons = {
"win32": electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({
width: 32,
height: 32
}),
"linux": electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
width: 32,
height: 32
}),
"darwin": electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
width: 20,
height: 20
}),
}
console.log(this.i18n)
this.tray = new electron.Tray(process.platform === 'win32' ? icons.win32 : (process.platform === 'darwin' ? icons.darwin : icons.linux))
this.tray.setToolTip(electron.app.getName())
this.setTray(false)
this.tray.on('double-click', () => {
if (this.win) {
if (this.win.isVisible()) {
this.win.focus()
} else {
this.win.show()
}
}
})
this.win.on('show', () => {
this.setTray(true)
})
this.win.on('restore', () => {
this.setTray(true)
})
this.win.on('hide', () => {
this.setTray(false)
})
this.win.on('minimize', () => {
this.setTray(false)
})
}
/**
* Sets the tray context menu to a given state
* @param visible - BrowserWindow Visibility
*/
private setTray(visible: boolean = this.win.isVisible()) {
const menu = electron.Menu.buildFromTemplate([
{
label: (visible ? this.i18n['action.tray.minimize'] : `${this.i18n['action.tray.show']} ${electron.app.getName()}`),
click: () => {
if (this.win) {
if (visible) {
this.win.hide()
} else {
this.win.show()
}
}
}
},
{
label: this.i18n['action.tray.quit'],
click: () => {
electron.app.quit()
}
}
])
this.tray.setContextMenu(menu)
}
}

View file

@ -6,7 +6,7 @@ export class ConfigStore {
private defaults: any = {
"general": {
"close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray
"close_button_hide": true,
"open_on_startup": false,
"discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music
"discord_rpc_clear_on_pause": true,

View file

@ -6,7 +6,7 @@ import * as express from "express";
import * as getPort from "get-port";
import * as yt from "youtube-search-without-api-key";
import * as fs from "fs";
import { Stream } from "stream";
import {Stream} from "stream";
import * as qrcode from "qrcode-terminal";
import * as os from "os";
import * as mm from 'music-metadata';
@ -19,6 +19,7 @@ export class Win {
private app: any | undefined = null;
private store: any | undefined = null;
private devMode: boolean = !electron.app.isPackaged;
public i18n: any = {};
constructor(app: electron.App, store: any) {
this.app = app;
@ -76,7 +77,7 @@ export class Win {
* Creates the browser window
*/
async createWindow(): Promise<Electron.BrowserWindow> {
this.clientPort = await getPort({ port: 9000 });
this.clientPort = await getPort({port: 9000});
this.verifyFiles();
// Load the previous state with fallback to defaults
@ -140,7 +141,7 @@ export class Win {
*/
private startWebServer(): void {
const app = express();
app.use(express.static(path.join(this.paths.srcPath, "./renderer/")));
app.set("views", path.join(this.paths.srcPath, "./renderer/views"));
app.set("view engine", "ejs");
@ -157,7 +158,7 @@ export class Win {
res.redirect("https://discord.gg/applemusic");
}
});
app.get("/", (req, res) => {
res.render("main", this.EnvironmentVariables);
});
@ -199,7 +200,7 @@ export class Win {
remote.set("views", path.join(this.paths.srcPath, "./web-remote/views"));
remote.set("view engine", "ejs");
getPort({port: 6942}).then((port) => {
this.remotePort = port;
this.remotePort = port;
// Start Remote Discovery
this.broadcastRemote()
remote.listen(this.remotePort, () => {
@ -262,7 +263,7 @@ export class Win {
if (itspod != null)
details.requestHeaders["Cookie"] = `itspod=${itspod}`;
}
callback({ requestHeaders: details.requestHeaders });
callback({requestHeaders: details.requestHeaders});
}
);
@ -288,18 +289,29 @@ export class Win {
event.returnValue = process.platform;
});
let i18nBase = fs.readFileSync(path.join(__dirname, "../../src/i18n/en_US.jsonc"), "utf8");
i18nBase = jsonc.parse(i18nBase)
try {
let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${this.store.general.language}.jsonc`), "utf8");
i18n = jsonc.parse(i18n)
this.i18n = Object.assign(i18nBase, i18n)
} catch (e) {
console.error(e);
}
electron.ipcMain.on("get-i18n", (event, key) => {
let i18nBase = fs.readFileSync(path.join(__dirname, "../../src/i18n/en_US.jsonc"), "utf8");
i18nBase = jsonc.parse(i18nBase)
try {
let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${key}.jsonc`), "utf8");
i18n = jsonc.parse(i18n)
Object.assign(i18nBase, i18n)
}catch(e) {
this.i18n = Object.assign(i18nBase, i18n)
} catch (e) {
console.error(e);
event.returnValue = e;
}
this.i18n = i18nBase;
event.returnValue = i18nBase;
});
@ -330,11 +342,6 @@ export class Win {
event.returnValue = this.devMode;
});
electron.ipcMain.on("close", () => {
// listen for close event
this.win.close();
});
electron.ipcMain.on("put-library-songs", (event, arg) => {
fs.writeFileSync(
path.join(this.paths.ciderCache, "library-songs.json"),
@ -415,8 +422,8 @@ export class Win {
return await yt.search(u);
});
electron.ipcMain.handle("setVibrancy", (event, key, value) => {
this.win.setVibrancy(value);
electron.ipcMain.on("close", () => {
this.win.close();
});
electron.ipcMain.on("maximize", () => {
@ -429,7 +436,7 @@ export class Win {
});
electron.ipcMain.on("unmaximize", () => {
// listen for maximize event
this.win.unmaximize();
this.win.unmaximize();
});
electron.ipcMain.on("minimize", () => {
@ -443,7 +450,7 @@ export class Win {
});
electron.ipcMain.on("windowmin", (event, width, height) => {
this.win.setMinimumSize(width,height);
this.win.setMinimumSize(width, height);
})
electron.ipcMain.on("windowontop", (event, ontop) => {
@ -451,7 +458,7 @@ export class Win {
});
// Set scale
electron.ipcMain.on("windowresize", (event, width, height, lock = false) => {
electron.ipcMain.on("windowresize", (event, width, height, lock = false) => {
this.win.setContentSize(width, height);
this.win.setResizable(!lock);
});
@ -462,9 +469,9 @@ export class Win {
})
//Fullscreen
electron.ipcMain.on('detachDT', (event, _) => {
this.win.webContents.openDevTools({ mode: 'detach' });
this.win.webContents.openDevTools({mode: 'detach'});
})
electron.ipcMain.on('play', (event, type, id) => {
this.win.webContents.executeJavaScript(`
@ -497,7 +504,7 @@ export class Win {
}
//QR Code
electron.ipcMain.handle('showQR', async (event , _) => {
electron.ipcMain.handle('showQR', async (event, _) => {
let url = `http://${getIp()}:${this.remotePort}`;
electron.shell.openExternal(`https://cider.sh/pair-remote?url=${btoa(encodeURI(url))}`);
/*
@ -510,11 +517,11 @@ export class Win {
'get url'
fetch(url)
.then(res => res.buffer())
.then(async(buffer) => {
.then(async (buffer) => {
try {
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a');
let SoundCheckTag = metadata.native.iTunes[1].value
console.log('sc',SoundCheckTag)
console.log('sc', SoundCheckTag)
this.win.webContents.send('SoundCheckTag', SoundCheckTag)
} catch (error) {
console.error(error.message);
@ -564,6 +571,25 @@ export class Win {
});
}
let isQuiting = false
this.win.on("close", (event: Event) => {
if ((this.store.general.close_button_hide || process.platform === "darwin" )&& !isQuiting) {
event.preventDefault();
this.win.hide();
} else {
this.win.destroy();
}
})
electron.app.on('before-quit', () => {
isQuiting = true
});
electron.app.on('window-all-closed', () => {
electron.app.quit()
})
this.win.on("closed", () => {
this.win = null;
});
@ -571,19 +597,20 @@ export class Win {
// Set window Handler
this.win.webContents.setWindowOpenHandler((x: any) => {
if (x.url.includes("apple") || x.url.includes("localhost")) {
return { action: "allow" };
return {action: "allow"};
}
electron.shell.openExternal(x.url).catch(console.error);
return { action: "deny" };
return {action: "deny"};
});
}
private async broadcastRemote() {
function getIp() {
let ip :any = false;
let ip: any = false;
let alias = 0;
const ifaces: any = os.networkInterfaces() ;
const ifaces: any = os.networkInterfaces();
for (var dev in ifaces) {
ifaces[dev].forEach( (details: any) => {
ifaces[dev].forEach((details: any) => {
if (details.family === 'IPv4') {
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) {
if (details.address.substring(0, 8) === '192.168.' ||
@ -595,14 +622,15 @@ export class Win {
}
}
}
}) ;
});
}
return ip;
}
const myString = `http://${getIp()}:${this.remotePort}`;
let mdns = require('mdns-js');
const encoded = new Buffer(myString).toString('base64');
var x = mdns.tcp('cider-remote');
var x = mdns.tcp('cider-remote');
var txt_record = {
"Ver": "131077",
'DvSv': '3689',
@ -613,7 +641,7 @@ export class Win {
"CtlN": "Cider",
"iV": "196623"
}
let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, { name: encoded, txt: txt_record });
let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, {name: encoded, txt: txt_record});
server2.start();
console.log('remote broadcasted')
}

View file

@ -32,7 +32,7 @@ electron.app.on('ready', () => {
electron.components.whenReady().then(async () => {
win = await Cider.createWindow()
App.bwCreated(win);
App.bwCreated(win, Cider.i18n);
/// please dont change this for plugins to get proper and fully initialized Win objects
plug.callPlugins('onReady', win);
win.on("ready-to-show", () => {

View file

@ -1,171 +0,0 @@
import * as electron from 'electron';
import * as path from 'path';
export default class MinimizeToTray {
/**
* Private variables for interaction in plugins
*/
private _win: any;
private _app: any;
private _store: any;
private _tray: any;
private _forceQuit = false;
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'Minimize to tray';
public description: string = 'Allow Cider to minimize to tray';
public version: string = '1.0.0';
public author: string = 'vapormusic';
constructor(app: any, store: any) {
this._app = app;
this._store = store;
}
private SetContextMenu(visibility : any) {
let self = this
if (visibility) {
this._tray.setContextMenu(electron.Menu.buildFromTemplate([
// {
// label: 'Check for Updates',
// click: function () {
// app.ame.utils.checkForUpdates(true)
// }
// },
{
label: 'Minimize to Tray',
click: function () {
if (typeof self._win.hide === 'function') {
self._win.hide();
self.SetContextMenu(false);
}
}
},
{
label: 'Quit',
click: function () {
self._forceQuit = true; self._app.quit();
}
}
]));
} else {
this._tray.setContextMenu(electron.Menu.buildFromTemplate([
// {
// label: 'Check for Updates',
// click: function () {
// this._app.ame.utils.checkForUpdates(true)
// }
// },
{
label: `Show ${electron.app.getName()}`,
click: function () {
if (typeof self._win.show === 'function') {
self._win.show();
self.SetContextMenu(true);
}
}
},
{
label: 'Quit',
click: function () {
self._forceQuit = true; self._app.quit();
}
}
]));
}
return true
}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
const winTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({
width: 32,
height: 32
})
const macTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
width: 20,
height: 20
})
const linuxTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
width: 32,
height: 32
})
let trayIcon : any ;
if (process.platform === "win32") {
trayIcon = winTray
} else if (process.platform === "linux") {
trayIcon = linuxTray
} else if (process.platform === "darwin") {
trayIcon = macTray
}
this._tray = new electron.Tray(trayIcon)
this._tray.setToolTip(this._app.getName());
this.SetContextMenu(true);
this._tray.on('double-click', () => {
if (typeof this._win.show === 'function') {
if (this._win.isVisible()) {
this._win.focus()
} else {
this._win.show()
}
}
})
electron.ipcMain.handle("update-store-mtt", (event, value) => {
this._store.general["close_behavior"] = value;
})
electron.ipcMain.on("win-close", (event, value) => {
console.log("tray", this._store.general["close_behavior"] )
if (this._forceQuit || this._store.general["close_behavior"] == '0' ) {
this._app.quit();
} else if (this._store.general["close_behavior"] == '1') {
this._win.minimize();
} else {
this._win.hide();
this.SetContextMenu(false);
}
});
this._win.on("close", (e :any) => {
if (this._forceQuit || this._store.general["close_behavior"] == '0' ) {
this._app.quit();
} else if (this._store.general["close_behavior"] == '1') {
e.preventDefault();
this._win.minimize();
} else {
e.preventDefault();
this._win.hide();
this.SetContextMenu(false);
}
});
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.state = current state)
*/
onPlaybackStateDidChange(attributes: object): void {
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
}
}

View file

@ -3533,8 +3533,7 @@ const app = new Vue({
}
},
closeWindow(){
// window.close doesnt call the win "close" event for some reason
ipcRenderer.send('win-close');
ipcRenderer.send('close');
},
checkForUpdate(){
ipcRenderer.send('check-for-update')

View file

@ -1,778 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="preconnect" href="https://amp-api.music.apple.com/" crossorigin/>
<link rel="preconnect" href="https://api.music.apple.com/" crossorigin/>
<link rel="preconnect" href="https://is1-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is2-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is3-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is4-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is5-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://play.itunes.apple.com/" crossorigin/>
<link rel="preconnect" href="https://aod-ssl.itunes.apple.com/" crossorigin/>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Cider</title>
<link rel="stylesheet" href="style-old.css?v=2">
<script src="vue.js"></script>
<script src="sortable.min.js"></script>
<script src="vuedraggable.umd.min.js"></script>
<link rel="manifest" href="./manifest.json?v=2">
</head>
<body oncontextmenu="return false;" loading="1">
<div id="app">
<div id="app-main">
<div class="app-chrome">
<div class="app-chrome--left">
<div class="app-chrome-item full-height">
<div class="app-title"></div>
</div>
<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="mk.skipToPreviousItem()"></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="mk.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 active" @click="mk.repeatMode = 2"
v-else-if="mk.repeatMode == 1"></button>
<button class="playback-button--small repeat repeatOne" @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">
<template v-if="mkReady()">
<div class="app-playback-controls">
<div class="artwork" :style="{'--artwork': getNowPlayingArtwork(42)}"></div>
<div class="playback-info">
<div class="song-name">
{{ mk.nowPlayingItem["attributes"]["name"] }}
</div>
<div class="song-artist">
{{ mk.nowPlayingItem["attributes"]["artistName"] }} — {{
mk.nowPlayingItem["attributes"]["albumName"] }}
</div>
<div class="song-progress">
<input type="range" step="0.01" min="0"
@change="mk.seekToTime($event.target.value)"
:max="mk.currentPlaybackDuration"
:value="playerLCD.playbackDuration">
</div>
</div>
<div class="actions">❤️</div>
</div>
</template>
</div>
</div>
<div class="app-chrome--right">
<div class="app-chrome-item volume display--large">
<input type="range" class="" step="0.01" min="0" max="1" v-model="mk.volume"
v-if="typeof mk.volume != 'undefined'">
</div>
<div class="app-chrome-item generic">
<button class="playback-button--small">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 22" version="1.1" fill="#fff"
style="width: 100%; height: 100%; fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421">
<path
d="M16.811,12.75c0.245,-0.355 0.389,-0.786 0.389,-1.25c0,-1.215 -0.985,-2.2 -2.2,-2.2c-1.215,0 -2.2,0.985 -2.2,2.2c0,0.466 0.145,0.898 0.392,1.254l-0.83,1.047c-0.537,-0.616 -0.862,-1.42 -0.862,-2.301c0,-1.933 1.567,-3.5 3.5,-3.5c1.933,0 3.5,1.567 3.5,3.5c0,0.879 -0.324,1.683 -0.859,2.297l-0.83,-1.047Zm1.271,1.604c0.694,-0.749 1.118,-1.752 1.118,-2.854c0,-2.32 -1.88,-4.2 -4.2,-4.2c-2.32,0 -4.2,1.88 -4.2,4.2c0,1.103 0.425,2.107 1.121,2.857l-0.814,1.028c-0.993,-0.995 -1.607,-2.368 -1.607,-3.885c0,-3.038 2.462,-5.5 5.5,-5.5c3.038,0 5.5,2.462 5.5,5.5c0,1.515 -0.613,2.887 -1.604,3.882l-0.814,-1.028Zm1.252,1.58c1.151,-1.126 1.866,-2.697 1.866,-4.434c0,-3.424 -2.776,-6.2 -6.2,-6.2c-3.424,0 -6.2,2.776 -6.2,6.2c0,1.739 0.716,3.311 1.869,4.437l-0.811,1.023c-1.452,-1.368 -2.358,-3.308 -2.358,-5.46c0,-4.142 3.358,-7.5 7.5,-7.5c4.142,0 7.5,3.358 7.5,7.5c0,2.15 -0.905,4.089 -2.355,5.457l-0.811,-1.023Zm-0.227,2.066l-8.219,0c-0.355,0 -0.515,-0.434 -0.27,-0.717l4.058,-5.12c0.178,-0.217 0.474,-0.217 0.652,0l4.058,5.12c0.237,0.283 0.085,0.717 -0.279,0.717Z"
style="fill-rule:nonzero"></path>
</svg>
</button>
</div>
<div class="app-chrome-item generic">
<button class="playback-button--small queue"></button>
</div>
<div class="app-chrome-item generic">
<button class="playback-button--small lyrics"
@click="drawertest = !drawertest; lyricon =!lyricon; if(drawertest == true){loadLyrics();}"
></button>
</div>
<div class="app-chrome-item full-height">
<div class="window-controls">
<div class="minimize" @click="ipcRenderer.send('minimize')"></div>
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
<div class="close" @click="ipcRenderer.send('close')"></div>
</div>
</div>
</div>
</div>
<div class="app-navigation">
<div id="app-sidebar">
<div class="app-sidebar-header">
<div class="search-input-container">
<div class="search-input--icon"></div>
<input type="search"
spellcheck="false"
@click="showSearch()"
@change="showSearch();searchQuery()"
placeholder="Search..."
v-model="search.term"
class="search-input">
</div>
</div>
<div class="app-sidebar-content">
<div class="app-sidebar-header-text">
Apple Music
</div>
<sidebar-library-item name="Listen Now" page="listen_now"></sidebar-library-item>
<sidebar-library-item name="Browse" page="browse"></sidebar-library-item>
<sidebar-library-item name="Radio" page="radio"></sidebar-library-item>
<div class="app-sidebar-header-text">
Library
</div>
<sidebar-library-item name="Songs" page="library-songs"></sidebar-library-item>
<sidebar-library-item name="Albums" page="library-albums"></sidebar-library-item>
<sidebar-library-item name="Artists" page="library-artists"></sidebar-library-item>
<sidebar-library-item name="Made For You" page="library-madeforyou"></sidebar-library-item>
<div class="app-sidebar-header-text">
Playlists
</div>
<button class="app-sidebar-item" v-for="item in playlists.listing" :key="item.id" :href="item.href"
@click='app.page=`playlist_` + item.id ; showingPlaylist = [];getPlaylistFromID(app.page.substring(9))'>
{{ item.attributes.name }}
</button>
</div>
<transition name="wpfade">
<div class="app-sidebar-content" v-if="chrome.menuOpened">
<button class="app-sidebar-item" @click="chrome.hideUserInfo = !chrome.hideUserInfo">
Toggle Personal Info
</button>
<button class="app-sidebar-item">
About
</button>
<button class="app-sidebar-item">
Discord
</button>
<button class="app-sidebar-item">
Settings
</button>
<button class="app-sidebar-item">
Sign Out
</button>
</div>
</transition>
<div class="app-sidebar-footer">
<input type="range" class="display--small">
<button class="app-sidebar-button" style="width:100%"
@click="chrome.menuOpened = !chrome.menuOpened">
<template v-if="chrome.userinfo.attributes">
<img class="sidebar-user-icon" loading="lazy"
:src="getMediaItemArtwork(chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : '', 26)"
/>
</template>
<div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
<template v-if="chrome.userinfo.attributes">
<div class="fullname text-overflow-elipsis">{{ chrome.userinfo.attributes.name }}</div>
<div class="handle-text text-overflow-elipsis">@{{ chrome.userinfo.attributes.handle
}}
</div>
</template>
<template v-else>
Sign in
</template>
</div>
<div class="sidebar-user-text" v-else>
Cider
</div>
</button>
</div>
<div class="app-sidebar-notification" v-if="library.songs.downloadState == 1">
<div>Updating your library...</div>
<div>{{ library.songs.meta.progress }} / {{ library.songs.meta.total }}</div>
<div style="width: 100%">
<progress style="width: 80%;" :value="library.songs.meta.progress"
:max="library.songs.meta.total"></progress>
</div>
</div>
</div>
<div id="app-content">
<!-- Playlist / Album page-->
<transition name="wpfade">
<template v-if="page.includes('playlist_')">
<cider-playlist :data="showingPlaylist"></cider-playlist>
</template>
</transition>
<transition name="wpfade">
<template v-if="page.includes('album_')">
<cider-playlist :data="showingPlaylist"></cider-playlist>
</template>
</transition>
</transition>
<!-- Browse -->
<transition name="wpfade">
<template v-if="page == 'browse'">
<div class="content-inner">
<button id="apple-music-authorize" class="md-btn md-btn-primary" @click="init()">Start
MusicKit
</button>
<button id="apple-music-unauthorize" class="md-btn md-btn-primary" @click="unauthorize()">
Stop
MusicKit
</button>
<br>
<template v-if="mk.nowPlayingItem">
currentPlaybackProgress: {{ app.mk.currentPlaybackProgress }}
<br>
currentPlaybackDuration: {{ app.mk.currentPlaybackDuration }}
</template>
<div><input type="text" v-model="quickPlayQuery">
<button @click="quickPlay(quickPlayQuery)">Play</button>
</div>
<h1 class="header-text">Browse</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, urna eu tincidunt
consectetur, nisl nunc euismod nisi, eu porttitor nisl nisi euismod nisi.
</p>
<div class="media-item--small">
<div class="artwork">
</div>
<div class="text">
Text
</div>
<div class="subtext">
Subtext
</div>
</div>
<br>
<br>
<h1 class="header-text">Listen Now</h1>
<div class="winbox">
<div class="fancy">990kbps</div>
<div class="">
<button class="md-btn md-btn-primary">Audio Quality Settings</button>
</div>
</div>
<button class="md-btn" @click="drawertest = !drawertest">Toggle Drawer</button>
<button class="md-btn">Button</button>
<button class="md-btn md-btn-primary">Button</button>
</div>
</template>
</transition>
<!-- Listen Now -->
<transition v-on:enter="getListenNow()" name="wpfade">
<template v-if="page == 'listen_now'" @created="console.log('listennow')">
<cider-listen-now :data="listennow"></cider-listen-now>
</template>
</transition>
<!-- Radio -->
<transition v-on:enter="getRadioStations()" name="wpfade">
<template v-if="page == 'radio'" @created="console.log('radio')">
<div class="content-inner">
<h1 class="header-text">Radio</h1>
<h3>Recent Stations</h3>
<mediaitem-square :item="item" v-for="item in radio.personal"></mediaitem-square>
</div>
</template>
</transition>
<!-- Search -->
<transition name="wpfade">
<template v-if="page == 'search'">
<cider-search :search="search"></cider-search>
</template>
</transition>
<!-- Library - Songs -->
<transition name="wpfade" v-on:enter="getLibrarySongsFull()">
<template v-if="page == 'library-songs'">
<div class="content-inner">
<h1 class="header-text">Songs</h1>
<div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div>
<input type="search"
style="width:100%;"
spellcheck="false"
placeholder="Search..."
@input="searchLibrarySongs"
v-model="library.songs.search"
class="search-input">
</div>
<mediaitem-list-item :item="item"
v-for="item in library.songs.displayListing"></mediaitem-list-item>
</div>
</template>
</transition>
<!-- Library - Albums -->
<transition name="wpfade" v-on:enter="getLibraryAlbums()">
<template v-if="page == 'library-albums'">
<div class="content-inner">
<h1 class="header-text">Albums</h1>
<div class="search-input-container" style="width:100%;margin: 16px 0px;">
<div class="search-input--icon"></div>
<input type="search"
style="width:100%;"
spellcheck="false"
placeholder="Search..."
class="search-input">
</div>
<mediaitem-square-large :item="item"
v-for="item in library.albums.listing"></mediaitem-square-large>
</div>
</template>
</transition>
</div>
<transition name="drawertransition">
<div class="app-drawer" v-if="drawertest">
<lyrics-view v-if="drawertest && lyricon" :time="lyriccurrenttime" :lyrics="lyrics"></lyrics-view>
</div>
</transition>
</div>
</div>
<transition name="wpfade">
<img v-show="chrome.artworkReady"
@load="chrome.artworkReady = true"
class="bg-artwork"
:src="getNowPlayingArtworkBG(32)">
</transition>
<transition name="wpfade">
<div class="bg-artwork--placeholder" v-else></div>
</transition>
</div>
<script type="text/x-template" id="mediaitem-artwork">
<template v-if="type == 'artists'">
<div class="mediaitem-artwork rounded"
>
<img :src="app.getMediaItemArtwork(url, size)"
class="mediaitem-artwork--img">
</div>
</template>
<template v-else>
<div class="mediaitem-artwork"
>
<img :src="app.getMediaItemArtwork(url, size)"
class="mediaitem-artwork--img">
</div>
</template>
</script>
<!-- Generic Collection of MediaItems -->
<script type="text/x-template" id="collection-view-generic">
<template>
<div class="content-inner">
</div>
</template>
</script>
<!-- Listen Now -->
<script type="text/x-template" id="cider-listen-now">
<div class="content-inner">
<h1 class="header-text">Listen Now</h1>
<template v-for="recom in data.data">
<div class="row">
<div class="col">
<h3>{{ recom.attributes.title ? recom.attributes.title.stringForDisplay : ""}}</h3>
</div>
<div class="col-auto flex-center" v-if="recom.relationships.contents.data.length >= 10">
<button class="cd-btn-seeall">See All</button>
</div>
</div>
<template v-if="recom.attributes.display.kind == 'MusicCoverShelf'">
<mediaitem-scroller-horizontal-large
:items="recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-else-if="recom.attributes.display.kind == 'MusicSuperHeroShelf'">
</template>
<template v-else>
<mediaitem-scroller-horizontal-sp
:items="recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-sp>
</template>
</template>
</div>
</script>
<!-- Album / Playlist View -->
<script type="text/x-template" id="cider-playlist">
<div class="content-inner">
<template v-if="data != [] && data.attributes != []">
<div class="playlist-display row">
<div class="col-auto">
<mediaitem-artwork
:url="(data.attributes != null && data.attributes.artwork != null) ? data.attributes.artwork.url : (data.relationships.tracks.data.length > 0 ? data.relationships.tracks.data[0].attributes.artwork.url ?? '':'')"
size="200"
></mediaitem-artwork>
</div>
<div class="col playlist-info">
<div class="playlist-name">{{data.attributes.name ?? (data.attributes.title ?? '') ?? ''}}</div>
<div class="playlist-artist" v-if="data.attributes.artistName">{{data.attributes.artistName ??
''}}
</div>
<div class="playlist-desc"
v-html="((data.attributes.editorialNotes) ? (data.attributes.editorialNotes.short ?? (data.attributes.editorialNotes.standard ?? '') ) : (data.attributes.description ? (data.attributes.description.short ?? (data.attributes.description.standard ?? '')) : ''))"></div>
</div>
</div>
<mediaitem-list-item :item="item"
v-for="item in data.relationships.tracks.data"></mediaitem-list-item>
<div class="playlist-time">{{app.getTotalTime()}}</div>
</template>
</div>
</script>
<!-- Search -->
<script type="text/x-template" id="cider-search">
<div class="content-inner">
<div class="row">
<div class="col-sm" style="width: auto;" v-if="getTopResult()">
<template>
<h3>Top Result</h3>
<mediaitem-square-large :item="getTopResult()"></mediaitem-square>
</template>
</div>
<div class="col" v-if="search.results.songs">
<div class="row">
<div class="col">
<h3>Songs</h3>
</div>
<div class="col-auto flex-center" v-if="search.results.songs.data.length >= 6">
<button class="cd-btn-seeall">See All</button>
</div>
</div>
<div>
<mediaitem-list-item :item="item"
v-for="item in search.results.songs.data.limit(6)"></mediaitem-list-item>
</div>
</div>
</div>
<template v-if="search.results['meta']">
<template v-if="search.results.albums">
<div class="row">
<div class="col">
<h3>Albums</h3>
</div>
<div class="col-auto flex-center" v-if="search.results.albums.data.length >= 10">
<button class="cd-btn-seeall">See All</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="search.results.albums.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="search.results.artists">
<div class="row">
<div class="col">
<h3>Artists</h3>
</div>
<div class="col-auto flex-center" v-if="search.results.artists.data.length >= 5">
<button class="cd-btn-seeall">See All</button>
</div>
</div>
<mediaitem-square-large :item="item"
v-for="item in search.results.artists.data.limit(5)"></mediaitem-square-large>
</template>
<template v-if="search.results.playlists">
<div class="row">
<div class="col">
<h3>Playlists</h3>
</div>
<div class="col-auto flex-center" v-if="search.results.playlists.data.length >= 10">
<button class="cd-btn-seeall">See All</button>
</div>
</div>
<mediaitem-square-large :item="item"
v-for="item in search.results.playlists.data.limit(10)"></mediaitem-square-large>
</template>
</template>
</div>
</script>
<script type="text/x-template" id="am-musiccovershelf">
<h1>{{ component.attributes.title.stringForDisplay }}</h1>
</script>
<!-- Sidebar Item -->
<script type="text/x-template" id="sidebar-library-item">
<button class="app-sidebar-item"
:class="$parent.getSidebarItemClass(page)"
@click="$parent.page = page">{{ name }}
</button>
</script>
<!-- Horizontal MediaItem Scroller -->
<script type="text/x-template" id="mediaitem-scroller-horizontal">
<template>
<div class="cd-hmedia-scroller">
<mediaitem-square :item="item"
v-for="item in items"></mediaitem-square>
</div>
</template>
</script>
<!-- Horizontal MediaItem Scroller (Large) -->
<script type="text/x-template" id="mediaitem-scroller-horizontal-large">
<template>
<div class="cd-hmedia-scroller">
<mediaitem-square-large :item="item"
v-for="item in items"></mediaitem-square-large>
</div>
</template>
</script>
<!-- Horizontal MediaItem Scroller (SP : Special) -->
<script type="text/x-template" id="mediaitem-scroller-horizontal-sp">
<template>
<div class="cd-hmedia-scroller">
<mediaitem-square-sp :item="item"
v-for="item in items"></mediaitem-square-sp>
</div>
</template>
</script>
<!-- MediaItem List Item -->
<script type="text/x-template" id="mediaitem-list-item">
<template>
<div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)"
class="cd-mediaitem-list-item">
<div class="artwork">
<mediaitem-artwork
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
size="34"
:type="item.type"
></mediaitem-artwork>
</div>
<div class="info-rect">
<div class="title text-overflow-elipsis">
{{ item.attributes.name }}
</div>
<div class="subtitle text-overflow-elipsis">
<template v-if="item.attributes.artistName">
{{ item.attributes.artistName }}
<template v-if="item.attributes.albumName">
— {{ item.attributes.albumName }}
</template>
</template>
</div>
</div>
<div class="content-rating" v-if="item.attributes.contentRating">
{{ item.attributes.contentRating }}
</div>
</div>
</template>
</script>
<!-- MediaItem Horizontal Rectangle -->
<script type="text/x-template" id="mediaitem-hrect">
<template>
<div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)"
class="cd-mediaitem-hrect">
<div class="artwork">
<mediaitem-artwork
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
size="70"
:type="item.type"
></mediaitem-artwork>
</div>
<div class="info-rect">
<div class="title text-overflow-elipsis">
{{ item.attributes.name }}
</div>
<div class="subtitle text-overflow-elipsis">
{{ item.type }}
<template v-if="item.attributes.artistName">
∙ {{ item.attributes.artistName }}
</template>
</div>
</div>
</div>
</template>
</script>
<!-- MediaItem Square -->
<script type="text/x-template" id="mediaitem-square">
<template>
<div @click="app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)"
class="cd-mediaitem-square">
<div class="artwork">
<mediaitem-artwork
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
size="300"
:type="item.type"
></mediaitem-artwork>
</div>
<div class="title text-overflow-elipsis">
{{ item.attributes.name }}
</div>
<div class="subtitle text-overflow-elipsis" v-if="item.attributes.artistName">
{{ item.attributes.artistName }}
</div>
</div>
</template>
</script>
<!-- MediaItem Square (Large) -->
<script type="text/x-template" id="mediaitem-square-large">
<template>
<div style="position: relative; display: inline-flex;">
<div @click.self='app.routeView(item)'
class="cd-mediaitem-square-large">
<div class="artwork">
<mediaitem-artwork
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
size="300"
:type="item.type"
></mediaitem-artwork>
</div>
<div class="cd-mediaitem-square-large-overlay" @click.self='app.routeView(item)'>
<div class="button" style="
border-radius: 50%;
background: rgba(50,50,50,0.7);"
:style="[(!(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('radioStation') && !(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('song')) ? {'margin': '140px',
width: '40px',
height: '40px',} :
{margin: '35px',
width: '120px',
height: '120px',}]"
@click="app.playMediaItem(item)">
<svg fill="white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" class="glyph">
<path d="M11.3545232,18.4180929 L18.4676039,14.242665 C19.0452323,13.9290954 19.0122249,13.1204156 18.4676039,12.806846 L11.3545232,8.63141809 C10.7603912,8.26833741 9.98471883,8.54889976 9.98471883,9.19254279 L9.98471883,17.8404645 C9.98471883,18.5006112 10.7108802,18.7976773 11.3545232,18.4180929 Z"></path>
</svg>
</div>
</div>
<div class="title text-overflow-elipsis" @click='app.routeView(item)'>
{{ item.attributes.name ?? '' }}
</div>
<div class="subtitle text-overflow-elipsis" v-if="item.attributes.artistName">
{{ item.attributes.artistName ?? '' }}
</div>
</div>
<div class="cd-mediaitem-square-large-overlay" @click.self='app.routeView(item)'>
<div class="button" style="
border-radius: 50%;
background: rgba(50,50,50,0.7);"
:style="[(!(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('radioStation') && !(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('song')) ? {'margin': '140px',
width: '40px',
height: '40px',} :
{margin: '35px',
width: '120px',
height: '120px',}]"
@click="app.playMediaItem(item)">
<svg fill="white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" class="glyph">
<path d="M11.3545232,18.4180929 L18.4676039,14.242665 C19.0452323,13.9290954 19.0122249,13.1204156 18.4676039,12.806846 L11.3545232,8.63141809 C10.7603912,8.26833741 9.98471883,8.54889976 9.98471883,9.19254279 L9.98471883,17.8404645 C9.98471883,18.5006112 10.7108802,18.7976773 11.3545232,18.4180929 Z"></path>
</svg>
</div>
</div>
</div>
</template>
</script>
<!-- MediaItem Square SP -->
<script type="text/x-template" id="mediaitem-square-sp">
<template>
<div style="position: relative; display: inline-flex;">
<div @click.self='app.routeView(item)'
class="cd-mediaitem-square-sp"
:style="{'--spcolor' : (item.attributes.artwork.bgColor != null) ? ('#'+item.attributes.artwork.bgColor) : `black`}">
<div class="artwork">
<mediaitem-artwork
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
size="300"
:type="item.type"
></mediaitem-artwork>
</div>
<div class="cd-mediaitem-square-large-overlay" @click.self='app.routeView(item)'>
<div class="button" style="
border-radius: 50%;
background: rgba(50,50,50,0.7);"
:style="[(!(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('radioStation') && !(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('song')) ? {'margin': '140px',
width: '40px',
height: '40px',} :
{margin: '35px',
width: '120px',
height: '120px',}]"
@click="app.playMediaItem(item)">
<svg fill="white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" class="glyph">
<path d="M11.3545232,18.4180929 L18.4676039,14.242665 C19.0452323,13.9290954 19.0122249,13.1204156 18.4676039,12.806846 L11.3545232,8.63141809 C10.7603912,8.26833741 9.98471883,8.54889976 9.98471883,9.19254279 L9.98471883,17.8404645 C9.98471883,18.5006112 10.7108802,18.7976773 11.3545232,18.4180929 Z"></path>
</svg>
</div>
</div>
<div class="title text-overflow-elipsis"
:style="{'color' : (item.attributes.artwork.textColor1 != null) ? ('#'+item.attributes.artwork.textColor1) : `#eee`}"
style="font-weight: 600">
{{ item.attributes.name }}
</div>
<div class="subtitle text-overflow-elipsis"
:style="{'color' : (item.attributes.artwork.textColor1 != null) ? ('#'+item.attributes.artwork.textColor1) : `#eee`}"
style="padding-left: 4px;padding-right: 4px; display: -webkit-box;-webkit-box-orient: vertical; -webkit-line-clamp: 2;white-space: normal;">
{{ (item.attributes.editorialNotes != null) ? item.attributes.editorialNotes.short
:(item.attributes.artistName ?? '') }}
</div>
</div>
<div class="cd-mediaitem-square-large-overlay" @click.self='app.routeView(item)'>
<div class="button" style="
border-radius: 50%;
background: rgba(50,50,50,0.7);"
:style="[(!(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('radioStation') && !(item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')): (item.type ?? '')).includes('song')) ? {'margin': '140px',
width: '40px',
height: '40px',} :
{margin: '35px',
width: '120px',
height: '120px',}]"
@click="app.playMediaItem(item)">
<svg fill="white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" class="glyph">
<path d="M11.3545232,18.4180929 L18.4676039,14.242665 C19.0452323,13.9290954 19.0122249,13.1204156 18.4676039,12.806846 L11.3545232,8.63141809 C10.7603912,8.26833741 9.98471883,8.54889976 9.98471883,9.19254279 L9.98471883,17.8404645 C9.98471883,18.5006112 10.7108802,18.7976773 11.3545232,18.4180929 Z"></path>
</svg>
</div>
</div>
</div>
</template>
</script>
<script type="text/x-template" id="lyrics-view">
<div class="md-body lyric-body">
<template v-if="lyrics">
<template v-for="lyric in lyrics" v-if="lyric.line != 'lrcInstrumental'">
<h3 class="lyric-line" @click="app.seekTo(lyric.startTime, false)"
v-bind:class="{ active: app.getLyricClass(lyric.startTime, lyric.endTime)}">
{{ lyric.line }}
<div class="lyrics-translation" v-if="lyric.translation && lyric.translation != ''">
{{ lyric.translation }}
<div>
</h3>
</template>
<template v-else>
<h3 class="lyric-line" @click="app.seekTo(lyric.startTime, false)" :start="lyric.startTime"
:end="lyric.endTime"
v-bind:class="{ active: app.getLyricClass(lyric.startTime, lyric.endTime)}">
<div class="lyricWaiting">
<div class='WaitingDot1'></div>
<div class='WaitingDot2'></div>
<div class='WaitingDot3'></div>
</div>
</h3>
</template>
</template>
<template v-else>
No Lyrics Available
</template>
</div>
</script>
<script src="https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js"></script>
<script src="index.js?v=1"></script>
</body>
</html>

View file

@ -598,20 +598,10 @@
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz("settings.option.experimental.closeButtonBehaviour")}}
{{$root.getLz("settings.option.experimental.close_button_hide")}}
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" v-model="app.cfg.general.close_behavior" @change="sendDataToMTT()">
<option value="0">
{{$root.getLz("settings.option.experimental.closeButtonBehaviour.quit")}}
</option>
<option value="1">
{{$root.getLz("settings.option.experimental.closeButtonBehaviour.minimizeTaskbar")}}
</option>
<option value="2">
{{$root.getLz("settings.option.experimental.closeButtonBehaviour.minimizeTray")}}
</option>
</select>
<input type="checkbox" v-model="app.cfg.general.close_button_hide" switch/>
</div>
</div>
<div class="md-option-line">