Merge branch 'ciderapp:develop' into develop
This commit is contained in:
commit
4105f84e6f
26 changed files with 872 additions and 433 deletions
|
@ -42,7 +42,7 @@
|
|||
"airtunes2": "git+https://github.com/vapormusic/node_airtunes2.git#hap",
|
||||
"castv2-client": "^1.2.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"discord-rpc": "^4.0.1",
|
||||
"discord-auto-rpc": "^1.0.16",
|
||||
"dns-js": "git+https://github.com/ciderapp/node-dns-js.git",
|
||||
"ejs": "^3.1.6",
|
||||
"electron-fetch": "^1.7.4",
|
||||
|
@ -82,7 +82,7 @@
|
|||
"electron-builder-notarize-pkg": "^1.2.0",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"musickit-typescript": "^1.2.4",
|
||||
"typescript": "^4.6.3",
|
||||
"typescript": "^4.6.4",
|
||||
"vue-devtools": "^5.1.4",
|
||||
"webpack": "~5.72.0"
|
||||
},
|
||||
|
@ -109,9 +109,9 @@
|
|||
}
|
||||
],
|
||||
"build": {
|
||||
"electronVersion": "18.2.0",
|
||||
"electronVersion": "18.2.1",
|
||||
"electronDownload": {
|
||||
"version": "18.2.0+wvcus",
|
||||
"version": "18.2.1+wvcus",
|
||||
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
||||
},
|
||||
"appId": "cider",
|
||||
|
|
|
@ -428,6 +428,11 @@
|
|||
"settings.header.visual.theme.github.page": "Themes from GitHub",
|
||||
"settings.option.visual.theme.github.install.confirm": "Are you sure you want to install {{ repo }}?",
|
||||
"settings.prompt.visual.theme.github.URL": "Enter the URL of the theme you want to install",
|
||||
"settings.prompt.visual.theme.uninstallTheme": "Are you sure you want to uninstall {{ theme }}?",
|
||||
"settings.option.visual.theme.checkForUpdates": "Check for updates",
|
||||
"settings.option.visual.theme.manageStyles": "Manage Styles",
|
||||
"settings.option.visual.theme.uninstall": "Uninstall",
|
||||
"settings.option.visual.theme.viewInfo": "View Info",
|
||||
"settings.notyf.visual.theme.install.success": "Theme installed successfully",
|
||||
"settings.notyf.visual.theme.install.error": "Theme installation failed",
|
||||
"settings.header.visual.plugin": "Plugin",
|
||||
|
|
|
@ -428,6 +428,11 @@
|
|||
"settings.header.visual.theme.github.page": "Themes from GitHub",
|
||||
"settings.option.visual.theme.github.install.confirm": "Are you sure you want to install {{ repo }}?",
|
||||
"settings.prompt.visual.theme.github.URL": "Enter the URL of the theme you want to install",
|
||||
"settings.prompt.visual.theme.uninstallTheme": "Are you sure you want to uninstall {{ theme }}?",
|
||||
"settings.option.visual.theme.checkForUpdates": "Check for updates",
|
||||
"settings.option.visual.theme.manageStyles": "Manage Styles",
|
||||
"settings.option.visual.theme.uninstall": "Uninstall",
|
||||
"settings.option.visual.theme.viewInfo": "View Info",
|
||||
"settings.notyf.visual.theme.install.success": "Theme installed successfully",
|
||||
"settings.notyf.visual.theme.install.error": "Theme installation failed",
|
||||
"settings.header.visual.plugin": "Plugin",
|
||||
|
|
|
@ -4,7 +4,18 @@ import * as windowStateKeeper from "electron-window-state";
|
|||
import * as express from "express";
|
||||
import * as getPort from "get-port";
|
||||
import {search} from "youtube-search-without-api-key";
|
||||
import {existsSync, rmSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync} from "fs";
|
||||
import {
|
||||
existsSync,
|
||||
rmSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
statSync,
|
||||
unlinkSync,
|
||||
rmdirSync,
|
||||
lstatSync
|
||||
} from "fs";
|
||||
import {Stream} from "stream";
|
||||
import {networkInterfaces} from "os";
|
||||
import * as mm from 'music-metadata';
|
||||
|
@ -46,6 +57,7 @@ export class BrowserWindow {
|
|||
"pages/library-artists",
|
||||
"pages/browse",
|
||||
"pages/settings",
|
||||
"pages/installed-themes",
|
||||
"pages/listen_now",
|
||||
"pages/home",
|
||||
"pages/artist-feed",
|
||||
|
@ -178,6 +190,10 @@ export class BrowserWindow {
|
|||
page: "settings",
|
||||
component: `<cider-settings></cider-settings>`,
|
||||
condition: `page == 'settings'`
|
||||
}, {
|
||||
page: "installed-themes",
|
||||
component: `<installed-themes></installed-themes>`,
|
||||
condition: `page == 'installed-themes'`
|
||||
}, {
|
||||
page: "search",
|
||||
component: `<cider-search :search="search"></cider-search>`,
|
||||
|
@ -256,8 +272,10 @@ export class BrowserWindow {
|
|||
},
|
||||
};
|
||||
|
||||
public static watcher: any;
|
||||
|
||||
StartWatcher(path: string) {
|
||||
const watcher = watch(path, {
|
||||
BrowserWindow.watcher = watch(path, {
|
||||
ignored: /[\/\\]\./,
|
||||
persistent: true
|
||||
});
|
||||
|
@ -267,7 +285,7 @@ export class BrowserWindow {
|
|||
}
|
||||
|
||||
// Declare the listeners of the watcher
|
||||
watcher
|
||||
BrowserWindow.watcher
|
||||
.on('add', function (path: string) {
|
||||
// console.log('File', path, 'has been added');
|
||||
})
|
||||
|
@ -294,6 +312,10 @@ export class BrowserWindow {
|
|||
});
|
||||
}
|
||||
|
||||
async StopWatcher() {
|
||||
await BrowserWindow.watcher.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the browser window
|
||||
* @generator
|
||||
|
@ -698,6 +720,50 @@ export class BrowserWindow {
|
|||
};
|
||||
})
|
||||
|
||||
ipcMain.handle("uninstall-theme", async (event, path) => {
|
||||
await this.StopWatcher()
|
||||
const themesDir = utils.getPath("themes")
|
||||
// validate the path is in the themes directory
|
||||
try {
|
||||
if (path.startsWith(themesDir)) {
|
||||
// get last dir in path, can be either / or \ and may have a trailing slash
|
||||
const themeName = path.split(/[\\\/]/).pop()
|
||||
if (themeName == "Themes" || themeName == "themes") {
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
status: 3
|
||||
});
|
||||
return
|
||||
}
|
||||
// if path is directory, delete it
|
||||
if (lstatSync(path).isDirectory()) {
|
||||
await rmdirSync(path, {recursive: true});
|
||||
} else {
|
||||
// if path is file, delete it
|
||||
await unlinkSync(path);
|
||||
}
|
||||
// return the path
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
status: 0
|
||||
});
|
||||
} else {
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
status: 1
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
BrowserWindow.win.webContents.send("theme-uninstalled", {
|
||||
path: path,
|
||||
message: e.message,
|
||||
status: 2
|
||||
});
|
||||
}
|
||||
|
||||
this.StartWatcher(utils.getPath('themes'))
|
||||
})
|
||||
|
||||
ipcMain.handle("reinstall-widevine-cdm", () => {
|
||||
// remove WidevineCDM from appdata folder
|
||||
const widevineCdmPath = join(app.getPath("userData"), "./WidevineCdm");
|
||||
|
@ -813,7 +879,7 @@ export class BrowserWindow {
|
|||
} else if (statSync(join(utils.getPath("themes"), file)).isDirectory()) {
|
||||
let subFiles = readdirSync(join(utils.getPath("themes"), file));
|
||||
for (let subFile of subFiles) {
|
||||
if (subFile.endsWith(".less")) {
|
||||
if (subFile.endsWith("index.less")) {
|
||||
themes.push(join(file, subFile));
|
||||
}
|
||||
}
|
||||
|
@ -832,15 +898,20 @@ export class BrowserWindow {
|
|||
themePath = themePath.slice(0, -10);
|
||||
}
|
||||
if (existsSync(join(themePath, "theme.json"))) {
|
||||
let themeJson = JSON.parse(readFileSync(join(themePath, "theme.json"), "utf8"));
|
||||
themeObjects.push({
|
||||
name: themeJson.name || themeName,
|
||||
description: themeJson.description || themeDescription,
|
||||
path: themePath,
|
||||
file: theme,
|
||||
github_repo: themeJson.github_repo || "",
|
||||
commit: themeJson.commit || ""
|
||||
});
|
||||
try {
|
||||
let themeJson = JSON.parse(readFileSync(join(themePath, "theme.json"), "utf8"));
|
||||
themeObjects.push({
|
||||
name: themeJson.name || themeName,
|
||||
description: themeJson.description || themeDescription,
|
||||
path: themePath,
|
||||
file: theme,
|
||||
github_repo: themeJson.github_repo || "",
|
||||
commit: themeJson.commit || "",
|
||||
pack: themeJson.pack || false,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
themeObjects.push({
|
||||
name: themeName,
|
||||
|
@ -848,7 +919,8 @@ export class BrowserWindow {
|
|||
path: themePath,
|
||||
file: theme,
|
||||
github_repo: "",
|
||||
commit: ""
|
||||
commit: "",
|
||||
pack: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -971,6 +1043,11 @@ export class BrowserWindow {
|
|||
BrowserWindow.win.setResizable(!lock);
|
||||
});
|
||||
|
||||
// Move window
|
||||
ipcMain.on("windowmove", (_event, x, y) => {
|
||||
BrowserWindow.win.setBounds({x, y});
|
||||
});
|
||||
|
||||
//Fullscreen
|
||||
ipcMain.on('setFullScreen', (_event, flag) => {
|
||||
BrowserWindow.win.setFullScreen(flag)
|
||||
|
@ -1279,7 +1356,7 @@ export class BrowserWindow {
|
|||
BrowserWindow.win.webContents.executeJavaScript(`
|
||||
window.localStorage.setItem("currentTrack", JSON.stringify(app.mk.nowPlayingItem));
|
||||
window.localStorage.setItem("currentTime", JSON.stringify(app.mk.currentPlaybackTime));
|
||||
window.localStorage.setItem("currentQueue", JSON.stringify(app.mk.queue.items));
|
||||
window.localStorage.setItem("currentQueue", JSON.stringify(app.mk.queue._unplayedQueueItems));
|
||||
ipcRenderer.send('stopGCast','');`)
|
||||
BrowserWindow.win.destroy();
|
||||
}
|
||||
|
|
|
@ -52,11 +52,11 @@ export class Store {
|
|||
"keybindings": {
|
||||
"search": [
|
||||
process.platform == "darwin" ? "Command" : "Control",
|
||||
"S"
|
||||
"F"
|
||||
],
|
||||
"albums": [
|
||||
process.platform == "darwin" ? "Command" : "Control",
|
||||
"F"
|
||||
"S"
|
||||
],
|
||||
"artists": [
|
||||
process.platform == "darwin" ? "Command" : "Control",
|
||||
|
@ -123,6 +123,8 @@ export class Store {
|
|||
"quality": "HIGH",
|
||||
"seamless_audio": true,
|
||||
"normalization": false,
|
||||
"dBSPL": false,
|
||||
"dBSPLcalibration": 90,
|
||||
"maikiwiAudio": {
|
||||
"ciderPPE": false,
|
||||
"ciderPPE_value": "MAIKIWI",
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
import * as RPC from 'discord-rpc'
|
||||
import {AutoClient} from 'discord-auto-rpc'
|
||||
import {ipcMain} from "electron";
|
||||
import fetch from 'electron-fetch'
|
||||
|
||||
export default class DiscordRPC {
|
||||
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _app: any;
|
||||
private _attributes: any;
|
||||
private _connection: boolean = false;
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'Discord Rich Presence';
|
||||
public description: string = 'Discord RPC plugin for Cider';
|
||||
public version: string = '1.0.0';
|
||||
public version: string = '1.1.0';
|
||||
public author: string = 'vapormusic/Core (Cider Collective)';
|
||||
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _attributes: any;
|
||||
private ready: boolean = false;
|
||||
|
||||
/**
|
||||
* Plugin Initialization
|
||||
*/
|
||||
private _client: any = null;
|
||||
private _activity: RPC.Presence = {
|
||||
private _activityCache: any = {
|
||||
details: '',
|
||||
state: '',
|
||||
largeImageKey: '',
|
||||
|
@ -34,15 +33,72 @@ export default class DiscordRPC {
|
|||
instance: false
|
||||
};
|
||||
|
||||
private _activityCache: RPC.Presence = {
|
||||
details: '',
|
||||
state: '',
|
||||
largeImageKey: '',
|
||||
largeImageText: '',
|
||||
smallImageKey: '',
|
||||
smallImageText: '',
|
||||
instance: false
|
||||
};
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(_win: any): void {
|
||||
const self = this
|
||||
this.connect();
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
ipcMain.on('updateRPCImage', (_event, imageurl) => {
|
||||
if (!this._utils.getStoreValue("general.privateEnabled")) {
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
|
||||
method: 'POST',
|
||||
body: JSON.stringify({url: imageurl}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': _win.webContents.getUserAgent()
|
||||
},
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url
|
||||
self.setActivity(self._attributes)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.setActivity(attributes)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.setActivity(attributes)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
|
@ -58,58 +114,86 @@ export default class DiscordRPC {
|
|||
}
|
||||
const clientId = this._utils.getStoreValue("general.discordrpc.client") === "Cider" ? '911790844204437504' : '886578863147192350';
|
||||
|
||||
// Apparently needed for ask to join, join, spectate etc.
|
||||
RPC.register(clientId)
|
||||
|
||||
// Create the client
|
||||
this._client = new RPC.Client({transport: "ipc"});
|
||||
this._client = new AutoClient({transport: "ipc"});
|
||||
|
||||
// Runs on Ready
|
||||
this._client.on('ready', () => {
|
||||
this._client.once('ready', () => {
|
||||
console.info(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${this._client.user.id}.`);
|
||||
|
||||
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
|
||||
console.info(`[DiscordRPC][connect] Restoring activity cache.`);
|
||||
this._client.setActivity(this._activityCache)
|
||||
}
|
||||
})
|
||||
|
||||
// Handles Errors
|
||||
this._client.on('error', (err: any) => {
|
||||
console.error(`[DiscordRPC] ${err}`);
|
||||
this.disconnect()
|
||||
});
|
||||
|
||||
// If Discord is closed, allow reconnecting
|
||||
this._client.transport.once('close', () => {
|
||||
console.info(`[DiscordRPC] Connection closed`);
|
||||
this.disconnect()
|
||||
});
|
||||
|
||||
// Login to Discord
|
||||
this._client.login({clientId})
|
||||
this._client.endlessLogin({clientId: clientId})
|
||||
.then(() => {
|
||||
this._connection = true;
|
||||
this.ready = true
|
||||
})
|
||||
.catch((e: any) => console.error(`[DiscordRPC][connect] ${e}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from Discord RPC
|
||||
* Sets the activity
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
private disconnect() {
|
||||
private setActivity(attributes: any) {
|
||||
if (!this._client) {
|
||||
return
|
||||
}
|
||||
|
||||
this._client.destroy().then(() => {
|
||||
this._connection = false;
|
||||
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
|
||||
}).catch((e: any) => console.error(`[DiscordRPC][disconnect] ${e}`));
|
||||
// Check if show buttons is (true) or (false)
|
||||
let activity: Object = {
|
||||
details: this._utils.getStoreValue("general.discordrpc.details_format"),
|
||||
state: this._utils.getStoreValue("general.discordrpc.state_format"),
|
||||
largeImageKey: attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024'),
|
||||
largeImageText: attributes.albumName,
|
||||
instance: false // Whether the activity is in a game session
|
||||
}
|
||||
|
||||
// Clean up, allow creating a new connection
|
||||
this._client = null;
|
||||
// Filter the activity
|
||||
activity = this.filterActivity(activity, attributes)
|
||||
|
||||
if (!this.ready) {
|
||||
this._activityCache = activity
|
||||
return
|
||||
}
|
||||
|
||||
// Set the activity
|
||||
if (!attributes.status && this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
|
||||
this._client.clearActivity()
|
||||
} else if (activity && this._activityCache !== activity) {
|
||||
this._client.setActivity(activity)
|
||||
}
|
||||
this._activityCache = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the Discord activity object
|
||||
*/
|
||||
private static filterActivity(activity: any, attributes: any): Object {
|
||||
private filterActivity(activity: any, attributes: any): Object {
|
||||
|
||||
// Add the buttons if people want them
|
||||
if (!this._utils.getStoreValue("general.discordrpc.hide_buttons")) {
|
||||
activity.buttons = [
|
||||
{label: 'Listen on Cider', url: attributes.url.cider},
|
||||
{label: 'View on Apple Music', url: attributes.url.appleMusic}
|
||||
] //To change attributes.url => preload/cider-preload.js
|
||||
}
|
||||
|
||||
// Add the timestamp if its playing
|
||||
if (attributes.status) {
|
||||
activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime)
|
||||
activity.endTimestamp = attributes.endTime
|
||||
}
|
||||
|
||||
// If the user wants to keep the activity when paused
|
||||
if (!this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
|
||||
activity.smallImageKey = attributes.status ? 'play' : 'pause';
|
||||
activity.smallImageText = attributes.status ? 'Playing' : 'Paused';
|
||||
}
|
||||
|
||||
/**
|
||||
* Works with:
|
||||
|
@ -173,138 +257,4 @@ export default class DiscordRPC {
|
|||
}
|
||||
return activity
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity
|
||||
* @param {activity} activity
|
||||
*/
|
||||
private setActivity(activity: any) {
|
||||
if (!this._connection || !this._client || !activity) {
|
||||
return
|
||||
}
|
||||
|
||||
// Filter the activity
|
||||
activity = DiscordRPC.filterActivity(activity, this._attributes)
|
||||
|
||||
// Set the activity
|
||||
if (!this._attributes.status && this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
|
||||
this._client.clearActivity()
|
||||
} else if (this._activity && this._activityCache !== this._activity && this._activity.details) {
|
||||
this._client.setActivity(activity)
|
||||
this._activityCache = this._activity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity of the client
|
||||
* @param {object} attributes
|
||||
*/
|
||||
private updateActivity(attributes: any) {
|
||||
if (!this._utils.getStoreValue("general.discordrpc.enabled") || this._utils.getStoreValue("general.privateEnabled")) {
|
||||
return
|
||||
} else if (!this._client || !this._connection) {
|
||||
this.connect()
|
||||
}
|
||||
|
||||
// Check if show buttons is (true) or (false)
|
||||
this._activity = {
|
||||
details: this._utils.getStoreValue("general.discordrpc.details_format"),
|
||||
state: this._utils.getStoreValue("general.discordrpc.state_format"),
|
||||
largeImageKey: attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024'),
|
||||
largeImageText: attributes.albumName,
|
||||
instance: false // Whether the activity is in a game session
|
||||
}
|
||||
|
||||
// Add the buttons if people want them
|
||||
if (!this._utils.getStoreValue("general.discordrpc.hide_buttons")) {
|
||||
this._activity.buttons = [
|
||||
{label: 'Listen on Cider', url: attributes.url.cider},
|
||||
{label: 'View on Apple Music', url: attributes.url.appleMusic}
|
||||
] //To change attributes.url => preload/cider-preload.js
|
||||
}
|
||||
|
||||
// Add the timestamp if its playing
|
||||
if (attributes.status) {
|
||||
this._activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime)
|
||||
this._activity.endTimestamp = attributes.endTime
|
||||
}
|
||||
|
||||
// If the user wants to keep the activity when paused
|
||||
if (!this._utils.getStoreValue("general.discordrpc.clear_on_pause")) {
|
||||
this._activity.smallImageKey = attributes.status ? 'play' : 'pause';
|
||||
this._activity.smallImageText = attributes.status ? 'Playing' : 'Paused';
|
||||
}
|
||||
|
||||
this.setActivity(this._activity)
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: { getStore: () => any; getApp: () => any; }) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
this._app = utils.getApp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(_win: any): void {
|
||||
let self = this
|
||||
this.connect();
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
ipcMain.on('updateRPCImage', (_event, imageurl) => {
|
||||
if (!this._utils.getStoreValue("general.privateEnabled")) {
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
|
||||
method: 'POST',
|
||||
body: JSON.stringify({url: imageurl}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': _win.webContents.getUserAgent()
|
||||
},
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url
|
||||
self.updateActivity(self._attributes)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
if (this._client) {
|
||||
this.disconnect()
|
||||
}
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.updateActivity(attributes)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.updateActivity(attributes)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ export default class RAOP {
|
|||
this.portairplay = ipport;
|
||||
this.device = this.airtunes.add(ipv4, {
|
||||
port: ipport,
|
||||
volume: 60,
|
||||
volume: 50,
|
||||
password: sepassword,
|
||||
txt: txt
|
||||
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 373 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 364 B |
|
@ -364,6 +364,21 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.cd-mediaitem-list-item .heart-unfilled {
|
||||
background-image: url("assets/feather/heart.svg");
|
||||
height: 12px;
|
||||
width: 36px;
|
||||
filter: contrast(0);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.cd-mediaitem-list-item .heart-filled {
|
||||
background-image: url("assets/feather/heart-fill.svg");
|
||||
height: 12px;
|
||||
width: 36px;
|
||||
filter: contrast(0);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.cd-mediaitem-list-item .explicit-icon {
|
||||
background-image: url("assets/explicit.svg");
|
||||
height: 12px;
|
||||
|
@ -372,9 +387,7 @@
|
|||
background-repeat: no-repeat;
|
||||
}
|
||||
.heart-icon {
|
||||
position: absolute;
|
||||
filter: contrast(0);
|
||||
background-repeat: no-repeat;
|
||||
display: flex
|
||||
}
|
||||
@keyframes load-bar {
|
||||
10% {
|
||||
|
|
|
@ -359,7 +359,11 @@
|
|||
align-items: center;
|
||||
border-radius: var(--mediaItemRadius);
|
||||
position: relative;
|
||||
|
||||
&:hover{
|
||||
.heart-icon{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.popular {
|
||||
background-image: url(assets/star.svg);
|
||||
background-repeat: no-repeat;
|
||||
|
@ -448,6 +452,22 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.heart-unfilled {
|
||||
-webkit-mask-image: url("assets/feather/heart.svg");
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.heart-filled {
|
||||
-webkit-mask-image: url("assets/feather/heart-fill.svg");
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.explicit-icon {
|
||||
background-image: url("./assets/explicit.svg");
|
||||
height: 12px;
|
||||
|
@ -457,10 +477,9 @@
|
|||
}
|
||||
|
||||
.heart-icon {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right:0;
|
||||
filter: contrast(0);
|
||||
background-repeat: no-repeat;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
/* CSS.gg
|
||||
|
|
|
@ -353,7 +353,7 @@
|
|||
|
||||
&:hover {
|
||||
&::before {
|
||||
transition: transform .1s ease-in, opacity .1s ease-in;
|
||||
transition: transform 0s ease-in, opacity 0s ease-in;
|
||||
opacity : 1;
|
||||
transform : scale(1);
|
||||
}
|
||||
|
|
|
@ -488,63 +488,79 @@
|
|||
|
||||
/* Album / Playlist Page */
|
||||
.playlist-page {
|
||||
--bgColor : transparent;
|
||||
padding : 0px;
|
||||
--bgColor : transparent;
|
||||
padding : 0px;
|
||||
//background: linear-gradient(180deg, var(--bgColor) 32px, var(--bgColor) 18px, transparent 60px, transparent 100%);
|
||||
top : 0;
|
||||
padding-top : var(--navigationBarHeight);
|
||||
display:flex;
|
||||
top : 0;
|
||||
padding-top : var(--navigationBarHeight);
|
||||
display : flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
height : 100%;
|
||||
overflow : hidden;
|
||||
|
||||
.cd-mediaitem-list-item {
|
||||
&:hover {
|
||||
.heart-icon {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.heart-icon {
|
||||
left: -25px;
|
||||
}
|
||||
}
|
||||
|
||||
.editTracksBtn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1;
|
||||
top : 20px;
|
||||
right : 20px;
|
||||
z-index : 1;
|
||||
|
||||
>span {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap : 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.mediaContainer {
|
||||
transition: width 0.5s ease-in-out, height 0.5s ease-in-out;
|
||||
width: 260px;height:260px;
|
||||
width : 260px;
|
||||
height : 260px;
|
||||
}
|
||||
|
||||
.playlist-body {
|
||||
padding : 32px;
|
||||
padding : 32px;
|
||||
// margin-top: -75px;
|
||||
overflow-y:overlay;
|
||||
height:100%;
|
||||
padding:0px;
|
||||
overflow-y : overlay;
|
||||
height : 100%;
|
||||
padding : 0px;
|
||||
background-color: var(--color1);
|
||||
|
||||
&.scrollbody {
|
||||
.tabs {
|
||||
display: flex;
|
||||
display : flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
height : 100%;
|
||||
|
||||
.nav-link {
|
||||
text-transform:capitalize;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
height: 100%;
|
||||
height : 100%;
|
||||
overflow: hidden;
|
||||
margin:0px;
|
||||
margin : 0px;
|
||||
|
||||
.tab-pane {
|
||||
height: 100%;
|
||||
overflow-y: overlay;
|
||||
overflow-x:hidden;
|
||||
padding: var(--contentInnerPadding);
|
||||
height : 100%;
|
||||
overflow-y : overlay;
|
||||
overflow-x : hidden;
|
||||
padding : var(--contentInnerPadding);
|
||||
padding-inline : 40px;
|
||||
-webkit-mask-image: linear-gradient(180deg, transparent, white 20px);
|
||||
|
||||
.well {
|
||||
margin:0px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -563,7 +579,7 @@
|
|||
background : rgba(0, 0, 0, 0.25);
|
||||
top : var(--navigationBarHeight);
|
||||
transition : opacity 0.1s var(--appleEase);
|
||||
display: none;
|
||||
display : none;
|
||||
}
|
||||
|
||||
.playlist-display {
|
||||
|
@ -649,14 +665,14 @@
|
|||
}
|
||||
|
||||
.playlist-desc {
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
transition : height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
box-sizing : border-box;
|
||||
font-size : 14px;
|
||||
flex-shrink : unset;
|
||||
margin-right: 5px;
|
||||
max-height : 100px;
|
||||
position : relative;
|
||||
height : 4vh;
|
||||
height : 4vh;
|
||||
|
||||
.content {
|
||||
height : 4vh;
|
||||
|
@ -750,11 +766,11 @@
|
|||
}
|
||||
|
||||
.playlist-time {
|
||||
font-size: 0.9em;
|
||||
margin : 6px;
|
||||
opacity : 0.7;
|
||||
font-size : 0.9em;
|
||||
margin : 6px;
|
||||
opacity : 0.7;
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
height: 0.9em;
|
||||
height : 0.9em;
|
||||
}
|
||||
|
||||
&.inline-playlist {
|
||||
|
@ -802,8 +818,8 @@
|
|||
|
||||
.pilldim {
|
||||
.nav-pills {
|
||||
width: max-content;
|
||||
margin: 0 auto;
|
||||
width : max-content;
|
||||
margin : 0 auto;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -813,26 +829,24 @@
|
|||
transition: min-height 0.5s ease-in-out;
|
||||
min-height: 200px;
|
||||
|
||||
.playlistInfo {
|
||||
|
||||
}
|
||||
.playlistInfo {}
|
||||
|
||||
.mediaContainer {
|
||||
transition: width 0.5s ease-in-out, height 0.5s ease-in-out;
|
||||
width: 128px!important;
|
||||
height: 128px!important;
|
||||
width : 128px !important;
|
||||
height : 128px !important;
|
||||
}
|
||||
|
||||
.playlist-time {
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
height: 0px;
|
||||
opacity: 0;
|
||||
height : 0px;
|
||||
opacity : 0;
|
||||
}
|
||||
|
||||
.playlist-desc {
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
height: 0px!important;
|
||||
opacity: 0;
|
||||
height : 0px !important;
|
||||
opacity : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -920,7 +934,7 @@
|
|||
pointer-events : none;
|
||||
|
||||
.header-content {
|
||||
z-index : 1;
|
||||
z-index : 1;
|
||||
// margin-top: -16px;
|
||||
}
|
||||
|
||||
|
@ -1112,9 +1126,56 @@
|
|||
|
||||
/* Artist Page End */
|
||||
|
||||
// Settings page
|
||||
.settings-page {
|
||||
padding: 0px;
|
||||
|
||||
.installed-themes-page {
|
||||
|
||||
.themeContextMenu {
|
||||
background: transparent;
|
||||
color : var(--keyColor);
|
||||
border : 0px;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
&.addon {
|
||||
background: rgb(86 86 86 / 20%);
|
||||
}
|
||||
&.applied {
|
||||
background: var(--keyColor-disabled);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.repo-header {
|
||||
font-size : 16px;
|
||||
position : sticky;
|
||||
top : 0;
|
||||
left : 0;
|
||||
right : 0;
|
||||
width : 100%;
|
||||
height : 50px;
|
||||
z-index : 1;
|
||||
background : rgba(36, 36, 36, 0.5);
|
||||
display : flex;
|
||||
justify-content: center;
|
||||
align-items : center;
|
||||
backdrop-filter: var(--glassFilter);
|
||||
overflow : hidden;
|
||||
border-bottom : 1px solid rgb(0 0 0 / 18%);
|
||||
border-top : 1px solid rgb(135 135 135 / 18%);
|
||||
}
|
||||
|
||||
.style-editor-container {
|
||||
height : 100%;
|
||||
flex : 1;
|
||||
background: var(--color2);
|
||||
padding : 0px;
|
||||
overflow-y: overlay;
|
||||
|
||||
.list-group-item {
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.stylestack-editor {
|
||||
width: 100%;
|
||||
|
@ -1125,17 +1186,34 @@
|
|||
}
|
||||
|
||||
.themeLabel {
|
||||
display:flex;
|
||||
display : flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.handle {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
|
||||
&:hover {
|
||||
cursor: grab;
|
||||
}
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.removeItem {
|
||||
border: 0px;
|
||||
background: transparent;
|
||||
height: 32px;
|
||||
border : 0px;
|
||||
background : transparent;
|
||||
height : 32px;
|
||||
font-weight: bold;
|
||||
color: var(--textColor);
|
||||
cursor: pointer;
|
||||
color : var(--textColor);
|
||||
cursor : pointer;
|
||||
}
|
||||
|
||||
.stylesDropdown {
|
||||
|
@ -1145,7 +1223,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings page
|
||||
.settings-page {
|
||||
padding: 0px;
|
||||
|
||||
.nav {
|
||||
width : 90%;
|
||||
|
@ -1163,8 +1245,9 @@
|
|||
|
||||
.settings-option-body-webview {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
width : 100%;
|
||||
}
|
||||
|
||||
.settings-option-body {
|
||||
margin: 16px;
|
||||
}
|
||||
|
|
|
@ -148,6 +148,10 @@ const app = new Vue({
|
|||
},
|
||||
tmpHeight: '',
|
||||
tmpWidth: '',
|
||||
tmpX: '',
|
||||
tmpY: '',
|
||||
miniTmpX: '',
|
||||
miniTmpY: '',
|
||||
tmpVar: [],
|
||||
notification: false,
|
||||
chrome: {
|
||||
|
@ -284,6 +288,9 @@ const app = new Vue({
|
|||
}
|
||||
}
|
||||
},
|
||||
formatVolumeTooltip() {
|
||||
return this.cfg.audio.dBSPL ? (Number(this.cfg.audio.dBSPLcalibration) + (Math.log10(this.mk.volume) * 20)).toFixed(2) + ' dB SPL' : (Math.log10(this.mk.volume) * 20).toFixed(2) + ' dBFS'
|
||||
},
|
||||
mainMenuVisibility(val) {
|
||||
if (val) {
|
||||
(this.mk.isAuthorized) ? this.chrome.menuOpened = !this.chrome.menuOpened : false;
|
||||
|
@ -593,9 +600,7 @@ const app = new Vue({
|
|||
},
|
||||
async init() {
|
||||
let self = this
|
||||
if (this.cfg.visual.theme != "default.less" && this.cfg.visual.theme != "") {
|
||||
this.setTheme(this.cfg.visual.theme)
|
||||
}
|
||||
|
||||
if (this.cfg.visual.styles.length != 0) {
|
||||
await this.reloadStyles()
|
||||
}
|
||||
|
@ -703,6 +708,7 @@ const app = new Vue({
|
|||
let lastItem = window.localStorage.getItem("currentTrack")
|
||||
let time = window.localStorage.getItem("currentTime")
|
||||
let queue = window.localStorage.getItem("currentQueue")
|
||||
app.mk.queue.position = 0; // Reset queue position.
|
||||
if (lastItem != null) {
|
||||
lastItem = JSON.parse(lastItem)
|
||||
let kind = lastItem.attributes.playParams.kind;
|
||||
|
@ -722,7 +728,7 @@ const app = new Vue({
|
|||
if (queue != null) {
|
||||
queue = JSON.parse(queue)
|
||||
if (queue && queue.length > 0) {
|
||||
let ids = queue.map(e => (e.playParams ? e.playParams.id : (e.attributes.playParams ? e.attributes.playParams.id : '')))
|
||||
let ids = queue.map(e => (e.playParams ? e.playParams.id : (e.item.attributes.playParams ? e.item.attributes.playParams.id : '')))
|
||||
let i = 0;
|
||||
if (ids.length > 0) {
|
||||
for (let id of ids) {
|
||||
|
@ -833,6 +839,14 @@ const app = new Vue({
|
|||
ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
|
||||
})
|
||||
|
||||
this.mk.addEventListener(MusicKit.Events.queueItemsDidChange, ()=>{
|
||||
if (self.$refs.queue) {
|
||||
setTimeout(()=>{
|
||||
self.$refs.queue.updateQueue();
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
|
||||
this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => {
|
||||
if (self.$refs.queue) {
|
||||
self.$refs.queue.updateQueue();
|
||||
|
@ -1148,8 +1162,10 @@ const app = new Vue({
|
|||
|
||||
async function deepScan(parent = "p.playlistsroot") {
|
||||
console.debug(`scanning ${parent}`)
|
||||
const playlistData = await app.mk.api.v3.music(`/v1/me/library/playlist-folders/${parent}/children/`)
|
||||
await asyncForEach(playlistData.data.data, async (playlist) => {
|
||||
// const playlistData = await app.mk.api.v3.music(`/v1/me/library/playlist-folders/${parent}/children/`)
|
||||
const playlistData = await MusicKitTools.v3Continuous({href: `/v1/me/library/playlist-folders/${parent}/children/`})
|
||||
console.log(playlistData)
|
||||
await asyncForEach(playlistData, async (playlist) => {
|
||||
playlist.parent = parent
|
||||
if (
|
||||
playlist.type != "library-playlist-folders" &&
|
||||
|
@ -4107,13 +4123,19 @@ const app = new Vue({
|
|||
if (flag) {
|
||||
this.tmpWidth = window.innerWidth;
|
||||
this.tmpHeight = window.innerHeight;
|
||||
this.tmpX = window.screenX;
|
||||
this.tmpY = window.screenY;
|
||||
ipcRenderer.send('unmaximize');
|
||||
ipcRenderer.send('windowmin', 250, 250)
|
||||
if (this.miniTmpX !== '' && this.miniTmpY !== '') ipcRenderer.send('windowmove', this.miniTmpX, this.miniTmpY)
|
||||
ipcRenderer.send('windowresize', 300, 300, false)
|
||||
app.appMode = 'mini';
|
||||
} else {
|
||||
this.miniTmpX = window.screenX;
|
||||
this.miniTmpY = window.screenY;
|
||||
ipcRenderer.send('windowmin', 844, 410)
|
||||
ipcRenderer.send('windowresize', this.tmpWidth, this.tmpHeight, false)
|
||||
ipcRenderer.send('windowmove', this.tmpX, this.tmpY)
|
||||
ipcRenderer.send('windowontop', false)
|
||||
//this.cfg.visual.miniplayer_top_toggle = true;
|
||||
app.appMode = 'player';
|
||||
|
|
|
@ -12944,6 +12944,7 @@ body[platform='darwin'] #window-controls-container {
|
|||
}
|
||||
body[platform='darwin'] .app-chrome .app-chrome-item > .app-mainmenu {
|
||||
opacity: 0;
|
||||
width: 52px;
|
||||
pointer-events: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
|
|
@ -3289,6 +3289,7 @@ body[platform='darwin'] {
|
|||
|
||||
.app-chrome .app-chrome-item > .app-mainmenu {
|
||||
opacity: 0;
|
||||
width: 52px;
|
||||
pointer-events: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
:class="{'active': this.cfg.audio.volume == 0}"></button>
|
||||
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume"
|
||||
v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()"
|
||||
v-b-tooltip.hover :title="`${(Math.log10(mk.volume) * 20).toFixed(2)} dB`">
|
||||
v-b-tooltip.hover :title="formatVolumeTooltip()">
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small miniplayer"
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
:class="{'active': this.cfg.audio.volume == 0}"></button>
|
||||
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume"
|
||||
v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()"
|
||||
v-b-tooltip.hover :title="`${(Math.log10(mk.volume) * 20).toFixed(2)} dB`">
|
||||
v-b-tooltip.hover :title="formatVolumeTooltip()">
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small miniplayer"
|
||||
|
|
|
@ -216,7 +216,7 @@
|
|||
<input type="range" class="" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0"
|
||||
:max="cfg.audio.maxVolume" v-model="mk.volume" v-if="typeof mk.volume != 'undefined'"
|
||||
@change="checkMuteChange()" v-b-tooltip.hover
|
||||
:title="`${(Math.log10(mk.volume) * 20).toFixed(2)} dB`">
|
||||
:title="formatVolumeTooltip()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="md-option-segment md-option-segment_auto percent">
|
||||
<input type="number"
|
||||
style="width: 100%; text-align: center; margin-right: 5px;" min="0"
|
||||
step="5" v-model="volume"/>
|
||||
step="2" v-model="volume"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-option-line">
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
<button class="volume-button--small volume" @click="app.muteButtonPressed()" :class="{'active': app.cfg.audio.volume == 0}"></button>
|
||||
<input type="range" class="slider" @wheel="app.volumeWheel" :step="app.cfg.audio.volumeStep" min="0" :max="app.cfg.audio.maxVolume" v-model="app.mk.volume"
|
||||
v-if="typeof app.mk.volume != 'undefined'" @change="app.checkMuteChange()"
|
||||
v-b-tooltip.hover :title="`${(Math.log10(app.mk.volume) * 20).toFixed(2)} dB`">
|
||||
v-b-tooltip.hover :title="$root.formatVolumeTooltip()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -65,8 +65,9 @@
|
|||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="heart-icon" v-if="isLoved">
|
||||
<div class="svg-icon" :style="{'--url': 'url(./assets/feather/heart-fill.svg)'}"></div>
|
||||
<div class="heart-icon" v-if="!(app.mk.isPlaying && (((app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id )) == itemId) || (app.mk.nowPlayingItem.id == item.id)))">
|
||||
<!-- <div class="heart-unfilled" v-if="isLoved == false" :style="{'--url': 'url(./assets/feather/heart.svg)'}" /> -->
|
||||
<div class="heart-filled" v-if="isLoved == true" :style="{'--url': 'url(./assets/feather/heart-fill.svg)'}" />
|
||||
</div>
|
||||
<div class="explicit-icon" v-if="item.attributes && item.attributes.contentRating == 'explicit'"></div>
|
||||
<template v-if="showMetaData == true" @dblclick="route()">
|
||||
|
|
368
src/renderer/views/pages/installed-themes.ejs
Normal file
368
src/renderer/views/pages/installed-themes.ejs
Normal file
|
@ -0,0 +1,368 @@
|
|||
<script type="text/x-template" id="installed-themes">
|
||||
<div class="content-inner github-themes-page installed-themes-page">
|
||||
<div class="gh-header">
|
||||
<div class="row">
|
||||
<div class="col nopadding">
|
||||
<h1 class="header-text">
|
||||
{{ $root.getLz("settings.option.visual.theme.manageStyles") }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-auto nopadding flex-center">
|
||||
<button class="md-btn md-btn-small md-btn-block" @click="$root.appRoute('themes-github')">
|
||||
{{$root.getLz('settings.option.visual.theme.github.explore')}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto flex-center">
|
||||
<button class="md-btn md-btn-small md-btn-block" @click="$root.checkForThemeUpdates()">
|
||||
{{ $root.getLz('settings.option.visual.theme.checkForUpdates') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto nopadding flex-center">
|
||||
<button class="md-btn md-btn-small md-btn-block" @click="openThemesFolder()">
|
||||
{{$root.getLz('settings.option.visual.theme.github.openfolder')}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-content">
|
||||
<div class="repos-list">
|
||||
<div class="repo-header">
|
||||
<h4>Available</h4>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<template v-for="theme in themes">
|
||||
<li @click="addStyle(theme.file)"
|
||||
@contextmenu="contextMenu($event, theme)"
|
||||
class="list-group-item list-group-item-dark"
|
||||
:class="{'applied': $root.cfg.visual.styles.includes(theme.file)}">
|
||||
|
||||
<b-row>
|
||||
<b-col class="themeLabel">{{theme.name}}</b-col>
|
||||
<template v-if="$root.cfg.visual.styles.includes(theme.file)">
|
||||
<b-col sm="auto" v-if="theme.pack">
|
||||
<button class="themeContextMenu codicon codicon-package"></button>
|
||||
</b-col>
|
||||
<b-col sm="auto">
|
||||
<button class="themeContextMenu codicon codicon-check"></button>
|
||||
</b-col>
|
||||
</template>
|
||||
<template v-else>
|
||||
<b-col sm="auto" v-if="theme.pack">
|
||||
<button class="themeContextMenu codicon codicon-package"></button>
|
||||
</b-col>
|
||||
<b-col sm="auto">
|
||||
<button @click.stop="contextMenu($event, theme)" class="themeContextMenu codicon codicon-list-unordered"></button>
|
||||
</b-col>
|
||||
</template>
|
||||
|
||||
</b-row>
|
||||
</li>
|
||||
<li @click="addStyle(packEntry.file)"
|
||||
@contextmenu="contextMenu($event, theme)"
|
||||
class="list-group-item list-group-item-dark addon"
|
||||
v-for="packEntry in theme.pack"
|
||||
:class="{'applied': $root.cfg.visual.styles.includes(packEntry.file)}"
|
||||
v-if="theme.pack">
|
||||
|
||||
<b-row>
|
||||
<b-col class="themeLabel">{{packEntry.name}}</b-col>
|
||||
<template v-if="$root.cfg.visual.styles.includes(packEntry.file)">
|
||||
<b-col sm="auto">
|
||||
<button class="themeContextMenu codicon codicon-check"></button>
|
||||
</b-col>
|
||||
</template>
|
||||
<template v-else>
|
||||
<b-col sm="auto">
|
||||
<button class="themeContextMenu codicon codicon-diff-added"></button>
|
||||
</b-col>
|
||||
</template>
|
||||
</b-row>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="style-editor-container">
|
||||
<div class="repo-header">
|
||||
<h4>Applied</h4>
|
||||
</div>
|
||||
<stylestack-editor ref="stackEditor" v-if="themes.length != 0" :themes="themes"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// do not translate
|
||||
Vue.component('stylestack-editor', {
|
||||
/*html*/
|
||||
template: `
|
||||
<div class="stylestack-editor" >
|
||||
<draggable class="list-group" v-model="$root.cfg.visual.styles" @end="$root.reloadStyles()">
|
||||
<b-list-group-item variant="dark" v-for="theme in $root.cfg.visual.styles" :key="theme">
|
||||
<b-row>
|
||||
<b-col sm="auto">
|
||||
<div class="handle codicon codicon-grabber"></div>
|
||||
</b-col>
|
||||
<b-col class="themeLabel">{{getThemeName(theme)}}</b-col>
|
||||
<b-col sm="auto">
|
||||
<button class="removeItem codicon codicon-close" @click="remove(theme)"></button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-list-group-item>
|
||||
</draggable>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
themes: {
|
||||
type: Array,
|
||||
default: [],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
selected: null,
|
||||
newTheme: null,
|
||||
themeList: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.themes)
|
||||
this.themeList = [...this.themes]
|
||||
|
||||
this.themeList.forEach(theme => {
|
||||
if (theme.pack) {
|
||||
theme.pack.forEach(packEntry => {
|
||||
packEntry.file = theme.file.replace('index.less', '') + packEntry.file
|
||||
this.themeList.push(packEntry)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
gitHubExplore() {
|
||||
this.$root.appRoute("themes-github")
|
||||
},
|
||||
getThemeName(filename) {
|
||||
try {
|
||||
return this.themeList.find(theme => theme.file === filename).name;
|
||||
} catch (e) {
|
||||
return filename;
|
||||
}
|
||||
},
|
||||
moveUp() {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
const index = styles.indexOf(this.selected)
|
||||
if (index > 0) {
|
||||
styles.splice(index, 1)
|
||||
styles.splice(index - 1, 0, this.selected)
|
||||
}
|
||||
this.$root.reloadStyles()
|
||||
},
|
||||
moveDown() {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
const index = styles.indexOf(this.selected)
|
||||
if (index < styles.length - 1) {
|
||||
styles.splice(index, 1)
|
||||
styles.splice(index + 1, 0, this.selected)
|
||||
}
|
||||
this.$root.reloadStyles()
|
||||
},
|
||||
remove(style) {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
const index = styles.indexOf(style)
|
||||
styles.splice(index, 1)
|
||||
this.$root.reloadStyles()
|
||||
},
|
||||
addStyle(style) {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
styles.push(style)
|
||||
this.$root.reloadStyles()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
Vue.component('installed-themes', {
|
||||
template: "#installed-themes",
|
||||
props: [],
|
||||
data: function () {
|
||||
return {
|
||||
repos: [],
|
||||
openRepo: {
|
||||
id: -1,
|
||||
name: '',
|
||||
description: '',
|
||||
html_url: '',
|
||||
stargazers_count: 0,
|
||||
owner: {
|
||||
avatar_url: ''
|
||||
},
|
||||
readme: ""
|
||||
},
|
||||
themesInstalled: [],
|
||||
themes: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getThemesList();
|
||||
},
|
||||
methods: {
|
||||
getThemesList() {
|
||||
let self = this
|
||||
let themes = ipcRenderer.sendSync("get-themes")
|
||||
themes.unshift({
|
||||
name: "Acrylic Grain",
|
||||
file: "grain.less"
|
||||
})
|
||||
themes.unshift({
|
||||
name: "Sweetener",
|
||||
file: "sweetener.less"
|
||||
})
|
||||
themes.unshift({
|
||||
name: "Reduce Visuals",
|
||||
file: "reduce_visuals.less"
|
||||
})
|
||||
themes.unshift({
|
||||
name: "Inline Drawer",
|
||||
file: "inline_drawer.less"
|
||||
})
|
||||
themes.unshift({
|
||||
name: "Dark",
|
||||
file: "dark.less"
|
||||
})
|
||||
this.themes = themes
|
||||
},
|
||||
contextMenu(event, theme) {
|
||||
let self = this
|
||||
let menu = {
|
||||
items: {
|
||||
"uninstall": {
|
||||
name: app.getLz("settings.option.visual.theme.uninstall"),
|
||||
disabled: true,
|
||||
action: () => {
|
||||
bootbox.confirm(app.stringTemplateParser(app.getLz("settings.prompt.visual.theme.uninstallTheme"), {
|
||||
theme: theme.name ?? theme.file
|
||||
}), (res) => {
|
||||
if (res) {
|
||||
console.debug(theme)
|
||||
ipcRenderer.once("theme-uninstalled", (event, args) => {
|
||||
console.debug(event, args)
|
||||
self.getThemesList()
|
||||
})
|
||||
ipcRenderer.invoke("uninstall-theme", theme.path)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
"viewInfo": {
|
||||
name: app.getLz("settings.option.visual.theme.viewInfo"),
|
||||
disabled: true,
|
||||
action: () => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (theme.path) {
|
||||
menu.items.uninstall.disabled = false
|
||||
}
|
||||
this.$root.showMenuPanel(menu, event)
|
||||
},
|
||||
openThemesFolder() {
|
||||
ipcRenderer.invoke("open-path", "themes")
|
||||
},
|
||||
getInstalledThemes() {
|
||||
let self = this
|
||||
const themes = ipcRenderer.sendSync("get-themes")
|
||||
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
|
||||
themes.forEach(theme => {
|
||||
if (theme.github_repo !== "" && typeof theme.commit != "") {
|
||||
self.themesInstalled.push(theme.github_repo.toLowerCase())
|
||||
}
|
||||
})
|
||||
},
|
||||
addStyle(filename) {
|
||||
this.$refs.stackEditor.addStyle(filename)
|
||||
},
|
||||
showRepo(repo) {
|
||||
const self = this
|
||||
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
fetch(readmeUrl, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
self.openRepo = repo
|
||||
self.openRepo.readme = self.convertReadMe(result);
|
||||
})
|
||||
.catch(error => {
|
||||
self.openRepo = repo
|
||||
self.openRepo.readme = `This repository doesn't have a README.md file.`;
|
||||
console.log('error', error)
|
||||
});
|
||||
},
|
||||
convertReadMe(text) {
|
||||
return marked.parse(text)
|
||||
},
|
||||
installThemeRepo(repo) {
|
||||
let self = this
|
||||
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.theme.github.install.confirm'), {
|
||||
repo: repo.full_name
|
||||
});
|
||||
bootbox.confirm(msg, (res) => {
|
||||
if (res) {
|
||||
ipcRenderer.once("theme-installed", (event, arg) => {
|
||||
if (arg.success) {
|
||||
self.themes = ipcRenderer.sendSync("get-themes")
|
||||
self.getInstalledThemes()
|
||||
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
|
||||
} else {
|
||||
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
|
||||
}
|
||||
});
|
||||
ipcRenderer.invoke("get-github-theme", repo.html_url)
|
||||
}
|
||||
})
|
||||
},
|
||||
installThemeURL() {
|
||||
let self = this
|
||||
bootbox.prompt(app.getLz('settings.prompt.visual.theme.github.URL'), (result) => {
|
||||
if (result) {
|
||||
ipcRenderer.once("theme-installed", (event, arg) => {
|
||||
if (arg.success) {
|
||||
self.themes = ipcRenderer.sendSync("get-themes")
|
||||
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
|
||||
} else {
|
||||
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
|
||||
}
|
||||
});
|
||||
ipcRenderer.invoke("get-github-theme", result)
|
||||
}
|
||||
});
|
||||
},
|
||||
getRepos() {
|
||||
let self = this
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true", requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
let items = JSON.parse(result).items
|
||||
self.repos = items
|
||||
})
|
||||
.catch(error => console.log('error', error));
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -22,7 +22,7 @@
|
|||
<hr>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>{{ loaded.attributes.listenTimeInMinutes }} {{$root.getLz('term.time.minutes')}}</h4>
|
||||
<h4>{{ convertToHours(loaded.attributes.listenTimeInMinutes) }} {{$root.getLz('term.time.hours')}}</h4>
|
||||
<h4>{{ loaded.attributes.uniqueAlbumCount }} {{$root.getLz('term.uniqueAlbums')}}</h4>
|
||||
<h4>{{ loaded.attributes.uniqueArtistCount }} {{$root.getLz('term.uniqueArtists')}}</h4>
|
||||
<h4>{{ loaded.attributes.uniqueSongCount }} {{$root.getLz('term.uniqueSongs')}}</h4>
|
||||
|
@ -40,7 +40,7 @@
|
|||
<mediaitem-square :item="artistData.relationships.artist.data[0]"></mediaitem-square>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
{{ artistData.attributes.listenTimeInMinutes }} {{$root.getLz('term.time.minutes', {'count': artistData.attributes.listenTimeInMinutes})}}<br>
|
||||
{{ convertToHours(artistData.attributes.listenTimeInMinutes) }} {{$root.getLz('term.time.hours', {'count': convertToHours(artistData.attributes.listenTimeInMinutes) })}}<br>
|
||||
{{$root.getLz('term.listenedTo')}} {{ artistData.attributes.playCount }} {{$root.getLz('term.times')}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,7 +55,7 @@
|
|||
<mediaitem-square :item="albumData.relationships.album.data[0]"></mediaitem-square>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
{{ albumData.attributes.listenTimeInMinutes }} {{$root.getLz('term.time.minutes', {'count': albumData.attributes.listenTimeInMinutes})}}<br>
|
||||
{{ convertToHours(albumData.attributes.listenTimeInMinutes) }} {{$root.getLz('term.time.hours', {'count': convertToHours(albumData.attributes.listenTimeInMinutes)})}}<br>
|
||||
{{ albumData.attributes.playCount }} {{$root.getLz('term.plays')}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -151,6 +151,9 @@
|
|||
let playlist = await app.mk.api.v3.music(replayData.relationships.playlist.data[0].href, {extend: "editorialArtwork,editorialVideo"})
|
||||
replayData.playlist = playlist.data.data[0]
|
||||
this.loaded = replayData
|
||||
},
|
||||
convertToHours(minutes) {
|
||||
return Math.floor(minutes / 60)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -441,6 +441,30 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-option-line" v-show="app.cfg.advanced.AudioContext && app.cfg.audio.normalization">
|
||||
<div class="md-option-segment">
|
||||
dB SPL Display
|
||||
<br>
|
||||
<small>(Advanced users only) Display dB SPL instead of dBFS on the volume slider.</small>
|
||||
</div>
|
||||
<div class="md-option-segment md-option-segment_auto">
|
||||
<label>
|
||||
<input type="checkbox" v-model="app.cfg.audio.dBSPL" switch/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-option-line" v-show="app.cfg.audio.dBSPL">
|
||||
<div class="md-option-segment">
|
||||
0 dBFS Calibration
|
||||
<br>
|
||||
<small>Enter the peak Z-weighted dB SPL when Cider is at 0 dBFS.</small>
|
||||
</div>
|
||||
<div class="md-option-segment md-option-segment_auto">
|
||||
<label>
|
||||
<input type="number" v-model="app.cfg.audio.dBSPLcalibration"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-tab>
|
||||
|
@ -456,20 +480,8 @@
|
|||
{{$root.getLz('settings.header.visual.theme')}}
|
||||
</div>
|
||||
<div class="md-option-segment md-option-segment_auto">
|
||||
<label>
|
||||
<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="dark.less">{{$root.getLz('settings.option.visual.theme.dark')}}
|
||||
</option>
|
||||
<option v-for="theme in themes" :value="theme.file">{{ theme.name }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="md-btn md-btn-small md-btn-block" @click="gitHubExplore()"
|
||||
style="margin-top: 8px">
|
||||
{{$root.getLz('settings.option.visual.theme.github.explore')}}
|
||||
<button class="md-btn md-btn-block" @click="$root.appRoute('installed-themes')">
|
||||
{{$root.getLz('settings.option.visual.theme.manageStyles')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1192,19 +1204,6 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md-option-line">
|
||||
<!-- Do not translate -->
|
||||
<div class="md-option-segment">
|
||||
Style Editor<br>
|
||||
<small>Mix and match various theme components to get Cider looking exactly how you
|
||||
want.</small>
|
||||
</div>
|
||||
<div class="md-option-segment">
|
||||
<stylestack-editor :themes="themes"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md-option-line">
|
||||
<div class="md-option-segment">
|
||||
{{$root.getLz('settings.option.experimental.unknownPlugin')}}
|
||||
|
@ -1412,112 +1411,6 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// do not translate
|
||||
Vue.component('stylestack-editor', {
|
||||
/*html*/
|
||||
template: `
|
||||
<div class="stylestack-editor">
|
||||
<draggable class="list-group" v-model="$root.cfg.visual.styles" @end="$root.reloadStyles()">
|
||||
<b-list-group-item variant="dark" v-for="theme in $root.cfg.visual.styles" :key="theme">
|
||||
<b-row>
|
||||
<b-col class="themeLabel">{{getThemeName(theme)}}</b-col>
|
||||
<b-col sm="auto">
|
||||
<button class="removeItem codicon codicon-close" @click="remove(theme)"></button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-list-group-item>
|
||||
<b-list-group-item slot="footer" style="-webkit-user-drag: none" variant="dark">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-dropdown class="stylesDropdown" variant="primary" text="Add Style...">
|
||||
<b-dropdown-item v-for="theme in themeList" @click="addStyle(theme.file)">{{theme.name}}</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-btn @click="gitHubExplore()">{{$root.getLz('settings.option.visual.theme.github.explore')}}</b-btn>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-list-group-item>
|
||||
</draggable>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
themes: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
selected: null,
|
||||
newTheme: null,
|
||||
themeList: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.themeList = [...this.themes]
|
||||
this.themeList.unshift({
|
||||
name: "Acrylic Grain",
|
||||
file: "grain.less"
|
||||
})
|
||||
this.themeList.unshift({
|
||||
name: "Sweetener",
|
||||
file: "sweetener.less"
|
||||
})
|
||||
this.themeList.unshift({
|
||||
name: "Reduce Visuals",
|
||||
file: "reduce_visuals.less"
|
||||
})
|
||||
this.themeList.unshift({
|
||||
name: "Inline Drawer",
|
||||
file: "inline_drawer.less"
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
gitHubExplore() {
|
||||
this.$root.appRoute("themes-github")
|
||||
},
|
||||
getThemeName(filename) {
|
||||
try {
|
||||
return this.themeList.find(theme => theme.file === filename).name;
|
||||
} catch (e) {
|
||||
return filename;
|
||||
}
|
||||
},
|
||||
moveUp() {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
const index = styles.indexOf(this.selected)
|
||||
if (index > 0) {
|
||||
styles.splice(index, 1)
|
||||
styles.splice(index - 1, 0, this.selected)
|
||||
}
|
||||
this.$root.reloadStyles()
|
||||
},
|
||||
moveDown() {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
const index = styles.indexOf(this.selected)
|
||||
if (index < styles.length - 1) {
|
||||
styles.splice(index, 1)
|
||||
styles.splice(index + 1, 0, this.selected)
|
||||
}
|
||||
this.$root.reloadStyles()
|
||||
},
|
||||
remove(style) {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
const index = styles.indexOf(style)
|
||||
styles.splice(index, 1)
|
||||
this.$root.reloadStyles()
|
||||
},
|
||||
addStyle(style) {
|
||||
const styles = this.$root.cfg.visual.styles
|
||||
styles.push(style)
|
||||
this.$root.reloadStyles()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
Vue.component('cider-settings', {
|
||||
template: "#cider-settings",
|
||||
|
@ -1629,10 +1522,10 @@
|
|||
app.cfg.general.keybindings.browse = [app.platform == "darwin" ? "Command" : "Control", "B"];
|
||||
app.cfg.general.keybindings.togglePrivateSession = [app.platform == "darwin" ? "Command" : "Control", "P"];
|
||||
app.cfg.general.keybindings.webRemote = [app.platform == "darwin" ? "Command" : "Control", "W"];
|
||||
app.cfg.general.keybindings.audioSettings = [app.platform == "darwin" ? "Option" : "Shift", "A"];
|
||||
app.cfg.general.keybindings.pluginMenu = [app.platform == "darwin" ? "Option" : "Shift", "P"];
|
||||
app.cfg.general.keybindings.castToDevices = [app.platform == "darwin" ? "Option" : "Shift", "C"];
|
||||
app.cfg.general.keybindings.settings = [app.platform == "darwin" ? "Option" : "Shift", "S"];
|
||||
app.cfg.general.keybindings.audioSettings = [app.platform == "darwin" ? "Option" : "Alt", "A"];
|
||||
app.cfg.general.keybindings.pluginMenu = [app.platform == "darwin" ? "Option" : "Alt", "P"];
|
||||
app.cfg.general.keybindings.castToDevices = [app.platform == "darwin" ? "Option" : "Alt", "C"];
|
||||
app.cfg.general.keybindings.settings = [app.platform == "darwin" ? "Option" : "Alt", "S"];
|
||||
app.cfg.general.keybindings.openDeveloperTools = [app.platform == "darwin" ? "Command" : "Control", app.platform == "darwin" ? "Option" : "Shift", "I"];
|
||||
notyf.success(app.getLz('settings.notyf.general.keybindings.update.success'));
|
||||
bootbox.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {
|
||||
|
|
|
@ -5,17 +5,14 @@
|
|||
<div class="col nopadding">
|
||||
<h1 class="header-text">{{$root.getLz('settings.header.visual.theme.github.page')}}</h1>
|
||||
</div>
|
||||
<div class="col-auto flex-center">
|
||||
<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="dark.less">{{$root.getLz('settings.option.visual.theme.dark')}}</option>
|
||||
<option v-for="theme in themes" :value="theme.file">{{ theme.name }}</option>
|
||||
</select>
|
||||
<div class="col-auto nopadding flex-center">
|
||||
<button class="md-btn md-btn-small md-btn-block" @click="$root.appRoute('installed-themes')">
|
||||
{{$root.getLz('settings.option.visual.theme.manageStyles')}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto flex-center">
|
||||
<button class="md-btn md-btn-small md-btn-block" @click="openThemesFolder()">
|
||||
{{$root.getLz('settings.option.visual.theme.github.openfolder')}}
|
||||
<button class="md-btn md-btn-small md-btn-block" @click="$root.checkForThemeUpdates()">
|
||||
Check for updates
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto nopadding flex-center">
|
||||
|
@ -106,7 +103,6 @@
|
|||
this.themes = ipcRenderer.sendSync("get-themes")
|
||||
this.getRepos();
|
||||
this.getInstalledThemes();
|
||||
app.checkForThemeUpdates()
|
||||
},
|
||||
methods: {
|
||||
openThemesFolder() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue