Merged main branch and updates win.ts with some dumb stuff that still hasn't fixed it
13
package.json
|
@ -32,11 +32,13 @@
|
||||||
"electron-store": "^8.0.1",
|
"electron-store": "^8.0.1",
|
||||||
"electron-updater": "^4.6.1",
|
"electron-updater": "^4.6.1",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"es6-promise": "^4.2.8",
|
|
||||||
"express": "^4.17.2",
|
"express": "^4.17.2",
|
||||||
"get-port": "^5.1.1",
|
"get-port": "^5.1.1",
|
||||||
"lastfmapi": "^0.1.1",
|
"lastfmapi": "^0.1.1",
|
||||||
"mpris-service": "^2.1.2",
|
"mpris-service": "^2.1.2",
|
||||||
|
"music-metadata": "^7.11.4",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"v8-compile-cache": "^2.3.0",
|
"v8-compile-cache": "^2.3.0",
|
||||||
"ws": "^8.4.0",
|
"ws": "^8.4.0",
|
||||||
|
@ -76,9 +78,9 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"build": {
|
"build": {
|
||||||
"electronVersion": "15.3.4",
|
"electronVersion": "16.0.6",
|
||||||
"electronDownload": {
|
"electronDownload": {
|
||||||
"version": "15.3.4-wvvmp",
|
"version": "16.0.6+wvcus",
|
||||||
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
||||||
},
|
},
|
||||||
"appId": "cider",
|
"appId": "cider",
|
||||||
|
@ -102,7 +104,10 @@
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": [
|
"target": [
|
||||||
"AppImage"
|
"AppImage",
|
||||||
|
"deb",
|
||||||
|
"snap",
|
||||||
|
"rpm"
|
||||||
],
|
],
|
||||||
"synopsis": "A new look into listening and enjoying music in style and performance. ",
|
"synopsis": "A new look into listening and enjoying music in style and performance. ",
|
||||||
"category": "AudioVideo",
|
"category": "AudioVideo",
|
||||||
|
|
|
@ -1,407 +0,0 @@
|
||||||
const { BrowserWindow, ipcMain, shell, app, screen } = require("electron")
|
|
||||||
const { join } = require("path")
|
|
||||||
const getPort = require("get-port");
|
|
||||||
const express = require("express");
|
|
||||||
const path = require("path");
|
|
||||||
const windowStateKeeper = require("electron-window-state");
|
|
||||||
const os = require('os');
|
|
||||||
const yt = require('youtube-search-without-api-key');
|
|
||||||
const discord = require('./discordrpc');
|
|
||||||
const lastfm = require('./lastfm');
|
|
||||||
const { writeFile, writeFileSync, existsSync, mkdirSync } = require('fs');
|
|
||||||
const fs = require('fs');
|
|
||||||
const mpris = require('./mpris');
|
|
||||||
const mm = require('music-metadata');
|
|
||||||
const fetch = require('electron-fetch').default;
|
|
||||||
const { Stream } = require('stream');
|
|
||||||
|
|
||||||
// Analytics for debugging.
|
|
||||||
const ElectronSentry = require("@sentry/electron");
|
|
||||||
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" });
|
|
||||||
|
|
||||||
const CiderBase = {
|
|
||||||
win: null,
|
|
||||||
requests: [],
|
|
||||||
audiostream: new Stream.PassThrough(),
|
|
||||||
async Start() {
|
|
||||||
this.clientPort = await getPort({ port: 9000 });
|
|
||||||
this.win = this.CreateBrowserWindow()
|
|
||||||
},
|
|
||||||
clientPort: 0,
|
|
||||||
CreateBrowserWindow() {
|
|
||||||
this.VerifyFiles()
|
|
||||||
// Set default window sizes
|
|
||||||
const mainWindowState = windowStateKeeper({
|
|
||||||
defaultWidth: 1024,
|
|
||||||
defaultHeight: 600
|
|
||||||
});
|
|
||||||
|
|
||||||
let win = null
|
|
||||||
const options = {
|
|
||||||
icon: join(__dirname, `../../resources/icons/icon.ico`),
|
|
||||||
width: mainWindowState.width,
|
|
||||||
height: mainWindowState.height,
|
|
||||||
x: mainWindowState.x,
|
|
||||||
y: mainWindowState.y,
|
|
||||||
minWidth: 844,
|
|
||||||
minHeight: 410,
|
|
||||||
frame: false,
|
|
||||||
title: "Cider",
|
|
||||||
vibrancy: 'dark',
|
|
||||||
// transparent: true,
|
|
||||||
hasShadow: false,
|
|
||||||
webPreferences: {
|
|
||||||
webviewTag: true,
|
|
||||||
plugins: true,
|
|
||||||
nodeIntegration: true,
|
|
||||||
nodeIntegrationInWorker: false,
|
|
||||||
webSecurity: false,
|
|
||||||
allowRunningInsecureContent: true,
|
|
||||||
enableRemoteModule: true,
|
|
||||||
sandbox: true,
|
|
||||||
nativeWindowOpen: true,
|
|
||||||
contextIsolation: false,
|
|
||||||
preload: join(__dirname, '../preload/cider-preload.js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CiderBase.InitWebServer()
|
|
||||||
|
|
||||||
// Create the BrowserWindow
|
|
||||||
if (process.platform === "darwin" || process.platform === "linux") {
|
|
||||||
win = new BrowserWindow(options)
|
|
||||||
} else {
|
|
||||||
if (app.cfg.get("visual.window_transparency") !== "disabled") {
|
|
||||||
const { BrowserWindow } = require("electron-acrylic-window");
|
|
||||||
}
|
|
||||||
win = new BrowserWindow(options)
|
|
||||||
win.setVibrancy("dark")
|
|
||||||
}
|
|
||||||
|
|
||||||
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
|
||||||
win.webContents.session.webRequest.onBeforeRequest(
|
|
||||||
{
|
|
||||||
urls: ["https://*/*.js"]
|
|
||||||
},
|
|
||||||
(details, callback) => {
|
|
||||||
if (details.url.includes("hls.js")) {
|
|
||||||
callback({
|
|
||||||
redirectURL: `http://localhost:${CiderBase.clientPort}/apple-hls.js`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
callback({
|
|
||||||
cancel: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
win.webContents.session.webRequest.onBeforeSendHeaders(async (details, callback) => {
|
|
||||||
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
|
||||||
details.requestHeaders['sec-fetch-site'] = 'same-site';
|
|
||||||
details.requestHeaders['DNT'] = '1';
|
|
||||||
let itspod = await win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`)
|
|
||||||
if (itspod != null)
|
|
||||||
details.requestHeaders['Cookie'] = `itspod=${itspod}`
|
|
||||||
}
|
|
||||||
callback({ requestHeaders: details.requestHeaders })
|
|
||||||
})
|
|
||||||
|
|
||||||
let location = `http://localhost:${CiderBase.clientPort}/`
|
|
||||||
win.loadURL(location)
|
|
||||||
win.on("closed", () => {
|
|
||||||
win = null
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register listeners on Window to track size and position of the Window.
|
|
||||||
mainWindowState.manage(win);
|
|
||||||
|
|
||||||
// IPC stuff (senders)
|
|
||||||
ipcMain.on("cider-platform", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on("get-gpu-mode", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on("is-dev", (event) => {
|
|
||||||
event.returnValue = !app.isPackaged
|
|
||||||
})
|
|
||||||
|
|
||||||
// IPC stuff (listeners)
|
|
||||||
ipcMain.on('close', () => { // listen for close event
|
|
||||||
win.close();
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-songs', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-songs.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-artists', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-artists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-albums', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-albums.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-playlists', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-playlists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-recentlyAdded', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-songs', (event) => {
|
|
||||||
let librarySongs = fs.readFileSync(join(app.paths.ciderCache, "library-songs.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(librarySongs)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-artists', (event) => {
|
|
||||||
let libraryArtists = fs.readFileSync(join(app.paths.ciderCache, "library-artists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryArtists)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-albums', (event) => {
|
|
||||||
let libraryAlbums = fs.readFileSync(join(app.paths.ciderCache, "library-albums.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryAlbums)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-playlists', (event) => {
|
|
||||||
let libraryPlaylists = fs.readFileSync(join(app.paths.ciderCache, "library-playlists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryPlaylists)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-recentlyAdded', (event) => {
|
|
||||||
let libraryRecentlyAdded = fs.readFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryRecentlyAdded)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('getYTLyrics', async (event, track, artist) => {
|
|
||||||
var u = track + " " + artist + " official video";
|
|
||||||
const videos = await yt.search(u);
|
|
||||||
return videos
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
|
|
||||||
return (defaultValue ? app.cfg.get(key, true) : app.cfg.get(key));
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('setStoreValue', (event, key, value) => {
|
|
||||||
app.cfg.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('getStore', (event) => {
|
|
||||||
event.returnValue = app.cfg.store
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('setStore', (event, store) => {
|
|
||||||
app.cfg.store = store
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('setVibrancy', (event, key, value) => {
|
|
||||||
win.setVibrancy(value)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('maximize', () => { // listen for maximize event
|
|
||||||
if (win.isMaximized()) {
|
|
||||||
win.unmaximize()
|
|
||||||
} else {
|
|
||||||
win.maximize()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('minimize', () => { // listen for minimize event
|
|
||||||
win.minimize();
|
|
||||||
})
|
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
let WND_STATE = {
|
|
||||||
MINIMIZED: 0,
|
|
||||||
NORMAL: 1,
|
|
||||||
MAXIMIZED: 2,
|
|
||||||
FULL_SCREEN: 3
|
|
||||||
}
|
|
||||||
let wndState = WND_STATE.NORMAL
|
|
||||||
|
|
||||||
win.on("resize", (_event) => {
|
|
||||||
const isMaximized = win.isMaximized()
|
|
||||||
const isMinimized = win.isMinimized()
|
|
||||||
const isFullScreen = win.isFullScreen()
|
|
||||||
const state = wndState;
|
|
||||||
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
|
||||||
wndState = WND_STATE.MINIMIZED
|
|
||||||
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
|
||||||
wndState = WND_STATE.FULL_SCREEN
|
|
||||||
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
|
||||||
wndState = WND_STATE.MAXIMIZED
|
|
||||||
win.webContents.executeJavaScript(`app.chrome.maximized = true`)
|
|
||||||
} else if (state !== WND_STATE.NORMAL) {
|
|
||||||
wndState = WND_STATE.NORMAL
|
|
||||||
win.webContents.executeJavaScript(`app.chrome.maximized = false`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set window Handler
|
|
||||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
||||||
if (url.includes("apple") || url.includes("localhost")) {
|
|
||||||
return { action: "allow" }
|
|
||||||
}
|
|
||||||
shell.openExternal(url).catch(() => {
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
action: 'deny'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set scale
|
|
||||||
ipcMain.on('setScreenScale', (event, scale) => {
|
|
||||||
win.webContents.setZoomFactor(parseFloat(scale))
|
|
||||||
})
|
|
||||||
|
|
||||||
win.webContents.setZoomFactor(screen.getPrimaryDisplay().scaleFactor)
|
|
||||||
|
|
||||||
mpris.connect(win)
|
|
||||||
|
|
||||||
lastfm.authenticate()
|
|
||||||
// Discord
|
|
||||||
discord.connect((app.cfg.get("general.discord_rpc") == 1) ? '911790844204437504' : '886578863147192350');
|
|
||||||
ipcMain.on('playbackStateDidChange', (_event, a) => {
|
|
||||||
app.media = a;
|
|
||||||
discord.updateActivity(a)
|
|
||||||
mpris.updateState(a)
|
|
||||||
lastfm.scrobbleSong(a)
|
|
||||||
lastfm.updateNowPlayingSong(a)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('nowPlayingItemDidChange', (_event, a) => {
|
|
||||||
app.media = a;
|
|
||||||
discord.updateActivity(a)
|
|
||||||
mpris.updateAttributes(a)
|
|
||||||
lastfm.scrobbleSong(a)
|
|
||||||
lastfm.updateNowPlayingSong(a)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on("getPreviewURL", (_event, url) => {
|
|
||||||
fetch(url)
|
|
||||||
.then(res => res.buffer())
|
|
||||||
.then(async (buffer) => {
|
|
||||||
try {
|
|
||||||
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a');
|
|
||||||
SoundCheckTag = metadata.native.iTunes[1].value
|
|
||||||
win.webContents.send('SoundCheckTag', SoundCheckTag)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('writeAudio', function (event, buffer) {
|
|
||||||
CiderBase.audiostream.write(Buffer.from(buffer));
|
|
||||||
})
|
|
||||||
|
|
||||||
return win
|
|
||||||
},
|
|
||||||
VerifyFiles() {
|
|
||||||
const expectedDirectories = [
|
|
||||||
"CiderCache"
|
|
||||||
]
|
|
||||||
const expectedFiles = [
|
|
||||||
"library-songs.json",
|
|
||||||
"library-artists.json",
|
|
||||||
"library-albums.json",
|
|
||||||
"library-playlists.json",
|
|
||||||
"library-recentlyAdded.json",
|
|
||||||
]
|
|
||||||
for (let i = 0; i < expectedDirectories.length; i++) {
|
|
||||||
if (!existsSync(path.join(app.getPath("userData"), expectedDirectories[i]))) {
|
|
||||||
mkdirSync(path.join(app.getPath("userData"), expectedDirectories[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < expectedFiles.length; i++) {
|
|
||||||
const file = path.join(app.paths.ciderCache, expectedFiles[i])
|
|
||||||
if (!existsSync(file)) {
|
|
||||||
writeFileSync(file, JSON.stringify([]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
EnvironmentVariables: {
|
|
||||||
"env": {
|
|
||||||
platform: os.platform(),
|
|
||||||
dev: app.isPackaged
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LinkHandler: (startArgs) => {
|
|
||||||
if (!startArgs) return;
|
|
||||||
console.log("lfmtoken", String(startArgs))
|
|
||||||
if (String(startArgs).includes('auth')) {
|
|
||||||
let authURI = String(startArgs).split('/auth/')[1]
|
|
||||||
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
|
||||||
const authKey = authURI.split('lastfm?token=')[1];
|
|
||||||
app.cfg.set('lastfm.enabled', true);
|
|
||||||
app.cfg.set('lastfm.auth_token', authKey);
|
|
||||||
CiderBase.win.webContents.send('LastfmAuthenticated', authKey);
|
|
||||||
lastfm.authenticate()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const formattedSongID = startArgs.replace('ame://', '').replace('/', '');
|
|
||||||
console.warn(`[LinkHandler] Attempting to load song id: ${formattedSongID}`);
|
|
||||||
|
|
||||||
// setQueue can be done with album, song, url, playlist id
|
|
||||||
this.win.webContents.executeJavaScript(`
|
|
||||||
MusicKit.getInstance().setQueue({ song: '${formattedSongID}'}).then(function(queue) {
|
|
||||||
MusicKit.getInstance().play();
|
|
||||||
});
|
|
||||||
`).catch((err) => console.error(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
async InitWebServer() {
|
|
||||||
const webapp = express();
|
|
||||||
const webRemotePath = path.join(__dirname, '../renderer/');
|
|
||||||
webapp.set("views", path.join(webRemotePath, "views"));
|
|
||||||
webapp.set("view engine", "ejs");
|
|
||||||
|
|
||||||
webapp.use(function (req, res, next) {
|
|
||||||
// if not localhost
|
|
||||||
if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && req.headers["user-agent"].includes("Cider"))) {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webapp.use(express.static(webRemotePath));
|
|
||||||
webapp.get('/', function (req, res) {
|
|
||||||
//res.sendFile(path.join(webRemotePath, 'index_old.html'));
|
|
||||||
res.render("main", CiderBase.EnvironmentVariables)
|
|
||||||
});
|
|
||||||
webapp.get('/audio.webm', function (req, res) {
|
|
||||||
try {
|
|
||||||
req.connection.setTimeout(Number.MAX_SAFE_INTEGER);
|
|
||||||
// CiderBase.requests.push({req: req, res: res});
|
|
||||||
// var pos = CiderBase.requests.length - 1;
|
|
||||||
// req.on("close", () => {
|
|
||||||
// console.info("CLOSED", CiderBase.requests.length);
|
|
||||||
// requests.splice(pos, 1);
|
|
||||||
// console.info("CLOSED", CiderBase.requests.length);
|
|
||||||
// });
|
|
||||||
CiderBase.audiostream.on('data', (data) => {
|
|
||||||
try {
|
|
||||||
res.write(data);
|
|
||||||
} catch (ex) {
|
|
||||||
console.log(ex)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (ex) { console.log(ex) }
|
|
||||||
});
|
|
||||||
webapp.listen(CiderBase.clientPort, function () {
|
|
||||||
console.log(`Cider client port: ${CiderBase.clientPort}`);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CiderBase;
|
|
|
@ -1,17 +1,25 @@
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
import * as electronAcrylic from "electron-acrylic-window"
|
// import * as electronAcrylic from "electron-acrylic-window"
|
||||||
import * as windowStateKeeper from "electron-window-state";
|
import * as windowStateKeeper from "electron-window-state";
|
||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import * as getPort from "get-port";
|
import * as getPort from "get-port";
|
||||||
import * as yt from "youtube-search-without-api-key";
|
import * as yt from "youtube-search-without-api-key";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import {Stream} from "stream";
|
||||||
|
|
||||||
export class Win {
|
export class Win {
|
||||||
win: any | undefined;
|
win: any | undefined = null;
|
||||||
app: electron.App | undefined;
|
app: electron.App | undefined;
|
||||||
|
|
||||||
private srcPath: string = path.join(__dirname, "../../src");
|
private paths: any = {
|
||||||
private resourcePath: string = path.join(__dirname, "../../resources");
|
srcPath: path.join(__dirname, "../../src"),
|
||||||
|
resourcePath: path.join(__dirname, "../../resources"),
|
||||||
|
ciderCache: path.resolve(electron.app.getPath("userData"), "CiderCache"),
|
||||||
|
themes: path.resolve(electron.app.getPath("userData"), "Themes"),
|
||||||
|
plugins: path.resolve(electron.app.getPath("userData"), "Plugins"),
|
||||||
|
}
|
||||||
|
private audioStream: any = new Stream.PassThrough();
|
||||||
private clientPort: number = 0;
|
private clientPort: number = 0;
|
||||||
private EnvironmentVariables: object = {
|
private EnvironmentVariables: object = {
|
||||||
"env": {
|
"env": {
|
||||||
|
@ -20,16 +28,17 @@ export class Win {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private options: any = {
|
private options: any = {
|
||||||
icon: path.join(this.resourcePath, `icons/icon.ico`),
|
icon: path.join(this.paths.resourcePath, `icons/icon.` + (process.platform === "win32" ? "ico" : "png")),
|
||||||
width: 1024,
|
width: 1024,
|
||||||
height: 600,
|
height: 600,
|
||||||
x: undefined,
|
x: undefined,
|
||||||
y: undefined,
|
y: undefined,
|
||||||
minWidth: 850,
|
minWidth: 844,
|
||||||
minHeight: 400,
|
minHeight: 410,
|
||||||
frame: false,
|
frame: false,
|
||||||
title: "Cider",
|
title: "Cider",
|
||||||
transparent: process.platform === "darwin",
|
vibrancy: 'dark',
|
||||||
|
// transparent: true,
|
||||||
hasShadow: false,
|
hasShadow: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
|
@ -42,16 +51,14 @@ export class Win {
|
||||||
sandbox: true,
|
sandbox: true,
|
||||||
nativeWindowOpen: true,
|
nativeWindowOpen: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
preload: path.join(this.srcPath, 'preload/cider-preload.js')
|
preload: path.join(this.paths.srcPath, './preload/cider-preload.js')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the browser window
|
* Creates the browser window
|
||||||
*/
|
*/
|
||||||
async createWindow(): Promise<any | undefined> {
|
createWindow(): void {
|
||||||
this.clientPort = await getPort({port: 9000});
|
|
||||||
|
|
||||||
// Load the previous state with fallback to defaults
|
// Load the previous state with fallback to defaults
|
||||||
const windowState = windowStateKeeper({
|
const windowState = windowStateKeeper({
|
||||||
defaultWidth: 1024,
|
defaultWidth: 1024,
|
||||||
|
@ -60,61 +67,75 @@ export class Win {
|
||||||
this.options.width = windowState.width;
|
this.options.width = windowState.width;
|
||||||
this.options.height = windowState.height;
|
this.options.height = windowState.height;
|
||||||
|
|
||||||
this.startWebServer()
|
// Start the webserver for the browser window to load
|
||||||
|
this.startWebServer().then(() => {
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
this.win = new electronAcrylic.BrowserWindow(this.options);
|
// this.win = new electronAcrylic.BrowserWindow(this.options);
|
||||||
} else {
|
} else {
|
||||||
this.win = new electron.BrowserWindow(this.options);
|
this.win = new electron.BrowserWindow(this.options);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
// Create the browser window.
|
|
||||||
|
|
||||||
console.debug('Browser window created');
|
|
||||||
|
|
||||||
// and load the renderer.
|
// and load the renderer.
|
||||||
this.startSession(this.win);
|
this.startSession(this.win);
|
||||||
this.startHandlers(this.win);
|
this.startHandlers(this.win);
|
||||||
|
|
||||||
|
// Register listeners on Window to track size and position of the Window.
|
||||||
|
windowState.manage(this.win);
|
||||||
|
|
||||||
return this.win;
|
return this.win;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the webserver for the renderer process.
|
* Starts the webserver for the renderer process.
|
||||||
*/
|
*/
|
||||||
private startWebServer(): void {
|
public async startWebServer(): Promise<void> {
|
||||||
const webapp = express();
|
this.clientPort = await getPort({port: 9000});
|
||||||
const webRemotePath = path.join(this.srcPath, 'renderer');
|
const app = express();
|
||||||
webapp.set("views", path.join(webRemotePath, "views"));
|
|
||||||
webapp.set("view engine", "ejs");
|
|
||||||
|
|
||||||
webapp.use(function (req, res, next) {
|
// app.use(express.static(path.join(this.paths.srcPath, './renderer/'))); // this breaks everything
|
||||||
// if not localhost
|
app.set("views", path.join(this.paths.srcPath, './renderer/views'));
|
||||||
// @ts-ignore
|
app.set("view engine", "ejs");
|
||||||
if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && req.headers["user-agent"].includes("Cider"))) {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webapp.use(express.static(webRemotePath));
|
// this is also causing issues
|
||||||
webapp.get('/', (req, res) => {
|
// app.use((req, res, next) => {
|
||||||
//res.sendFile(path.join(webRemotePath, 'index_old.html'));
|
// // if not localhost
|
||||||
console.log(req)
|
//
|
||||||
|
// // @ts-ignore
|
||||||
|
// if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && req.headers["user-agent"].includes("Cider"))) {
|
||||||
|
// next();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
// res.send("Hello world!");
|
||||||
|
// res.sendFile(path.join(webRemotePath, 'index_old.html'));
|
||||||
res.render("main", this.EnvironmentVariables)
|
res.render("main", this.EnvironmentVariables)
|
||||||
});
|
});
|
||||||
// webelectron.app.get('/audio.webm', (req, res) => {
|
|
||||||
|
// app.get('/audio.webm', (req, res) => {
|
||||||
// try {
|
// try {
|
||||||
// req.connection.setTimeout(Number.MAX_SAFE_INTEGER);
|
// req.connection.setTimeout(Number.MAX_SAFE_INTEGER);
|
||||||
// this.audiostream.on('data', (data) => {
|
// // CiderBase.requests.push({req: req, res: res});
|
||||||
|
// // var pos = CiderBase.requests.length - 1;
|
||||||
|
// // req.on("close", () => {
|
||||||
|
// // console.info("CLOSED", CiderBase.requests.length);
|
||||||
|
// // requests.splice(pos, 1);
|
||||||
|
// // console.info("CLOSED", CiderBase.requests.length);
|
||||||
|
// // });
|
||||||
|
// this.audioStream.on('data', (data: any) => {
|
||||||
// try {
|
// try {
|
||||||
// res.write(data);
|
// res.write(data);
|
||||||
// } catch (ex) {
|
// } catch (ex) {
|
||||||
// console.log(ex)
|
// console.log(ex)
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
// } catch (ex) { console.log(ex) }
|
// } catch (ex) {
|
||||||
|
// console.log(ex)
|
||||||
|
// }
|
||||||
// });
|
// });
|
||||||
webapp.listen(this.clientPort, () => {
|
|
||||||
|
app.listen(this.clientPort, () => {
|
||||||
console.log(`Cider client port: ${this.clientPort}`);
|
console.log(`Cider client port: ${this.clientPort}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -122,8 +143,7 @@ export class Win {
|
||||||
/**
|
/**
|
||||||
* Starts the session for the renderer process.
|
* Starts the session for the renderer process.
|
||||||
*/
|
*/
|
||||||
private startSession(win: any): void {const self = this;
|
private startSession(win: any): void {
|
||||||
|
|
||||||
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
||||||
win.webContents.session.webRequest.onBeforeRequest(
|
win.webContents.session.webRequest.onBeforeRequest(
|
||||||
{
|
{
|
||||||
|
@ -132,7 +152,7 @@ export class Win {
|
||||||
(details: { url: string | string[]; }, callback: (arg0: { redirectURL?: string; cancel?: boolean; }) => void) => {
|
(details: { url: string | string[]; }, callback: (arg0: { redirectURL?: string; cancel?: boolean; }) => void) => {
|
||||||
if (details.url.includes("hls.js")) {
|
if (details.url.includes("hls.js")) {
|
||||||
callback({
|
callback({
|
||||||
redirectURL: `http://localhost:${self.clientPort}/apple-hls.js`
|
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
callback({
|
callback({
|
||||||
|
@ -146,20 +166,15 @@ export class Win {
|
||||||
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
||||||
details.requestHeaders['sec-fetch-site'] = 'same-site';
|
details.requestHeaders['sec-fetch-site'] = 'same-site';
|
||||||
details.requestHeaders['DNT'] = '1';
|
details.requestHeaders['DNT'] = '1';
|
||||||
let ItsPod = await win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`)
|
let itspod = await win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`)
|
||||||
if (ItsPod != null)
|
if (itspod != null)
|
||||||
details.requestHeaders['Cookie'] = `itspod=${ItsPod}`
|
details.requestHeaders['Cookie'] = `itspod=${itspod}`
|
||||||
}
|
}
|
||||||
callback({requestHeaders: details.requestHeaders})
|
callback({requestHeaders: details.requestHeaders})
|
||||||
})
|
})
|
||||||
|
|
||||||
const location = `http://localhost:${this.clientPort}/`
|
let location = `http://localhost:${this.clientPort}/`
|
||||||
console.log('yeah')
|
|
||||||
win.loadURL(location)
|
win.loadURL(location)
|
||||||
.then(() => {
|
|
||||||
console.debug(`Cider client location: ${location}`);
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,6 +182,116 @@ export class Win {
|
||||||
* @param win The BrowserWindow
|
* @param win The BrowserWindow
|
||||||
*/
|
*/
|
||||||
private startHandlers(win: any): void {
|
private startHandlers(win: any): void {
|
||||||
|
|
||||||
|
/**********************************************************************************************************************
|
||||||
|
* ipcMain Events
|
||||||
|
****************************************************************************************************************** */
|
||||||
|
electron.ipcMain.on("cider-platform", (event) => {
|
||||||
|
event.returnValue = process.platform
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-gpu-mode", (event) => {
|
||||||
|
event.returnValue = process.platform
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on("is-dev", (event) => {
|
||||||
|
event.returnValue = !electron.app.isPackaged
|
||||||
|
})
|
||||||
|
electron.ipcMain.on('close', () => { // listen for close event
|
||||||
|
win.close();
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('put-library-songs', (event, arg) => {
|
||||||
|
fs.writeFileSync(path.join(this.paths.ciderCache, "library-songs.json"), JSON.stringify(arg))
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('put-library-artists', (event, arg) => {
|
||||||
|
fs.writeFileSync(path.join(this.paths.ciderCache, "library-artists.json"), JSON.stringify(arg))
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('put-library-albums', (event, arg) => {
|
||||||
|
fs.writeFileSync(path.join(this.paths.ciderCache, "library-albums.json"), JSON.stringify(arg))
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('put-library-playlists', (event, arg) => {
|
||||||
|
fs.writeFileSync(path.join(this.paths.ciderCache, "library-playlists.json"), JSON.stringify(arg))
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('put-library-recentlyAdded', (event, arg) => {
|
||||||
|
fs.writeFileSync(path.join(this.paths.ciderCache, "library-recentlyAdded.json"), JSON.stringify(arg))
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('get-library-songs', (event) => {
|
||||||
|
let librarySongs = fs.readFileSync(path.join(this.paths.ciderCache, "library-songs.json"), "utf8")
|
||||||
|
event.returnValue = JSON.parse(librarySongs)
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('get-library-artists', (event) => {
|
||||||
|
let libraryArtists = fs.readFileSync(path.join(this.paths.ciderCache, "library-artists.json"), "utf8")
|
||||||
|
event.returnValue = JSON.parse(libraryArtists)
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('get-library-albums', (event) => {
|
||||||
|
let libraryAlbums = fs.readFileSync(path.join(this.paths.ciderCache, "library-albums.json"), "utf8")
|
||||||
|
event.returnValue = JSON.parse(libraryAlbums)
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('get-library-playlists', (event) => {
|
||||||
|
let libraryPlaylists = fs.readFileSync(path.join(this.paths.ciderCache, "library-playlists.json"), "utf8")
|
||||||
|
event.returnValue = JSON.parse(libraryPlaylists)
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('get-library-recentlyAdded', (event) => {
|
||||||
|
let libraryRecentlyAdded = fs.readFileSync(path.join(this.paths.ciderCache, "library-recentlyAdded.json"), "utf8")
|
||||||
|
event.returnValue = JSON.parse(libraryRecentlyAdded)
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.handle('getYTLyrics', async (event, track, artist) => {
|
||||||
|
const u = track + " " + artist + " official video";
|
||||||
|
return await yt.search(u)
|
||||||
|
})
|
||||||
|
|
||||||
|
// electron.ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
|
||||||
|
// return (defaultValue ? app.cfg.get(key, true) : app.cfg.get(key));
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// electron.ipcMain.handle('setStoreValue', (event, key, value) => {
|
||||||
|
// app.cfg.set(key, value);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// electron.ipcMain.on('getStore', (event) => {
|
||||||
|
// event.returnValue = app.cfg.store
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// electron.ipcMain.on('setStore', (event, store) => {
|
||||||
|
// app.cfg.store = store
|
||||||
|
// })
|
||||||
|
|
||||||
|
electron.ipcMain.handle('setVibrancy', (event, key, value) => {
|
||||||
|
win.setVibrancy(value)
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('maximize', () => { // listen for maximize event
|
||||||
|
if (win.isMaximized()) {
|
||||||
|
win.unmaximize()
|
||||||
|
} else {
|
||||||
|
win.maximize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('minimize', () => { // listen for minimize event
|
||||||
|
win.minimize();
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set scale
|
||||||
|
electron.ipcMain.on('setScreenScale', (event, scale) => {
|
||||||
|
win.webContents.setZoomFactor(parseFloat(scale))
|
||||||
|
})
|
||||||
|
|
||||||
|
/* *********************************************************************************************
|
||||||
|
* Window Events
|
||||||
|
* **********************************************************************************************/
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
let WND_STATE = {
|
let WND_STATE = {
|
||||||
MINIMIZED: 0,
|
MINIMIZED: 0,
|
||||||
|
@ -187,83 +312,26 @@ export class Win {
|
||||||
wndState = WND_STATE.FULL_SCREEN
|
wndState = WND_STATE.FULL_SCREEN
|
||||||
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
||||||
wndState = WND_STATE.MAXIMIZED
|
wndState = WND_STATE.MAXIMIZED
|
||||||
win.webContents.executeJavaScript(`electron.app.chrome.maximized = true`)
|
win.webContents.executeJavaScript(`app.chrome.maximized = true`)
|
||||||
} else if (state !== WND_STATE.NORMAL) {
|
} else if (state !== WND_STATE.NORMAL) {
|
||||||
wndState = WND_STATE.NORMAL
|
wndState = WND_STATE.NORMAL
|
||||||
win.webContents.executeJavaScript(`electron.app.chrome.maximized = false`)
|
win.webContents.executeJavaScript(`app.chrome.maximized = false`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
win.on("closed", () => {
|
||||||
|
this.win = null
|
||||||
|
})
|
||||||
|
|
||||||
// Set window Handler
|
// Set window Handler
|
||||||
win.webContents.setWindowOpenHandler((x: any) => {
|
win.webContents.setWindowOpenHandler((x: any) => {
|
||||||
if (x.url.includes("apple") || x.url.includes("localhost")) {
|
if (x.url.includes("apple") || x.url.includes("localhost")) {
|
||||||
return {action: "allow"}
|
return {action: "allow"}
|
||||||
}
|
}
|
||||||
electron.shell.openExternal(x.url).catch(() => {
|
electron.shell.openExternal(x.url).catch(console.error)
|
||||||
})
|
return {action: 'deny'}
|
||||||
return {
|
|
||||||
action: 'deny'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------
|
|
||||||
// Renderer IPC Listeners
|
|
||||||
//-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
electron.ipcMain.on("cider-platform", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on("get-gpu-mode", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on("is-dev", (event) => {
|
|
||||||
event.returnValue = !electron.app.isPackaged
|
|
||||||
})
|
|
||||||
|
|
||||||
// IPC stuff (listeners)
|
|
||||||
electron.ipcMain.on('close', () => { // listen for close event
|
|
||||||
win.close();
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.handle('getYTLyrics', async (event, track, artist) => {
|
|
||||||
const u = track + " " + artist + " official video";
|
|
||||||
return await yt.search(u)
|
|
||||||
})
|
|
||||||
|
|
||||||
// electron.ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
|
|
||||||
// return (defaultValue ? electron.app.cfg.get(key, true) : electron.app.cfg.get(key));
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// electron.ipcMain.handle('setStoreValue', (event, key, value) => {
|
|
||||||
// electron.app.cfg.set(key, value);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// electron.ipcMain.on('getStore', (event) => {
|
|
||||||
// event.returnValue = electron.app.cfg.store
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// electron.ipcMain.on('setStore', (event, store) => {
|
|
||||||
// electron.app.cfg.store = store
|
|
||||||
// })
|
|
||||||
|
|
||||||
electron.ipcMain.on('maximize', () => { // listen for maximize event
|
|
||||||
if (win.isMaximized()) {
|
|
||||||
win.unmaximize()
|
|
||||||
} else {
|
|
||||||
win.maximize()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('minimize', () => { // listen for minimize event
|
|
||||||
win.minimize();
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set scale
|
|
||||||
electron.ipcMain.on('setScreenScale', (event, scale) => {
|
|
||||||
win.webContents.setZoomFactor(parseFloat(scale))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,90 +0,0 @@
|
||||||
const lastfm = require('./plugins/lastfm');
|
|
||||||
const win = require('./base/win')
|
|
||||||
|
|
||||||
// Analytics for debugging.
|
|
||||||
const ElectronSentry = require("@sentry/electron");
|
|
||||||
const {app} = require("electron");
|
|
||||||
ElectronSentry.init({dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214"});
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------
|
|
||||||
// Public Methods
|
|
||||||
//-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the application (called on on-ready). - Starts BrowserWindow and WebServer
|
|
||||||
*/
|
|
||||||
Start() {
|
|
||||||
app.win = win.createBrowserWindow()
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the main application (run before on-ready)
|
|
||||||
*/
|
|
||||||
async Init() {
|
|
||||||
// Initialize the config.
|
|
||||||
const {init} = require("./base/store");
|
|
||||||
await init()
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------
|
|
||||||
// Append Commandline Arguments
|
|
||||||
//-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Hardware Acceleration
|
|
||||||
// Enable WebGPU and list adapters (EXPERIMENTAL.)
|
|
||||||
// Note: THIS HAS TO BE BEFORE ANYTHING GETS INITIALIZED.
|
|
||||||
switch (app.cfg.get("visual.hw_acceleration")) {
|
|
||||||
default:
|
|
||||||
case "default":
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "webgpu":
|
|
||||||
console.info("WebGPU is enabled.");
|
|
||||||
app.commandLine.appendSwitch('enable-unsafe-webgpu')
|
|
||||||
break;
|
|
||||||
case "disabled":
|
|
||||||
console.info("Hardware acceleration is disabled.");
|
|
||||||
app.commandLine.appendSwitch('disable-gpu')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === "linux") {
|
|
||||||
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
|
||||||
}
|
|
||||||
|
|
||||||
app.commandLine.appendSwitch('high-dpi-support', 'true');
|
|
||||||
app.commandLine.appendSwitch('force-device-scale-factor', '1');
|
|
||||||
app.commandLine.appendSwitch('disable-pinch');
|
|
||||||
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=1024')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles all links being opened in the application.
|
|
||||||
*/
|
|
||||||
LinkHandler(startArgs) {
|
|
||||||
if (!startArgs) return;
|
|
||||||
console.log("lfmtoken", String(startArgs))
|
|
||||||
if (String(startArgs).includes('auth')) {
|
|
||||||
let authURI = String(startArgs).split('/auth/')[1]
|
|
||||||
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
|
||||||
const authKey = authURI.split('lastfm?token=')[1];
|
|
||||||
app.cfg.set('lastfm.enabled', true);
|
|
||||||
app.cfg.set('lastfm.auth_token', authKey);
|
|
||||||
app.win.webContents.send('LastfmAuthenticated', authKey);
|
|
||||||
lastfm.authenticate()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const formattedSongID = startArgs.replace('ame://', '').replace('/', '');
|
|
||||||
console.warn(`[LinkHandler] Attempting to load song id: ${formattedSongID}`);
|
|
||||||
|
|
||||||
// setQueue can be done with album, song, url, playlist id
|
|
||||||
this.win.webContents.executeJavaScript(`
|
|
||||||
MusicKit.getInstance().setQueue({ song: '${formattedSongID}'}).then(function(queue) {
|
|
||||||
MusicKit.getInstance().play();
|
|
||||||
});
|
|
||||||
`).catch((err) => console.error(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -16,7 +16,7 @@ import {Win} from "./base/win";
|
||||||
const Cider = new Win()
|
const Cider = new Win()
|
||||||
|
|
||||||
app.on("ready", () => {
|
app.on("ready", () => {
|
||||||
Cider.createWindow();
|
Cider.startWebServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
const {app} = require('electron'),
|
|
||||||
DiscordRPC = require('discord-rpc')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to Discord RPC
|
|
||||||
* @param {string} clientId
|
|
||||||
*/
|
|
||||||
connect: function (clientId) {
|
|
||||||
app.discord = {isConnected: false};
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0) return;
|
|
||||||
|
|
||||||
DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc.
|
|
||||||
const client = new DiscordRPC.Client({transport: "ipc"});
|
|
||||||
app.discord = Object.assign(client, {error: false, activityCache: null, isConnected: false});
|
|
||||||
|
|
||||||
// Login to Discord
|
|
||||||
app.discord.login({clientId})
|
|
||||||
.then(() => {
|
|
||||||
app.discord.isConnected = true;
|
|
||||||
})
|
|
||||||
.catch((e) => console.error(`[DiscordRPC][connect] ${e}`));
|
|
||||||
|
|
||||||
app.discord.on('ready', () => {
|
|
||||||
console.log(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${client.user.username} (${client.user.id})`);
|
|
||||||
|
|
||||||
if (app.discord.activityCache) {
|
|
||||||
client.setActivity(app.discord.activityCache).catch((e) => console.error(e));
|
|
||||||
app.discord.activityCache = null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handles Errors
|
|
||||||
app.discord.on('error', err => {
|
|
||||||
console.error(`[DiscordRPC] ${err}`);
|
|
||||||
this.disconnect()
|
|
||||||
app.discord.isConnected = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects from Discord RPC
|
|
||||||
*/
|
|
||||||
disconnect: function () {
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0 || !app.discord.isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
app.discord.destroy().then(() => {
|
|
||||||
app.discord.isConnected = false;
|
|
||||||
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
|
|
||||||
}).catch((e) => console.error(`[DiscordRPC][disconnect] ${e}`));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the activity of the client
|
|
||||||
* @param {object} attributes
|
|
||||||
*/
|
|
||||||
updateActivity: function (attributes) {
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0) return;
|
|
||||||
|
|
||||||
if (!app.discord.isConnected) {
|
|
||||||
this.connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.discord.isConnected) return;
|
|
||||||
|
|
||||||
// console.log('[DiscordRPC][updateActivity] Updating Discord Activity.')
|
|
||||||
|
|
||||||
const listenURL = `https://applemusicelectron.com/p?id=${attributes.playParams.id}`
|
|
||||||
//console.log(attributes)
|
|
||||||
let ActivityObject = {
|
|
||||||
details: attributes.name,
|
|
||||||
state: `by ${attributes.artistName}`,
|
|
||||||
startTimestamp: attributes.startTime,
|
|
||||||
endTimestamp: attributes.endTime,
|
|
||||||
largeImageKey: (attributes.artwork.url.replace('{w}', '512').replace('{h}', '512')) ?? 'cider',
|
|
||||||
largeImageText: attributes.albumName,
|
|
||||||
smallImageKey: (attributes.status ? 'play' : 'pause'),
|
|
||||||
smallImageText: (attributes.status ? 'Playing' : 'Paused'),
|
|
||||||
instance: true,
|
|
||||||
buttons: [
|
|
||||||
{label: "Listen on Cider", url: listenURL},
|
|
||||||
]
|
|
||||||
};
|
|
||||||
if (ActivityObject.largeImageKey == "" || ActivityObject.largeImageKey == null) {
|
|
||||||
ActivityObject.largeImageKey = (app.cfg.get("general.discord_rpc") == 1) ? "cider" : "logo"
|
|
||||||
}
|
|
||||||
// console.log(`[LinkHandler] Listening URL has been set to: ${listenURL}`);
|
|
||||||
|
|
||||||
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
|
|
||||||
delete ActivityObject.smallImageKey
|
|
||||||
delete ActivityObject.smallImageText
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check all the values work
|
|
||||||
if (!((new Date(attributes.endTime)).getTime() > 0)) {
|
|
||||||
delete ActivityObject.startTimestamp
|
|
||||||
delete ActivityObject.endTimestamp
|
|
||||||
}
|
|
||||||
if (!attributes.artistName) {
|
|
||||||
delete ActivityObject.state
|
|
||||||
}
|
|
||||||
if (!ActivityObject.largeImageText || ActivityObject.largeImageText.length < 2) {
|
|
||||||
delete ActivityObject.largeImageText
|
|
||||||
}
|
|
||||||
if (ActivityObject.details.length > 128) {
|
|
||||||
AcitivityObject.details = ActivityObject.details.substring(0, 125) + '...'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear if if needed
|
|
||||||
if (!attributes.status) {
|
|
||||||
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
|
|
||||||
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][clearActivity] ${e}`));
|
|
||||||
ActivityObject = null
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
delete ActivityObject.startTimestamp
|
|
||||||
delete ActivityObject.endTimestamp
|
|
||||||
ActivityObject.smallImageKey = 'pause'
|
|
||||||
ActivityObject.smallImageText = 'Paused'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ActivityObject) {
|
|
||||||
try {
|
|
||||||
// console.log(`[DiscordRPC][setActivity] Setting activity to ${JSON.stringify(ActivityObject)}`);
|
|
||||||
app.discord.setActivity(ActivityObject)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[DiscordRPC][setActivity] ${err}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
const {app, Notification} = require('electron'),
|
|
||||||
fs = require('fs'),
|
|
||||||
{resolve} = require('path'),
|
|
||||||
sessionPath = resolve(app.getPath('userData'), 'session.json'),
|
|
||||||
apiCredentials = require('../../../resources/lfmApiCredentials.json'),
|
|
||||||
LastfmAPI = require('lastfmapi');
|
|
||||||
|
|
||||||
const lfm = {
|
|
||||||
authenticateFromFile: function () {
|
|
||||||
let sessionData = require(sessionPath)
|
|
||||||
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
|
|
||||||
app.lastfm.setSessionCredentials(sessionData.name, sessionData.key)
|
|
||||||
console.log("[LastFM][authenticateFromFile] Logged in.")
|
|
||||||
},
|
|
||||||
|
|
||||||
authenticate: function () {
|
|
||||||
if (app.cfg.get('lastfm.auth_token')) {
|
|
||||||
app.cfg.set('lastfm.enabled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.cfg.get('lastfm.enabled') || !app.cfg.get('lastfm.auth_token')) {
|
|
||||||
app.cfg.set('lastfm.enabled', false);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const lfmAPI = new LastfmAPI({
|
|
||||||
'api_key': apiCredentials.key,
|
|
||||||
'secret': apiCredentials.secret
|
|
||||||
});
|
|
||||||
|
|
||||||
app.lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
|
|
||||||
|
|
||||||
fs.stat(sessionPath, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
|
|
||||||
console.log("[LastFM][Auth] Beginning authentication from configuration")
|
|
||||||
app.lastfm.authenticate(app.cfg.get('lastfm.auth_token'), function (err, session) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
|
|
||||||
console.log("[LastFM] Saving session info to disk.")
|
|
||||||
let tempData = JSON.stringify(session)
|
|
||||||
fs.writeFile(sessionPath, tempData, (err) => {
|
|
||||||
if (err)
|
|
||||||
console.log("[LastFM][fs]", err)
|
|
||||||
else {
|
|
||||||
console.log("[LastFM][fs] File was written successfully.")
|
|
||||||
lfm.authenticateFromFile()
|
|
||||||
new Notification({
|
|
||||||
title: app.getName(),
|
|
||||||
body: "Successfully logged into LastFM using Authentication Key."
|
|
||||||
}).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
lfm.authenticateFromFile()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
scrobbleSong: async function (attributes) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, app.cfg.get('lastfm.scrobble_after') * 1000));
|
|
||||||
const currentAttributes = app.media;
|
|
||||||
|
|
||||||
if (!app.lastfm || app.lastfm.cachedAttributes === attributes ) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.lastfm.cachedAttributes) {
|
|
||||||
if (app.lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentAttributes.status && currentAttributes === attributes) {
|
|
||||||
if (fs.existsSync(sessionPath)) {
|
|
||||||
// Scrobble playing song.
|
|
||||||
if (attributes.status === true) {
|
|
||||||
app.lastfm.track.scrobble({
|
|
||||||
'artist': lfm.filterArtistName(attributes.artistName),
|
|
||||||
'track': attributes.name,
|
|
||||||
'album': attributes.albumName,
|
|
||||||
'albumArtist': this.filterArtistName(attributes.artistName),
|
|
||||||
'timestamp': new Date().getTime() / 1000
|
|
||||||
}, function (err, scrobbled) {
|
|
||||||
if (err) {
|
|
||||||
return console.error('[LastFM] An error occurred while scrobbling', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
|
|
||||||
});
|
|
||||||
app.lastfm.cachedAttributes = attributes
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.authenticate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return console.log('[LastFM] Did not add ', attributes.name , '-' , lfm.filterArtistName(attributes.artistName), 'because now playing a other song.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
filterArtistName: function (artist) {
|
|
||||||
if (!app.cfg.get('lastfm.enabledRemoveFeaturingArtists')) return artist;
|
|
||||||
|
|
||||||
artist = artist.split(' ');
|
|
||||||
if (artist.includes('&')) {
|
|
||||||
artist.length = artist.indexOf('&');
|
|
||||||
}
|
|
||||||
if (artist.includes('and')) {
|
|
||||||
artist.length = artist.indexOf('and');
|
|
||||||
}
|
|
||||||
artist = artist.join(' ');
|
|
||||||
if (artist.includes(',')) {
|
|
||||||
artist = artist.split(',')
|
|
||||||
artist = artist[0]
|
|
||||||
}
|
|
||||||
return artist.charAt(0).toUpperCase() + artist.slice(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNowPlayingSong: function (attributes) {
|
|
||||||
if (!app.lastfm ||app.lastfm.cachedNowPlayingAttributes === attributes | !app.cfg.get('lastfm.NowPlaying')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.lastfm.cachedNowPlayingAttributes) {
|
|
||||||
if (app.lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(sessionPath)) {
|
|
||||||
// update Now Playing
|
|
||||||
if (attributes.status === true) {
|
|
||||||
app.lastfm.track.updateNowPlaying({
|
|
||||||
'artist': lfm.filterArtistName(attributes.artistName),
|
|
||||||
'track': attributes.name,
|
|
||||||
'album': attributes.albumName,
|
|
||||||
'albumArtist': this.filterArtistName(attributes.artistName)
|
|
||||||
}, function (err, nowPlaying) {
|
|
||||||
if (err) {
|
|
||||||
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
|
|
||||||
});
|
|
||||||
app.lastfm.cachedNowPlayingAttributes = attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.authenticate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = lfm;
|
|
|
@ -1,113 +0,0 @@
|
||||||
let mediaPlayer = null;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to the MPRIS interface.
|
|
||||||
* @param {Object} win - The BrowserWindow.
|
|
||||||
*/
|
|
||||||
connect: (win) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
const Player = require('mpris-service');
|
|
||||||
|
|
||||||
mediaPlayer = Player({
|
|
||||||
name: 'Cider',
|
|
||||||
identity: 'Cider',
|
|
||||||
supportedUriSchemes: [],
|
|
||||||
supportedMimeTypes: [],
|
|
||||||
supportedInterfaces: ['player']
|
|
||||||
});
|
|
||||||
mediaPlayer = Object.assign(mediaPlayer, { canQuit: true, canControl: true, canPause: true, canPlay: true, canGoNext: true })
|
|
||||||
|
|
||||||
|
|
||||||
let pos_atr = {durationInMillis: 0};
|
|
||||||
mediaPlayer.getPosition = function () {
|
|
||||||
const durationInMicro = pos_atr.durationInMillis * 1000;
|
|
||||||
const percentage = parseFloat("0") || 0;
|
|
||||||
return durationInMicro * percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaPlayer.active = true
|
|
||||||
|
|
||||||
mediaPlayer.on('playpause', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('play', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('pause', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('next', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('previous', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the MPRIS interface.
|
|
||||||
* @param {Object} attributes - The attributes of the track.
|
|
||||||
*/
|
|
||||||
updateAttributes: (attributes) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
const MetaData = {
|
|
||||||
'mpris:trackid': mediaPlayer.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
|
|
||||||
'mpris:length': attributes.durationInMillis * 1000, // In microseconds
|
|
||||||
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
|
|
||||||
'xesam:title': `${attributes.name}`,
|
|
||||||
'xesam:album': `${attributes.albumName}`,
|
|
||||||
'xesam:artist': [`${attributes.artistName}`,],
|
|
||||||
'xesam:genre': attributes.genreNames
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaPlayer.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaPlayer.metadata = MetaData
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the playback state of the MPRIS interface.
|
|
||||||
* @param {Object} attributes - The attributes of the track.
|
|
||||||
*/
|
|
||||||
updateState: (attributes) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
function setPlaybackIfNeeded(status) {
|
|
||||||
if (mediaPlayer.playbackStatus === status) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mediaPlayer.playbackStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (attributes.status) {
|
|
||||||
case true: // Playing
|
|
||||||
setPlaybackIfNeeded('Playing');
|
|
||||||
break;
|
|
||||||
case false: // Paused
|
|
||||||
setPlaybackIfNeeded('Paused');
|
|
||||||
break;
|
|
||||||
default: // Stopped
|
|
||||||
setPlaybackIfNeeded('Stopped');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the MPRIS interface.
|
|
||||||
*/
|
|
||||||
clearActivity: () => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
mediaPlayer.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'}
|
|
||||||
mediaPlayer.playbackStatus = 'Stopped';
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -98,59 +98,7 @@ input[type=range].md-slider::-webkit-slider-runnable-track {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.md-close-btn {
|
|
||||||
-webkit-mask-image: url("ameres://icons/webui/close.svg");
|
|
||||||
-webkit-mask-repeat: no-repeat;
|
|
||||||
-webkit-mask-position: center;
|
|
||||||
background-color: white;
|
|
||||||
opacity: 0.75;
|
|
||||||
-webkit-mask-size: contain;
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-btn {
|
|
||||||
background: rgba(100, 100, 100, 0.5);
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 15px;
|
|
||||||
border: 1px solid rgb(100 100 100 / 35%);
|
|
||||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.3), 0px 1px 1px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-btn.md-btn-block {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-btn:active {
|
|
||||||
filter: brightness(75%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-select {
|
|
||||||
padding: 5px 10px;
|
|
||||||
font-size: 1em;
|
|
||||||
font-family: inherit;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid rgb(100 100 100 / 35%);
|
|
||||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.3), 0px 1px 1px rgba(0, 0, 0, 0.4);
|
|
||||||
background: #363636;
|
|
||||||
color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-select:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-select > option {
|
|
||||||
font-size: 1em;
|
|
||||||
font-family: inherit;
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-btn.md-btn-primary {
|
|
||||||
background: var(--keyColor);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-transparent {
|
.md-transparent {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
@ -179,7 +127,6 @@ input[type=range].md-slider::-webkit-slider-runnable-track {
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.md-btn {
|
.md-btn {
|
||||||
background: rgb(255 255 255);
|
|
||||||
box-shadow: rgb(0 0 0 / 10%) 0px 0px 1px, rgb(0 0 0 / 20%) 0px 1px 1px;
|
box-shadow: rgb(0 0 0 / 10%) 0px 0px 1px, rgb(0 0 0 / 20%) 0px 1px 1px;
|
||||||
border: 1px solid rgb(0 0 0 / 15%);
|
border: 1px solid rgb(0 0 0 / 15%);
|
||||||
}
|
}
|
||||||
|
|
1
src/renderer/assets/feather/disc.svg
Normal file
|
@ -0,0 +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-disc"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="3"></circle></svg>
|
After Width: | Height: | Size: 295 B |
1
src/renderer/assets/feather/folder.svg
Normal file
|
@ -0,0 +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-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
After Width: | Height: | Size: 311 B |
1
src/renderer/assets/feather/globe.svg
Normal file
|
@ -0,0 +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-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
|
After Width: | Height: | Size: 409 B |
1
src/renderer/assets/feather/heart.svg
Normal file
|
@ -0,0 +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>
|
After Width: | Height: | Size: 371 B |
1
src/renderer/assets/feather/home.svg
Normal file
|
@ -0,0 +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-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
After Width: | Height: | Size: 332 B |
1
src/renderer/assets/feather/list.svg
Normal file
|
@ -0,0 +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-list" style="width: min-content;" ><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
|
After Width: | Height: | Size: 511 B |
1
src/renderer/assets/feather/music.svg
Normal file
|
@ -0,0 +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-music"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
|
After Width: | Height: | Size: 327 B |
1
src/renderer/assets/feather/play-circle.svg
Normal file
|
@ -0,0 +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-play-circle"><circle cx="12" cy="12" r="10"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg>
|
After Width: | Height: | Size: 313 B |
1
src/renderer/assets/feather/play.svg
Normal file
|
@ -0,0 +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-play"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
After Width: | Height: | Size: 263 B |
1
src/renderer/assets/feather/plus-circle.svg
Normal file
|
@ -0,0 +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-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
After Width: | Height: | Size: 351 B |
1
src/renderer/assets/feather/plus.svg
Normal file
|
@ -0,0 +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-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
After Width: | Height: | Size: 304 B |
1
src/renderer/assets/feather/radio.svg
Normal file
|
@ -0,0 +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-radio"><circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path></svg>
|
After Width: | Height: | Size: 389 B |
1
src/renderer/assets/feather/share-2.svg
Normal file
|
@ -0,0 +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-share-2"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
After Width: | Height: | Size: 445 B |
1
src/renderer/assets/feather/share.svg
Normal file
|
@ -0,0 +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-share"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="15"></line></svg>
|
After Width: | Height: | Size: 364 B |
1
src/renderer/assets/feather/thumbs-down.svg
Normal file
|
@ -0,0 +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-thumbs-down"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path></svg>
|
After Width: | Height: | Size: 374 B |
1
src/renderer/assets/feather/thumbs-up.svg
Normal file
|
@ -0,0 +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-thumbs-up"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg>
|
After Width: | Height: | Size: 348 B |
1
src/renderer/assets/feather/user.svg
Normal file
|
@ -0,0 +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-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
After Width: | Height: | Size: 313 B |
1
src/renderer/assets/feather/x-circle.svg
Normal file
|
@ -0,0 +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-x-circle"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
|
After Width: | Height: | Size: 346 B |
BIN
src/renderer/assets/spinner.gif
Normal file
After Width: | Height: | Size: 80 KiB |
99
src/renderer/audio/audio.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
var CiderAudio = {
|
||||||
|
context : null,
|
||||||
|
source : null,
|
||||||
|
audioNodes : {
|
||||||
|
gainNode : null,
|
||||||
|
spatialNode : null,
|
||||||
|
spatialInput: null,
|
||||||
|
},
|
||||||
|
init: function (cb = function () { }) {
|
||||||
|
//AudioOutputs.fInit = true;
|
||||||
|
searchInt = setInterval(function () {
|
||||||
|
if (document.getElementById("apple-music-player")) {
|
||||||
|
//AudioOutputs.eqReady = true;
|
||||||
|
document.getElementById("apple-music-player").crossOrigin = "anonymous";
|
||||||
|
CiderAudio.connectContext(document.getElementById("apple-music-player"), 0);
|
||||||
|
|
||||||
|
cb();
|
||||||
|
clearInterval(searchInt);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
off: function(){
|
||||||
|
try{
|
||||||
|
CiderAudio.audioNodes.gainNode.disconnect();
|
||||||
|
CiderAudio.audioNodes.spatialNode.disconnect();
|
||||||
|
CiderAudio.source.connect(CiderAudio.context.destination);} catch(e){}
|
||||||
|
},
|
||||||
|
connectContext: function (mediaElem){
|
||||||
|
|
||||||
|
if (!CiderAudio.context){
|
||||||
|
CiderAudio.context = new (window.AudioContext || window.webkitAudioContext);
|
||||||
|
}
|
||||||
|
if (!CiderAudio.source){
|
||||||
|
CiderAudio.source = CiderAudio.context.createMediaElementSource(mediaElem);
|
||||||
|
} else {try{CiderAudio.source.disconnect(CiderAudio.context.destination)}catch(e){}}
|
||||||
|
CiderAudio.audioNodes.gainNode = CiderAudio.context.createGain()
|
||||||
|
CiderAudio.source.connect(CiderAudio.audioNodes.gainNode);
|
||||||
|
CiderAudio.audioNodes.gainNode.connect(CiderAudio.context.destination);
|
||||||
|
if(app.cfg.audio.normalization){
|
||||||
|
CiderAudio.normalizerOn()
|
||||||
|
}
|
||||||
|
if (app.cfg.audio.spatial){
|
||||||
|
CiderAudio.spatialOn()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
normalizerOn: function (){},
|
||||||
|
normalizerOff: function (){
|
||||||
|
CiderAudio.audioNodes.gainNode.gain.setTargetAtTime(1, CiderAudio.context.currentTime+ 1, 0.5);
|
||||||
|
},
|
||||||
|
spatialOn: function (){
|
||||||
|
try{
|
||||||
|
CiderAudio.audioNodes.gainNode.connect(CiderAudio.context.destination);} catch(e){}
|
||||||
|
CiderAudio.audioNodes.spatialNode = new ResonanceAudio(CiderAudio.context);
|
||||||
|
CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.context.destination);
|
||||||
|
let roomDimensions = {
|
||||||
|
width: 32,
|
||||||
|
height: 12,
|
||||||
|
depth: 32,
|
||||||
|
};
|
||||||
|
let roomMaterials = {
|
||||||
|
// Room wall materials
|
||||||
|
left: 'metal',
|
||||||
|
right: 'metal',
|
||||||
|
front: 'brick-bare',
|
||||||
|
back: 'brick-bare',
|
||||||
|
down: 'acoustic-ceiling-tiles',
|
||||||
|
up: 'acoustic-ceiling-tiles',
|
||||||
|
};
|
||||||
|
CiderAudio.audioNodes.spatialNode.setRoomProperties(roomDimensions, roomMaterials);
|
||||||
|
CiderAudio.audioNodes.spatialInput = CiderAudio.audioNodes.spatialNode.createSource();
|
||||||
|
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.spatialInput.input);
|
||||||
|
},
|
||||||
|
spatialOff: function (){
|
||||||
|
try{
|
||||||
|
CiderAudio.audioNodes.spatialNode.output.disconnect(CiderAudio.context.destination);
|
||||||
|
CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.audioNodes.spatialInput.input);} catch(e){}
|
||||||
|
CiderAudio.audioNodes.gainNode.connect(CiderAudio.context.destination);
|
||||||
|
},
|
||||||
|
sendAudio: function (){
|
||||||
|
var options = {
|
||||||
|
mimeType : 'audio/webm; codecs=opus'
|
||||||
|
};
|
||||||
|
var destnode = CiderAudio.context.createMediaStreamDestination();
|
||||||
|
CiderAudio.audioNodes.gainNode.connect(destnode)
|
||||||
|
var mediaRecorder = new MediaRecorder(destnode.stream,options);
|
||||||
|
mediaRecorder.start(1);
|
||||||
|
mediaRecorder.ondataavailable = function(e) {
|
||||||
|
e.data.arrayBuffer().then(buffer => {
|
||||||
|
ipcRenderer.send('writeAudio',buffer)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (app.cfg.advanced.AudioContext){
|
||||||
|
CiderAudio.init()
|
||||||
|
|
||||||
|
}
|
27
src/renderer/logo-simple.svg
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 313 105" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(3.125,0,0,3.125,0,0)">
|
||||||
|
<g transform="matrix(1.38696,0,0,1.38696,-183.553,-76.7485)">
|
||||||
|
<g transform="matrix(16,0,0,16,160.665,73.4177)">
|
||||||
|
<path d="M0.705,-0.473C0.685,-0.641 0.558,-0.737 0.389,-0.737C0.197,-0.737 0.05,-0.602 0.05,-0.364C0.05,-0.126 0.195,0.01 0.389,0.01C0.576,0.01 0.688,-0.114 0.705,-0.248L0.549,-0.249C0.535,-0.171 0.474,-0.126 0.392,-0.126C0.281,-0.126 0.206,-0.208 0.206,-0.364C0.206,-0.515 0.28,-0.601 0.393,-0.601C0.477,-0.601 0.538,-0.553 0.549,-0.473L0.705,-0.473Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(16,0,0,16,172.699,73.4177)">
|
||||||
|
<path d="M0.06,-0L0.212,-0L0.212,-0.545L0.06,-0.545L0.06,-0ZM0.136,-0.616C0.181,-0.616 0.218,-0.65 0.218,-0.692C0.218,-0.734 0.181,-0.769 0.136,-0.769C0.092,-0.769 0.055,-0.734 0.055,-0.692C0.055,-0.65 0.092,-0.616 0.136,-0.616Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(16,0,0,16,177.051,73.4177)">
|
||||||
|
<path d="M0.261,0.009C0.349,0.009 0.395,-0.042 0.416,-0.087L0.423,-0.087L0.423,-0L0.572,-0L0.572,-0.727L0.421,-0.727L0.421,-0.454L0.416,-0.454C0.396,-0.498 0.352,-0.553 0.261,-0.553C0.141,-0.553 0.04,-0.46 0.04,-0.272C0.04,-0.089 0.137,0.009 0.261,0.009ZM0.309,-0.112C0.235,-0.112 0.195,-0.178 0.195,-0.273C0.195,-0.367 0.234,-0.432 0.309,-0.432C0.383,-0.432 0.424,-0.37 0.424,-0.273C0.424,-0.175 0.382,-0.112 0.309,-0.112Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(16,0,0,16,187.216,73.4177)">
|
||||||
|
<path d="M0.309,0.011C0.444,0.011 0.535,-0.055 0.556,-0.156L0.416,-0.165C0.401,-0.124 0.362,-0.102 0.311,-0.102C0.236,-0.102 0.188,-0.152 0.188,-0.234L0.188,-0.234L0.559,-0.234L0.559,-0.276C0.559,-0.461 0.447,-0.553 0.303,-0.553C0.142,-0.553 0.038,-0.439 0.038,-0.27C0.038,-0.097 0.141,0.011 0.309,0.011ZM0.188,-0.328C0.191,-0.39 0.238,-0.44 0.305,-0.44C0.371,-0.44 0.417,-0.393 0.417,-0.328L0.188,-0.328Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(16,0,0,16,196.778,73.4177)">
|
||||||
|
<path d="M0.06,-0L0.212,-0L0.212,-0.309C0.212,-0.376 0.261,-0.422 0.327,-0.422C0.348,-0.422 0.377,-0.418 0.391,-0.414L0.391,-0.548C0.378,-0.551 0.359,-0.553 0.344,-0.553C0.283,-0.553 0.233,-0.518 0.213,-0.45L0.207,-0.45L0.207,-0.545L0.06,-0.545L0.06,-0Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<circle cx="17" cy="16.667" r="12.333" style="fill:rgb(235,235,235);"/>
|
||||||
|
<g id="g147" transform="matrix(0.297476,0,0,0.297476,-16.7212,-19.0819)">
|
||||||
|
<path id="path41" d="M113.553,67.234C111.989,67.44 110.373,67.38 108.796,67.538C105.581,67.86 102.374,68.548 99.283,69.479C85.683,73.576 74.237,83.188 67.62,95.728C58.974,112.114 59.92,132.602 69.683,148.275C73.899,155.044 79.648,160.705 86.34,165.013C90.987,168.005 96.247,170.235 101.606,171.575C107.932,173.157 114.607,173.607 121.076,172.802C139.182,170.549 155.227,158.921 162.858,142.301C165.794,135.905 167.337,128.957 167.644,121.946C168.67,98.52 152.877,76.772 130.7,69.694C126.785,68.444 122.737,67.681 118.642,67.399C116.992,67.285 115.202,67.018 113.553,67.234M112.226,83.813C116.656,83.29 121.632,84.115 125.833,85.514C130.752,87.152 135.254,89.717 139.108,93.203C153.094,105.857 154.806,128.026 143.09,142.744C140.415,146.104 137.152,149.006 133.466,151.217C128.993,153.901 123.948,155.665 118.753,156.221C113.863,156.745 108.876,156.366 104.15,154.943C99.89,153.66 95.81,151.611 92.313,148.85C77.992,137.54 73.888,116.673 83.621,101.038C86.308,96.722 89.827,93.066 93.973,90.133C99.448,86.26 105.67,84.586 112.226,83.813M112.226,89.802C106.903,90.501 101.862,91.851 97.402,95.001C93.9,97.475 90.841,100.562 88.619,104.246C80.752,117.287 83.774,134.719 95.853,144.135C98.477,146.18 101.448,147.812 104.593,148.901C109.23,150.505 114.244,150.887 119.085,150.235C123.337,149.662 127.573,148.069 131.143,145.694C135.459,142.822 139.035,139.018 141.507,134.447C148.532,121.459 144.666,104.627 132.913,95.798C129.999,93.609 126.685,91.926 123.178,90.925C119.742,89.945 115.8,89.332 112.226,89.802M107.138,109.717C108.23,109.447 110.211,111.095 111.12,111.636C114.483,113.639 117.898,115.564 121.297,117.505C122.215,118.029 124.992,119.047 124.931,120.287C124.871,121.486 122.008,122.553 121.076,123.084C117.931,124.873 114.817,126.715 111.673,128.504C110.475,129.186 108.937,130.499 107.58,130.751C105.799,131.083 106.363,127.602 106.363,126.593L106.363,113.76C106.363,112.776 105.865,110.031 107.138,109.717Z" style="fill:rgb(255,91,109);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5 KiB |
BIN
src/renderer/logotmp.afdesign
Normal file
29
src/renderer/logotmp.svg
Normal file
After Width: | Height: | Size: 11 KiB |
12492
src/renderer/vibrant.min.js
vendored
Normal file
|
@ -1,30 +1,139 @@
|
||||||
<script type="text/x-template" id="fullscreen-view">
|
<script type="text/x-template" id="fullscreen-view">
|
||||||
<div class="fullscreen-view">
|
<div class="fullscreen-view">
|
||||||
<div class="row fs-row">
|
<div class="background">
|
||||||
<div class="col artwork-col">
|
<div class="bgArtworkMaterial">
|
||||||
<div class="artwork">
|
<div class="bg-artwork-container">
|
||||||
<mediaitem-artwork
|
<img v-if="(app.cfg.visual.bg_artwork_rotation || app.animateBackground)" class="bg-artwork a" :src="image.replace('600x600','30x30') ?? ''">
|
||||||
:size="600"
|
<img v-if="(app.cfg.visual.bg_artwork_rotation || app.animateBackground)" class="bg-artwork b" :src="image.replace('600x600','30x30') ?? ''">
|
||||||
url=""
|
<img v-if="!(app.cfg.visual.bg_artwork_rotation || app.animateBackground)" class="bg-artwork no-animation" :src="image.replace('600x600','30x30') ?? ''">
|
||||||
></mediaitem-artwork>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="fs-info">
|
|
||||||
<div>Name</div>
|
|
||||||
<div>Name</div>
|
|
||||||
<div>Name</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row fs-row">
|
||||||
|
<div class="col artwork-col">
|
||||||
|
<div class="artwork" @click="app.appMode = 'player'">
|
||||||
|
<mediaitem-artwork
|
||||||
|
:size="600"
|
||||||
|
:url="image ?? ''"
|
||||||
|
></mediaitem-artwork>
|
||||||
|
</div>
|
||||||
|
<div class="controls-parents">
|
||||||
|
<template v-if="app.mkReady()">
|
||||||
|
<div class="app-playback-controls" @mouseover="app.chrome.progresshover = true"
|
||||||
|
@mouseleave="app.chrome.progresshover = false" @contextmenu="app.nowPlayingContextMenu">
|
||||||
|
<div class="playback-info">
|
||||||
|
<div class="song-name">
|
||||||
|
{{ app.mk.nowPlayingItem["attributes"]["name"] }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap;">
|
||||||
|
<div class="item-navigate song-artist" style="display: inline-block;"
|
||||||
|
@click="app.getNowPlayingItemDetailed(`artist`)">
|
||||||
|
{{ app.mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||||
|
</div>
|
||||||
|
<div class="song-artist item-navigate" style="display: inline-block;"
|
||||||
|
@click="app.getNowPlayingItemDetailed('album')">
|
||||||
|
{{ (app.mk.nowPlayingItem["attributes"]["albumName"]) ? (" - " +
|
||||||
|
app.mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="song-progress">
|
||||||
|
<div class="song-duration" style="justify-content: space-between; height: 1px;"
|
||||||
|
:style="[app.chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
|
||||||
|
<p style="width: auto">{{ app.convertToMins(app.getSongProgress()) }}</p>
|
||||||
|
<p style="width: auto">{{ app.convertToMins(app.mk.currentPlaybackDuration) }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="range" step="0.01" min="0" :style="app.progressBarStyle()"
|
||||||
|
@input="app.playerLCD.desiredDuration = $event.target.value;app.playerLCD.userInteraction = true"
|
||||||
|
@mouseup="app.mk.seekToTime($event.target.value);app.playerLCD.desiredDuration = 0;app.playerLCD.userInteraction = false"
|
||||||
|
:max="app.mk.currentPlaybackDuration" :value="app.getSongProgress()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-buttons">
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button--small shuffle" v-if="app.mk.shuffleMode == 0"
|
||||||
|
@click="app.mk.shuffleMode = 1"></button>
|
||||||
|
<button class="playback-button--small shuffle active" v-else
|
||||||
|
@click="app.mk.shuffleMode = 0"></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button previous" @click="app.prevButton()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button pause" @click="app.mk.pause()" v-if="app.mk.isPlaying"></button>
|
||||||
|
<button class="playback-button play" @click="app.mk.play()" v-else></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button next" @click="app.mk.skipToNextItem()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button--small repeat" v-if="app.mk.repeatMode == 0"
|
||||||
|
@click="app.mk.repeatMode = 1"></button>
|
||||||
|
<button class="playback-button--small repeat active" @click="app.mk.repeatMode = 2"
|
||||||
|
v-else-if="app.mk.repeatMode == 1"></button>
|
||||||
|
<button class="playback-button--small repeat repeatOne" @click="app.mk.repeatMode = 0"
|
||||||
|
v-else-if="app.mk.repeatMode == 2"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col right-col" v-if="tabMode != ''">
|
||||||
|
<!-- <div class="fs-info">
|
||||||
|
<div>Name</div>
|
||||||
|
<div>Name</div>
|
||||||
|
<div>Name</div>
|
||||||
|
</div> -->
|
||||||
|
<div class="lyrics-col" v-if="tabMode == 'lyrics'">
|
||||||
|
<lyrics-view :yoffset="120" :time="time" :lyrics="lyrics"
|
||||||
|
:richlyrics="richlyrics"></lyrics-view>
|
||||||
|
</div>
|
||||||
|
<div class="queue-col" v-if="tabMode == 'queue'">
|
||||||
|
<cider-queue v-if="tabMode == 'queue'" ref="queue" ></cider-queue>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-toggles">
|
||||||
|
<div class="lyrics" :class="{active: tabMode == 'lyrics'}" @click="tabMode = (tabMode == 'lyrics') ? '' : 'lyrics'"></div>
|
||||||
|
<div class="queue" :class="{active: tabMode == 'queue'}" @click="tabMode = (tabMode == 'queue') ? '' :'queue'"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component('fullscreen-view', {
|
Vue.component('fullscreen-view', {
|
||||||
template: '#fullscreen-view',
|
template: '#fullscreen-view',
|
||||||
|
props: {
|
||||||
|
time: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
lyrics: {
|
||||||
|
type: Array,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
richlyrics: {
|
||||||
|
type: Array,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
app: this.$root,
|
||||||
|
tabMode: "lyrics",
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sayHello: function () {
|
sayHello: function () {
|
||||||
alert('Hello world!');
|
alert('Hello world!');
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
visibilityChanged: function (isVisible, entry) {
|
visibilityChanged: function (isVisible, entry) {
|
||||||
this.isVisible = isVisible
|
// this.isVisible = isVisible
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,16 +42,15 @@
|
||||||
<script>
|
<script>
|
||||||
Vue.component('lyrics-view', {
|
Vue.component('lyrics-view', {
|
||||||
template: '#lyrics-view',
|
template: '#lyrics-view',
|
||||||
props: ["time", "lyrics", "richlyrics", "translation", "onindex"],
|
props: ["time", "lyrics", "richlyrics", "translation", "onindex", "yoffset"],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
app: this.$root,
|
app: this.$root,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
time: function () {
|
time: function () {
|
||||||
if (app.lyricon && app.drawer.open && this.$refs.lyricsview) {
|
if (((app.lyricon && app.drawer.open) || app.appMode == 'fullscreen') && this.$refs.lyricsview) {
|
||||||
let currentLine = this.$refs.lyricsview.querySelector(`.lyric-line.active`)
|
let currentLine = this.$refs.lyricsview.querySelector(`.lyric-line.active`)
|
||||||
if (currentLine && currentLine.getElementsByClassName('lyricWaiting').length > 0) {
|
if (currentLine && currentLine.getElementsByClassName('lyricWaiting').length > 0) {
|
||||||
let duration = currentLine.getAttribute("end") - currentLine.getAttribute("start");
|
let duration = currentLine.getAttribute("end") - currentLine.getAttribute("start");
|
||||||
|
@ -119,21 +118,40 @@
|
||||||
if(startTime != 9999999) this.app.seekTo(startTime, false);
|
if(startTime != 9999999) this.app.seekTo(startTime, false);
|
||||||
},
|
},
|
||||||
getActiveLyric() {
|
getActiveLyric() {
|
||||||
// console.log(this.time);
|
|
||||||
const delayfix = 0.1
|
const delayfix = 0.1
|
||||||
const prevLine = app.currentLyricsLine;
|
const prevLine = app.currentLyricsLine;
|
||||||
for (var i = 0; i < this.lyrics.length; i++) {
|
for (var i = 0; i < this.lyrics.length; i++) {
|
||||||
if (this.time + delayfix >= this.lyrics[i].startTime && this.time + delayfix <= app.lyrics[i].endTime) {
|
if (this.time + delayfix >= this.lyrics[i].startTime && this.time + delayfix <= app.lyrics[i].endTime) {
|
||||||
if (app.currentLyricsLine != i) {
|
if (app.currentLyricsLine != i) {
|
||||||
app.currentLyricsLine = i;
|
app.currentLyricsLine = i;
|
||||||
if (app.lyricon && app.drawer.open && this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`)) {
|
if (((app.lyricon && app.drawer.open) || app.appMode == 'fullscreen') && this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`)) {
|
||||||
if(this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`)) {this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).classList.remove("active");}
|
if(this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`)) {this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).classList.remove("active");}
|
||||||
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).classList.add("active")
|
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).classList.add("active")
|
||||||
if (checkIfScrollIsStatic) {
|
if (this.checkIfScrollIsStatic) {
|
||||||
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).scrollIntoView({
|
let lyricElement = this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`)
|
||||||
behavior: "smooth",
|
// this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).scrollIntoView({
|
||||||
block: "center"
|
// behavior: "smooth",
|
||||||
|
// block: "nearest", inline: 'start'
|
||||||
|
// })
|
||||||
|
let parent = lyricElement.parentElement
|
||||||
|
let parentRect = parent.getBoundingClientRect()
|
||||||
|
let lyricElementRect = lyricElement.getBoundingClientRect()
|
||||||
|
let parentScrollTop = parent.scrollTop
|
||||||
|
let parentScrollLeft = parent.scrollLeft
|
||||||
|
let parentScrollTopDiff = parentScrollTop - parentRect.top
|
||||||
|
let parentScrollLeftDiff = parentScrollLeft - parentRect.left
|
||||||
|
let lyricElementScrollTop = lyricElementRect.top + parentScrollTopDiff
|
||||||
|
let lyricElementScrollLeft = lyricElementRect.left + parentScrollLeftDiff
|
||||||
|
let scrollTopDiff = lyricElementScrollTop - parentScrollTop
|
||||||
|
let scrollLeftDiff = lyricElementScrollLeft - parentScrollLeft
|
||||||
|
let scrollTop = parent.scrollTop + scrollTopDiff
|
||||||
|
let scrollLeft = parent.scrollLeft + scrollLeftDiff
|
||||||
|
parent.scrollTo({
|
||||||
|
top: scrollTop - (this.yoffset ?? 128),
|
||||||
|
left: scrollLeft,
|
||||||
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (app.currentLyricsLine == 0 && app.drawer.open) {
|
} else if (app.currentLyricsLine == 0 && app.drawer.open) {
|
||||||
|
@ -144,7 +162,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
if (app.drawer.open){
|
if ((app.drawer.open) || app.appMode == 'fullscreen'){
|
||||||
try{this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).childNodes.classList.remove("verse-active");} catch(e){}
|
try{this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).childNodes.classList.remove("verse-active");} catch(e){}
|
||||||
for (child of this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${app.currentLyricsLine}"]`).querySelectorAll(".verse")){
|
for (child of this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${app.currentLyricsLine}"]`).querySelectorAll(".verse")){
|
||||||
if (this.time + 0.1 >= child.getAttribute("lyricstart") * 1 + child.getAttribute("versestart") * 1){
|
if (this.time + 0.1 >= child.getAttribute("lyricstart") * 1 + child.getAttribute("versestart") * 1){
|
||||||
|
@ -164,7 +182,18 @@
|
||||||
return this.richlyrics[index].l
|
return this.richlyrics[index].l
|
||||||
}
|
}
|
||||||
else return []
|
else return []
|
||||||
|
},
|
||||||
|
checkIfScrollIsStatic : setInterval(() => {
|
||||||
|
try {
|
||||||
|
if (position === this.$refs.lyricsview.scrollTop) {
|
||||||
|
clearInterval(checkIfScrollIsStatic)
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
position = this.$refs.lyricsview.scrollTop
|
||||||
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
}, 50)
|
||||||
|
,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<script type="text/x-template" id="mediaitem-artwork">
|
<script type="text/x-template" id="mediaitem-artwork">
|
||||||
<div class="mediaitem-artwork" :class="[{'rounded': (type == 'artists')}, classes]"
|
<div class="mediaitem-artwork" :class="[{'rounded': (type == 'artists')}, classes]" :key="url"
|
||||||
v-observe-visibility="{callback: visibilityChanged}">
|
v-observe-visibility="{callback: visibilityChanged}">
|
||||||
<img :src="app.getMediaItemArtwork(url, size, width)"
|
<img :src="app.getMediaItemArtwork(url, size, width)"
|
||||||
decoding="async" loading="lazy"
|
decoding="async" loading="lazy"
|
||||||
|
@ -15,11 +15,11 @@
|
||||||
template: '#mediaitem-artwork',
|
template: '#mediaitem-artwork',
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: [String, Number],
|
||||||
default: '120'
|
default: '120'
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
type: Number,
|
type: [String, Number],
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="artwork" v-if="showArtwork == true">
|
<div class="artwork" v-if="showArtwork == true">
|
||||||
<mediaitem-artwork
|
<mediaitem-artwork
|
||||||
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
|
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
|
||||||
size="50"
|
:size="48"
|
||||||
:type="item.type"></mediaitem-artwork>
|
:type="item.type"></mediaitem-artwork>
|
||||||
<button class="overlay-play" @click="playTrack()"><%- include("../svg/play.svg") %></button>
|
<button class="overlay-play" @click="playTrack()"><%- include("../svg/play.svg") %></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
{{ item.attributes.genreNames[0] ?? "" }}
|
{{ item.attributes.genreNames[0] ?? "" }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="duration" v-if="showDuration" @dblclick="app.routeView(item)">
|
<div class="duration" v-if="displayDuration" @dblclick="app.routeView(item)">
|
||||||
{{ msToMinSec(item.attributes.durationInMillis ?? 0) }}
|
{{ msToMinSec(item.attributes.durationInMillis ?? 0) }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -71,7 +71,8 @@
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
addedToLibrary: false,
|
addedToLibrary: false,
|
||||||
guid: this.uuidv4(),
|
guid: this.uuidv4(),
|
||||||
app: this.$root
|
app: this.$root,
|
||||||
|
displayDuration: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -84,7 +85,19 @@
|
||||||
'show-duration': {type: Boolean, default: true},
|
'show-duration': {type: Boolean, default: true},
|
||||||
'contextExt': {type: Object, required: false},
|
'contextExt': {type: Object, required: false},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
let duration = this.item.attributes.durationInMillis ?? 0
|
||||||
|
if (duration == 0 || !this.showDuration) {
|
||||||
|
this.displayDuration = false
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
dragStart(evt) {
|
||||||
|
evt.dataTransfer.setData('text/plain', JSON.stringify({
|
||||||
|
type: this.item.attributes.playParams.kind ?? this.item.type,
|
||||||
|
id: this.item.id
|
||||||
|
}))
|
||||||
|
},
|
||||||
uuidv4() {
|
uuidv4() {
|
||||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
|
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
|
||||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||||
|
@ -166,7 +179,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contextMenu(event) {
|
async contextMenu(event) {
|
||||||
let self = this
|
let self = this
|
||||||
let data_type = this.getDataType()
|
let data_type = this.getDataType()
|
||||||
let item_id = this.item.attributes.playParams.id ?? this.item.id
|
let item_id = this.item.attributes.playParams.id ?? this.item.id
|
||||||
|
@ -234,20 +247,12 @@
|
||||||
normal: {
|
normal: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
"icon": "./assets/feather/list.svg",
|
||||||
"name": "Add to Playlist...",
|
"name": "Add to Playlist...",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.promptAddToPlaylist()
|
app.promptAddToPlaylist()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Start Radio",
|
|
||||||
"action": function () {
|
|
||||||
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
|
|
||||||
app.mk.play()
|
|
||||||
app.selectedMediaItems = []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Play Next",
|
"name": "Play Next",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -265,17 +270,72 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"icon": "./assets/feather/radio.svg",
|
||||||
|
"name": "Start Radio",
|
||||||
|
"action": function () {
|
||||||
|
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
|
||||||
|
app.mk.play()
|
||||||
|
app.selectedMediaItems = []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/heart.svg",
|
||||||
|
"id": "love",
|
||||||
|
"name": "Love",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.love(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/x-circle.svg",
|
||||||
|
"id": "unlove",
|
||||||
|
"name": "Unlove",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.unlove(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/thumbs-down.svg",
|
||||||
|
"id": "dislike",
|
||||||
|
"name": "Dislike",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.dislike(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/x-circle.svg",
|
||||||
|
"id": "undo_dislike",
|
||||||
|
"name": "Undo dislike",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.unlove(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/user.svg",
|
||||||
"name": "Go to Artist",
|
"name": "Go to Artist",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.searchAndNavigate(self.item, 'artist')
|
app.searchAndNavigate(self.item, 'artist')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"icon": "./assets/feather/disc.svg",
|
||||||
"name": "Go to Album",
|
"name": "Go to Album",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.searchAndNavigate(self.item, 'album')
|
app.searchAndNavigate(self.item, 'album')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/share.svg",
|
||||||
|
"name": "Share",
|
||||||
|
"action": function () {
|
||||||
|
self.app.copyToClipboard(self.item.attributes.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -288,6 +348,19 @@
|
||||||
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
|
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try{
|
||||||
|
let rating = await app.getRating(self.item).catch();
|
||||||
|
if (rating) {
|
||||||
|
if(rating == 0) {
|
||||||
|
menus.normal.items.find(x => x.id == 'love').disabled = false
|
||||||
|
menus.normal.items.find(x => x.id == 'dislike').disabled = false
|
||||||
|
}else if(rating == 1) {
|
||||||
|
menus.normal.items.find(x => x.id == 'unlove').disabled = false
|
||||||
|
}else if(rating == -1) {
|
||||||
|
menus.normal.items.find(x => x.id == 'undo_dislike').disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(_){}
|
||||||
CiderContextMenu.Create(event, menus[useMenu])
|
CiderContextMenu.Create(event, menus[useMenu])
|
||||||
},
|
},
|
||||||
visibilityChanged: function (isVisible, entry) {
|
visibilityChanged: function (isVisible, entry) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script type="text/x-template" id="mediaitem-scroller-horizontal-sp">
|
<script type="text/x-template" id="mediaitem-scroller-horizontal-sp">
|
||||||
<template>
|
<div class="cd-hmedia-scroller hmedia-scroller-card">
|
||||||
<div class="cd-hmedia-scroller">
|
<template>
|
||||||
<mediaitem-square-sp :item="item"
|
<mediaitem-square kind="card" :item="item" size="300"
|
||||||
v-for="item in items"></mediaitem-square-sp>
|
v-for="item in items"></mediaitem-square>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script type="text/x-template" id="mediaitem-square-large">
|
<script type="text/x-template" id="mediaitem-square-large">
|
||||||
<template>
|
|
||||||
<div ref="main" style="position: relative; display: inline-flex;" @contextmenu="contextMenu">
|
<div ref="main" style="position: relative; display: inline-flex;" @contextmenu="contextMenu">
|
||||||
<div @click.self='app.routeView(item)'
|
<div @click.self='app.routeView(item)'
|
||||||
class="cd-mediaitem-square-large" ref="main2">
|
class="cd-mediaitem-square-large" ref="main2">
|
||||||
|
@ -67,7 +66,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -76,6 +74,8 @@
|
||||||
props: ['item'],
|
props: ['item'],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
isVisible: false,
|
||||||
|
addedToLibrary: false,
|
||||||
app: this.$root,
|
app: this.$root,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -92,14 +92,47 @@
|
||||||
0 /*left*/, null
|
0 /*left*/, null
|
||||||
);
|
);
|
||||||
this.$refs.main.dispatchEvent(evt);
|
this.$refs.main.dispatchEvent(evt);
|
||||||
}
|
},
|
||||||
, contextMenu(event) {
|
async isInLibrary() {
|
||||||
|
if (this.item.type && !this.item.type.includes("library")) {
|
||||||
|
var params = {
|
||||||
|
"fields[playlists]": "inLibrary",
|
||||||
|
"fields[albums]": "inLibrary",
|
||||||
|
"relate": "library",
|
||||||
|
"extend": this.revisedRandId()
|
||||||
|
}
|
||||||
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
|
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
|
||||||
|
} else {
|
||||||
|
this.addedToLibrary = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeFromLibrary(id) {
|
||||||
|
var params = {
|
||||||
|
"fields[playlists]": "inLibrary",
|
||||||
|
"fields[songs]": "inLibrary",
|
||||||
|
"fields[albums]": "inLibrary",
|
||||||
|
"relate": "library",
|
||||||
|
"extend": this.revisedRandId()
|
||||||
|
}
|
||||||
|
var id = this.item.id ?? this.item.attributes.playParams.id
|
||||||
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
|
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
|
||||||
|
id = res.relationships.library.data[0].id
|
||||||
|
}
|
||||||
|
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
|
||||||
|
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
|
app.mk.api.library.remove({[truekind]: id})
|
||||||
|
this.addedToLibrary = true
|
||||||
|
},
|
||||||
|
async contextMenu(event) {
|
||||||
if (!event) { event = this.$refs.main } else { console.log(event) }
|
if (!event) { event = this.$refs.main } else { console.log(event) }
|
||||||
let self = this
|
let self = this
|
||||||
let useMenu = "normal"
|
let useMenu = "normal"
|
||||||
|
await this.isInLibrary()
|
||||||
if (app.selectedMediaItems.length <= 1) {
|
if (app.selectedMediaItems.length <= 1) {
|
||||||
app.selectedMediaItems = []
|
app.selectedMediaItems = []
|
||||||
app.select_selectMediaItem(this.item.attributes.playParams.id ?? this.item.id, this.item.attributes.playParams.kind ?? this.item.type, this.index, this.guid)
|
app.select_selectMediaItem(this.item.attributes.playParams.id ?? this.item.id, this.item.attributes.playParams.kind ?? this.item.type, this.index, this.guid, this.item.attributes.playParams.isLibrary ?? false)
|
||||||
} else {
|
} else {
|
||||||
useMenu = "multiple"
|
useMenu = "multiple"
|
||||||
}
|
}
|
||||||
|
@ -154,7 +187,7 @@
|
||||||
{
|
{
|
||||||
"name": "Play Next",
|
"name": "Play Next",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
|
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
||||||
app.mk.queue._reindex()
|
app.mk.queue._reindex()
|
||||||
app.selectedMediaItems = []
|
app.selectedMediaItems = []
|
||||||
}
|
}
|
||||||
|
@ -162,11 +195,41 @@
|
||||||
{
|
{
|
||||||
"name": "Play Later",
|
"name": "Play Later",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
|
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
||||||
app.mk.queue._reindex()
|
app.mk.queue._reindex()
|
||||||
app.selectedMediaItems = []
|
app.selectedMediaItems = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "addToPlaylist",
|
||||||
|
"name": "Add to Playlist...",
|
||||||
|
"action": function () {
|
||||||
|
app.promptAddToPlaylist()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": (this.addedToLibrary) ? "Remove from Library..." : "Add to Library...",
|
||||||
|
"action": async function () {
|
||||||
|
let item_id = self.item.attributes.playParams.id ?? self.item.id;
|
||||||
|
let data_type = self.item.attributes.playParams.kind ?? self.item.type;
|
||||||
|
if (self.addedToLibrary != true) {
|
||||||
|
console.log("add");
|
||||||
|
app.addToLibrary(item_id);
|
||||||
|
self.addedToLibrary = true
|
||||||
|
} else {
|
||||||
|
console.log("remove");
|
||||||
|
await self.removeFromLibrary(item_id);
|
||||||
|
self.addedToLibrary = false
|
||||||
|
}
|
||||||
|
;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Share",
|
||||||
|
"action": function () {
|
||||||
|
self.app.copyToClipboard(self.item.attributes.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@
|
||||||
await this.isInLibrary()
|
await this.isInLibrary()
|
||||||
if (app.selectedMediaItems.length <= 1) {
|
if (app.selectedMediaItems.length <= 1) {
|
||||||
app.selectedMediaItems = []
|
app.selectedMediaItems = []
|
||||||
app.select_selectMediaItem(this.item.attributes.playParams.id ?? this.item.id, this.item.attributes.playParams.kind ?? this.item.type, this.index, this.guid)
|
app.select_selectMediaItem(this.item.attributes.playParams.id ?? this.item.id, this.item.attributes.playParams.kind ?? this.item.type, this.index, this.guid, this.item.attributes.playParams.isLibrary ?? false)
|
||||||
} else {
|
} else {
|
||||||
useMenu = "multiple"
|
useMenu = "multiple"
|
||||||
}
|
}
|
||||||
|
@ -214,11 +214,44 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "addToPlaylist",
|
||||||
"name": "Add to Playlist...",
|
"name": "Add to Playlist...",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.promptAddToPlaylist()
|
app.promptAddToPlaylist()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "love",
|
||||||
|
"name": "Love",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.love(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unlove",
|
||||||
|
"name": "Unlove",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.unlove(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dislike",
|
||||||
|
"name": "Dislike",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.dislike(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "undo_dislike",
|
||||||
|
"name": "Undo dislike",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.unlove(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": (this.addedToLibrary) ? "Remove from Library..." : "Add to Library...",
|
"name": (this.addedToLibrary) ? "Remove from Library..." : "Add to Library...",
|
||||||
"action": async function () {
|
"action": async function () {
|
||||||
|
@ -232,7 +265,21 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((self.item.attributes.playParams.kind ?? self.item.type).includes("playlist")) { menus.normal.items.splice(2, 1);}
|
let rating = await app.getRating(self.item)
|
||||||
|
if(rating == 0) {
|
||||||
|
menus.normal.items.find(x => x.id == 'love').disabled = false
|
||||||
|
menus.normal.items.find(x => x.id == 'dislike').disabled = false
|
||||||
|
}else if(rating == 1) {
|
||||||
|
menus.normal.items.find(x => x.id == 'unlove').disabled = false
|
||||||
|
}else if(rating == -1) {
|
||||||
|
menus.normal.items.find(x => x.id == 'undo_dislike').disabled = false
|
||||||
|
}
|
||||||
|
if ((self.item.attributes.playParams.kind ?? self.item.type).includes("playlist")) {
|
||||||
|
// remove the add to playlist option by id "addToPlaylist" using the .filter() method
|
||||||
|
menus.normal.items = menus.normal.items.filter(function (item) {
|
||||||
|
return item.id != "addToPlaylist"
|
||||||
|
})
|
||||||
|
}
|
||||||
CiderContextMenu.Create(event, menus[useMenu])
|
CiderContextMenu.Create(event, menus[useMenu])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<script type="text/x-template" id="mediaitem-square">
|
<script type="text/x-template" id="mediaitem-square">
|
||||||
<div tabindex="0"
|
<div tabindex="0"
|
||||||
class="cd-mediaitem-square" :class="getClasses()" @contextmenu="contextMenu"
|
class="cd-mediaitem-square" :class="getClasses()" @contextmenu="contextMenu"
|
||||||
v-observe-visibility="{callback: visibilityChanged}">
|
v-observe-visibility="{callback: visibilityChanged}"
|
||||||
|
:style="{'--spcolor': getBgColor()}"
|
||||||
|
@click.self='app.routeView(item)'>
|
||||||
<template v-if="isVisible">
|
<template v-if="isVisible">
|
||||||
<div class="artwork-container">
|
<div class="artwork-container">
|
||||||
<div class="artwork" @click='app.routeView(item)'>
|
<div class="artwork" @click='app.routeView(item)'>
|
||||||
|
@ -16,15 +18,25 @@
|
||||||
@click="contextMenu"><%- include("../svg/more.svg") %></button>
|
@click="contextMenu"><%- include("../svg/more.svg") %></button>
|
||||||
<button class="play-btn" v-if="!noplay.includes(item.type)"
|
<button class="play-btn" v-if="!noplay.includes(item.type)"
|
||||||
@click="app.playMediaItem(item)"><%- include("../svg/play.svg") %></button>
|
@click="app.playMediaItem(item)"><%- include("../svg/play.svg") %></button>
|
||||||
|
<div class="badge-container" v-if="itemBadges.length != 0">
|
||||||
|
<div class="socialBadge" v-for="badge in itemBadges.limit(1)">
|
||||||
|
<mediaitem-artwork
|
||||||
|
:url="(badge.attributes.artwork ? badge.attributes.artwork.url : '')"
|
||||||
|
:size="32"></mediaitem-artwork>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="title item-navigate text-overflow-elipsis" @click.self='app.routeView(item)'>
|
<div class="info-rect" :class="{'info-rect-card': kind == 'card'}" :style="{'--bgartwork': getArtworkUrl(size, true)}">
|
||||||
{{ item.attributes.name }}
|
<div class="title" v-if="item.attributes.artistNames == null || kind!= 'card'" @click='app.routeView(item)'>
|
||||||
|
<div class="item-navigate text-overflow-elipsis">{{ item.attributes.name }}</div>
|
||||||
|
<div v-if="item.attributes && item.attributes.contentRating == 'explicit'" style= "margin-top: -2.6px;margin-left: 3px;">🅴</div>
|
||||||
|
</div>
|
||||||
|
<div class="subtitle item-navigate text-overflow-elipsis" @click="getSubtitleNavigation()"
|
||||||
|
v-if="getSubtitle() != ''">
|
||||||
|
{{ getSubtitle() }}
|
||||||
|
</div>
|
||||||
|
<div class="subtitle" v-if="getSubtitle() == '' && kind != 'card'"> </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle item-navigate text-overflow-elipsis" @click="app.searchAndNavigate(item,'artist')"
|
|
||||||
v-if="item.attributes.artistName">
|
|
||||||
{{ item.attributes.artistName }}
|
|
||||||
</div>
|
|
||||||
<div class="subtitle" v-else> </div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
@ -43,8 +55,9 @@
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '300'
|
default: '190'
|
||||||
}
|
},
|
||||||
|
'contextExt': {type: Object, required: false},
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
@ -53,16 +66,105 @@
|
||||||
guid: this.uuidv4(),
|
guid: this.uuidv4(),
|
||||||
noplay: ["apple-curators"],
|
noplay: ["apple-curators"],
|
||||||
nomenu: ["artists", "stations", "apple-curators"],
|
nomenu: ["artists", "stations", "apple-curators"],
|
||||||
app: this.$root
|
app: this.$root,
|
||||||
|
badges: this.$root.socialBadges.badgeMap,
|
||||||
|
itemBadges: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.getBadges()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getBgColor() {
|
||||||
|
let color = `#${(this.item.attributes.artwork.bgColor != null) ? (this.item.attributes.artwork.bgColor) : `333333`}`
|
||||||
|
let c = color.substring(1); // strip #
|
||||||
|
var rgb = parseInt(c, 16); // convert rrggbb to decimal
|
||||||
|
var r = (rgb >> 16) & 0xff; // extract red
|
||||||
|
var g = (rgb >> 8) & 0xff; // extract green
|
||||||
|
var b = (rgb >> 0) & 0xff; // extract blue
|
||||||
|
|
||||||
|
var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
|
||||||
|
|
||||||
|
console.log(color)
|
||||||
|
console.log(luma)
|
||||||
|
if (luma > 140) {
|
||||||
|
return "#aaaaaa"
|
||||||
|
}else{
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
getSubtitle() {
|
||||||
|
if(this.kind == 'card') {
|
||||||
|
try {
|
||||||
|
if (typeof this.item.attributes.artistNames != "undefined") {
|
||||||
|
return this.item.attributes.artistNames
|
||||||
|
} else if (typeof this.item.attributes.editorialNotes != "undefined") {
|
||||||
|
return this.item.attributes.editorialNotes.short
|
||||||
|
} else if (typeof this.item.attributes.artistName != "undefined") {
|
||||||
|
return this.item.attributes.artistName
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}catch(e) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
if (typeof this.item.attributes.artistName != "undefined") {
|
||||||
|
return this.item.attributes.artistName
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSubtitleNavigation() {
|
||||||
|
if(this.kind == 'card') {
|
||||||
|
try {
|
||||||
|
if (typeof this.item.attributes.artistNames != "undefined") {
|
||||||
|
return app.routeView(this.item)
|
||||||
|
} else if (typeof this.item.attributes.editorialNotes != "undefined") {
|
||||||
|
return app.routeView(this.item)
|
||||||
|
} else if (typeof this.item.attributes.artistName != "undefined") {
|
||||||
|
return app.searchAndNavigate(this.item,'artist')
|
||||||
|
} else {
|
||||||
|
return app.routeView(this.item)
|
||||||
|
}
|
||||||
|
}catch(e) {
|
||||||
|
return app.routeView(this.item)
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
if (typeof this.item.attributes.artistName != "undefined") {
|
||||||
|
return app.searchAndNavigate(this.item,'artist')
|
||||||
|
} else {
|
||||||
|
return app.routeView(this.item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getBadges() {
|
||||||
|
let self = this
|
||||||
|
if (this.badges[this.item.attributes.playParams.id ?? this.item.id]) {
|
||||||
|
let friends = this.badges[this.item.attributes.playParams.id ?? this.item.id]
|
||||||
|
if (friends) {
|
||||||
|
friends.forEach(function (friend) {
|
||||||
|
self.app.mk.api.socialProfile(friend).then(data => {
|
||||||
|
self.itemBadges.push(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
revisedRandId() {
|
revisedRandId() {
|
||||||
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
|
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
|
||||||
},
|
},
|
||||||
async isInLibrary() {
|
async isInLibrary() {
|
||||||
if (this.item.type && !this.item.type.includes("library")) {
|
if (this.item.type && !this.item.type.includes("library")) {
|
||||||
var params = {"fields[playlists]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()}
|
var params = {
|
||||||
|
"fields[playlists]": "inLibrary",
|
||||||
|
"fields[albums]": "inLibrary",
|
||||||
|
"relate": "library",
|
||||||
|
"extend": this.revisedRandId()
|
||||||
|
}
|
||||||
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
|
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,7 +172,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async removeFromLibrary(id) {
|
async removeFromLibrary(id) {
|
||||||
var params = {"fields[playlists]": "inLibrary","fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()}
|
var params = {
|
||||||
|
"fields[playlists]": "inLibrary",
|
||||||
|
"fields[songs]": "inLibrary",
|
||||||
|
"fields[albums]": "inLibrary",
|
||||||
|
"relate": "library",
|
||||||
|
"extend": this.revisedRandId()
|
||||||
|
}
|
||||||
var id = this.item.id ?? this.item.attributes.playParams.id
|
var id = this.item.id ?? this.item.attributes.playParams.id
|
||||||
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
|
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
|
||||||
|
@ -86,14 +194,21 @@
|
||||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getArtworkUrl() {
|
getArtworkUrl(size = -1, includeUrl = false) {
|
||||||
let artwork = this.item.attributes.artwork ? this.item.attributes.artwork.url : ''
|
let artwork = this.item.attributes.artwork ? this.item.attributes.artwork.url : ''
|
||||||
|
if(size != -1) {
|
||||||
|
artwork = artwork.replace('{w}', size).replace('{h}', size).replace('{f}', "webp").replace('{c}', ((size === 900) ? "sr" : "cc"))
|
||||||
|
}
|
||||||
switch (this.kind) {
|
switch (this.kind) {
|
||||||
case "385":
|
case "385":
|
||||||
artwork = this.item.attributes.editorialArtwork.subscriptionHero.url
|
artwork = this.item.attributes.editorialArtwork.subscriptionHero.url
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return artwork
|
if(!includeUrl) {
|
||||||
|
return artwork
|
||||||
|
}else{
|
||||||
|
return `url("${artwork}")`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getClasses() {
|
getClasses() {
|
||||||
let type = this.item.type
|
let type = this.item.type
|
||||||
|
@ -104,12 +219,15 @@
|
||||||
default:
|
default:
|
||||||
return []
|
return []
|
||||||
break;
|
break;
|
||||||
|
case "card":
|
||||||
|
return ["mediaitem-card"]
|
||||||
|
break;
|
||||||
case "385": // editorial
|
case "385": // editorial
|
||||||
return ["mediaitem-brick"]
|
return ["mediaitem-brick"]
|
||||||
break;
|
break;
|
||||||
case "small":
|
case "small":
|
||||||
return ["mediaitem-small"]
|
return ["mediaitem-small"]
|
||||||
break;
|
break;
|
||||||
case "music-videos":
|
case "music-videos":
|
||||||
case "uploadedVideo":
|
case "uploadedVideo":
|
||||||
case "uploaded-videos":
|
case "uploaded-videos":
|
||||||
|
@ -120,7 +238,7 @@
|
||||||
visibilityChanged: function (isVisible, entry) {
|
visibilityChanged: function (isVisible, entry) {
|
||||||
this.isVisible = isVisible
|
this.isVisible = isVisible
|
||||||
},
|
},
|
||||||
async contextMenu(event) {
|
async contextMenu(event) {
|
||||||
if (this.nomenu.includes(this.item.type)) {
|
if (this.nomenu.includes(this.item.type)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -134,7 +252,7 @@
|
||||||
let useMenu = "normal"
|
let useMenu = "normal"
|
||||||
if (app.selectedMediaItems.length <= 1) {
|
if (app.selectedMediaItems.length <= 1) {
|
||||||
app.selectedMediaItems = []
|
app.selectedMediaItems = []
|
||||||
app.select_selectMediaItem(this.item.attributes.playParams.id ?? this.item.id, this.item.attributes.playParams.kind ?? this.item.type, this.index, this.guid)
|
app.select_selectMediaItem(this.item.attributes.playParams.id ?? this.item.id, this.item.attributes.playParams.kind ?? this.item.type, this.index, this.guid, this.item.attributes.playParams.isLibrary ?? false)
|
||||||
} else {
|
} else {
|
||||||
useMenu = "multiple"
|
useMenu = "multiple"
|
||||||
}
|
}
|
||||||
|
@ -186,6 +304,14 @@
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/list.svg",
|
||||||
|
"id": "addToPlaylist",
|
||||||
|
"name": "Add to Playlist...",
|
||||||
|
"action": function () {
|
||||||
|
app.promptAddToPlaylist()
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Play Next",
|
"name": "Play Next",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -203,31 +329,100 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Add to Playlist...",
|
"icon": "./assets/feather/plus.svg",
|
||||||
"action": function () {
|
"name": (this.addedToLibrary) ? "Remove from Library..." : "Add to Library...",
|
||||||
app.promptAddToPlaylist()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": (this.addedToLibrary) ? "Remove from Library..." : "Add to Library...",
|
|
||||||
"action": async function () {
|
"action": async function () {
|
||||||
let item_id = self.item.attributes.playParams.id ?? self.item.id;
|
let item_id = self.item.attributes.playParams.id ?? self.item.id;
|
||||||
let data_type = self.item.attributes.playParams.kind ?? self.item.type;
|
let data_type = self.item.attributes.playParams.kind ?? self.item.type;
|
||||||
if (self.addedToLibrary != true) { console.log("add"); app.addToLibrary(item_id); self.addedToLibrary = true}
|
if (self.addedToLibrary != true) {
|
||||||
else { console.log("remove"); await self.removeFromLibrary(item_id); self.addedToLibrary = false};
|
console.log("add");
|
||||||
|
app.addToLibrary(item_id);
|
||||||
|
self.addedToLibrary = true
|
||||||
|
} else {
|
||||||
|
console.log("remove");
|
||||||
|
await self.removeFromLibrary(item_id);
|
||||||
|
self.addedToLibrary = false
|
||||||
|
}
|
||||||
|
;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/heart.svg",
|
||||||
|
"id": "love",
|
||||||
|
"name": "Love",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.love(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/x-circle.svg",
|
||||||
|
"id": "unlove",
|
||||||
|
"name": "Unlove",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.unlove(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/thumbs-down.svg",
|
||||||
|
"id": "dislike",
|
||||||
|
"name": "Dislike",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.dislike(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/x-circle.svg",
|
||||||
|
"id": "undo_dislike",
|
||||||
|
"name": "Undo dislike",
|
||||||
|
"disabled": true,
|
||||||
|
"action": function () {
|
||||||
|
app.unlove(self.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/share.svg",
|
||||||
|
"name": "Share",
|
||||||
|
"action": function () {
|
||||||
|
self.app.copyToClipboard(self.item.attributes.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((self.item.attributes.playParams.kind ?? self.item.type).includes("playlist")) { menus.normal.items.splice(2, 1);}
|
if ((self.item.attributes.playParams.kind ?? self.item.type).includes("playlist")) {
|
||||||
|
// remove the add to playlist option by id "addToPlaylist" using the .filter() method
|
||||||
|
menus.normal.items = menus.normal.items.filter(function (item) {
|
||||||
|
return item.id != "addToPlaylist"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let rating = await app.getRating(self.item)
|
||||||
|
if (rating == 0) {
|
||||||
|
menus.normal.items.find(x => x.id == 'love').disabled = false
|
||||||
|
menus.normal.items.find(x => x.id == 'dislike').disabled = false
|
||||||
|
} else if (rating == 1) {
|
||||||
|
menus.normal.items.find(x => x.id == 'unlove').disabled = false
|
||||||
|
} else if (rating == -1) {
|
||||||
|
menus.normal.items.find(x => x.id == 'undo_dislike').disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.contextExt) {
|
||||||
|
if (this.contextExt.normal) {
|
||||||
|
menus.normal.items = menus.normal.items.concat(this.contextExt.normal)
|
||||||
|
}
|
||||||
|
if (this.contextExt.multiple) {
|
||||||
|
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
CiderContextMenu.Create(event, menus[useMenu])
|
CiderContextMenu.Create(event, menus[useMenu])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
beforeDestroy: function () {
|
beforeDestroy: function () {
|
||||||
this.item = null;
|
// this.item = null;
|
||||||
this.kind = null;
|
// this.kind = null;
|
||||||
this.size = null;
|
// this.size = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
221
src/renderer/views/components/sidebar-playlist.ejs
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
<script type="text/x-template" id="sidebar-playlist">
|
||||||
|
<div class="sidebar-playlist" :key="item.id">
|
||||||
|
<button class="app-sidebar-item app-sidebar-item-playlist" :key="item.id" v-if="item.type != 'library-playlist-folders'"
|
||||||
|
:class="{'active': $root.page.includes(item.id)}"
|
||||||
|
@contextmenu="playlistContextMenu($event, item.id)"
|
||||||
|
@dragstart="startDrag($event, item)"
|
||||||
|
@dragover="dragOver"
|
||||||
|
@drop="onDrop"
|
||||||
|
:href="item.href"
|
||||||
|
@click='$root.appRoute(`playlist_` + item.id); $root.showingPlaylist = [];$root.getPlaylistFromID($root.page.substring(9))'>
|
||||||
|
<template v-if="!renaming">
|
||||||
|
<div class="sidebar-icon" v-html="icon"></div> {{ item.attributes.name }}
|
||||||
|
</template>
|
||||||
|
<input type="text" v-model="item.attributes.name" class="pl-rename-field" @blur="rename()" @keydown.enter="rename()" v-else>
|
||||||
|
</button>
|
||||||
|
<button class="app-sidebar-item app-sidebar-item-playlist" :key="item.id" v-else
|
||||||
|
:class="[{'folder-button-active': folderOpened}, isPlaylistSelected]"
|
||||||
|
@contextmenu="playlistContextMenu($event, item.id)"
|
||||||
|
@dragstart="startDrag($event, item)"
|
||||||
|
@dragover="dragOver"
|
||||||
|
@drop="onDrop"
|
||||||
|
:href="item.href"
|
||||||
|
@click='getPlaylistChildren(item)'>
|
||||||
|
<span v-if="!folderOpened">📁</span>
|
||||||
|
<span v-else>📂</span>
|
||||||
|
<template v-if="!renaming">
|
||||||
|
{{ item.attributes.name }}
|
||||||
|
</template>
|
||||||
|
<input type="text" v-model="item.attributes.name" class="pl-rename-field" @blur="rename()" @keydown.enter="rename()" v-else>
|
||||||
|
</button>
|
||||||
|
<div class="folder-body" v-if="item.type === 'library-playlist-folders' && folderOpened">
|
||||||
|
<template v-if="children.length != 0">
|
||||||
|
<sidebar-playlist v-for="item in children" :item="item" :key="item.id"></sidebar-playlist>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="spinner"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('sidebar-playlist', {
|
||||||
|
template: '#sidebar-playlist',
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
folderOpened: false,
|
||||||
|
children: [],
|
||||||
|
playlistRoot: "p.playlistsroot",
|
||||||
|
renaming: false,
|
||||||
|
icon: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.icon = await this.$root.getSvgIcon("./assets/feather/list.svg")
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
rename() {
|
||||||
|
this.renaming = false
|
||||||
|
|
||||||
|
if(this.item.type === "library-playlist-folders") {
|
||||||
|
this.$root.editPlaylistFolder(this.item.id, this.item.attributes.name)
|
||||||
|
}else{
|
||||||
|
this.$root.editPlaylist(this.item.id, this.item.attributes.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getChildren() {
|
||||||
|
let self = this
|
||||||
|
this.children = []
|
||||||
|
this.children = this.$root.playlists.listing.filter(child => {
|
||||||
|
if(child.parent == self.item.id) {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async move(item, sendTo) {
|
||||||
|
let self = this
|
||||||
|
console.log(sendTo)
|
||||||
|
let type = item.type.replace("library-", "")
|
||||||
|
let typeTo = sendTo.type
|
||||||
|
this.$root.mk.api.v3.music(`/v1/me/library/${type}/${item.id}/parent`, {}, {
|
||||||
|
fetchOptions: {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: [{
|
||||||
|
id: sendTo.id,
|
||||||
|
type: typeTo
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// find the item in this.$root.playlists.listing and store it in a variable
|
||||||
|
this.$root.playlists.listing.filter(playlist => {
|
||||||
|
if(playlist.id == item.id) {
|
||||||
|
console.log(playlist)
|
||||||
|
playlist.parent = sendTo.id
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(typeof this.$parent.getChildren == "function") {
|
||||||
|
this.$parent.getChildren()
|
||||||
|
console.log(this.$parent.children)
|
||||||
|
}
|
||||||
|
await this.getChildren()
|
||||||
|
this.$root.sortPlaylists()
|
||||||
|
// await this.$root.refreshPlaylists()
|
||||||
|
},
|
||||||
|
playlistContextMenu(event, playlist_id) {
|
||||||
|
let menu = {
|
||||||
|
items: {
|
||||||
|
"moveToParent": {
|
||||||
|
name: "Move to top",
|
||||||
|
action: () => {
|
||||||
|
let self = this
|
||||||
|
this.move(this.item, {
|
||||||
|
id: this.playlistRoot,
|
||||||
|
type: "library-playlist-folders"
|
||||||
|
})
|
||||||
|
setTimeout(()=>{self.getChildren()}, 2000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rename": {
|
||||||
|
name: "Rename",
|
||||||
|
action: () => {
|
||||||
|
this.renaming = true
|
||||||
|
setTimeout(()=>{
|
||||||
|
document.querySelector(".pl-rename-field").focus()
|
||||||
|
document.querySelector(".pl-rename-field").select()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deleteFromPlaylist": {
|
||||||
|
name: "Delete from library",
|
||||||
|
action: () => {
|
||||||
|
this.$root.deletePlaylist(playlist_id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"addToFavorites": {
|
||||||
|
name: "Add to favorites",
|
||||||
|
disabled: true,
|
||||||
|
action: () => {
|
||||||
|
this.addFavorite(playlist_id, "library-playlists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.item.type === "library-playlist-folders") {
|
||||||
|
menu.items.addToFavorites.disabled = true
|
||||||
|
}
|
||||||
|
CiderContextMenu.Create(event, menu)
|
||||||
|
},
|
||||||
|
dragOver(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.dataTransfer.dropEffect = "move";
|
||||||
|
},
|
||||||
|
onDrop (evt) {
|
||||||
|
let data = JSON.parse(evt.dataTransfer.getData("text/plain"))
|
||||||
|
evt.preventDefault();
|
||||||
|
if(data.id == this.item.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(data)
|
||||||
|
if(data) {
|
||||||
|
if(this.item.type == "library-playlists" || this.item.type == "library-playlist-folders") {
|
||||||
|
if(data.type == "library-playlists" && this.item.type == "library-playlists") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.move(data, this.item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startDrag (evt) {
|
||||||
|
evt.dataTransfer.dropEffect = 'move'
|
||||||
|
evt.dataTransfer.effectAllowed = 'move'
|
||||||
|
evt.dataTransfer.setData('text/plain', JSON.stringify(this.item))
|
||||||
|
},
|
||||||
|
getPlaylistChildren(item) {
|
||||||
|
let self = this
|
||||||
|
this.children = []
|
||||||
|
this.getChildren()
|
||||||
|
this.toggleFolder()
|
||||||
|
this.$root.mk.api.library.playlistFolderChildren(item.id).then(children => {
|
||||||
|
children.forEach(child => {
|
||||||
|
if(!self.$root.playlists.listing.find(listing => listing.id == child.id)) {
|
||||||
|
child.parent = self.item.id
|
||||||
|
self.$root.playlists.listing.push(child)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.$root.playlists.listing.sort((a, b) => {
|
||||||
|
if (a.type === 'library-playlist-folders' && b.type !== 'library-playlist-folders') {
|
||||||
|
return -1
|
||||||
|
} else if (a.type !== 'library-playlist-folders' && b.type === 'library-playlist-folders') {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.getChildren()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
isPlaylistSelected(item) {
|
||||||
|
if(this.$root.showingPlaylist.id == item.id) {
|
||||||
|
return ["active"]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleFolder() {
|
||||||
|
this.folderOpened = !this.folderOpened;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
355
src/renderer/views/components/spatial-properties.ejs
Normal file
|
@ -0,0 +1,355 @@
|
||||||
|
<script type="text/x-template" id="spatial-properties">
|
||||||
|
<div class="modal-fullscreen spatialproperties-panel">
|
||||||
|
<div class="modal-window" v-if="ready">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">Spatial Properties</div>
|
||||||
|
<button class="close-btn" @click="close()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<template v-if="roomEditType == 'dimensions'">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"><h3>Room Dimensions</h3></div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<button class="md-btn" @click="roomEditType = 'positions'">Set Positions</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
Width
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="room_dimensions.width" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="room_dimensions.width" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
Height
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="room_dimensions.height" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="room_dimensions.height" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
Depth
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="room_dimensions.depth" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="room_dimensions.depth" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label v-if="!app.cfg.audio.normalization">
|
||||||
|
Gain
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="app.cfg.audio.spatial_properties.gain" step="0.1"/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col visual-container">
|
||||||
|
<div class="visual" :style="objectContainerStyle()">
|
||||||
|
<div class="face" :style="[faceStyle()]"></div>
|
||||||
|
<div class="face" :style="[faceStyle(), topFaceStyle()]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="roomEditType == 'positions'">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"><h3>Room Positions</h3></div>
|
||||||
|
<div class="col-auto flex-center">
|
||||||
|
<button class="md-btn" @click="roomEditType = 'dimensions'">Set Dimensions</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
X (Listener)
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="listener_position[0]" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="listener_position[0]" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
Y (Listener)
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="listener_position[1]" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="listener_position[1]" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
Z (Listener)
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="listener_position[2]" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="listener_position[2]" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
X (Audio Source)
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="audio_position[0]" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="audio_position[0]" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
Y (Audio Source)
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="audio_position[1]" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="audio_position[1]" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
Z (Audio Source)
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<input type="range" class="md-slider" min="0" max="100" @change="setRoom()" style="width: 100%;"
|
||||||
|
v-model="audio_position[2]" step="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 flex-center">
|
||||||
|
<input type="number" min="0" @change="setRoom()" style="width: 100%;text-align: center"
|
||||||
|
v-model="audio_position[2]" step="1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col visual-container">
|
||||||
|
<div class="visual">
|
||||||
|
<div class="face" :style="[faceStyle()]"></div>
|
||||||
|
<div class="face" :style="[faceStyle(), topFaceStyle()]"></div>
|
||||||
|
|
||||||
|
<!-- <div class="listener" :style="[listenerStyle()]">L</div> -->
|
||||||
|
<!-- <div class="audiosource" :style="[audioSourceStyle()]">A</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"><h3>Room Materials</h3></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<label>
|
||||||
|
Up
|
||||||
|
<select class="md-select" @change="setRoom()"
|
||||||
|
v-model="room_materials.up">
|
||||||
|
<option v-for="prop in roomProps" :value="prop">{{ prop }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col flex-center">
|
||||||
|
<label>
|
||||||
|
Left
|
||||||
|
<select class="md-select" @change="setRoom()"
|
||||||
|
v-model="room_materials.left">
|
||||||
|
<option v-for="prop in roomProps" :value="prop">{{ prop }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<label>
|
||||||
|
Front
|
||||||
|
<select class="md-select" @change="setRoom()"
|
||||||
|
v-model="room_materials.front">
|
||||||
|
<option v-for="prop in roomProps" :value="prop">{{ prop }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Back
|
||||||
|
<select class="md-select" @change="setRoom()"
|
||||||
|
v-model="room_materials.back">
|
||||||
|
<option v-for="prop in roomProps" :value="prop">{{ prop }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<label>
|
||||||
|
Right
|
||||||
|
<select class="md-select" @change="setRoom()"
|
||||||
|
v-model="room_materials.right">
|
||||||
|
<option v-for="prop in roomProps" :value="prop">{{ prop }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col flex-center">
|
||||||
|
<label>
|
||||||
|
Down
|
||||||
|
<select class="md-select" @change="setRoom()"
|
||||||
|
v-model="room_materials.down">
|
||||||
|
<option v-for="prop in roomProps" :value="prop">{{ prop }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('spatial-properties', {
|
||||||
|
template: '#spatial-properties',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
app: this.$root,
|
||||||
|
room_dimensions: null,
|
||||||
|
room_materials: null,
|
||||||
|
listener_position: null,
|
||||||
|
audio_position: null,
|
||||||
|
roomEditType: "dimensions",
|
||||||
|
roomProps: [
|
||||||
|
'transparent',
|
||||||
|
'acoustic-ceiling-tiles',
|
||||||
|
'brick-bare',
|
||||||
|
'brick-painted',
|
||||||
|
'concrete-block-coarse',
|
||||||
|
'concrete-block-painted',
|
||||||
|
'curtain-heavy',
|
||||||
|
'fiber-glass-insulation',
|
||||||
|
'glass-thin',
|
||||||
|
'glass-thick',
|
||||||
|
'grass',
|
||||||
|
'linoleum-on-concrete',
|
||||||
|
'marble',
|
||||||
|
'metal',
|
||||||
|
'parquet-on-concrete',
|
||||||
|
'plaster-smooth',
|
||||||
|
'plywood-panel',
|
||||||
|
'polished-concrete-or-tile',
|
||||||
|
'sheetrock',
|
||||||
|
'water-or-ice-surface',
|
||||||
|
'wood-ceiling',
|
||||||
|
'wood-panel',
|
||||||
|
'uniform'
|
||||||
|
],
|
||||||
|
visualMultiplier: 4,
|
||||||
|
ready: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {},
|
||||||
|
mounted() {
|
||||||
|
this.room_dimensions = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.room_dimensions))
|
||||||
|
this.room_materials = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.room_materials))
|
||||||
|
this.audio_position = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.audio_position))
|
||||||
|
this.listener_position = JSON.parse(JSON.stringify(this.$root.cfg.audio.spatial_properties.listener_position))
|
||||||
|
if(typeof this.app.mk.nowPlayingItem != "undefined") {
|
||||||
|
this.setRoom()
|
||||||
|
}
|
||||||
|
this.ready = true
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
listenerStyle() {
|
||||||
|
let style = {
|
||||||
|
transform: `rotateX(60deg) rotateZ(-45deg) translateX(${this.listener_position[0]}px) translateY(${this.listener_position[2]}px) translateZ(${100 + +this.listener_position[1]}px)`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
audioSourceStyle() {
|
||||||
|
let style = {
|
||||||
|
transform: `rotateX(60deg) rotateZ(-45deg) translateX(${this.audio_position[0]}px) translateY(${this.audio_position[2]}px) translateZ(${100 + +this.audio_position[1]}px)`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
topFaceStyle() {
|
||||||
|
let style = {
|
||||||
|
transform: `rotateX(60deg) rotateZ(-45deg) translateZ(${this.room_dimensions.height * this.visualMultiplier}px)`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
objectContainerStyle() {
|
||||||
|
let scale = 1
|
||||||
|
if(this.room_dimensions.width * this.visualMultiplier > 300) {
|
||||||
|
scale = 300 / (this.room_dimensions.width * this.visualMultiplier)
|
||||||
|
}
|
||||||
|
let style = {
|
||||||
|
transform: `scale(${scale})`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
faceStyle() {
|
||||||
|
let style = {
|
||||||
|
width: `${this.room_dimensions.width * this.visualMultiplier}px`,
|
||||||
|
height: `${this.room_dimensions.depth * this.visualMultiplier}px`,
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$root.cfg.audio.spatial_properties.room_dimensions = this.room_dimensions
|
||||||
|
this.$root.cfg.audio.spatial_properties.room_materials = this.room_materials
|
||||||
|
this.$root.cfg.audio.spatial_properties.audio_position = this.audio_position
|
||||||
|
this.$root.cfg.audio.spatial_properties.listener_position = this.listener_position
|
||||||
|
app.resetState()
|
||||||
|
},
|
||||||
|
setRoom() {
|
||||||
|
window.CiderAudio.audioNodes.spatialNode.setRoomProperties(this.room_dimensions, this.room_materials);
|
||||||
|
CiderAudio.audioNodes.spatialInput.setPosition(...this.audio_position)
|
||||||
|
CiderAudio.audioNodes.spatialNode.setListenerPosition(...this.listener_position)
|
||||||
|
if(!this.app.cfg.audio.normalization) {
|
||||||
|
window.CiderAudio.audioNodes.gainNode.gain.value = app.cfg.audio.spatial_properties.gain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -18,9 +18,11 @@
|
||||||
<link rel="stylesheet/less" type="text/css" href="style.less"/>
|
<link rel="stylesheet/less" type="text/css" href="style.less"/>
|
||||||
<script src="less.js"></script>
|
<script src="less.js"></script>
|
||||||
<script src="<%- (env.dev ? "vue.js" : "vue.dev.js") %>"></script>
|
<script src="<%- (env.dev ? "vue.js" : "vue.dev.js") %>"></script>
|
||||||
|
<script src="vuex.min.js"></script>
|
||||||
<script src="sortable.min.js"></script>
|
<script src="sortable.min.js"></script>
|
||||||
<script src="vue-observe-visibility.min.js"></script>
|
<script src="vue-observe-visibility.min.js"></script>
|
||||||
<script src="sortable.min.js"></script>
|
<script src="sortable.min.js"></script>
|
||||||
|
<script src="vibrant.min.js"></script>
|
||||||
<script src="vuedraggable.umd.min.js"></script>
|
<script src="vuedraggable.umd.min.js"></script>
|
||||||
<link rel="manifest" href="./manifest.json?v=2">
|
<link rel="manifest" href="./manifest.json?v=2">
|
||||||
<script src="https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js"></script>
|
<script src="https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js"></script>
|
||||||
|
@ -67,7 +69,9 @@
|
||||||
<template v-if="mkReady()">
|
<template v-if="mkReady()">
|
||||||
<div class="app-playback-controls" @mouseover="chrome.progresshover = true"
|
<div class="app-playback-controls" @mouseover="chrome.progresshover = true"
|
||||||
@mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu">
|
@mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu">
|
||||||
<div class="artwork"></div>
|
<div class="artwork" @click="drawer.open = false; appMode = 'fullscreen'">
|
||||||
|
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||||
|
</div>
|
||||||
<div class="playback-info">
|
<div class="playback-info">
|
||||||
<div class="song-name">
|
<div class="song-name">
|
||||||
{{ mk.nowPlayingItem["attributes"]["name"] }}
|
{{ mk.nowPlayingItem["attributes"]["name"] }}
|
||||||
|
@ -114,7 +118,7 @@
|
||||||
<div class="app-chrome--right">
|
<div class="app-chrome--right">
|
||||||
<div class="app-chrome-item volume display--large">
|
<div class="app-chrome-item volume display--large">
|
||||||
<div class="app-chrome-item volume-icon"></div>
|
<div class="app-chrome-item volume-icon"></div>
|
||||||
<input type="range" class="" step="0.01" min="0" max="1" v-model="mk.volume"
|
<input type="range" class="" @wheel="volumeWheel" step="0.01" min="0" max="1" v-model="mk.volume"
|
||||||
v-if="typeof mk.volume != 'undefined'">
|
v-if="typeof mk.volume != 'undefined'">
|
||||||
</div>
|
</div>
|
||||||
<div class="app-chrome-item generic" v-if="false">
|
<div class="app-chrome-item generic" v-if="false">
|
||||||
|
@ -168,29 +172,21 @@
|
||||||
<div class="app-sidebar-header-text">
|
<div class="app-sidebar-header-text">
|
||||||
Apple Music
|
Apple Music
|
||||||
</div>
|
</div>
|
||||||
<sidebar-library-item v-if="isDev" name="Home" page="home"></sidebar-library-item>
|
<sidebar-library-item name="Home" svg-icon="./assets/feather/home.svg" page="home"></sidebar-library-item>
|
||||||
<sidebar-library-item name="Listen Now" page="listen_now"></sidebar-library-item>
|
<sidebar-library-item name="Listen Now" svg-icon="./assets/feather/play-circle.svg" page="listen_now"></sidebar-library-item>
|
||||||
<sidebar-library-item name="Browse" page="browse"></sidebar-library-item>
|
<sidebar-library-item name="Browse" svg-icon="./assets/feather/globe.svg" page="browse"></sidebar-library-item>
|
||||||
<sidebar-library-item name="Radio" page="radio"></sidebar-library-item>
|
<sidebar-library-item name="Radio" svg-icon="./assets/feather/radio.svg" page="radio"></sidebar-library-item>
|
||||||
<div class="app-sidebar-header-text">
|
<div class="app-sidebar-header-text">
|
||||||
Library
|
Library
|
||||||
</div>
|
</div>
|
||||||
<sidebar-library-item name="Recently Added" page="library-recentlyadded"></sidebar-library-item>
|
<sidebar-library-item name="Recently Added" svg-icon="./assets/feather/plus-circle.svg" page="library-recentlyadded"></sidebar-library-item>
|
||||||
<sidebar-library-item name="Songs" page="library-songs"></sidebar-library-item>
|
<sidebar-library-item name="Songs" svg-icon="./assets/feather/music.svg" page="library-songs"></sidebar-library-item>
|
||||||
<sidebar-library-item name="Albums" page="library-albums"></sidebar-library-item>
|
<sidebar-library-item name="Albums" svg-icon="./assets/feather/disc.svg" page="library-albums"></sidebar-library-item>
|
||||||
<sidebar-library-item name="Artists" page="library-artists"></sidebar-library-item>
|
<sidebar-library-item name="Artists" svg-icon="./assets/feather/user.svg" 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" @contextmenu="playlistHeaderContextMenu">
|
<div class="app-sidebar-header-text" @contextmenu="playlistHeaderContextMenu">
|
||||||
Playlists
|
Playlists
|
||||||
</div>
|
</div>
|
||||||
<button class="app-sidebar-item" v-for="item in playlists.listing" :key="item.id"
|
<sidebar-playlist v-for="item in getPlaylistFolderChildren('p.playlistsroot')" :item="item"></sidebar-playlist>
|
||||||
v-if="item.attributes.name"
|
|
||||||
@contextmenu="playlistContextMenu($event, item.id)"
|
|
||||||
@dragover="()=>{}"
|
|
||||||
:href="item.href"
|
|
||||||
@click='appRoute(`playlist_` + item.id); showingPlaylist = [];getPlaylistFromID(page.substring(9))'>
|
|
||||||
{{ item.attributes.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="usermenu-container" v-if="chrome.menuOpened">
|
<div class="usermenu-container" v-if="chrome.menuOpened">
|
||||||
|
@ -215,6 +211,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="usermenu-item" v-if="cfg.advanced.AudioContext && cfg.audio.spatial" @click="modals.spatialProperties = true">
|
||||||
|
Spatialized Audio Settings
|
||||||
|
</button>
|
||||||
<button class="usermenu-item">
|
<button class="usermenu-item">
|
||||||
About
|
About
|
||||||
</button>
|
</button>
|
||||||
|
@ -232,7 +231,7 @@
|
||||||
</transition>
|
</transition>
|
||||||
<div class="app-sidebar-footer">
|
<div class="app-sidebar-footer">
|
||||||
<input type="range" class="web-slider display--small" step="0.01" min="0" max="1"
|
<input type="range" class="web-slider display--small" step="0.01" min="0" max="1"
|
||||||
v-model="mk.volume"
|
v-model="mk.volume" @wheel="volumeWheel"
|
||||||
v-if="typeof mk.volume != 'undefined'">
|
v-if="typeof mk.volume != 'undefined'">
|
||||||
<button class="app-sidebar-button" style="width:100%"
|
<button class="app-sidebar-button" style="width:100%"
|
||||||
:class="{active: chrome.menuOpened}"
|
:class="{active: chrome.menuOpened}"
|
||||||
|
@ -302,6 +301,12 @@
|
||||||
<cider-home></cider-home>
|
<cider-home></cider-home>
|
||||||
</template>
|
</template>
|
||||||
</transition>
|
</transition>
|
||||||
|
<!-- Home -->
|
||||||
|
<transition name="wpfade">
|
||||||
|
<template v-if="page == 'artist-feed'">
|
||||||
|
<cider-artist-feed></cider-artist-feed>
|
||||||
|
</template>
|
||||||
|
</transition>
|
||||||
<!-- Playlist / Album page-->
|
<!-- Playlist / Album page-->
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<template v-if="page.includes('playlist_')">
|
<template v-if="page.includes('playlist_')">
|
||||||
|
@ -443,8 +448,17 @@
|
||||||
</div>
|
</div>
|
||||||
<transition name="drawertransition">
|
<transition name="drawertransition">
|
||||||
<div class="app-drawer" v-if="drawer.open">
|
<div class="app-drawer" v-if="drawer.open">
|
||||||
<lyrics-view v-if="drawer.panel == 'lyrics'" :time="lyriccurrenttime" :lyrics="lyrics"
|
<div class="bgArtworkMaterial">
|
||||||
:richlyrics="richlyrics"></lyrics-view>
|
<div class="bg-artwork-container">
|
||||||
|
<img class="bg-artwork a" :src="$store.state.artwork.playerLCD">
|
||||||
|
<img class="bg-artwork b" :src="$store.state.artwork.playerLCD">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<lyrics-view v-if="drawer.panel == 'lyrics'" :time="lyriccurrenttime" :lyrics="lyrics"
|
||||||
|
:richlyrics="richlyrics"></lyrics-view>
|
||||||
|
<div v-if="drawer.panel == 'lyrics'" class="lyric-footer">
|
||||||
|
<button class="md-btn" @click="modularUITest(!fullscreenLyrics)">{{fullscreenLyrics ? "Default View":'Fullscreen View'}}</button>
|
||||||
|
</div>
|
||||||
<cider-queue ref="queue" v-if="drawer.panel == 'queue'"></cider-queue>
|
<cider-queue ref="queue" v-if="drawer.panel == 'queue'"></cider-queue>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -453,12 +467,16 @@
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="fsModeSwitch">
|
<transition name="fsModeSwitch">
|
||||||
<div class="fullscreen-view-container" v-if="appMode == 'fullscreen'">
|
<div class="fullscreen-view-container" v-if="appMode == 'fullscreen'">
|
||||||
<fullscreen-view></fullscreen-view>
|
<fullscreen-view :image="currentArtUrl.replace('50x50', '600x600')" :time="lyriccurrenttime" :lyrics="lyrics" :richlyrics="richlyrics"></fullscreen-view>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<img v-show="chrome.artworkReady" @load="chrome.artworkReady = true" class="bg-artwork"
|
<div class="bg-artwork-container" :class="{noanimation: (!cfg.visual.bg_artwork_rotation || !animateBackground)}">
|
||||||
>
|
<img @load="chrome.artworkReady = true" class="bg-artwork a "
|
||||||
|
>
|
||||||
|
<img class="bg-artwork b"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="wpfade">
|
<transition name="wpfade">
|
||||||
<div class="bg-artwork--placeholder"></div>
|
<div class="bg-artwork--placeholder"></div>
|
||||||
|
@ -466,6 +484,9 @@
|
||||||
<transition name="modal">
|
<transition name="modal">
|
||||||
<add-to-playlist :playlists="playlists.listing" v-if="modals.addToPlaylist"></add-to-playlist>
|
<add-to-playlist :playlists="playlists.listing" v-if="modals.addToPlaylist"></add-to-playlist>
|
||||||
</transition>
|
</transition>
|
||||||
|
<transition name="modal">
|
||||||
|
<spatial-properties v-if="modals.spatialProperties"></spatial-properties>
|
||||||
|
</transition>
|
||||||
<div id="apple-music-video-container">
|
<div id="apple-music-video-container">
|
||||||
<div id="apple-music-video-player-controls">
|
<div id="apple-music-video-player-controls">
|
||||||
<div id="player-exit" title="Close" @click="app.exitMV()">
|
<div id="player-exit" title="Close" @click="app.exitMV()">
|
||||||
|
@ -512,6 +533,9 @@
|
||||||
<!-- Home -->
|
<!-- Home -->
|
||||||
<%- include('pages/home') %>
|
<%- include('pages/home') %>
|
||||||
|
|
||||||
|
<!-- Artist Feed -->
|
||||||
|
<%- include('pages/artist-feed') %>
|
||||||
|
|
||||||
<!-- Playlists / Albums -->
|
<!-- Playlists / Albums -->
|
||||||
<%- include('pages/cider-playlist') %>
|
<%- include('pages/cider-playlist') %>
|
||||||
|
|
||||||
|
@ -538,10 +562,15 @@
|
||||||
<script type="text/x-template" id="sidebar-library-item">
|
<script type="text/x-template" id="sidebar-library-item">
|
||||||
<button class="app-sidebar-item"
|
<button class="app-sidebar-item"
|
||||||
:class="$parent.getSidebarItemClass(page)" @click="$root.appRoute(page)">
|
:class="$parent.getSidebarItemClass(page)" @click="$root.appRoute(page)">
|
||||||
|
<div class="sidebar-icon" v-html="svgIconData" v-if="svgIconData != ''"></div>
|
||||||
{{ name }}
|
{{ name }}
|
||||||
</button>
|
</button>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Playlist Listing -->
|
||||||
|
<%- include('components/sidebar-playlist') %>
|
||||||
|
<!-- Spatial Properties -->
|
||||||
|
<%- include('components/spatial-properties') %>
|
||||||
<!-- Add to playlist -->
|
<!-- Add to playlist -->
|
||||||
<%- include('components/add-to-playlist') %>
|
<%- include('components/add-to-playlist') %>
|
||||||
<!-- Queue -->
|
<!-- Queue -->
|
||||||
|
@ -585,5 +614,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script src="index.js?v=1"></script>
|
<script src="index.js?v=1"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/resonance-audio/build/resonance-audio.min.js"></script>
|
||||||
|
<script src="/audio/audio.js?v=1"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
68
src/renderer/views/pages/artist-feed.ejs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<script type="text/x-template" id="cider-artist-feed">
|
||||||
|
<div class="content-inner">
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row nopadding">
|
||||||
|
<div class="col nopadding">
|
||||||
|
<h3>Your Artists Feed</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="well" style="margin-top:0px;">
|
||||||
|
<template v-if="artistFeed.length > 0">
|
||||||
|
<mediaitem-list-item v-for="item in artistFeed" :item="item"></mediaitem-list-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="spinner"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('cider-artist-feed', {
|
||||||
|
template: '#cider-artist-feed',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
app: this.$root,
|
||||||
|
followedArtists: this.$root.cfg.home.followedArtists,
|
||||||
|
artistFeed: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
let self = this
|
||||||
|
await this.getArtistFeed()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getArtistFeed() {
|
||||||
|
let artists = this.followedArtists
|
||||||
|
let self = this
|
||||||
|
this.app.mk.api.artists(artists, {
|
||||||
|
"views": "featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see",
|
||||||
|
"extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero",
|
||||||
|
"extend[playlists]": "trackCount",
|
||||||
|
"include[songs]": "albums",
|
||||||
|
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount",
|
||||||
|
"limit[artists:top-songs]": 20,
|
||||||
|
"art[url]": "f"
|
||||||
|
}, {includeResponseMeta: !0}).then(artistData => {
|
||||||
|
artistData.data.forEach(item => {
|
||||||
|
if (item.views["latest-release"].data.length != 0) {
|
||||||
|
self.artistFeed.push(item.views["latest-release"].data[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// sort artistFeed by attributes.releaseDate descending, date is formatted as "YYYY-MM-DD"
|
||||||
|
this.artistFeed.sort((a, b) => {
|
||||||
|
let dateA = new Date(a.attributes.releaseDate)
|
||||||
|
let dateB = new Date(b.attributes.releaseDate)
|
||||||
|
return dateB - dateA
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -12,7 +12,7 @@
|
||||||
<mediaitem-artwork
|
<mediaitem-artwork
|
||||||
shadow="large"
|
shadow="large"
|
||||||
:url="data.attributes.artwork ? data.attributes.artwork.url : ''"
|
:url="data.attributes.artwork ? data.attributes.artwork.url : ''"
|
||||||
size="220" type="artists"></mediaitem-artwork>
|
size="190" type="artists"></mediaitem-artwork>
|
||||||
<button class="overlay-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
<button class="overlay-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
|
||||||
app.mk.play()
|
app.mk.play()
|
||||||
})">
|
})">
|
||||||
|
@ -45,9 +45,9 @@
|
||||||
<div class="col-auto" v-if="data.views['latest-release'].data.length != 0">
|
<div class="col-auto" v-if="data.views['latest-release'].data.length != 0">
|
||||||
<h3>Latest Release</h3>
|
<h3>Latest Release</h3>
|
||||||
<div style="width: auto;margin: 0 auto;">
|
<div style="width: auto;margin: 0 auto;">
|
||||||
<mediaitem-square-sp v-for="song in data.views['latest-release'].data"
|
<mediaitem-square kind="card" v-for="song in data.views['latest-release'].data"
|
||||||
:item="song">
|
:item="song">
|
||||||
</mediaitem-square-sp>
|
</mediaitem-square>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col" v-if="data.views['top-songs']">
|
<div class="col" v-if="data.views['top-songs']">
|
||||||
|
@ -135,6 +135,27 @@
|
||||||
methods: {
|
methods: {
|
||||||
artistMenu (event) {
|
artistMenu (event) {
|
||||||
let self = this
|
let self = this
|
||||||
|
let followAction = "follow"
|
||||||
|
let followActions = {
|
||||||
|
follow: {
|
||||||
|
name: "Follow Artist",
|
||||||
|
action: ()=>{
|
||||||
|
self.app.cfg.home.followedArtists.push(self.data.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unfollow: {
|
||||||
|
name: "Unfollow Artist",
|
||||||
|
action: ()=>{
|
||||||
|
let index = self.app.cfg.home.followedArtists.indexOf(self.data.id)
|
||||||
|
if (index > -1) {
|
||||||
|
self.app.cfg.home.followedArtists.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.app.cfg.home.followedArtists.includes(self.data.id)) {
|
||||||
|
followAction = "unfollow"
|
||||||
|
}
|
||||||
CiderContextMenu.Create(event, {
|
CiderContextMenu.Create(event, {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
@ -145,13 +166,12 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
followActions[followAction],
|
||||||
name: "Follow Artist",
|
|
||||||
action: ()=>{}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Share",
|
name: "Share",
|
||||||
action: ()=>{}
|
action: ()=>{
|
||||||
|
self.app.copyToClipboard(self.data.attributes.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="content-inner playlist-page" v-if="data != [] && data.attributes != null"
|
<div class="content-inner playlist-page" v-if="data != [] && data.attributes != null"
|
||||||
:style="{'--bgColor': (data.attributes.artwork != null && data.attributes.artwork['bgColor'] != null) ? ('#' + data.attributes.artwork.bgColor) : ''}">
|
:style="{'--bgColor': (data.attributes.artwork != null && data.attributes.artwork['bgColor'] != null) ? ('#' + data.attributes.artwork.bgColor) : ''}">
|
||||||
<template v-if="app.playlists.loadingState == 0">
|
<template v-if="app.playlists.loadingState == 0">
|
||||||
<div class="content-inner centered">Loading...</div>
|
<div class="content-inner centered">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.playlists.loadingState == 1">
|
<template v-if="app.playlists.loadingState == 1">
|
||||||
<div class="playlist-display row"
|
<div class="playlist-display row"
|
||||||
|
@ -83,14 +85,25 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="playlist-time">
|
<div class="friends-info" v-if="itemBadges.length != 0">
|
||||||
{{data.attributes.releaseDate}}
|
<div class="well">
|
||||||
|
<div class="badge-container">
|
||||||
|
<div class="socialBadge" :title="`${badge.attributes.name} - @${badge.attributes.handle}`" v-for="badge in itemBadges">
|
||||||
|
<mediaitem-artwork
|
||||||
|
:url="badge.attributes.artwork.url"
|
||||||
|
:size="60"></mediaitem-artwork>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="playlist-time">
|
||||||
|
{{getFormattedDate(data.attributes.releaseDate)}}
|
||||||
|
</div>
|
||||||
|
<div class="playlist-time total">{{app.getTotalTime()}}</div>
|
||||||
<div class="playlist-time item-navigate" @click="app.searchAndNavigate(data,'recordLabel') "
|
<div class="playlist-time item-navigate" @click="app.searchAndNavigate(data,'recordLabel') "
|
||||||
style="width: 50%;">
|
style="width: 50%;">
|
||||||
{{data.attributes.copyright}}
|
{{data.attributes.copyright}}
|
||||||
</div>
|
</div>
|
||||||
<div class="playlist-time">{{app.getTotalTime()}}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,7 +119,9 @@
|
||||||
drag: false,
|
drag: false,
|
||||||
nameEditing: false,
|
nameEditing: false,
|
||||||
inLibrary: null,
|
inLibrary: null,
|
||||||
app: this.$root
|
app: this.$root,
|
||||||
|
itemBadges: [],
|
||||||
|
badgesRequested: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
@ -117,9 +132,35 @@
|
||||||
watch: {
|
watch: {
|
||||||
data: function () {
|
data: function () {
|
||||||
this.isInLibrary()
|
this.isInLibrary()
|
||||||
|
this.getBadges()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getBadges() {
|
||||||
|
return
|
||||||
|
if(this.badgesRequested) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.badgesRequested = true
|
||||||
|
this.itemBadges = []
|
||||||
|
let self = this
|
||||||
|
var id = 0
|
||||||
|
try {
|
||||||
|
id = this.data.attributes.playParams.id
|
||||||
|
} catch (e) {
|
||||||
|
id = this.data.id
|
||||||
|
}
|
||||||
|
this.$root.getSocialBadges((badges) => {
|
||||||
|
let friends = badges[id]
|
||||||
|
if(friends) {
|
||||||
|
friends.forEach(function (friend) {
|
||||||
|
self.app.mk.api.socialProfile(friend).then(data => {
|
||||||
|
self.itemBadges.push(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
async isInLibrary() {
|
async isInLibrary() {
|
||||||
if (this.data.type && !this.data.type.includes("library")) {
|
if (this.data.type && !this.data.type.includes("library")) {
|
||||||
// please keep using vars here
|
// please keep using vars here
|
||||||
|
@ -132,7 +173,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editPlaylist() {
|
editPlaylist() {
|
||||||
app.editPlaylist(this.data.id, this.data.attributes.name);
|
this.app.editPlaylist(this.data.id, this.data.attributes.name);
|
||||||
|
this.app.playlists.listing.forEach(playlist => {
|
||||||
|
if (playlist.id == this.data.id) {
|
||||||
|
playlist.attributes.name = this.data.attributes.name
|
||||||
|
}
|
||||||
|
})
|
||||||
this.nameEditing = false
|
this.nameEditing = false
|
||||||
},
|
},
|
||||||
addToLibrary(id) {
|
addToLibrary(id) {
|
||||||
|
@ -225,6 +271,19 @@
|
||||||
kind = data.attributes.playParams.kind;
|
kind = data.attributes.playParams.kind;
|
||||||
id = data.attributes.playParams.id;
|
id = data.attributes.playParams.id;
|
||||||
return `${kind}:${id}`
|
return `${kind}:${id}`
|
||||||
|
},
|
||||||
|
getFormattedDate: function (date) {
|
||||||
|
if (date == null || date === "") return "";
|
||||||
|
try {
|
||||||
|
var releaseDate = new Date(date);
|
||||||
|
month = new Intl.DateTimeFormat('en-US', {month: 'long'}).format(releaseDate);
|
||||||
|
date = releaseDate.getDate();
|
||||||
|
year = releaseDate.getFullYear();
|
||||||
|
|
||||||
|
return date + " " + month + " " + year;
|
||||||
|
} catch (e) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -64,7 +64,11 @@
|
||||||
},
|
},
|
||||||
scrollToTop() {
|
scrollToTop() {
|
||||||
let target = document.querySelector(".header-text")
|
let target = document.querySelector(".header-text")
|
||||||
target.scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"})
|
document.querySelector("#app-content").scrollTo({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
},
|
},
|
||||||
getNext() {
|
getNext() {
|
||||||
// if this.data.next is not null, then we can run this.data.next() and concat to this.data.data to get the next page
|
// if this.data.next is not null, then we can run this.data.next() and concat to this.data.data to get the next page
|
||||||
|
@ -81,9 +85,9 @@
|
||||||
this.triggerEnabled = true;
|
this.triggerEnabled = true;
|
||||||
});
|
});
|
||||||
if(typeof this.data.next == "function") {
|
if(typeof this.data.next == "function") {
|
||||||
this.data.next().then(data => next);
|
this.data.next().then(data => nextFn(data));
|
||||||
}else{
|
}else{
|
||||||
this.api.v3.music(this.data.next).then(data => nextFn);
|
this.api.v3.music(this.data.next).then(data => nextFn(data));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
console.log("No next page");
|
console.log("No next page");
|
||||||
|
|
|
@ -1,51 +1,69 @@
|
||||||
<script type="text/x-template" id="cider-home">
|
<script type="text/x-template" id="cider-home">
|
||||||
<div class="content-inner home-page">
|
<div class="content-inner home-page">
|
||||||
<div class="row">
|
<div v-if="page == 'main'">
|
||||||
<div class="col">
|
<div class="row">
|
||||||
<h3>Home</h3>
|
<div class="col">
|
||||||
<div class="well profile-well">
|
<h3>Recently Played</h3>
|
||||||
<div class="user-icon">
|
<div class="well artistfeed-well">
|
||||||
<mediaitem-artwork shadow="none" :url="profile.attributes.artwork.url" size="300"></mediaitem-artwork>
|
<template v-if="isSectionReady('recentlyPlayed')">
|
||||||
|
<mediaitem-list-item v-for="item in recentlyPlayed.limit(6)"
|
||||||
|
:item="item"></mediaitem-list-item>
|
||||||
|
</template>
|
||||||
|
<div class="spinner" v-else></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row nopadding">
|
||||||
|
<div class="col nopadding">
|
||||||
|
<h3>Your Artists Feed</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto nopadding flex-center">
|
||||||
|
<button class="cd-btn-seeall" @click="app.appRoute('artist-feed')">See All</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="well artistfeed-well" style="margin-top:0px;">
|
||||||
|
<template v-if="artistFeed.length > 0">
|
||||||
|
<mediaitem-list-item v-for="item in artistFeed.limit(6)" :item="item"></mediaitem-list-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="spinner"></div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<h3>{{ profile.attributes.name }}</h3>
|
|
||||||
<h4>@{{ profile.attributes.handle }}</h4>
|
|
||||||
<button class="md-btn">Share</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<!-- <div class="row" v-if="app.isDev">-->
|
||||||
<h3>Recently Played</h3>
|
<!-- <div class="col">-->
|
||||||
<div class="well">
|
<!-- <h3>Your Favorites</h3>-->
|
||||||
<mediaitem-list-item v-for="item in recentlyPlayed.limit(6)" :item="item"></mediaitem-list-item>
|
<!-- <div class="well">-->
|
||||||
|
<!-- <div class="hint-text" v-if="favorites.length == 0">Items you have added to your favorites will-->
|
||||||
|
<!-- appear here.-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <mediaitem-scroller-horizontal kind="small" :items="favorites"-->
|
||||||
|
<!-- :item="item"></mediaitem-scroller-horizontal>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>Made For You</h3>
|
||||||
|
<div class="well">
|
||||||
|
<template v-if="isSectionReady('madeForYou')">
|
||||||
|
<mediaitem-square kind="small" v-for="item in madeForYou" :item="item"></mediaitem-square>
|
||||||
|
</template>
|
||||||
|
<div class="spinner" v-else></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row" v-if="friendsListeningTo && friendsListeningTo != []">
|
||||||
<div class="row">
|
<div class="col">
|
||||||
<div class="col">
|
<h3>Friends Listening To</h3>
|
||||||
<h3>Your Favorites</h3>
|
<div class="well">
|
||||||
<div class="well">
|
<template v-if="isSectionReady('friendsListeningTo')">
|
||||||
<mediaitem-scroller-horizontal kind="small" :items="friendsListeningTo" :item="item"></mediaitem-scroller-horizontal>
|
<mediaitem-square kind="small" v-for="item in friendsListeningTo"
|
||||||
</div>
|
:item="item"></mediaitem-square>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
<div class="spinner" v-else></div>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col">
|
|
||||||
<h3>Made For You</h3>
|
|
||||||
<div class="well">
|
|
||||||
<mediaitem-square kind="small" v-for="item in madeForYou" :item="item"></mediaitem-square>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<h3>Your Artist Feed</h3>
|
|
||||||
<div class="well">
|
|
||||||
<mediaitem-list-item v-for="item in recentlyPlayed.limit(6)" :item="item"></mediaitem-list-item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h3>Friends Listening To</h3>
|
|
||||||
<div class="well">
|
|
||||||
<mediaitem-square kind="small" v-for="item in friendsListeningTo" :item="item"></mediaitem-square>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,75 +73,153 @@
|
||||||
<script>
|
<script>
|
||||||
Vue.component('cider-home', {
|
Vue.component('cider-home', {
|
||||||
template: '#cider-home',
|
template: '#cider-home',
|
||||||
data: function () {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
app: this.$root,
|
app: this.$root,
|
||||||
|
followedArtists: this.$root.cfg.home.followedArtists,
|
||||||
|
favoriteItems: this.$root.cfg.home.favoriteItems,
|
||||||
madeForYou: [],
|
madeForYou: [],
|
||||||
recentlyPlayed: [],
|
recentlyPlayed: [],
|
||||||
friendsListeningTo: [],
|
friendsListeningTo: [],
|
||||||
|
replayPlaylists: [],
|
||||||
|
favorites: [],
|
||||||
profile: {},
|
profile: {},
|
||||||
modify: 0
|
modify: 0,
|
||||||
|
artistFeed: [],
|
||||||
|
showingArtistFeed: false,
|
||||||
|
page: "main",
|
||||||
|
sectionsReady: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
let self = this
|
let self = this
|
||||||
this.getListenNowData()
|
this.getListenNowData()
|
||||||
|
await this.getArtistFeed()
|
||||||
|
await this.getFavorites()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isSectionReady(section) {
|
||||||
|
return this.sectionsReady.includes(section)
|
||||||
|
},
|
||||||
|
removeFavoriteContext() {
|
||||||
|
let self = this
|
||||||
|
return {
|
||||||
|
name: "Remove from Favorites",
|
||||||
|
action: function(item) {
|
||||||
|
let index = self.favoriteItems.findIndex(x => x.id == item.id)
|
||||||
|
if (index > -1) {
|
||||||
|
self.favoriteItems.splice(index, 1)
|
||||||
|
self.app.cfg.home.favoriteItems = self.favoriteItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getFavorites() {
|
||||||
|
let self = this
|
||||||
|
let libraryPlaylists = []
|
||||||
|
let playlists = []
|
||||||
|
for (let item of this.favoriteItems) {
|
||||||
|
if (item.type == "library-playlists") {
|
||||||
|
libraryPlaylists.push(item.id)
|
||||||
|
} else if (item.type == "playlists") {
|
||||||
|
playlists.push(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (playlists.length != 0) {
|
||||||
|
this.app.mk.api.playlists(playlists).then(playlistsData => {
|
||||||
|
self.favorites.push(...playlistsData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (libraryPlaylists.length != 0) {
|
||||||
|
this.app.mk.api.library.playlists(libraryPlaylists).then(playlistsData => {
|
||||||
|
self.favorites.push(...playlistsData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getArtistFeed() {
|
||||||
|
let artists = this.followedArtists
|
||||||
|
let self = this
|
||||||
|
this.app.mk.api.artists(artists, {
|
||||||
|
"views": "featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see",
|
||||||
|
"extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero",
|
||||||
|
"extend[playlists]": "trackCount",
|
||||||
|
"include[songs]": "albums",
|
||||||
|
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount",
|
||||||
|
"limit[artists:top-songs]": 20,
|
||||||
|
"art[url]": "f"
|
||||||
|
}, {
|
||||||
|
includeResponseMeta: !0
|
||||||
|
}).then(artistData => {
|
||||||
|
artistData.data.forEach(item => {
|
||||||
|
if (item.views["latest-release"].data.length != 0) {
|
||||||
|
self.artistFeed.push(item.views["latest-release"].data[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// sort artistFeed by attributes.releaseDate descending, date is formatted as "YYYY-MM-DD"
|
||||||
|
this.artistFeed.sort((a, b) => {
|
||||||
|
let dateA = new Date(a.attributes.releaseDate)
|
||||||
|
let dateB = new Date(b.attributes.releaseDate)
|
||||||
|
return dateB - dateA
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
getRecentlyPlayed() {
|
getRecentlyPlayed() {
|
||||||
|
|
||||||
},
|
},
|
||||||
async getListenNowData() {
|
async getListenNowData() {
|
||||||
let self = this
|
let self = this
|
||||||
this.app.mk.api.personalRecommendations("",
|
this.app.mk.api.personalRecommendations("", {
|
||||||
{
|
name: "listen-now",
|
||||||
name: "listen-now",
|
with: "friendsMix,library,social",
|
||||||
with: "friendsMix,library,social",
|
"art[social-profiles:url]": "c",
|
||||||
"art[social-profiles:url]": "c",
|
"art[url]": "c,f",
|
||||||
"art[url]": "c,f",
|
"omit[resource]": "autos",
|
||||||
"omit[resource]": "autos",
|
"relate[editorial-items]": "contents",
|
||||||
"relate[editorial-items]": "contents",
|
extend: ["editorialCard", "editorialVideo"],
|
||||||
extend: ["editorialCard", "editorialVideo"],
|
"extend[albums]": ["artistUrl"],
|
||||||
"extend[albums]": ["artistUrl"],
|
"extend[library-albums]": ["artistUrl", "editorialVideo"],
|
||||||
"extend[library-albums]": ["artistUrl", "editorialVideo"],
|
"extend[playlists]": ["artistNames", "editorialArtwork", "editorialVideo"],
|
||||||
"extend[playlists]": ["artistNames", "editorialArtwork", "editorialVideo"],
|
"extend[library-playlists]": ["artistNames", "editorialArtwork", "editorialVideo"],
|
||||||
"extend[library-playlists]": ["artistNames", "editorialArtwork", "editorialVideo"],
|
"extend[social-profiles]": "topGenreNames",
|
||||||
"extend[social-profiles]": "topGenreNames",
|
"include[albums]": "artists",
|
||||||
"include[albums]": "artists",
|
"include[songs]": "artists",
|
||||||
"include[songs]": "artists",
|
"include[music-videos]": "artists",
|
||||||
"include[music-videos]": "artists",
|
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"],
|
||||||
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"],
|
"fields[artists]": ["name", "url"],
|
||||||
"fields[artists]": ["name", "url"],
|
"extend[stations]": ["airDate", "supportsAirTimeUpdates"],
|
||||||
"extend[stations]": ["airDate", "supportsAirTimeUpdates"],
|
"meta[stations]": "inflectionPoints",
|
||||||
"meta[stations]": "inflectionPoints",
|
types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-profiles,social-upsells",
|
||||||
types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-profiles,social-upsells",
|
platform: "web"
|
||||||
platform: "web"
|
}, {
|
||||||
},
|
includeResponseMeta: !0,
|
||||||
{
|
reload: !0
|
||||||
includeResponseMeta: !0,
|
}).then((data) => {
|
||||||
reload: !0
|
|
||||||
}
|
|
||||||
).then((data) => {
|
|
||||||
console.log(data.data[1])
|
console.log(data.data[1])
|
||||||
self.recentlyPlayed = data.data[1].relationships.contents.data
|
try {
|
||||||
self.friendsListeningTo = data.data.filter(section => {
|
self.madeForYou = data.data.filter(section => {
|
||||||
if (section.meta.metrics.moduleType == "11") {
|
if (section.meta.metrics.moduleType == "6") {
|
||||||
return section
|
return section
|
||||||
}
|
};
|
||||||
;
|
})[0].relationships.contents.data
|
||||||
})[0].relationships.contents.data
|
} catch (err) {}
|
||||||
self.madeForYou = data.data.filter(section => {
|
self.sectionsReady.push("madeForYou")
|
||||||
if (section.meta.metrics.moduleType == "6") {
|
|
||||||
return section
|
try {
|
||||||
}
|
self.recentlyPlayed = data.data[1].relationships.contents.data
|
||||||
;
|
self.friendsListeningTo = data.data.filter(section => {
|
||||||
})[0].relationships.contents.data
|
if (section.meta.metrics.moduleType == "11") {
|
||||||
|
return section
|
||||||
|
};
|
||||||
|
})[0].relationships.contents.data
|
||||||
|
} catch (err) {}
|
||||||
|
self.sectionsReady.push("recentlyPlayed")
|
||||||
|
self.sectionsReady.push("friendsListeningTo")
|
||||||
});
|
});
|
||||||
|
|
||||||
app.mk.api.v3.music("/v1/me/social/profile/").then((response) => {
|
app.mk.api.v3.music("/v1/me/social/profile/").then((response) => {
|
||||||
self.profile = response.data.data[0]
|
self.profile = response.data.data[0]
|
||||||
console.log("!!!")
|
|
||||||
console.log(response.data.data[0])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
16
src/renderer/views/pages/new/library-songs.ejs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<script type="text/x-template" id="hello-world">
|
||||||
|
<div class="content-inner">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('library-songs', {
|
||||||
|
template: '#library-songs',
|
||||||
|
methods: {
|
||||||
|
sayHello: function () {
|
||||||
|
alert('Hello world!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -9,10 +9,10 @@
|
||||||
Audio Quality
|
Audio Quality
|
||||||
</div>
|
</div>
|
||||||
<div class="md-option-segment md-option-segment_auto">
|
<div class="md-option-segment md-option-segment_auto">
|
||||||
<select class="md-select" style="width:180px;">
|
<select class="md-select" style="width:180px;" v-model="app.cfg.audio.quality" v-on:change="changeAudioQuality">
|
||||||
<option value="extreme">Extreme</option>
|
<option value="990">Extreme</option>
|
||||||
<option value="high">High</option>
|
<option value="256">High</option>
|
||||||
<option value="low">Low</option>
|
<option value="64">Low</option>
|
||||||
<option value="auto">Auto</option>
|
<option value="auto">Auto</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,6 +25,34 @@
|
||||||
<input type="checkbox" v-model="app.cfg.audio.seamless_audio" switch/>
|
<input type="checkbox" v-model="app.cfg.audio.seamless_audio" switch/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="md-option-line">
|
||||||
|
<div class="md-option-segment">
|
||||||
|
Enable Advanced Functionality
|
||||||
|
<br>
|
||||||
|
<small>Enabling AudioContext functionality will allow for extended audio features like Audio Normalization , Equalizers and Visualizers, however on some systems this may cause stuttering in audio tracks.</small>
|
||||||
|
</div>
|
||||||
|
<div class="md-option-segment md-option-segment_auto">
|
||||||
|
<input type="checkbox" v-model="app.cfg.advanced.AudioContext" v-on:change="toggleAudioContext" switch/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-option-line" v-show="app.cfg.advanced.AudioContext">
|
||||||
|
<div class="md-option-segment" >
|
||||||
|
Audio Normalization
|
||||||
|
</div>
|
||||||
|
<div class="md-option-segment md-option-segment_auto">
|
||||||
|
<input type="checkbox" v-model="app.cfg.audio.normalization" v-on:change="toggleNormalization" switch/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-option-line" v-show="app.cfg.advanced.AudioContext">
|
||||||
|
<div class="md-option-segment" >
|
||||||
|
Audio Spatialization
|
||||||
|
<br>
|
||||||
|
<small>Spatialize audio and make audio more 3-dimensional (note: This is not Dolby Atmos)</small>
|
||||||
|
</div>
|
||||||
|
<div class="md-option-segment md-option-segment_auto">
|
||||||
|
<input type="checkbox" v-model="app.cfg.audio.spatial" v-on:change="toggleSpatial" switch/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="md-option-header">
|
<div class="md-option-header">
|
||||||
<span>Visual</span>
|
<span>Visual</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,6 +82,14 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="md-option-line">
|
||||||
|
<div class="md-option-segment">
|
||||||
|
Animated Window Background
|
||||||
|
</div>
|
||||||
|
<div class="md-option-segment md-option-segment_auto">
|
||||||
|
<input type="checkbox" switch v-model="app.cfg.visual.bg_artwork_rotation"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="md-option-line">
|
<div class="md-option-line">
|
||||||
<div class="md-option-segment">
|
<div class="md-option-segment">
|
||||||
Window Transparency
|
Window Transparency
|
||||||
|
@ -72,7 +108,7 @@
|
||||||
<small>(Requires relaunch)</small>
|
<small>(Requires relaunch)</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-option-segment md-option-segment_auto">
|
<div class="md-option-segment md-option-segment_auto">
|
||||||
<select class="md-select" style="width:180px;" v-model="app.cfg.visual.hw_acceleration">
|
<select class="md-select" style="width:180px;" v-model="app.cfg.visual.hw_acceleration" >
|
||||||
<option value="default">Default</option>
|
<option value="default">Default</option>
|
||||||
<option value="webgpu">WebGPU</option>
|
<option value="webgpu">WebGPU</option>
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
|
@ -443,16 +479,6 @@
|
||||||
<div class="md-option-header">
|
<div class="md-option-header">
|
||||||
<span>Unfinished / Non Functional</span>
|
<span>Unfinished / Non Functional</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-option-line">
|
|
||||||
<div class="md-option-segment">
|
|
||||||
Enable AudioContext Functionality
|
|
||||||
<br>
|
|
||||||
<small>Enabling AudioContext functionality will allow for extended audio features like Equalizers and Visualizers, however on some systems this may cause stuttering in audio tracks.</small>
|
|
||||||
</div>
|
|
||||||
<div class="md-option-segment md-option-segment_auto">
|
|
||||||
<input type="checkbox" switch/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md-option-line">
|
<div class="md-option-line">
|
||||||
<div class="md-option-segment">
|
<div class="md-option-segment">
|
||||||
Theme
|
Theme
|
||||||
|
@ -547,7 +573,30 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
toggleAudioContext: function(){
|
||||||
|
if (app.cfg.advanced.AudioContext){
|
||||||
|
CiderAudio.init();
|
||||||
|
if (app.cfg.audio.normalization){
|
||||||
|
CiderAudio.normalizerOn()}
|
||||||
|
if (app.cfg.audio.spatial){
|
||||||
|
CiderAudio.spatialOn()}
|
||||||
|
} else {
|
||||||
|
CiderAudio.off();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleNormalization : function(){
|
||||||
|
if (app.cfg.audio.normalization){
|
||||||
|
CiderAudio.normalizerOn()
|
||||||
|
} else {CiderAudio.normalizerOff()}
|
||||||
|
},
|
||||||
|
toggleSpatial : function(){
|
||||||
|
if (app.cfg.audio.spatial){
|
||||||
|
CiderAudio.spatialOn()
|
||||||
|
} else {CiderAudio.spatialOff()}
|
||||||
|
},
|
||||||
|
changeAudioQuality : function(){
|
||||||
|
app.mk.bitrate = app.cfg.audio.quality
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
|
@ -2,6 +2,8 @@
|
||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
<h3>Welcome to element park. *BERR NERR NERR NERR NERRRRR BERR NER NER NER NERRR BERRR NR NR NRRRR*</h3>
|
<h3>Welcome to element park. *BERR NERR NERR NERR NERRRRR BERR NER NER NER NERRR BERRR NR NR NRRRR*</h3>
|
||||||
<button @click="app.playMediaItemById('1592151778', 'album')">Play Test Album</button>
|
<button @click="app.playMediaItemById('1592151778', 'album')">Play Test Album</button>
|
||||||
<cider-queue ref="queue"></cider-queue>
|
{{ $store.state.test }}
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<button class="md-btn">Cider Button</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
1
src/renderer/views/svg/explicit.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="9" height="9" viewBox="0 0 9 9" xmlns="http://www.w3.org/2000/svg" class="glyph" aria-hidden="true"><path d="M1.59 8.991h5.82c1.043 0 1.582-.538 1.582-1.566v-5.85C8.992.547 8.453.008 7.41.008H1.59C.552.008.008.542.008 1.575v5.85c0 1.028.544 1.566 1.582 1.566zm1.812-2.273c-.332 0-.505-.211-.505-.553V2.753c0-.341.173-.553.505-.553h2.264c.245 0 .408.14.408.385 0 .235-.163.384-.408.384H3.854v1.106h1.71c.226 0 .38.125.38.355 0 .221-.154.346-.38.346h-1.71V5.95h1.812c.245 0 .408.14.408.385 0 .235-.163.384-.408.384H3.402z"></path></svg>
|
After Width: | Height: | Size: 546 B |
1
src/renderer/views/svg/list.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"/></svg>
|
After Width: | Height: | Size: 831 B |
1
src/renderer/views/svg/quote-right.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M464 32H336c-26.5 0-48 21.5-48 48v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48zm-288 0H48C21.5 32 0 53.5 0 80v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48z"/></svg>
|
After Width: | Height: | Size: 640 B |