Merge branch 'main' of https://github.com/ciderapp/Cider into main

This commit is contained in:
Jason Chen 2022-06-17 11:33:32 -07:00
commit 90dcde279a
137 changed files with 8653 additions and 4265 deletions

View file

@ -0,0 +1,8 @@
<?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 1024 1024" 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;">
<rect x="0" y="0" width="1024" height="1024" style="fill:rgb(110,110,110);"/>
<g transform="matrix(6.05996,0,0,6.05996,189.003,209)">
<path d="M93.161,0.071C59.66,-1.043 32.22,11.314 32.22,11.314L32.2,74.023C28.789,72.669 24.641,72.348 20.428,73.372C11.345,75.579 5.397,83.192 7.143,90.379C8.889,97.566 17.667,101.604 26.749,99.398C35.313,97.317 41.087,90.429 40.256,83.626L40.256,36.771C40.256,36.771 59.66,29.987 84.829,28.286L84.829,63.135C81.455,61.843 77.386,61.55 73.25,62.555C64.167,64.761 58.219,72.374 59.965,79.562C61.71,86.749 70.488,90.786 79.571,88.58C87.502,86.653 93.042,80.603 93.158,74.316L93.161,74.32L93.161,0.071Z" style="fill-opacity:0.16;fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,294 +1,367 @@
var notyf = new Notyf();
const MusicKitObjects = {
LibraryPlaylist: function () {
this.id = ""
this.type = "library-playlist-folders"
this.href = ""
this.attributes = {
dateAdded: "",
name: ""
}
this.playlists = []
}
}
LibraryPlaylist: function () {
this.id = "";
this.type = "library-playlist-folders";
this.href = "";
this.attributes = {
dateAdded: "",
name: "",
};
this.playlists = [];
},
};
// limit an array to a certain number of items
Array.prototype.limit = function (n) {
return this.slice(0, n);
return this.slice(0, n);
};
Vue.component('animated-number', {
Vue.component("animated-number", {
template: "<div style='display: inline-block;'>{{ displayNumber }}</div>",
props: { number: { default: 0 } },
template: "<div style='display: inline-block;'>{{ displayNumber }}</div>",
props: { 'number': { default: 0 } },
data() {
return {
displayNumber: 0,
interval: false,
};
},
data() {
return {
displayNumber: 0,
interval: false
ready() {
this.displayNumber = this.number ? this.number : 0;
},
watch: {
number() {
clearInterval(this.interval);
if (this.number == this.displayNumber) {
return;
}
this.interval = window.setInterval(() => {
if (this.displayNumber != this.number) {
var change = (this.number - this.displayNumber) / 10;
change = change >= 0 ? Math.ceil(change) : Math.floor(change);
this.displayNumber = this.displayNumber + change;
}
}, 20);
},
},
});
ready() {
this.displayNumber = this.number ? this.number : 0;
Vue.component("sidebar-library-item", {
template: "#sidebar-library-item",
props: {
name: {
type: String,
required: true,
},
watch: {
number() {
clearInterval(this.interval);
if (this.number == this.displayNumber) {
return;
}
this.interval = window.setInterval(() => {
if (this.displayNumber != this.number) {
var change = (this.number - this.displayNumber) / 10;
change = change >= 0 ? Math.ceil(change) : Math.floor(change);
this.displayNumber = this.displayNumber + change;
}
}, 20);
}
page: {
type: String,
required: true,
},
svgIcon: {
type: String,
required: false,
default: "",
},
cdClick: {
type: Function,
required: false,
},
},
data: function () {
return {
app: app,
svgIconData: "",
};
},
async mounted() {
if (this.svgIcon) {
this.svgIconData = await this.app.getSvgIcon(this.svgIcon);
}
})
Vue.component('sidebar-library-item', {
template: '#sidebar-library-item',
props: {
name: {
type: String,
required: true
},
page: {
type: String,
required: true
},
svgIcon: {
type: String,
required: false,
default: ''
},
cdClick: {
type: Function,
required: false
}
},
data: function () {
return {
app: app,
svgIconData: ""
}
},
async mounted() {
if (this.svgIcon) {
this.svgIconData = await this.app.getSvgIcon(this.svgIcon)
}
},
methods: {}
},
methods: {},
});
function fallbackinitMusicKit() {
const request = new XMLHttpRequest();
const request = new XMLHttpRequest();
function loadAlternateKey() {
let parsedJson = JSON.parse(this.responseText)
MusicKit.configure({
developerToken: parsedJson.developerToken,
app: {
name: 'Apple Music',
build: '1978.4.1',
version: "1.0"
},
sourceType: 24,
suppressErrorDialog: true
})
setTimeout(() => {
app.init()
if (app.cfg.visual.window_background_style == "mica" && !app.isDev) {
app.spawnMica()
}
}, 1000)
}
function loadAlternateKey() {
let parsedJson = JSON.parse(this.responseText);
MusicKit.configure({
developerToken: parsedJson.developerToken,
app: {
name: "Apple Music",
build: "1978.4.1",
version: "1.0",
},
sourceType: 24,
suppressErrorDialog: true,
});
setTimeout(() => {
app.init();
if (app.cfg.visual.window_background_style == "mica" && !app.isDev) {
app.spawnMica();
}
}, 1000);
}
request.addEventListener("load", loadAlternateKey);
request.open("GET", "https://raw.githubusercontent.com/lujjjh/LitoMusic/main/token.json");
request.send();
request.addEventListener("load", loadAlternateKey);
request.open(
"GET",
"https://raw.githubusercontent.com/lujjjh/LitoMusic/main/token.json"
);
request.send();
}
document.addEventListener('musickitloaded', function () {
console.log('MusicKit loaded')
// MusicKit global is now defined
function initMusicKit() {
let parsedJson = JSON.parse(this.responseText)
MusicKit.configure({
developerToken: parsedJson.token,
app: {
name: 'Apple Music',
build: '1978.4.1',
version: "1.0"
},
sourceType: 24,
suppressErrorDialog: true
}).then(() => {
function waitForApp() {
if (typeof app.init !== "undefined") {
app.init()
if (app.cfg.visual.window_background_style == "mica" && !app.isDev) {
app.spawnMica()
}
}
else {
setTimeout(waitForApp, 250);
}
}
waitForApp()
})
}
function initMusicKit() {
const request = new XMLHttpRequest();
request.timeout = 5000;
request.addEventListener("load", initMusicKit);
request.onreadystatechange = function (aEvt) {
if (request.readyState == 4) {
if (request.status != 200)
fallbackinitMusicKit()
let parsedJson = JSON.parse(this.responseText);
MusicKit.configure({
developerToken: parsedJson.token,
app: {
name: "Apple Music",
build: "1978.4.1",
version: "1.0",
},
sourceType: 24,
suppressErrorDialog: true,
}).then(() => {
function waitForApp() {
if (typeof app.init !== "undefined") {
app.init();
if (app.cfg.visual.window_background_style == "mica" && !app.isDev) {
app.spawnMica();
}
};
request.open("GET", "https://api.cider.sh/v1/");
request.send();
} else {
setTimeout(waitForApp, 250);
}
}
waitForApp();
});
}
// check for widevine failure and reconfigure the instance.
window.addEventListener("drmUnsupported", function () {
initMusicKit()
});
function capiInit() {
const request = new XMLHttpRequest();
request.timeout = 5000;
request.addEventListener("load", initMusicKit);
request.onreadystatechange = function (aEvt) {
if (request.readyState == 4) {
if (request.status != 200) fallbackinitMusicKit();
}
};
request.open("GET", "https://api.cider.sh/v1/");
request.send();
}
document.addEventListener("musickitloaded", function () {
if (showOobe()) return;
console.log("MusicKit loaded");
// MusicKit global is now defined
capiInit()
});
if ('serviceWorker' in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js?v=1');
});
window.addEventListener("drmUnsupported", function () {
initMusicKit();
});
if ("serviceWorker" in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener("load", () => {
navigator.serviceWorker.register("sw.js?v=1");
});
}
const getBase64FromUrl = async (url) => {
const data = await fetch(url);
const blob = await data.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
resolve(base64data);
}
});
}
const data = await fetch(url);
const blob = await data.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
resolve(base64data);
};
});
};
function Clone(obj) {
return JSON.parse(JSON.stringify(obj));
return JSON.parse(JSON.stringify(obj));
}
function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
).toString(16)
);
}
function xmlToJson(xml) {
// Create the return object
let obj = {};
// Create the return object
let obj = {};
if (xml.nodeType == 1) { // element
// do attributes
if (xml.attributes.length > 0) {
obj["@attributes"] = {};
for (var j = 0; j < xml.attributes.length; j++) {
let attribute = xml.attributes.item(j);
obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
}
}
} else if (xml.nodeType == 3) { // text
obj = xml.nodeValue;
if (xml.nodeType == 1) {
// element
// do attributes
if (xml.attributes.length > 0) {
obj["@attributes"] = {};
for (var j = 0; j < xml.attributes.length; j++) {
let attribute = xml.attributes.item(j);
obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
}
}
} else if (xml.nodeType == 3) {
// text
obj = xml.nodeValue;
}
// do children
if (xml.hasChildNodes()) {
for (var i = 0; i < xml.childNodes.length; i++) {
var item = xml.childNodes.item(i);
var nodeName = item.nodeName;
if (typeof (obj[nodeName]) == "undefined") {
obj[nodeName] = xmlToJson(item);
} else {
if (typeof (obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xmlToJson(item));
}
// do children
if (xml.hasChildNodes()) {
for (var i = 0; i < xml.childNodes.length; i++) {
var item = xml.childNodes.item(i);
var nodeName = item.nodeName;
if (typeof obj[nodeName] == "undefined") {
obj[nodeName] = xmlToJson(item);
} else {
if (typeof obj[nodeName].push == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xmlToJson(item));
}
}
console.log(obj);
return obj;
};
}
console.log(obj);
return obj;
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
var checkIfScrollIsStatic = setInterval(() => {
try {
if (position === document.getElementsByClassName('lyric-body')[0].scrollTop) {
clearInterval(checkIfScrollIsStatic)
// do something
}
position = document.getElementsByClassName('lyric-body')[0].scrollTop
} catch (e) {
try {
if (
position === document.getElementsByClassName("lyric-body")[0].scrollTop
) {
clearInterval(checkIfScrollIsStatic);
// do something
}
position = document.getElementsByClassName("lyric-body")[0].scrollTop;
} catch (e) { }
}, 50);
// WebGPU Console Notification
async function webGPU() {
try {
const currentGPU = await navigator.gpu.requestAdapter()
console.log("WebGPU enabled on", currentGPU.name, "with feature ID", currentGPU.features.size)
} catch (e) {
console.log("WebGPU disabled / WebGPU initialization failed")
}
try {
const currentGPU = await navigator.gpu.requestAdapter();
console.log(
"WebGPU enabled on",
currentGPU.name,
"with feature ID",
currentGPU.features.size
);
} catch (e) {
console.log("WebGPU disabled / WebGPU initialization failed");
}
}
function isJson(item) {
item = typeof item !== "string"
? JSON.stringify(item)
: item;
try {
item = JSON.parse(item);
} catch (e) {
return false;
}
if (typeof item === "object" && item !== null) {
return true;
}
item = typeof item !== "string" ? JSON.stringify(item) : item;
try {
item = JSON.parse(item);
} catch (e) {
return false;
}
if (typeof item === "object" && item !== null) {
return true;
}
return false;
}
webGPU().then()
webGPU().then();
function showOobe() {
return false
if (localStorage.getItem("music.ampwebplay.media-user-token") && localStorage.getItem("seenOOBE")) {
return false
} else {
function waitForApp() {
if (typeof app.init !== "undefined") {
app.oobeInit();
} else {
setTimeout(waitForApp, 250);
}
}
waitForApp();
return true
}
}
let screenWidth = screen.width;
let screenHeight = screen.height;
window.onerror = function (error) {
console.log(error)
bootbox.alert("Error occurred: " + error)
};
document.addEventListener("DOMContentLoaded", async function () {
// app.oobeInit()
});
document.addEventListener(
"contextmenu",
function (e) {
if (
e.target.tagName.toLowerCase() == "textarea" ||
(e.target.tagName.toLowerCase() == "input" &&
e.target.type != "checkbox" &&
e.target.type != "radio" &&
e.target.disabled == false)
) {
e.preventDefault();
const menuPanel = {
items: {
cut: {
name: app.getLz("action.cut"),
action: function () {
document.execCommand("cut");
},
},
copy: {
name: app.getLz("action.copy"),
action: function () {
document.execCommand("copy");
},
},
paste: {
name: app.getLz("action.paste"),
action: function () {
document.execCommand("paste");
},
},
delete: {
name: app.getLz("action.delete"),
action: function () {
document.execCommand("delete");
},
},
selectAll: {
name: app.getLz("action.selectAll"),
action: function () {
document.execCommand("selectAll");
},
},
},
};
app.showMenuPanel(menuPanel, e);
}
},
false
);

View file

@ -23,6 +23,10 @@
padding: 14px;
background: var(--opaquePageBGColor);
font-size: 0.85em;
&.child {
background: rgb(0 0 0 / 15%);
}
}
.md-option-segment.md-option-segment_auto {

View file

@ -17,4 +17,5 @@
--ciderColor: @ciderColor;
--appOpacity: @appOpacity;
--transparencyRate: @transparencyRate;
--macOSChromeColor: rgb(14 14 14 / 32%);
}

View file

@ -6,7 +6,7 @@
@font-face {
font-family: "codicon";
font-display: block;
src: url("codicon.ttf") format("truetype");
src: url("./codicon.ttf?f06865699f1720ee6ca6e0a4aa084d76") format("truetype");
}
.codicon[class*='codicon-'] {
@ -43,6 +43,10 @@
opacity: 0.5;
}
.codicon-modifier-hidden {
opacity: 0;
}
/* custom speed & easing for loading icon */
.codicon-loading {
animation-duration: 1s !important;
@ -551,3 +555,7 @@
.codicon-arrow-circle-left:before { content: "\ebfd" }
.codicon-arrow-circle-right:before { content: "\ebfe" }
.codicon-arrow-circle-up:before { content: "\ebff" }
.codicon-layout-sidebar-right-off:before { content: "\ec00" }
.codicon-layout-panel-off:before { content: "\ec01" }
.codicon-layout-sidebar-left-off:before { content: "\ec02" }
.codicon-blank:before { content: "\ec03" }

Binary file not shown.

View file

@ -274,7 +274,7 @@
.song-name {
text-align : left;
font-size : 0.98em;
font-size : 0.8em;
font-weight : 500;
width : 100%;
-webkit-mask-image: linear-gradient(-90deg, transparent 0%, transparent 10%, black 20%);
@ -283,8 +283,12 @@
.song-artist,
.song-album {
font-size: 0.75em;
opacity : 0.8;
cursor : pointer;
opacity: 0.8;
cursor: pointer;
white-space: nowrap;
max-width: 360px;
-webkit-mask-image: linear-gradient(-90deg, transparent 0%, transparent 10%, black 20%);
width: 100%;
&:hover {
text-decoration: underline;

View file

@ -211,7 +211,6 @@
}
}
// Media Item Elements
.mediaitem-artwork {
@ -221,9 +220,6 @@
position: relative;
width: 100%;
height: 100%;
background-image: url("https://beta.music.apple.com/assets/product/MissingArtworkMusic.svg");
background-size: cover;
background-position: center;
.animatedartwork-view-box {
position: absolute;
@ -270,6 +266,9 @@
object-fit: cover;
image-rendering: -webkit-optimize-contrast;
pointer-events: none;
background-image: url("./assets/MissingArtwork.svg");
background-size: cover;
background-position: center;
}
&.no-shadow {
@ -289,6 +288,11 @@
}
}
#artworkLCD img {
image-rendering: auto;
}
/* queue item */
.cd-queue-item {
border-bottom: 0px solid rgb(200 200 200 / 10%);
@ -393,11 +397,26 @@
align-items: center;
border-radius: var(--mediaItemRadius);
position: relative;
&:hover{
.heart-icon{
.listitem-content {
width: 100%;
height: 60px;
display: flex;
flex: 0 0 auto;
flex-direction: row;
font-size: 14px;
justify-content: center-between;
align-items: center;
border-radius: var(--mediaItemRadius);
position: relative;
}
&:hover {
.heart-icon {
display: none;
}
}
.popular {
background-image: url(assets/star.svg);
background-repeat: no-repeat;
@ -411,7 +430,6 @@
.artwork {
height: 42px;
width: 42px;
border-radius: var(--mediaItemRadius);
object-fit: cover;
object-position: center;
flex: 0 0 auto;
@ -421,6 +439,11 @@
outline: none;
position: relative;
overflow: hidden;
border-radius: var(--mediaItemRadiusSmall);
.mediaitem-artwork {
border-radius: var(--mediaItemRadiusSmall);
}
.overlay-play {
background: rgba(0, 0, 0, 0.5);
@ -522,27 +545,35 @@
10% {
box-shadow: inset 0 -4px 0
}
20% {
box-shadow: inset 0 -10px 0
}
30% {
box-shadow: inset 0 -12px 0
}
40% {
box-shadow: inset 0 -8px 0
}
50% {
box-shadow: inset 0 -4px 0
}
60% {
box-shadow: inset 0 -6px 0
}
80% {
box-shadow: inset 0 -12px 0
}
90% {
box-shadow: inset 0 -6px 0
}
to {
box-shadow: inset 0 -2px 0
}
@ -927,6 +958,7 @@
/* mediaitem-square */
.cd-mediaitem-square {
--transitionDuration: .25s;
--scaleRate: 1.25;
--scaleRateArtwork: 1;
width: 200px;
@ -938,6 +970,7 @@
justify-content: center;
align-items: center;
border-radius: 6px;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.artwork-container {
position: relative;
@ -952,6 +985,8 @@
flex: 0 0 auto;
margin: 6px;
cursor: pointer;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.mediaitem-artwork {
box-shadow: unset;
}
@ -1024,15 +1059,33 @@
}
}
@media (min-width: 1600px) {
width: calc(200px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container>.artwork {
width: calc(190px * var(--scaleRateArtwork));
height: calc(190px * var(--scaleRateArtwork));
&:not(.mediaitem-card):not(.mediaitem-brick):not(.mediaitem-video):not(.noscale) {
@media (min-width: 1460px) {
--scaleRate: 1.1;
--scaleRateArtwork: 0.9;
width: calc(200px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container > .artwork {
width: calc(190px * var(--scaleRateArtwork));
height: calc(190px * var(--scaleRateArtwork));
}
}
@media (min-width: 1550px) {
--scaleRate: 1.25;
--scaleRateArtwork: 1;
width: calc(200px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container > .artwork {
width: calc(190px * var(--scaleRateArtwork));
height: calc(190px * var(--scaleRateArtwork));
}
}
}
.info-rect {
width: 90%;
height: 100%;
@ -1072,7 +1125,7 @@
margin: 2em;
border-radius: 10px;
>.codicon {
> .codicon {
font-size: 4em;
font-weight: bold;
opacity: 0.5;
@ -1080,8 +1133,6 @@
}
&.mediaitem-video {
--scaleRate: 1.25;
--scaleRateArtwork: 1.25;
height: 200px;
width: 240px;
@ -1090,12 +1141,29 @@
width: 212px;
}
@media (min-width: 1600px) {
width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container>.artwork {
width: calc(212px * var(--scaleRateArtwork));
height: calc(120px * var(--scaleRateArtwork));
&:not(.noscale) {
@media (min-width: 1460px) {
--scaleRate: 1.1;
--scaleRateArtwork: 1.1;
width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork));
}
}
@media (min-width: 1550px) {
--scaleRate: 1.25;
--scaleRateArtwork: 1.25;
width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork));
}
}
}
}
@ -1108,6 +1176,32 @@
height: 123px;
width: 220px;
}
&:not(.noscale) {
@media (min-width: 1460px) {
--scaleRate: 1.1;
--scaleRateArtwork: 1.1;
width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork));
}
}
@media (min-width: 1550px) {
--scaleRate: 1.25;
--scaleRateArtwork: 1.25;
width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate));
.artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork));
}
}
}
}
&.mediaitem-small {
@ -1123,7 +1217,7 @@
&.mediaitem-card {
background: #ccc;
background: var(--spcolor);
height: 298px;
height: 302px;
width: 230px;
overflow: hidden;
position: relative;
@ -1136,7 +1230,7 @@
overflow: hidden;
border-radius: 0px;
margin: 0;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear, filter 0.2s ease-in-out;
.mediaitem-artwork {
border-radius: 0px;
@ -1150,6 +1244,8 @@
padding: 10px 10px 14px;
position: relative;
width: 100%;
display: grid;
align-content: center;
&::before {
background: var(--bgartwork);
@ -1164,6 +1260,7 @@
z-index: 0;
opacity: 1;
filter: brightness(0.5) blur(50px) saturate(180%);
transition: filter 0.2s ease-in-out;
}
}
@ -1175,9 +1272,10 @@
font-size: 0.9em;
font-weight: 500;
z-index: 1;
&+ .subtitle {
& + .subtitle {
max-height: none !important;
margin-top: -0.5em;
// margin-top: -0.5em;
}
}
@ -1191,7 +1289,7 @@
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
max-height: 3.8em;
max-height: 4.8em;
z-index: 1;
}
@ -1207,14 +1305,34 @@
border-radius: inherit;
}
//@media (min-width: 1600px) {
// width: calc(230px * 1.25);
// height: calc(298px * 1.25);
// .artwork-container>.artwork {
// width: calc(230px * 1.25);
// height: calc(230px * 1.25);
// }
//}
&:hover {
.artwork{
filter: brightness(0.8);
}
.info-rect-card::before {
filter: brightness(0.3) blur(50px) saturate(180%);
}
}
&:not(.noscale) {
@media (min-width: 1460px) {
width: calc(230px * 1.1);
height: calc(298px * 1.1);
.artwork-container > .artwork {
width: calc(230px * 1.1);
height: calc(230px * 1.1);
}
}
@media (min-width: 1550px) {
width: calc(230px * 1.25);
height: calc(298px * 1.25);
.artwork-container > .artwork {
width: calc(230px * 1.25);
height: calc(230px * 1.25);
}
}
}
}
}
@ -1460,7 +1578,6 @@ input[type=checkbox][switch]:checked:active::before {
/* End Switch Checkbox */
.header-text {
margin: 0px;
}
@ -1580,10 +1697,31 @@ input[type=checkbox][switch]:checked:active::before {
background-repeat: no-repeat;
opacity: 0.70;
border-radius: 6px;
}
position: relative;
.playback-button:active {
transform: scale(0.95);
&:before {
content: "";
display: block;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--selected);
border-radius: inherit;
position: absolute;
opacity: 0;
z-index: -1;
transform: scale(0.5);
pointer-events: none;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
}
&:hover {
&:before {
transform: scale(1);
opacity: 1;
}
}
}
.playback-button--small {
@ -1599,22 +1737,47 @@ input[type=checkbox][switch]:checked:active::before {
border: 0px;
box-shadow: unset;
opacity: 0.70;
}
position: relative;
.playback-button:hover,
.playback-button--small:hover {
background-color: rgb(200 200 200 / 10%);
}
&:before {
content: "";
display: block;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--selected);
border-radius: inherit;
position: absolute;
opacity: 0;
z-index: -1;
transform: scale(0.5);
pointer-events: none;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
}
.playback-button:active,
.playback-button--small:active {
transform: scale(0.9);
&:hover {
&:before {
transform: scale(1);
opacity: 1;
}
}
}
.playback-button--small.active {
background-color: rgb(200 200 200 / 25%);
}
.playback-button:hover,
.playback-button--small:hover {
// background-color: var(--selected);
}
.playback-button:active,
.playback-button--small:active {
background-color: var(--selected-click);
}
.playback-button--small.search {
background-image: url("./assets/search.svg");
}
@ -1655,6 +1818,18 @@ input[type=checkbox][switch]:checked:active::before {
background-position: center;
}
.playback-button.collapseLibrary {
font-family: "codicon";
font-size: 1em;
color: var(--textColor);
display: grid;
place-items: center;
span {
position: absolute;
}
}
.playback-button.pause {
background-image: url('./assets/cider-icons/pause.svg');
background-size: 38px;
@ -1678,11 +1853,14 @@ input[type=checkbox][switch]:checked:active::before {
background-size: 60%;
background-position: center;
}
.playback-button.disabled, .playback-button--small.disabled {
.playback-button.disabled,
.playback-button--small.disabled {
opacity: 0.25 !important;
pointer-events: none;
transform: none !important;
&:hover{
&:hover {
background-color: transparent !important;
transform: none !important;
}
@ -1694,10 +1872,11 @@ input[type=checkbox][switch]:checked:active::before {
justify-content: center;
align-items: center;
color: white;
>svg {
height:16px;
width:16px;
pointer-events: none;
> svg {
height: 16px;
width: 16px;
pointer-events: none;
}
}
}
@ -1907,6 +2086,7 @@ input[type=checkbox][switch]:checked:active::before {
border-radius: inherit;
}
}
.artist-chip__name {
pointer-events: none;
}
@ -1989,36 +2169,37 @@ input[type=checkbox][switch]:checked:active::before {
// fancy pills
.nav-pills {
position : relative;
position: relative;
.nav-link {
transition: transform .3s var(--appleEase);
position : relative;
position: relative;
&:after {
--dist: 1px;
content : "";
position : absolute;
top : var(--dist);
bottom : var(--dist);
left : var(--dist);
right : var(--dist);
content: "";
position: absolute;
top: var(--dist);
bottom: var(--dist);
left: var(--dist);
right: var(--dist);
// width : 100%;
// height : 100%;
background-color: transparent;
border-radius : 50px;
z-index : -1;
opacity : 0;
border-radius: 50px;
z-index: -1;
opacity: 0;
transition: background-color .5s var(--appleEase), opacity 0.25s var(--appleEase), border-radius .32s var(--appleEase);
}
&:hover {
outline : none;
transform : scale(1.1);
outline: none;
transform: scale(1.1);
// background: #eee;
background : transparent;
color : #333;
background: transparent;
color: #333;
&:after {
opacity: 1;
@ -2031,11 +2212,11 @@ input[type=checkbox][switch]:checked:active::before {
}
&.active {
outline : none;
transform : scale(1.1);
outline: none;
transform: scale(1.1);
// background: #eee;
background : transparent;
color : #333;
background: transparent;
color: #333;
font-weight: 600;
&:after {
@ -2049,24 +2230,24 @@ input[type=checkbox][switch]:checked:active::before {
&:hover {
.nav-link.active {
outline : none;
transform : scale(1.0);
outline: none;
transform: scale(1.0);
background: transparent;
color: #eee;
transform : scale(1.0);
transform: scale(1.0);
&:after {
background : rgb(200 200 200 / 15%);
opacity : 1;
background: rgb(200 200 200 / 15%);
opacity: 1;
transition: color 0s;
// border-radius: 5px;
--dist: 4px;
}
&:hover {
transform : scale(1.1);
z-index : 1;
color : #333;
transform: scale(1.1);
z-index: 1;
color: #333;
&:after {
background: #eee;
@ -2078,15 +2259,15 @@ input[type=checkbox][switch]:checked:active::before {
}
&:after {
content : '';
position : absolute;
top : 0;
left : 0;
bottom : 0;
right : 0;
background : rgb(200 200 200 / 10%);
border-radius : 50px;
z-index : 0;
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgb(200 200 200 / 10%);
border-radius: 50px;
z-index: 0;
pointer-events: none;
}
}
@ -2095,6 +2276,7 @@ input[type=checkbox][switch]:checked:active::before {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
.grouping-btn {
padding: 16px;
appearance: none;
@ -2126,8 +2308,7 @@ input[type=checkbox][switch]:checked:active::before {
}
}
.app-sidebar-header
.search-input-container .search-hints-container {
.app-sidebar-header .search-input-container .search-hints-container {
top: 38px;
padding: 3px;
}
@ -2135,17 +2316,16 @@ input[type=checkbox][switch]:checked:active::before {
.content-inner {
&.library-page {
.heart-icon {
left: 7px;
left: 7px;
}
.cd-mediaitem-list-item {
padding-left: 25px;
}
}
&.library-artists-page {
.inner-container
.list-container
.podcasts-list {
.inner-container .list-container .podcasts-list {
background: rgba(27, 27, 27);
padding-top: 14px;
width: 272px;
@ -2153,9 +2333,10 @@ input[type=checkbox][switch]:checked:active::before {
.cd-mediaitem-list-item {
margin-left: 10px;
}
.cd-mediaitem-list-item:hover {
width: 96%;
}
}
}
}
}

View file

@ -153,22 +153,8 @@
}
.close-btn {
width : 50px;
height : 100%;
background-image : var(--gfx-closeBtn);
background-position: center;
background-repeat : no-repeat;
-webkit-app-region : no-drag;
appearance : none;
border : 0;
background-color : transparent;
position : absolute;
top : 0;
right : 0;
.menu-panel.menu-header-text.close-btn
&:hover {
background-color: rgb(196, 43, 28)
}
}
}
}
@ -191,22 +177,7 @@
}
.close-btn {
width : 50px;
height : 100%;
background-image : var(--gfx-closeBtn);
background-position: center;
background-repeat : no-repeat;
-webkit-app-region : no-drag;
appearance : none;
border : 0;
background-color : transparent;
position : absolute;
top : 0;
right : 0;
&:hover {
background-color: rgb(196, 43, 28)
}
.menu-panel.menu-header-text.close-btn
}
}
@ -371,23 +342,31 @@
}
}
.menu-header-text {
margin: 18px 6px;
.close-btn {
width : 50px;
height : 42px;
background-image : var(--gfx-closeBtn);
width: 36px;
height: 36px;
background-position: center;
background-repeat : no-repeat;
-webkit-app-region : no-drag;
appearance : none;
border : 0;
background-color : transparent;
position : absolute;
top : 0;
right : 0;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
appearance: none;
border: 0;
background-color: transparent;
position: absolute;
top: 10px;
right: 10px;
border-radius: 50px;
display: grid;
align-content: center;
&:before {
content: "";
font-family: "codicon";
color: var(--textColor);
font-size: 20px;
}
&:hover {
background-color: rgb(196, 43, 28)
@ -505,7 +484,8 @@
.popover-artwork {
width: 200px;
height: 200px;
margin: 0 0 20px 0;
margin: 0 auto;
margin-bottom: 20px;
}
.song-name {

View file

@ -3,4 +3,57 @@ body[platform="linux"] {
#window-controls-container {
//display: none;
}
.window-controls {
justify-content: flex-end;
align-items : center;
padding-right : 6px;
>div {
--iconSize: 16px;
&.close,
&.minmax,
&.minimize,
&.minmax.restore {
background-image: unset!important;
position : relative;
display : grid;
align-content : center;
text-align : center;
height : 36px!important;
width : 36px!important;
border-radius : 50px;
transition: background-color .1s ease-in-out;
&:hover {
background: rgb(200 200 200 / 10%)!important;
}
}
&.close::before {
font-family: "codicon";
font-size : var(--iconSize);
content : "";
}
&.minmax::before {
font-family: "codicon";
font-size : var(--iconSize);
content : "";
}
&.minimize::before {
font-family: "codicon";
font-size : var(--iconSize);
content : "";
}
&.restore::before {
font-family: "codicon";
font-size : var(--iconSize);
content : "";
}
}
}
}

View file

@ -13,5 +13,37 @@ body[platform="darwin"] {
&::before {
display: none;
}
.app-chrome {
background-color: var(--macOSChromeColor);
}
&.twopanel {
--chromeHeight1: 55px;
--chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2));
.app-chrome .app-chrome-item.search {
margin-right: 12px;
}
.app-chrome .app-mainmenu {
width: 46px;
}
.app-chrome.chrome-bottom {
background-color: var(--macOSChromeColor);
}
}
}
#app-main {
background-color: transparent;
.app-navigation {
background: transparent;
}
#app-content {
background-color: var(--baseColor);
}
}
}

View file

@ -114,23 +114,35 @@
margin: 2em;
}
}
// Search Page
&.search-page {
.searchToggle {
float: right;
>button {
min-width: 120px;
}
}
.cd-mediaitem-square.mediaitem-brick {
width: 530px !important;
.artwork-container .artwork{
height:168px !important;
width:507px !important;
z-index: 1;
}
.title{
font-weight: bold;
justify-content: left;
font-size: 18px;
margin-top: -40px;
z-index: 5;
pointer-events: none;
}
width: 530px !important;
.artwork-container .artwork {
height : 168px !important;
width : 507px !important;
z-index: 1;
}
.title {
font-weight : bold;
justify-content: left;
font-size : 18px;
margin-top : -40px;
z-index : 5;
pointer-events : none;
}
}
}
@ -553,7 +565,7 @@
overflow-y : overlay;
height : 100%;
padding : 0px;
background-color: var(--color1);
background-color: var(--color3);
&.scrollbody {
.tabs {
@ -622,7 +634,7 @@
opacity : .7;
animation : playlistArtworkFadeIn 1s var(--appleEase);
.artworkMaterial>img {
.artworkMaterial img {
filter : brightness(100%) blur(80px) saturate(100%) contrast(1);
object-position: center;
object-fit : cover;
@ -665,13 +677,23 @@
}
.nameEdit {
font-weight : 700;
font-size : 1.6rem;
flex-shrink : unset;
background : transparent;
border : 0px;
color : inherit;
font-family : inherit;
font-weight: 700;
font-size : 1.6rem;
flex-shrink: unset;
background : transparent;
border : 0px;
color : inherit;
font-family: inherit;
}
.descriptionEdit {
font-size : 14px;
flex-shrink: unset;
background : transparent;
border : 0px;
color : inherit;
font-family: inherit;
width : 60vw;
}
.descriptionEdit {
@ -950,6 +972,10 @@
right : 28px;
}
&.animated .artist-header {
min-height: 500px;
}
.artist-header {
//background: linear-gradient(45deg, var(--keyColor), #0e0e0e);
color : white;
@ -965,6 +991,19 @@
// margin-top: -16px;
}
.artist-hero {
height:100%;
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
.mediaitem-artwork {
border-radius: 0px;
}
}
.artworkContainer {
position : absolute;
@ -978,7 +1017,7 @@
opacity : .7;
animation : playlistArtworkFadeIn 1s var(--appleEase);
.artworkMaterial>img {
.artworkMaterial img {
filter : brightness(100%) blur(80px) saturate(100%) contrast(1);
object-position: center;
object-fit : cover;
@ -1001,6 +1040,7 @@
position : absolute;
overflow : hidden;
box-shadow: rgb(0 0 0 / 50%) 0 0 0 1000000px inset;
z-index: 1;
video {
overflow : hidden;
@ -1166,8 +1206,9 @@
&.addon {
background: rgb(86 86 86 / 20%);
}
&.applied {
background: var(--keyColor-disabled);
background : var(--keyColor-disabled);
pointer-events: none;
}
}
@ -1218,10 +1259,10 @@
}
.handle {
height: 100%;
display: flex;
height : 100%;
display : flex;
justify-content: center;
align-items: center;
align-items : center;
}
.list-group-item {
@ -1229,6 +1270,7 @@
&:hover {
cursor: grab;
}
&:active {
cursor: grabbing;
}
@ -1645,26 +1687,223 @@
}
}
.content-inner.cider-multiroom{
.content-inner.oobe {
position : absolute;
overflow : hidden;
top : 0;
left : 0;
bottom : 0;
right : 0;
display : grid;
place-items: center;
width : 100%;
background : #1e1e1e;
.oobe-view {
display : flex;
flex-direction : column;
justify-content: center;
align-items : center;
gap : 32px;
max-width : 1280px;
max-height : 720px;
align-self : center;
justify-self : center;
height : 100%;
width : 100%;
.oobe-header {
font-size : 3em;
text-shadow: var(--replayTextShadow);
font-weight: 600;
}
.oobe-body {
flex : 1;
width : 100%;
background : #ffffff0d;
border-radius: 20px;
padding : 3em;
overflow-y : scroll;
overflow-x : hidden;
@media screen and (max-width: 1161px) {
font-size: 13px;
}
&.text {
white-space: pre-wrap;
}
.blurb {
white-space: pre-wrap;
margin : 16px;
line-height: 1.5em;
}
&.visual {
padding: 1em;
.stylePicker {
border-radius: 10px;
overflow : hidden;
cursor : pointer;
transition : 0.25s all;
box-shadow : 0px 2px 6px rgba(0, 0, 0, 0.25);
width : 450px;
margin : 0 auto;
.visualPreview {
pointer-events: none;
transition : .25s all;
width : 100%;
}
.card-body {
padding : 0;
display : flex;
justify-content: center;
align-items : center;
}
.card-footer {
font-size : 1.25em;
font-weight: 500;
position : absolute;
bottom : 0;
left : 0;
width : 100%;
border : 0px;
text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.25);
font-weight: bold;
}
&.style-active {
outline: 4px solid var(--keyColor);
}
&:hover {
transform : scale(1.10) translateZ(-1px) translateY(10px);
z-index : 1;
box-shadow: 0px 12px 16px rgb(0 0 0 / 25%);
}
@media screen and (max-height: 688px) {
width: 270px;
}
}
}
}
.oobe-footer {
display : flex;
flex-direction : row;
justify-content: center;
align-items : center;
padding : 16px;
.md-btn {
font-size : 18px;
min-width : 128px;
text-align: center;
}
}
}
.oobe-titlebar {
position : absolute;
top : 0;
left : 0;
height : 46px;
width : 100%;
align-items : center;
justify-content : right;
display : flex;
-webkit-app-region: drag;
.button-group {
-webkit-app-region: no-drag;
display : flex;
flex-direction : row;
width : 100px;
height : 100%;
justify-content : center;
align-items : center;
gap : 16px;
>button {
height : 32px;
width : 32px;
font-size : 16px;
border-radius : 0px;
border : 0;
appearance : none;
position : relative;
display : flex;
justify-content: center;
align-items : center;
border-radius : 100%;
&.close {
background-color: #fc3c44aa;
&:hover {
background-color: #fc3c44;
}
}
&.min {
background-color: rgb(200 200 200 / 5%);
&:hover {
background-color: rgb(200 200 200 / 10%);
}
}
&.close::before {
font-family: "codicon";
content : "";
color : white;
}
&.min::before {
font-family: "codicon";
content : "";
color : white;
}
}
}
}
}
.content-inner.cider-multiroom {
padding: 0px;
.detail{
.detail {
padding: 32px;
}
.header-desc{
font-size: 1em;
.header-desc {
font-size : 1em;
font-weight: 400;
}
.artworkContainer{
.artworkContainer {
height: 300px;
width : 100%;
img{
height: 100%;
width: 100%;
overflow: hidden;
img {
height : 100%;
width : 100%;
overflow : hidden;
object-fit: cover;
filter: unset;
&:last-child{
transform: unset;
filter : unset;
&:last-child {
transform: unset;
}
}
}

View file

@ -1,107 +1,91 @@
import { CiderCache } from "./cidercache.js"
import { CiderCache } from "./cidercache.js";
async function spawnMica() {
if (typeof window.micaSpawned !== "undefined") {
return
if (typeof window.micaSpawned !== "undefined") {
return;
} else {
window.micaSpawned = true;
}
const micaDiv = document.createElement("div");
const blurIterations = 6;
micaDiv.id = "micaEffect";
micaDiv.style.position = "fixed";
micaDiv.style.top = "0";
micaDiv.style.left = "0";
micaDiv.style.right = "0";
micaDiv.style.bottom = "0";
micaDiv.style.zIndex = -1;
let lastScreenX;
let lastScreenY;
let lastScreenWidth;
let lastScreenHeight;
let regen = true;
let imgSrc = await ipcRenderer.sendSync("get-wallpaper", {
blurAmount: 256
});
// let micaCache = await CiderCache.getCache("mica-cache");
// if (!micaCache) {
// micaCache = {
// path: "",
// data: "",
// };
// }
// if (micaCache.path == imgSrc.path) {
// regen = false;
// imgSrc = micaCache;
// }
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let img = new Image();
micaDiv.style.backgroundImage = `url(${imgSrc.data})`;
document.body.appendChild(micaDiv);
function onScreenMove(cb) {
function detectScreenMove() {
if (lastScreenY !== window.screenY || lastScreenX !== window.screenX) {
lastScreenY = window.screenY;
lastScreenX = window.screenX;
cb();
}
// window size change
if (
lastScreenWidth !== window.innerWidth ||
lastScreenHeight !== window.innerHeight
) {
lastScreenWidth = window.innerWidth;
lastScreenHeight = window.innerHeight;
cb();
}
if (true) {
requestAnimationFrame(detectScreenMove);
}
}
if (true) {
requestAnimationFrame(detectScreenMove);
}
}
onScreenMove(function () {
const screenHeight = window.screen.height;
const screenWidth = window.screen.width;
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
const ratio = windowWidth / windowHeight;
const x = window.screenX;
const y = window.screenY;
micaDiv.style.backgroundSize = `${screenWidth}px ${screenHeight}px`;
// micaDiv.style.backgroundPosition = `-${x}px -${y}px`;
if (x < 0) {
micaDiv.style.backgroundPosition = `${screenWidth + x}px -${y}px`;
} else {
window.micaSpawned = true
micaDiv.style.backgroundPosition = `-${x}px -${y}px`;
}
const micaDiv = document.createElement('div');
const blurIterations = 6
micaDiv.id = 'micaEffect';
micaDiv.style.position = "fixed"
micaDiv.style.top = "0"
micaDiv.style.left = "0"
micaDiv.style.right = "0"
micaDiv.style.bottom = "0"
micaDiv.style.zIndex = -1
let lastScreenX;
let lastScreenY;
let lastScreenWidth;
let lastScreenHeight;
let regen = true
let imgSrc = await ipcRenderer.sendSync("get-wallpaper")
let micaCache = await CiderCache.getCache("mica-cache")
if (!micaCache) {
micaCache = {
path: "",
data: ""
}
}
if (micaCache.path == imgSrc.path) {
regen = false
imgSrc = micaCache
}
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let img = new Image();
img.src = imgSrc.data;
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
if (regen) {
for (let i = 0; i < blurIterations; i++) {
StackBlur.canvasRGB(canvas, 0, 0, img.width, img.height, 128);
}
micaCache.path = imgSrc.path
micaCache.data = canvas.toDataURL()
CiderCache.putCache("mica-cache", micaCache)
}
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
micaDiv.style.backgroundImage = `url(${micaCache.data})`;
document.body.appendChild(micaDiv);
// on animation finished set animation to unset
micaDiv.addEventListener('animationend', function () {
micaDiv.style.opacity = '1';
micaDiv.style.animation = 'unset';
})
}
function onScreenMove(cb) {
function detectScreenMove() {
if (lastScreenY !== window.screenY || lastScreenX !== window.screenX) {
lastScreenY = window.screenY;
lastScreenX = window.screenX;
cb();
}
// window size change
if (lastScreenWidth !== window.innerWidth || lastScreenHeight !== window.innerHeight) {
lastScreenWidth = window.innerWidth;
lastScreenHeight = window.innerHeight;
cb();
}
if (true) {
requestAnimationFrame(detectScreenMove);
}
}
if (true) {
requestAnimationFrame(detectScreenMove);
}
}
onScreenMove(function () {
const screenHeight = window.screen.height;
const screenWidth = window.screen.width;
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
const ratio = windowWidth / windowHeight;
const x = window.screenX;
const y = window.screenY;
micaDiv.style.backgroundSize = `${screenWidth}px ${screenHeight}px`;
// micaDiv.style.backgroundPosition = `-${x}px -${y}px`;
if (x < 0) {
micaDiv.style.backgroundPosition = `${screenWidth + x}px -${y}px`;
} else {
micaDiv.style.backgroundPosition = `-${x}px -${y}px`;
}
});
return true
});
return true;
}
export { spawnMica }
export { spawnMica };

View file

@ -26,6 +26,7 @@ const app = new Vue({
showHints: false,
results: {},
resultsSocial: {},
resultsLibrary: {},
limit: 10
},
fullscreenLyrics: false,
@ -116,6 +117,7 @@ const app = new Vue({
displayListing: [],
downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library
},
localsongs: []
},
playlists: {
listing: [],
@ -177,9 +179,7 @@ const app = new Vue({
"artwork": { "url": "./assets/logocut.png" }
}
},
forceDirectives: {
},
forceDirectives: {},
menuOpened: false,
maximized: false,
drawerOpened: false,
@ -241,8 +241,8 @@ const app = new Vue({
},
moreinfodata: [],
notyf: notyf,
idleTimer : null,
idleState : false,
idleTimer: null,
idleState: false,
},
watch: {
cfg: {
@ -274,6 +274,31 @@ const app = new Vue({
}, false)
},
methods: {
setWindowHash(route = "") {
window.location.hash = `#${route}`;
},
async oobeInit() {
this.appMode = "oobe"
this.setLz(this.cfg.general.language)
this.setLzManual()
clearTimeout(this.hangtimer)
document.body.removeAttribute("loading")
ipcRenderer.invoke("renderer-ready", true)
document.querySelector("#LOADER").remove()
},
getAppStyle() {
let finalStyle = {}
if (this.cfg.visual.window_background_style === "color") {
finalStyle["background-color"] = this.cfg.visual.windowColor
}
if (this.cfg.visual.customAccentColor) {
finalStyle["--keyColor"] = this.cfg.visual.accentColor
finalStyle["--songProgressColor"] = this.cfg.visual.accentColor
} else if (this.cfg.visual.purplePodcastPlaybackBar && MusicKit.getInstance().nowPlayingItem?.type == "podcast-episodes") {
finalStyle["--songProgressColor"] = '#6929D0'
}
return finalStyle
},
setTimeout(func, time) {
return setTimeout(func, time);
},
@ -297,10 +322,11 @@ const app = new Vue({
}
},
formatVolumeTooltip() {
return this.cfg.audio.dBSPL ? (Number(this.cfg.audio.dBSPLcalibration) + (Math.log10(this.mk.volume) * 20)).toFixed(2) + ' dB SPL' : (Math.log10(this.mk.volume) * 20).toFixed(2) + ' dBFS'
let advancedTooltip = this.cfg.audio.dBSPL ? (Number(this.cfg.audio.dBSPLcalibration) + (Math.log10(this.mk.volume) * 20)).toFixed(2) + ' dB SPL' : (Math.log10(this.mk.volume) * 20).toFixed(2) + ' dBFS'
return this.cfg.audio.advanced ? advancedTooltip : (this.mk.volume * 100).toFixed(0) + '%'
},
mainMenuVisibility(val) {
if(this.chrome.sidebarCollapsed) {
mainMenuVisibility(val, isContextMenu) {
if (this.chrome.sidebarCollapsed && !isContextMenu) {
this.chrome.sidebarCollapsed = false
return
}
@ -334,7 +360,8 @@ const app = new Vue({
this.listennow.timestamp = 0;
this.browsepage.timestamp = 0;
this.radio.timestamp = 0;
} catch (e) { }
} catch (e) {
}
},
/**
* Grabs translation for localization.
@ -490,18 +517,20 @@ const app = new Vue({
})
},
goToGrouping(url = "https://music.apple.com/WebObjects/MZStore.woa/wa/viewGrouping?cc=us&id=34") {
const id = url.split("id=")[1];
window.location.hash = `#groupings/${id}`
if (url.includes('viewTop')) {
window.location.hash = `#charts/top`
} else {
const id = url.split("id=")[1];
window.location.hash = `#groupings/${id}`
}
},
navigateForward() {
history.forward()
},
getHTMLStyle() {
if (app.cfg.visual.uiScale != 1) {
document.querySelector("#app").style.zoom = app.cfg.visual.uiScale
} else {
document.querySelector("#app").style.zoom = ""
}
ipcRenderer.send("setScreenScale", app.cfg.visual.uiScale);
},
resetState() {
this.menuPanel.visible = false;
@ -617,7 +646,9 @@ const app = new Vue({
},
async init() {
let self = this
if (!localStorage.getItem("seenOOBE")) {
localStorage.setItem("seenOOBE", 1)
}
if (this.cfg.visual.styles.length != 0) {
await this.reloadStyles()
}
@ -694,9 +725,6 @@ const app = new Vue({
// Set mk.volume to -1 (setting to 0 wont work, so temp solution setting to -1)
this.mk.volume = -1;
}
// ipcRenderer.invoke('getStoreValue', 'audio.volume').then((value) => {
// self.mk.volume = value
// })
// load cached library
let librarySongs = await CiderCache.getCache("library-songs")
@ -809,13 +837,30 @@ const app = new Vue({
}
})
/**
* DiscordRPC Reload Return Event
* @author @coredev-uk
*/
ipcRenderer.on('rpcReloaded', (e, user) => {
if (user.username) {
app.notyf.success(app.stringTemplateParser(app.getLz("settings.option.connectivity.discordRPC.reconnectedToUser"), {
user: `${user.username}#${user.discriminator}`,
userid: user.id
}));
}
})
ipcRenderer.on('getUpdatedLocalList', (event, data) => {
console.log("cider-local", data);
this.library.localsongs = data;
})
ipcRenderer.on('SoundCheckTag', (event, tag) => {
// let replaygain = self.parseSCTagToRG(tag)
try {
if (app.mk.nowPlayingItem.type !== 'song') {
CiderAudio.audioNodes.gainNode.gain.value = 0.70794578438;
}
else {
} else {
let soundcheck = tag.split(" ")
let numbers = []
for (let item of soundcheck) {
@ -824,18 +869,23 @@ const app = new Vue({
}
numbers.shift()
let peak = Math.max(numbers[6], numbers[7]) / 32768.0
let gain = Math.pow(10, ((-1.3 - (Math.log10(peak) * 20)) / 20))// EBU R 128 Compliant
let gain = Math.pow(10, ((-1.7 - (Math.log10(peak) * 20)) / 20))// EBU R 128 Compliant
console.debug(`[Cider][MaikiwiSoundCheck] Peak Gain: '${(Math.log10(peak) * 20).toFixed(2)}' dB | Adjusting '${(Math.log10(gain) * 20).toFixed(2)}' dB`)
try {
//CiderAudio.audioNodes.gainNode.gain.value = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak)))
CiderAudio.audioNodes.gainNode.gain.value = gain
} catch (e) { }
} catch (e) {
}
}
} catch (e) {
try { ipcRenderer.send('SoundCheckTag', event, tag); }
catch (e) {
try {ipcRenderer.send('SoundCheckTag', event, tag);}
catch (e) {console.log("[Cider][MaikiwiSoundCheck] Error [Gave up after 3 consecutive attempts]: " + e)}
} catch (e) {
try {
ipcRenderer.send('SoundCheckTag', event, tag);
} catch (e) {
try {
ipcRenderer.send('SoundCheckTag', event, tag);
} catch (e) {
console.log("[Cider][MaikiwiSoundCheck] Error [Gave up after 3 consecutive attempts]: " + e)
}
}
} // brute force until it works
})
@ -864,42 +914,58 @@ const app = new Vue({
ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
})
this.mk.addEventListener(MusicKit.Events.queueItemsDidChange, ()=>{
this.mk.addEventListener(MusicKit.Events.queueItemsDidChange, () => {
if (self.$refs.queue) {
setTimeout(()=>{
setTimeout(() => {
self.$refs.queue.updateQueue();
}, 100)
}
})
// Used for Live Radio stations to set Metadata
this.mk.addEventListener(MusicKit.Events.timedMetadataDidChange, (e) => {
app.mk.nowPlayingItem.attributes.name = e.title
app.mk.nowPlayingItem.attributes.artistName = e.performer
app.mk.nowPlayingItem.attributes.albumName = e.album
if(e.links[1]) {
app.currentArtUrl = e.links[1].url
app.currentArtUrlRaw = e.links[1].url
}else{
app.currentArtUrl = e.links[0].url
app.currentArtUrlRaw = e.links[0].url
}
app.mk.nowPlayingItem._songId = e._adamId ? e._adamId : -1
app.mk.nowPlayingItem.id = e._adamId ? e._adamId : -1
})
this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => {
if (self.$refs.queue) {
self.$refs.queue.updateQueue();
}
this.currentSongInfo = a
try {
if (app.mk.nowPlayingItem.flavor.includes("64")) {
if (localStorage.getItem("playingBitrate") !== "64") {
localStorage.setItem("playingBitrate", "64")
CiderAudio.hierarchical_loading();
}
}
else if (app.mk.nowPlayingItem.flavor.includes("256")) {
if (localStorage.getItem("playingBitrate") !== "256") {
if (app.cfg.advanced.AudioContext) {
try {
if (app.mk.nowPlayingItem.flavor.includes("64")) {
if (localStorage.getItem("playingBitrate") !== "64") {
localStorage.setItem("playingBitrate", "64")
CiderAudio.hierarchical_loading();
}
} else if (app.mk.nowPlayingItem.flavor.includes("256")) {
if (localStorage.getItem("playingBitrate") !== "256") {
localStorage.setItem("playingBitrate", "256")
CiderAudio.hierarchical_loading();
}
} else {
localStorage.setItem("playingBitrate", "256")
CiderAudio.hierarchical_loading();
}
}
else {
} catch (e) {
localStorage.setItem("playingBitrate", "256")
CiderAudio.hierarchical_loading();
}
} catch(e) {
localStorage.setItem("playingBitrate", "256")
CiderAudio.hierarchical_loading();
}
if (app.cfg.audio.normalization) {
// get unencrypted audio previews to get SoundCheck's normalization tag
try {
@ -960,7 +1026,7 @@ const app = new Vue({
}
setTimeout(() => {
let i = (document.querySelector('#apple-music-player')?.src ?? "")
if (i.endsWith(".m3u8") || i.endsWith(".m3u")){
if (i.endsWith(".m3u8") || i.endsWith(".m3u")) {
this._playRadioStream(i)
}
}, 1500)
@ -977,7 +1043,7 @@ const app = new Vue({
this.appRoute(window.location.hash)
}
if(this.page != "home") {
if (this.page != "home") {
this.resumeTabs()
}
this.mediaKeyFixes()
@ -988,7 +1054,7 @@ const app = new Vue({
this.$forceUpdate()
}, 500)
document.querySelector('#apple-music-video-player-controls').addEventListener('mousemove', () => {
this.showFoo('.music-player-info',2000);
this.showFoo('.music-player-info', 2000);
})
ipcRenderer.invoke("renderer-ready", true)
document.querySelector("#LOADER").remove()
@ -996,7 +1062,7 @@ const app = new Vue({
this.checkForThemeUpdates()
}
},
showFoo(querySelector,time) {
showFoo(querySelector, time) {
clearTimeout(this.idleTimer);
if (this.idleState == true) {
document.querySelector(querySelector).classList.remove("inactive");
@ -1019,7 +1085,11 @@ const app = new Vue({
.then(res => res.json())
.then(res => {
if (res[0].sha != theme.commit) {
const notify = notyf.open({ className: "notyf-info", type: "info", message: `[Themes] ${theme.name} has an update available.` })
const notify = notyf.open({
className: "notyf-info",
type: "info",
message: `[Themes] ${theme.name} has an update available.`
})
notify.on("click", () => {
app.appRoute("themes-github")
notyf.dismiss(notify)
@ -1118,9 +1188,20 @@ const app = new Vue({
},
getAppClasses() {
let classes = {}
if (this.cfg.advanced.experiments.includes('compactui')) {
classes.compact = true
switch (this.getThemeDirective('forceUI') ?? "none") {
case "compact":
classes.compact = true;
break;
case "standard":
classes.compact = false;
break;
default:
if (this.cfg.advanced.experiments.includes('compactui')) {
classes.compact = true;
}
break;
}
if (this.cfg.visual.window_background_style == "none") {
classes.simplebg = true
}
@ -1202,12 +1283,72 @@ const app = new Vue({
}
})
},
async syncFavorites() {
const notify = notyf.open({
className: "notyf-info",
type: "info",
message: `[${app.getLz('home.syncFavorites')}] ${app.getLz('home.syncFavorites.gettingArtists')}`
})
const results = await MusicKitTools.v3Continuous({
href: "/v1/me/library/artists", options: {
"include": ["catalog"],
"fields[artists]": ["inFavorites"]
}
})
let favs = []
// for each result
results.forEach(result => {
try {
if (result.relationships?.catalog?.data[0]?.attributes?.inFavorites) {
if(!favs.includes(result.relationships?.catalog?.data[0].id)) {
favs.push(result.relationships?.catalog?.data[0].id)
}
}
} catch (e) {
e = null
}
})
notyf.success(`[${app.getLz('home.syncFavorites')}] ${app.getLz('action.done')}`)
app.cfg.home.followedArtists = favs
return favs
},
async setArtistFavorite(id, val = true) {
if(val) {
if(!app.cfg.home.followedArtists.includes(id)) {
app.cfg.home.followedArtists.push(id)
}
await app.mk.api.v3.music(`/v1/me/favorites`, {
"art[url]": "f",
"ids[artists]": app.artistPage.data.id,
"l": app.mklang,
"platform": "web"
}, {
fetchOptions: {
method: "POST"
}
})
}else{
if(app.cfg.home.followedArtists.includes(id)) {
app.cfg.home.followedArtists.splice(app.cfg.home.followedArtists.indexOf(id), 1)
}
await app.mk.api.v3.music(`/v1/me/favorites`, {
"art[url]": "f",
"ids[artists]": app.artistPage.data.id,
"l": app.mklang,
"platform": "web"
}, {
fetchOptions: {
method: "DELETE"
}
})
}
},
async refreshPlaylists(localOnly = false, useCachedPlaylists = true) {
let self = this
let trackMap = this.cfg.advanced.playlistTrackMapping
let newListing = []
let trackMapping = {}
if (useCachedPlaylists) {
const cachedPlaylist = await CiderCache.getCache("library-playlists")
const cachedTrackMapping = await CiderCache.getCache("library-playlists-tracks")
@ -1235,7 +1376,7 @@ const app = new Vue({
async function deepScan(parent = "p.playlistsroot") {
console.debug(`scanning ${parent}`)
// const playlistData = await app.mk.api.v3.music(`/v1/me/library/playlist-folders/${parent}/children/`)
const playlistData = await MusicKitTools.v3Continuous({href: `/v1/me/library/playlist-folders/${parent}/children/`})
const playlistData = await MusicKitTools.v3Continuous({ href: `/v1/me/library/playlist-folders/${parent}/children/` })
console.log(playlistData)
await asyncForEach(playlistData, async (playlist) => {
playlist.parent = parent
@ -1267,10 +1408,12 @@ const app = new Vue({
}
})
}
} catch (e) { }
} catch (e) {
}
if (playlist.type == "library-playlist-folders") {
try {
await deepScan(playlist.id).catch(e => { })
await deepScan(playlist.id).catch(e => {
})
} catch (e) {
}
@ -1428,10 +1571,10 @@ const app = new Vue({
})
}
},
/**
/**
* @param {string} url, href for the initial request
* @memberof app
*/
*/
async showRoom(url) {
let self = this
const response = await this.mk.api.v3.music(url)
@ -1559,7 +1702,7 @@ const app = new Vue({
this.page = ""
const artistData = await this.mkapi("artists", false, id, {
"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": "centeredFullscreenBackground,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",
@ -1654,8 +1797,7 @@ const app = new Vue({
const mDisplay = m > 0 ? `${m} ${app.getLz("term.time.minute", { "count": m })}` : "";
return dDisplay + (dDisplay && hDisplay ? ", " : "") + hDisplay + (hDisplay && mDisplay ? ", " : "") + mDisplay;
}
else {
} else {
let returnTime = datetime.toISOString().substring(11, 19);
const timeGates = {
@ -1753,27 +1895,28 @@ const app = new Vue({
if (item.attributes.link.url.includes("viewMultiRoom")) {
const params = new Proxy(new URLSearchParams(item.attributes.link.url), {
get: (searchParams, prop) => searchParams.get(prop),
});
});
id = params.fcId
app.getTypeFromID("multiroom", id, false, {
platform: "web",
extend: "editorialArtwork,uber,lockupStyle"
}).then(()=> {
}).then(() => {
kind = "multiroom"
window.location.hash = `${kind}/${id}`
document.querySelector("#app-content").scrollTop = 0
})
return;
} else {
window.open(item.attributes.link.url)}
window.open(item.attributes.link.url)
}
}
} else if (kind == "multirooms"){
} else if (kind == "multirooms") {
app.getTypeFromID("multiroom", id, false, {
platform: "web",
extend: "editorialArtwork,uber,lockupStyle"
}).then(()=> {
}).then(() => {
kind = "multiroom"
window.location.hash = `${kind}/${id}`
document.querySelector("#app-content").scrollTop = 0
@ -1812,7 +1955,7 @@ const app = new Vue({
params["meta[albums:tracks]"] = 'popularity'
params["fields[albums]"] = "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialNotes,editorialVideo,name,playParams,releaseDate,url,copyright"
}
if (kind.includes("playlist") || kind.includes("album")){
if (kind.includes("playlist") || kind.includes("album")) {
app.page = (kind) + "_" + (id);
window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}`
app.getTypeFromID((kind), (id), (isLibrary), params);
@ -1847,33 +1990,40 @@ const app = new Vue({
}
},
isDisabled() {
if(!app.mk.nowPlayingItem || app.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation') {
if (!app.mk.nowPlayingItem || app.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation') {
return true;
}
return false;
},
isPrevDisabled() {
if(this.isDisabled() || (app.mk.queue._position == 0 && app.mk.currentPlaybackTime <= 2)) {
if (this.isDisabled() || (app.mk.queue._position == 0 && app.mk.currentPlaybackTime <= 2)) {
return true;
}
return false;
},
isNextDisabled() {
if(this.isDisabled() || app.mk.queue._position + 1 == app.mk.queue.length) {
if (this.isDisabled() || app.mk.queue._position + 1 == app.mk.queue.length) {
return true;
}
return false;
},
async getNowPlayingItemDetailed(target) {
let nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem))
if(nowPlayingItem.type === "radioStation" && app.mk.nowPlayingItem.id !== -1) {
nowPlayingItem.playParams = {kind: "songs"}
nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id
nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id
nowPlayingItem.id = app.mk.nowPlayingItem.id
}
try {
let u = await app.mkapi(app.mk.nowPlayingItem.playParams.kind,
(app.mk.nowPlayingItem.songId == -1),
(app.mk.nowPlayingItem.songId != -1) ? app.mk.nowPlayingItem.songId : app.mk.nowPlayingItem["id"],
let u = await app.mkapi(nowPlayingItem.playParams.kind,
(nowPlayingItem.songId == -1),
(nowPlayingItem.songId != -1) ? nowPlayingItem.songId : nowPlayingItem["id"],
{ "include[songs]": "albums,artists", l: app.mklang });
app.searchAndNavigate(u.data.data[0], target)
} catch (e) {
app.searchAndNavigate(app.mk.nowPlayingItem, target)
app.searchAndNavigate(nowPlayingItem, target)
}
},
async searchAndNavigate(item, target) {
@ -1989,16 +2139,6 @@ const app = new Vue({
this.getArtistFromID(id)
//this.getTypeFromID("artist",id,isLibrary,query)
},
followArtistById(id, follow) {
if (follow && !this.followingArtist(id)) {
this.cfg.home.followedArtists.push(id)
} else {
let index = this.cfg.home.followedArtists.indexOf(id)
if (index > -1) {
this.cfg.home.followedArtists.splice(index, 1)
}
}
},
followingArtist(id) {
console.debug(`check for ${id}`)
return this.cfg.home.followedArtists.includes(id)
@ -2015,8 +2155,7 @@ const app = new Vue({
app.mk.setStationQueue({ artist: 'a-' + id }).then(() => {
app.mk.play()
})
}
else {
} else {
app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '')
}
})
@ -2039,7 +2178,7 @@ const app = new Vue({
} finally {
if (kind == "appleCurator") {
app.appleCurator = a.data.data[0]
} else if (kind == "multiroom"){
} else if (kind == "multiroom") {
app.multiroom = a.data.data[0]
} else {
this.getPlaylistContinuous(a, true)
@ -2048,7 +2187,7 @@ const app = new Vue({
} finally {
if (kind == "appleCurator") {
app.appleCurator = a.data.data[0]
} else if (kind == "multiroom"){
} else if (kind == "multiroom") {
app.multiroom = a.data.data[0]
} else {
this.getPlaylistContinuous(a, true)
@ -2292,8 +2431,7 @@ const app = new Vue({
try {
if (method.includes(`multiroom`)) {
return await this.mk.api.v3.music(`v1/editorial/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2)
}
else if (library) {
} else if (library) {
return await this.mk.api.v3.music(`v1/me/library/${truemethod}/${term.toString()}`, params, params2)
} else {
return await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2)
@ -2320,6 +2458,7 @@ const app = new Vue({
let library = []
let cacheId = "library-songs"
let downloaded = null;
this.$store.commit("resetRecentlyAdded")
if ((this.library.songs.downloadState == 2) && !force) {
return
}
@ -2642,7 +2781,7 @@ const app = new Vue({
"include[songs]": "artists",
"include[music-videos]": "artists",
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"],
"fields[artists]": ["name", "url"],
"fields[artists]": ["name", "url", "artwork"],
"extend[stations]": ["airDate", "supportsAirTimeUpdates"],
"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-upsells",
@ -3042,7 +3181,8 @@ const app = new Vue({
} else { //4xx rejected
getToken(2, '', '', id, lang, '');
}
} catch (e) { }
} catch (e) {
}
}
req2.send();
}
@ -3100,8 +3240,7 @@ const app = new Vue({
translation: ''
});
app.lyrics = preLrc.reverse();
}
catch (e) {
} catch (e) {
app.lyrics = "";
}
};
@ -3143,6 +3282,7 @@ const app = new Vue({
function b64_to_utf8(str) {
return decodeURIComponent(escape(window.atob(str)));
}
const htmlDecode = (input) => {
const doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
@ -3173,8 +3313,7 @@ const app = new Vue({
translation: ''
});
app.lyrics = preLrc.reverse();
}
catch (e) {
} catch (e) {
console.log(e)
app.loadNeteaseLyrics();
app.lyrics = "";
@ -3532,6 +3671,18 @@ const app = new Vue({
friendlyTypes(type) {
// use switch statement to return friendly name for media types "songs,artists,albums,playlists,music-videos,stations,apple-curators,curators"
switch (type) {
case "library-songs":
return app.getLz('term.songs')
break;
case "library-artists":
return app.getLz('term.artists')
break;
case "library-albums":
return app.getLz('term.albums')
break;
case "library-playlists":
return app.getLz('term.playlists')
break;
case "song":
return app.getLz('term.songs')
break;
@ -3614,6 +3765,13 @@ const app = new Vue({
results.data.results["meta"] = results.data.meta
self.search.resultsSocial = results.data.results
})
this.search.resultsLibrary = await app.mk.api.library.search(app.search.term, {
types: 'library-songs,library-albums,library-playlists,library-artists',
limit: 25,
offset: 0
})
},
async inLibrary(items = []) {
let types = []
@ -3690,7 +3848,7 @@ const app = new Vue({
},
getMediaItemArtwork(url, height = 64, width) {
if (typeof url == "undefined" || url == "") {
return "https://beta.music.apple.com/assets/product/MissingArtworkMusic.svg"
return "./assets/MissingArtwork.svg"
}
height = parseInt(height * window.devicePixelRatio)
if (width) {
@ -3766,6 +3924,9 @@ const app = new Vue({
if (app.mk.nowPlayingItem != null && app.mk.nowPlayingItem.attributes != null && app.mk.nowPlayingItem.attributes.artwork != null && app.mk.nowPlayingItem.attributes.artwork.url != null && app.mk.nowPlayingItem.attributes.artwork.url != '') {
this.currentArtUrlRaw = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '')
this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize);
if (this.mk.nowPlayingItem._assets[0].artworkURL) {
this.currentArtUrl = this.mk.nowPlayingItem._assets[0].artworkURL
}
try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) {
@ -3776,6 +3937,9 @@ const app = new Vue({
if (data != null && data !== "" && data.attributes != null && data.attributes.artwork != null) {
this.currentArtUrlRaw = (data["attributes"]["artwork"]["url"] ?? '')
this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize);
if (this.mk.nowPlayingItem._assets[0].artworkURL) {
this.currentArtUrl = this.mk.nowPlayingItem._assets[0].artworkURL
}
ipcRenderer.send('updateRPCImage', this.currentArtUrl ?? '');
try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
@ -3988,10 +4152,10 @@ const app = new Vue({
setAirPlayCodeUI() {
this.modals.airplayPW = true
},
sendAirPlaySuccess(){
sendAirPlaySuccess() {
notyf.success('Device paired successfully!');
},
sendAirPlayFailed(){
sendAirPlayFailed() {
notyf.error('Device paring failed!');
},
windowFocus(val) {
@ -4183,7 +4347,15 @@ const app = new Vue({
this.showMenuPanel(menus[useMenu], event)
try {
let result = await this.inLibrary([this.mk.nowPlayingItem])
// if its a radio station, then change the attributes to match a song
const nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem))
if(nowPlayingItem.type == "radioStation" && app.mk.nowPlayingItem.id != -1) {
nowPlayingItem.type = "song"
nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id
nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id
nowPlayingItem.id = app.mk.nowPlayingItem.id
}
let result = await this.inLibrary([nowPlayingItem])
if (result[0].attributes.inLibrary) {
menus.normal.items.find(x => x.id == 'addToLibrary').hidden = true
menus.normal.items.find(x => x.id == 'removeFromLibrary').hidden = false
@ -4242,7 +4414,7 @@ const app = new Vue({
});
},
fullscreen(flag) {
this.fullscreenState = flag;
this.fullscreenState = flag;
if (flag) {
ipcRenderer.send('setFullScreen', true);
app.appMode = 'fullscreen';
@ -4257,7 +4429,7 @@ const app = new Vue({
app.appMode = 'player';
}
},
pip(){
pip() {
document.querySelector('video#apple-music-video-player').requestPictureInPicture()
// .then(pictureInPictureWindow => {
// pictureInPictureWindow.addEventListener("resize", () => {
@ -4438,16 +4610,19 @@ const app = new Vue({
_playRadioStream(e) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = process;
xhr.open("GET", e , true);
xhr.open("GET", e, true);
xhr.send();
let self = this
function process() {
if (xhr.readyState == 4) {
let sources = xhr.responseText.match(/^(?!#)(?!\s).*$/mg).filter(function(element){return (element);});
// Load first source
let src = sources[0];
app.mk._services.mediaItemPlayback._currentPlayer._playAssetURL(src, false)
}
if (xhr.readyState == 4) {
let sources = xhr.responseText.match(/^(?!#)(?!\s).*$/mg).filter(function (element) {
return (element);
});
// Load first source
let src = sources[0];
app.mk._services.mediaItemPlayback._currentPlayer._playAssetURL(src, false)
}
}
}
}

View file

@ -6,11 +6,24 @@ const store = new Vuex.Store({
// recentlyAdded: ipcRenderer.sendSync("get-library-recentlyAdded"),
// playlists: ipcRenderer.sendSync("get-library-playlists")
},
pageState: {
recentlyAdded: {
loaded: false,
nextUrl: null,
items: [],
size: "normal"
}
},
artwork: {
playerLCD: ""
}
},
mutations: {
resetRecentlyAdded(state) {
state.pageState.recentlyAdded.loaded = false;
state.pageState.recentlyAdded.nextUrl = null;
state.pageState.recentlyAdded.items = [];
},
setLCDArtwork(state, artwork) {
state.artwork.playerLCD = artwork
}

View file

@ -1,9 +1,8 @@
@import url("assets/fonts/Pretendard/pretendardvariable.css");
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;300;400;500;700;900&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100;300;400;500;700;900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+HK:wght@100;300;400;500;700;900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100;300;400;500;700;900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;300;400;500;700;900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100;300;400;500;700;900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100;300;400;500;700;900&display=swap");
@import url("less/appvars.less");
@import url("less/bootstrap-vue.min.less");
@import url("less/ameframework.less");
@ -20,7 +19,9 @@
--mediaItemShadow-Shadow: 0 8px 40px rgb(0 0 0 / 0.55);
--mediaItemShadow-ShadowSubtle: 0 4px 14px rgb(0 0 0 / 10%);
--ciderShadow-Generic: var(--mediaItemShadow), 0 8px 40px rgb(0 0 0 / 0.55);
--mediaItemRadius: 6px;
--mediaItemRadius: 8px;
--mediaItemRadiusSmall: 6px;
--mediaItemRadiusMedium: 8px;
--mediaItemRadiusRound: 100%;
--panelRadius: 10px;
--contentInnerPadding: 16px;
@ -31,6 +32,7 @@
--selected-click: rgb(80 80 80 / 30%);
--hover: rgb(200 200 200 / 10%);
// --keyColor: #fa586a;
--keyColorDefault: @keyColor;
--keyColor: @keyColor;
--keyColor-rgb: 250, 88, 106;
--keyColor-rollover: #ff8a9c;
@ -66,7 +68,7 @@ body {
background-size: cover;
background-position: center;
background: #0000;
font-family: "Pretendard Variable", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-family: "Pretendard Variable", "Noto Sans JP", "Noto Sans KR", "Noto Sans TC", "Noto Sans SC", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
transition: opacity .10s var(--appleEase);
}
@ -308,19 +310,19 @@ a.dropdown-item {
overflow: hidden;
pointer-events: none;
> img {
img {
position: absolute;
width: 200%;
opacity: 0.5;
filter: brightness(200%) blur(180px) saturate(280%) contrast(2);
}
> img:first-child {
img:first-child {
top: 0;
left: 0;
}
> img:last-child {
img:last-child {
bottom: 0;
right: 0;
transform: rotate(180deg);
@ -374,15 +376,14 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
}
#app-sidebar {
/* background-color: var(--color1); */
height: 100%;
width: var(--sidebarWidth);
display: flex;
flex-direction: column;
flex: 0 0 auto;
position: relative;
background : var(--sidebarColorMix);
max-width : var(--sidebarWidth);
background: linear-gradient(180deg, var(--baseColorMix) calc(var(--chromeHeight1) + 1px), var(--sidebarColorMix) calc(var(--chromeHeight1) + 1px));
max-width: var(--sidebarWidth);
padding-top: var(--chromeHeight1);
}
@ -449,13 +450,13 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
width: 100%;
padding: 6px;
border-radius: 6px;
border : 1px solid rgba(100, 100, 100, 0.35);
border-top : 1px solid rgba(100, 100, 100, 0.5);
border-bottom: 1px solid rgb(60 60 60 / 12%);
border: 1px solid rgba(100, 100, 100, 0.35);
border-top: 1px solid rgba(100, 100, 100, 0.5);
border-bottom: 1px solid rgb(60 60 60 / 62%);
font-family: inherit;
font-size: 14px;
background: rgba(100, 100, 100, 0.25);;
color: rgb(200 200 200);
background: #1e1e1e99;
color: #c8c8c8;
font-weight: 500;
padding-left: 32px;
position: relative;
@ -531,18 +532,15 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
&.collapseTab {
display:flex;
padding:0px;
padding:6px;
border:0;
>button {
appearance: none;
border:0px;
width: 100%;
position: relative;
padding: 12px;
padding-left: 32px;
padding-left: 40px;
text-align: left;
font-family: inherit;
background-color: var(--color2);
color: var(--textColor);
&:hover {
background-color: var(--selected);
}
@ -550,17 +548,17 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
background-color: var(--selected-click);
}
&:after {
content: '';
content: '';
display: flex;
justify-content: center;
align-items: center;
width: 32px;
width: 46px;
height: 100%;
position: absolute;
top: 0;
left: 0;
font-weight: bold;
font-size: 1.2em;
font-size: 1em;
font-family: "codicon";
}
}
@ -667,7 +665,9 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
#cmenu() {
.container {
position: absolute;
width: 100%;
left: 0px;
width: var(--sidebarWidth);
max-width: var(--sidebarWidth);
padding: 10px;
z-index: 1;
}
@ -1018,20 +1018,21 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
align-items: center;
-webkit-app-region: no-drag;
height: auto;
}
.app-chrome .app-chrome-item.generic {
width: 50px;
opacity: 0.70;
}
.app-chrome .app-chrome-item.volume {
width: 100px;
margin-right: 6px;
}
&.generic {
width: 50px;
opacity: 0.70;
}
.app-chrome .app-chrome-item.search {
margin-right: 6px;
&.volume {
width: 100px;
margin-right: 6px;
}
&.search {
margin-right: 6px;
}
}
.volume-button {
@ -1161,52 +1162,47 @@ input[type=range].web-slider::-webkit-slider-runnable-track {
&-macos {
width: 100px;
}
}
.app-chrome .app-chrome-item > .window-controls > div {
height: 100%;
width: 32px;
}
.app-chrome .app-chrome-item > .window-controls > div:hover {
background: rgb(200 200 200 / 10%);
}
.app-chrome .app-chrome-item > .window-controls > div.close {
width: 100%;
height: 100%;
background-image: var(--gfx-closeBtn);
background-position: center;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
&:hover {
background-color: rgb(196, 43, 28)
> div {
height: 100%;
width: 32px;
&:hover {
background: rgb(200 200 200 / 10%);
}
&.close {
width: 100%;
height: 100%;
background-image: var(--gfx-closeBtn);
background-position: center;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
&:hover {
background-color: rgb(196, 43, 28)
}
}
&.minmax {
background-image: var(--gfx-maxBtn);
background-position: center;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
width: 100%;
height: 100%;
}
&.minmax.restore {
background-image: var(--gfx-restoreBtn);
}
&.minimize {
background-image: var(--gfx-minBtn);
background-position: center;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
width: 100%;
height: 100%;
}
}
}
.app-chrome .app-chrome-item > .window-controls > div.minmax {
background-image: var(--gfx-maxBtn);
background-position: center;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
width: 100%;
height: 100%;
}
.app-chrome .app-chrome-item > .window-controls > div.minmax.restore {
background-image: var(--gfx-restoreBtn);
}
.app-chrome .app-chrome-item > .window-controls > div.minimize {
background-image: var(--gfx-minBtn);
background-position: center;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
width: 100%;
height: 100%;
}
body[platform="darwin"] .app-chrome .app-chrome-item > .window-controls > div.minimize {
display: none;
}
@ -1242,6 +1238,15 @@ body[platform="darwin"] .app-chrome .app-chrome-item > .window-controls > div.cl
}
.app-chrome .app-chrome-item > .app-playback-controls {
.info-rect{
mask-image: linear-gradient(-90deg, transparent 0%, black 10%, black 90%, transparent 100%);
-webkit-mask-image: linear-gradient(-90deg, transparent 3%, black 10%, black 90%, transparent 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width:100%;
}
.song-name {
font-weight: 600;
text-align: center;
@ -1409,7 +1414,7 @@ div[data-type="musicVideo"] .info-rect .title::before {
width: 12px;
height: 12px;
border-radius: 100%;
background: var(--keyColor);
background: var(--songProgressColor);
cursor: default;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
}
@ -1418,7 +1423,7 @@ div[data-type="musicVideo"] .info-rect .title::before {
width: 8px;
height: 8px;
border-radius: 100%;
background: var(--keyColor);
background: var(--songProgressColor);
cursor: default;
}
}
@ -1441,10 +1446,13 @@ div[data-type="musicVideo"] .info-rect .title::before {
background-position: center;
background-size: contain;
background-repeat: no-repeat;
border-radius: 4px;
border-radius: var(--mediaItemRadiusSmall);
flex: 0 0 auto;
margin: 6px;
image-rendering: -webkit-optimize-contrast;
.mediaitem-artwork {
border-radius: var(--mediaItemRadiusSmall);
}
}
.app-chrome .app-chrome-item > .app-playback-controls .actions {
@ -1515,7 +1523,7 @@ div[data-type="musicVideo"] .info-rect .title::before {
position: relative;
}
.app-chrome .app-chrome-item > .app-playback-controls > div > .song-artist-album {
.app-chrome .app-chrome-item > .app-playback-controls .song-artist-album {
font-weight: 400;
font-size: 12px;
text-align: center;
@ -1603,12 +1611,12 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
}
&::-webkit-slider-thumb:hover {
background-image: radial-gradient(var(--keyColor) 2px, transparent 3px, transparent 10px);
background-image: radial-gradient(var(--songProgressColor) 2px, transparent 3px, transparent 10px);
transform: scale(1.2);
}
&::-webkit-slider-thumb:active {
background-image: radial-gradient(var(--keyColor) 3px, transparent 4px, transparent 10px);
background-image: radial-gradient(var(--songProgressColor) 3px, transparent 4px, transparent 10px);
transform: scale(1);
}
@ -1645,7 +1653,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
overflow-x: hidden;
display: flex;
flex-flow: column;
font-family: 'Inter', 'Noto Sans JP', 'Source Han Sans SC', 'Source Han Sans HK', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans HK', 'Noto Sans KR', sans-serif;
font-family: "Pretendard Variable", "Noto Sans JP", "Noto Sans KR", "Noto Sans TC", "Noto Sans SC", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.lyric-body .no-lyrics {
@ -1679,6 +1687,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
.lyric-line.active .verse.verse-active {
opacity: 1;
transition: opacity 0.35s var(--appleEase);
}
.lyric-line:hover {
@ -1704,7 +1713,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
opacity: 1;
transform: scale(1);
/*background: var(--keyColor);*/
transition: transform 0.2s var(--appleEase);
transition: transform 0.2s var(--appleEase), opacity 0.35s var(--appleEase);
}
.lyric-line:not(.active) {
@ -1749,7 +1758,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
.lyrics-translation {
font-size: 1.6rem;
font-weight: 450;
font-family: 'Inter', 'Noto Sans JP', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans HK', 'Noto Sans KR', sans-serif;
font-family: "Pretendard Variable", "Noto Sans JP", "Noto Sans KR", "Noto Sans TC", "Noto Sans SC", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
filter: contrast(0.5);
}
@ -2147,12 +2156,12 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
}
&::-webkit-slider-thumb:hover {
background-image: radial-gradient(var(--keyColor) 2px, transparent 3px, transparent 10px);
background-image: radial-gradient(var(--songProgressColor) 2px, transparent 3px, transparent 10px);
transform: scale(1.2);
}
&::-webkit-slider-thumb:active {
background-image: radial-gradient(var(--keyColor) 3px, transparent 4px, transparent 10px);
background-image: radial-gradient(var(--songProgressColor) 3px, transparent 4px, transparent 10px);
transform: scale(1);
}
@ -2397,7 +2406,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
width: 12px;
height: 12px;
border-radius: 100%;
background: var(--keyColor);
background: var(--songProgressColor);
cursor: default;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
}
@ -2406,7 +2415,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
width: 8px;
height: 8px;
border-radius: 100%;
background: var(--keyColor);
background: var(--songProgressColor);
cursor: default;
}
}
@ -2535,12 +2544,12 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
}
&::-webkit-slider-thumb:hover {
background-image: radial-gradient(var(--keyColor) 2px, transparent 3px, transparent 10px);
background-image: radial-gradient(var(--songProgressColor) 2px, transparent 3px, transparent 10px);
transform: scale(1.2);
}
&::-webkit-slider-thumb:active {
background-image: radial-gradient(var(--keyColor) 3px, transparent 4px, transparent 10px);
background-image: radial-gradient(var(--songProgressColor) 3px, transparent 4px, transparent 10px);
transform: scale(1);
}
@ -2808,7 +2817,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
width: 12px;
height: 12px;
border-radius: 100%;
background: var(--keyColor);
background: var(--songProgressColor);
cursor: default;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
}
@ -2817,7 +2826,7 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
width: 8px;
height: 8px;
border-radius: 100%;
background: var(--keyColor);
background: var(--songProgressColor);
cursor: default;
}
}
@ -2955,6 +2964,16 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
transform: translateY(20px);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity .15s var(--appleEase);
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.modal-enter-active,
.modal-leave-active {
transition: opacity .1s var(--appleEase), transform .1s var(--appleEase);
@ -3453,22 +3472,7 @@ body.no-gpu {
}
.close-btn {
width: 50px;
height: 100%;
background-image: var(--gfx-closeBtn);
background-position: center;
background-repeat: no-repeat;
-webkit-app-region: no-drag;
appearance: none;
border: 0;
background-color: transparent;
position: absolute;
top: 0;
right: 0;
&:hover {
background-color: rgb(196, 43, 28)
}
.menu-panel.menu-header-text.close-btn
}
}

View file

@ -13,6 +13,32 @@ body {
}
}
.app-chrome:not(.chrome-bottom) {
backdrop-filter: unset;
background-color: var(--baseColor);
}
.menu-panel .menu-panel-body {
background: rgb(30 30 30);
}
.menu-panel .menu-panel-body .menu-option::before {
transition: unset!important;
}
#app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center .top-nav-group .app-sidebar-item:before {
transition: unset!important;
}
.playback-button:before, .playback-button--small:before {
transition: unset!important;
}
.floating-header {
backdrop-filter: unset!important;
background: rgb(0 0 0 / 80%)!important;
}
.replaycard-enter-active,
.replaycard-leave-active {
transition: unset;

View file

@ -57,10 +57,6 @@
.cd-mediaitem-square:not(.mediaitem-card) {
transition : transform .2s var(--appleEase);
transition-delay: .1s;
padding : 12px;
// background-color: red;
height: 220px;
.artwork-container {}
@ -73,9 +69,16 @@
transition-delay: .05s;
}
.artwork-container {
transform : scale(0.962) translateZ(0);
transition : transform .1s var(--appleEase);
transition-delay: 0s;
transform-origin: center;
}
&:hover {
.artwork-container {
transform : scale(1.1);
transform : scale(1.0);
transition : transform .1s var(--appleEase);
transition-delay: 0s;
transform-origin: center;

View file

@ -1,4 +1,4 @@
<div id="app-content" @scroll.passive="setContentScrollPos" :scrollpos="chrome.contentScrollPosY" scrollaxis="y" :style="{'overflow': (chrome.contentAreaScrolling ? '' : 'hidden')}">
<div id="app-content" :scrollpos="chrome.contentScrollPosY" scrollaxis="y" :style="{'overflow': (chrome.contentAreaScrolling ? '' : 'hidden')}">
<div id="navigation-bar" v-if="getThemeDirective('appNavigation') == 'seperate'">
<button class="nav-item" @click="navigateBack()">
<%- include('../svg/chevron-left.svg') %>
@ -24,11 +24,6 @@
</template>
</transition>
<% } %>
<!-- Library - Recently Added -->
<transition :name="chrome.desiredPageTransition" v-on:enter="getLibraryAlbumsFull(null, 0); searchLibraryAlbums(0);">
<%- include('../pages/library-recentlyadded') %>');
</transition>
<!-- Library - Made For You -->
<transition :name="chrome.desiredPageTransition" v-on:enter="getMadeForYou()">
<template v-if="page == 'library-madeforyou'">

View file

@ -1,4 +1,129 @@
<div class="app-navigation" v-cloak>
<transition name="wpfade">
<div class="usermenu-container" v-if="chrome.menuOpened">
<div class="usermenu-body">
<button
class="app-sidebar-button"
style="width: 100%"
@click="appRoute('apple-account-settings')"
>
<img
class="sidebar-user-icon"
loading="lazy"
:src="getMediaItemArtwork(chrome.hideUserInfo ? './assets/logocut.png' : (chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : ''), 26)"
/>
<div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
<template v-if="chrome.userinfo.id || mk.isAuthorized">
<div class="fullname text-overflow-elipsis">
{{
chrome.userinfo != null &&
chrome.userinfo.attributes != null
? chrome.userinfo.attributes.name ?? ""
: ""
}}
</div>
<div class="handle-text text-overflow-elipsis">
{{
chrome.userinfo != null &&
chrome.userinfo.attributes != null
? chrome.userinfo.attributes.handle ?? ""
: ""
}}
</div>
</template>
<template v-else>
<div @click="mk.authorize()">
{{ $root.getLz("term.login") }}
</div>
</template>
</div>
<div class="sidebar-user-text" v-else>
{{ $root.getLz("app.name") }}
</div>
</button>
<!-- Use 20px SVG for usermenu icon -->
<button
class="usermenu-item"
v-if="cfg.general.privateEnabled"
@click="cfg.general.privateEnabled = false"
>
<span class="usermenu-item-icon">
<%- include("../svg/x.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.disablePrivateSession")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('remote-pair')">
<span class="usermenu-item-icon">
<%- include("../svg/smartphone.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("action.showWebRemoteQR")
}}</span>
</button>
<button
class="usermenu-item"
@click="cfg.advanced.AudioContext ? modals.castMenu = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))"
>
<span class="usermenu-item-icon">
<%- include("../svg/cast.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.cast")
}}</span>
</button>
<button
class="usermenu-item"
@click="cfg.advanced.AudioContext ? modals.audioSettings = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))"
>
<span class="usermenu-item-icon">
<%- include("../svg/headphones.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.audioSettings")
}}</span>
</button>
<button
class="usermenu-item"
v-if="pluginInstalled"
@click="modals.pluginMenu = true"
>
<span class="usermenu-item-icon">
<%- include("../svg/grid.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.plugin")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('about')">
<span class="usermenu-item-icon">
<%- include("../svg/info.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.about")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('settings')">
<span class="usermenu-item-icon">
<%- include("../svg/settings.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.settings")
}}</span>
</button>
<button class="usermenu-item" @click="unauthorize()">
<span class="usermenu-item-icon" style="right: 2.5px">
<%- include("../svg/log-out.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.logout")
}}</span>
</button>
</div>
</div>
</transition>
<transition name="sidebartransition">
<%- include("sidebar") %>
</transition>

View file

@ -10,10 +10,10 @@
<b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="right">
<div class="content">
<div class="shadow-artwork">
<mediaitem-artwork :url="currentArtUrl" :url="currentArtUrlRaw"></mediaitem-artwork>
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="popover-artwork">
<mediaitem-artwork :size="210" :url="currentArtUrlRaw"></mediaitem-artwork>
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="song-name">{{ mk.nowPlayingItem["attributes"]["name"] }}</div>
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">{{ mk.nowPlayingItem["attributes"]["artistName"] }}</div>
@ -60,13 +60,27 @@
</div>
</template>
<template v-else>
<div class="app-playback-controls">
<div class="artwork" id="artworkLCD" style="pointer-events: none;">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="playback-info">
<div class="song-name">
</div>
</div>
</div>
</template>
</div>
</div>
<div class="app-chrome--center">
<div class="app-chrome-playback-duration-bottom">
<b-row>
<b-col sm="auto">{{ convertTime(getSongProgress()) }}</b-col>
<b-row v-if="mkReady()">
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(getSongProgress()) }}</b-col>
<b-col sm="auto" v-else>--:--</b-col>
<b-col>
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true"
@ -74,7 +88,8 @@
@touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
:max="mk.currentPlaybackDuration" :value="getSongProgress()">
</b-col>
<b-col sm="auto">{{ convertTime(mk.currentPlaybackDuration) }}</b-col>
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(mk.currentPlaybackDuration) }}</b-col>
<b-col sm="auto" v-else>{{ getLz("term.live") }}</b-col>
</b-row>
</div>
<div class="app-chrome-playback-controls">

View file

@ -1,217 +1,118 @@
<div
class="app-chrome"
:style="{'display': chrome.topChromeVisible ? '' : 'none'}"
>
<div class="app-chrome" :style="{'display': chrome.topChromeVisible ? '' : 'none'}">
<div class="app-chrome--left">
<div
class="app-chrome-item full-height"
v-if="chrome.windowControlPosition == 'left' && !chrome.nativeControls"
>
<div class="app-chrome-item full-height" v-if="chrome.windowControlPosition == 'left' && !chrome.nativeControls">
<div class="window-controls-macos">
<div class="close" @click="ipcRenderer.send('close')"></div>
<div class="minimize" @click="ipcRenderer.send('minimize')"></div>
<div
class="minmax restore"
v-if="chrome.maximized"
@click="ipcRenderer.send('maximize')"
></div>
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
</div>
</div>
<div class="app-chrome-item full-height" v-else>
<button
class="app-mainmenu"
@blur="mainMenuVisibility(false)"
@click="mainMenuVisibility(true)"
:class="{active: chrome.menuOpened}"
:aria-label="$root.getLz('term.quickNav')"
></button>
<button class="app-mainmenu" @blur="mainMenuVisibility(false, true)" @click="mainMenuVisibility(true, false)"
@contextmenu="mainMenuVisibility(true, true)" :class="{active: chrome.menuOpened}"
:aria-label="$root.getLz('term.quickNav')"></button>
</div>
<template v-if="getThemeDirective('appNavigation') != 'seperate'">
<div
class="vdiv"
v-if="getThemeDirective('windowLayout') == 'twopanel'"
></div>
<div class="vdiv" v-if="getThemeDirective('windowLayout') == 'twopanel'"></div>
<div class="app-chrome-item">
<button
class="playback-button navigation"
@click="navigateBack()"
:title="$root.getLz('term.navigateBack')"
v-b-tooltip.hover
>
<button class="playback-button navigation" @click="navigateBack()" :title="$root.getLz('term.navigateBack')"
v-b-tooltip.hover>
<%- include('../svg/chevron-left.svg') %>
</button>
</div>
<div class="app-chrome-item">
<button
class="playback-button navigation"
@click="navigateForward()"
:title="$root.getLz('term.navigateForward')"
v-b-tooltip.hover
>
<button class="playback-button navigation" @click="navigateForward()"
:title="$root.getLz('term.navigateForward')" v-b-tooltip.hover>
<%- include('../svg/chevron-right.svg') %>
</button>
</div>
<div
class="vdiv display--large"
v-if="getThemeDirective('windowLayout') != 'twopanel'"
></div>
<div class="app-chrome-item" v-if="getThemeDirective('windowLayout') == 'twopanel'">
<button class="playback-button collapseLibrary" v-b-tooltip.hover
:title="chrome.sidebarCollapsed ? getLz('action.showLibrary') : getLz('action.hideLibrary')"
@click="chrome.sidebarCollapsed = !chrome.sidebarCollapsed">
<transition name="fade">
<span v-if="chrome.sidebarCollapsed"></span>
</transition>
<transition name="fade">
<span v-if="!chrome.sidebarCollapsed"></span>
</transition>
</button>
</div>
<div class="vdiv display--large" v-if="getThemeDirective('windowLayout') != 'twopanel'"></div>
</template>
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div class="app-chrome-item display--large">
<button
class="playback-button--small shuffle"
v-if="mk.shuffleMode == 0"
:class="isDisabled() && 'disabled'"
@click="mk.shuffleMode = 1"
:title="$root.getLz('term.enableShuffle')"
v-b-tooltip.hover
></button>
<button
class="playback-button--small shuffle active"
v-else
:class="isDisabled() && 'disabled'"
@click="mk.shuffleMode = 0"
:title="$root.getLz('term.disableShuffle')"
v-b-tooltip.hover
></button>
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0" :class="isDisabled() && 'disabled'"
@click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')" v-b-tooltip.hover></button>
<button class="playback-button--small shuffle active" v-else :class="isDisabled() && 'disabled'"
@click="mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button
class="playback-button previous"
@click="prevButton()"
:class="isPrevDisabled() && 'disabled'"
:title="$root.getLz('term.previous')"
v-b-tooltip.hover
></button>
<button class="playback-button previous" @click="prevButton()" :class="isPrevDisabled() && 'disabled'"
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button
class="playback-button stop"
@click="mk.stop()"
<button class="playback-button stop" @click="mk.stop()"
v-if="mk.isPlaying && mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
:title="$root.getLz('term.stop')"
v-b-tooltip.hover
></button>
<button
class="playback-button pause"
@click="mk.pause()"
v-else-if="mk.isPlaying"
:title="$root.getLz('term.pause')"
v-b-tooltip.hover
></button>
<button
class="playback-button play"
@click="mk.play()"
v-else
:title="$root.getLz('term.play')"
v-b-tooltip.hover
></button>
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
<button class="playback-button pause" @click="mk.pause()" v-else-if="mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="mk.play()" v-else :title="$root.getLz('term.play')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button
class="playback-button next"
@click="skipToNextItem()"
:class="isNextDisabled() && 'disabled'"
:title="$root.getLz('term.next')"
v-b-tooltip.hover
></button>
<button class="playback-button next" @click="skipToNextItem()" :class="isNextDisabled() && 'disabled'"
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button
class="playback-button--small repeat"
v-if="mk.repeatMode == 0"
:class="isDisabled() && 'disabled'"
@click="mk.repeatMode = 1"
:title="$root.getLz('term.enableRepeatOne')"
v-b-tooltip.hover
></button>
<button
class="playback-button--small repeat repeatOne"
@click="mk.repeatMode = 2"
:class="isDisabled() && 'disabled'"
v-else-if="mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')"
v-b-tooltip.hover
></button>
<button
class="playback-button--small repeat active"
@click="mk.repeatMode = 0"
:class="isDisabled() && 'disabled'"
v-else-if="mk.repeatMode == 2"
:title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover
></button>
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0" :class="isDisabled() && 'disabled'"
@click="mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
:class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat active" @click="mk.repeatMode = 0"
:class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover></button>
</div>
</template>
</div>
<div class="app-chrome--center">
<div
class="app-chrome-item playback-controls"
v-if="getThemeDirective('windowLayout') != 'twopanel'"
>
<div class="app-chrome-item playback-controls" v-if="getThemeDirective('windowLayout') != 'twopanel'">
<template v-if="mkReady()">
<div
class="app-playback-controls"
@mouseover="chrome.progresshover = true"
@mouseleave="chrome.progresshover = false"
@contextmenu="nowPlayingContextMenu"
>
<div class="app-playback-controls" @mouseover="chrome.progresshover = true"
@mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu">
<div class="artwork" id="artworkLCD">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<b-popover
custom-class="mediainfo-popover"
target="artworkLCD"
triggers="hover"
placement="bottom"
>
<b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="bottom">
<div class="content">
<div class="shadow-artwork">
<mediaitem-artwork
:url="currentArtUrl"
:url="currentArtUrlRaw"
></mediaitem-artwork>
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="popover-artwork">
<mediaitem-artwork
:size="210"
:url="currentArtUrlRaw"
></mediaitem-artwork>
<mediaitem-artwork :size="210" :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="song-name">
{{ mk.nowPlayingItem["attributes"]["name"] }}
</div>
<div
class="song-artist"
@click="getNowPlayingItemDetailed(`artist`)"
>
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
<div
class="song-album"
@click="getNowPlayingItemDetailed(`album`)"
>
<div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
{{
mk.nowPlayingItem["attributes"]["albumName"]
? mk.nowPlayingItem["attributes"]["albumName"]
: ""
mk.nowPlayingItem["attributes"]["albumName"]
? mk.nowPlayingItem["attributes"]["albumName"]
: ""
}}
</div>
<hr />
<div class="btn-group" style="width: 100%">
<button
class="md-btn md-btn-small"
style="width: 100%"
@click="drawer.open = false; miniPlayer(true)"
>
<button class="md-btn md-btn-small" style="width: 100%" @click="drawer.open = false; miniPlayer(true)">
{{ $root.getLz("term.miniplayer") }}
</button>
<button
class="md-btn md-btn-small"
style="width: 100%"
@click="drawer.open = false; fullscreen(true)"
>
<button class="md-btn md-btn-small" style="width: 100%" @click="drawer.open = false; fullscreen(true)">
{{ $root.getLz("term.fullscreenView") }}
</button>
</div>
@ -219,183 +120,121 @@
</b-popover>
<div class="playback-info">
<div class="chrome-icon-container">
<div
class="audio-type private-icon"
v-if="cfg.general.privateEnabled === true"
></div>
<div
class="audio-type ppe-icon"
v-if="cfg.audio.maikiwiAudio.ciderPPE === true"
></div>
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div>
<div class="audio-type ppe-icon" v-if="cfg.audio.maikiwiAudio.ciderPPE === true"></div>
</div>
<div
class="song-name"
:class="[isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name') ? 'marquee' : '']"
>
{{ mk.nowPlayingItem["attributes"]["name"] }}
<div
class="explicit-icon"
v-if="mk.nowPlayingItem['attributes']['contentRating'] == 'explicit'"
style="display: inline-block"
></div>
</div>
<div class="song-artist-album">
<div
class="song-artist-album-content"
:class="[isElementOverflowing('#app-main > .app-chrome .app-chrome-item > .app-playback-controls > div >.song-artist-album > .song-artist-album-content') ? 'marquee' : '']"
style="
<div class="info-rect">
<div class="song-name"
:class="[isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name') ? 'marquee' : '']">
{{ mk.nowPlayingItem["attributes"]["name"] }}
<div class="explicit-icon" v-if="mk.nowPlayingItem['attributes']['contentRating'] == 'explicit'"
style="display: inline-block"></div>
</div>
<div class="song-artist-album">
<div class="song-artist-album-content"
:class="[isElementOverflowing('#app-main > .app-chrome .app-chrome-item > .app-playback-controls > div >.song-artist-album > .song-artist-album-content') ? 'marquee' : '']"
style="
display: inline-block;
-webkit-box-orient: horizontal;
white-space: nowrap;
"
>
<div
class="item-navigate song-artist"
style="display: inline-block"
@click="getNowPlayingItemDetailed(`artist`)"
>
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
<div
class="song-artist item-navigate"
style="display: inline-block"
@click="getNowPlayingItemDetailed('album')"
v-if="mk.nowPlayingItem['attributes']['albumName'] != ''"
>
<div class="separator" style="display: inline-block">
{{ "—" }}
">
<div class="item-navigate song-artist" style="display: inline-block"
@click="getNowPlayingItemDetailed(`artist`)">
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
{{
<div class="song-artist item-navigate" style="display: inline-block"
@click="getNowPlayingItemDetailed('album')"
v-if="mk.nowPlayingItem['attributes']['albumName'] != ''">
<div class="separator" style="display: inline-block">
{{ "—" }}
</div>
{{
mk.nowPlayingItem["attributes"]["albumName"]
? mk.nowPlayingItem["attributes"]["albumName"]
: ""
}}
? mk.nowPlayingItem["attributes"]["albumName"]
: ""
}}
</div>
</div>
</div>
</div>
<div class="song-progress">
<div
class="song-duration"
style="justify-content: space-between; height: 1px"
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]"
>
<div class="song-duration" style="justify-content: space-between; height: 1px"
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p>
<p style="width: auto">
{{ convertTime(mk.currentPlaybackDuration) }}
</p>
</div>
<input
type="range"
step="0.01"
min="0"
:style="progressBarStyle()"
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true"
@mouseup="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
@touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
:max="mk.currentPlaybackDuration"
:value="getSongProgress()"
/>
:max="mk.currentPlaybackDuration" :value="getSongProgress()" />
</div>
</div>
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
<div class="actions">
<button
class="lcdMenu"
@click="nowPlayingContextMenu"
:title="$root.getLz('term.more')"
v-b-tooltip.hover
>
<button class="lcdMenu" @click="nowPlayingContextMenu" :title="$root.getLz('term.more')"
v-b-tooltip.hover>
<div class="svg-icon"></div>
</button>
</div>
</template>
</div>
</template>
<template v-else>
<div class="app-playback-controls">
<div class="artwork" id="artworkLCD" style="pointer-events: none;">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="playback-info">
<div class="info-rect">
</div>
</div>
</div>
</template>
</div>
<div class="app-chrome-item" v-else>
<div class="top-nav-group">
<sidebar-library-item
:name="$root.getLz('home.title')"
svg-icon="./assets/feather/home.svg"
page="home"
>
<sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" page="home">
</sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.listenNow')"
svg-icon="./assets/feather/play-circle.svg"
page="listen_now"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.browse')"
svg-icon="./assets/feather/globe.svg"
page="browse"
>
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg"
page="listen_now"></sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" page="browse">
</sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" page="radio">
</sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.radio')"
svg-icon="./assets/feather/radio.svg"
page="radio"
></sidebar-library-item>
</div>
</div>
</div>
<div class="app-chrome--right">
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div class="app-chrome-item volume display--large">
<button
class="volume-button--small volume"
@click="muteButtonPressed()"
<button class="volume-button--small volume" @click="muteButtonPressed()"
:class="{'active': this.cfg.audio.volume == 0}"
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
v-b-tooltip.hover
></button>
<input
type="range"
@wheel="volumeWheel"
:step="cfg.audio.volumeStep"
min="0"
:max="cfg.audio.maxVolume"
v-model="mk.volume"
v-if="typeof mk.volume != 'undefined'"
@change="checkMuteChange()"
v-b-tooltip.hover
:title="formatVolumeTooltip()"
/>
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')" v-b-tooltip.hover></button>
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume"
v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()" v-b-tooltip.hover
:title="formatVolumeTooltip()" />
</div>
<div class="app-chrome-item generic">
<button
class="playback-button--small cast"
:title="$root.getLz('term.cast')"
<button class="playback-button--small cast" :title="$root.getLz('term.cast')"
@click="cfg.advanced.AudioContext ? modals.castMenu = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))"
v-b-tooltip.hover
></button>
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item generic">
<button
class="playback-button--small queue"
:title="$root.getLz('term.queue')"
v-b-tooltip.hover
:class="{'active': drawer.panel == 'queue'}"
@click="invokeDrawer('queue')"
></button>
<button class="playback-button--small queue" :title="$root.getLz('term.queue')" v-b-tooltip.hover
:class="{'active': drawer.panel == 'queue'}" @click="invokeDrawer('queue')"></button>
</div>
<div class="app-chrome-item generic">
<template v-if="lyrics && lyrics != [] && lyrics.length > 0">
<button
class="playback-button--small lyrics"
:title="$root.getLz('term.lyrics')"
v-b-tooltip.hover
:class="{'active': drawer.panel == 'lyrics'}"
@click="invokeDrawer('lyrics')"
></button>
<button class="playback-button--small lyrics" :title="$root.getLz('term.lyrics')" v-b-tooltip.hover
:class="{'active': drawer.panel == 'lyrics'}" @click="invokeDrawer('lyrics')"></button>
</template>
<template v-else>
<button
class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"
></button>
<button class="playback-button--small lyrics" :style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
</template>
</div>
</template>
@ -403,31 +242,16 @@
<div class="app-chrome-item search">
<div class="search-input-container">
<div class="search-input--icon"></div>
<input
type="search"
spellcheck="false"
@click="showSearch()"
@focus="search.showHints = true"
<input type="search" spellcheck="false" @click="showSearch()" @focus="search.showHints = true"
@blur="setTimeout(()=>{search.showHints = false}, 300)"
v-on:keyup.enter="searchQuery();search.showHints = false"
@change="showSearch();"
@input="getSearchHints()"
:placeholder="$root.getLz('term.search') + '...'"
v-model="search.term"
ref="searchInput"
class="search-input"
/>
v-on:keyup.enter="searchQuery();search.showHints = false" @change="showSearch();" @input="getSearchHints()"
:placeholder="$root.getLz('term.search') + '...'" v-model="search.term" ref="searchInput"
class="search-input" />
<div
class="search-hints-container"
v-if="search.showHints && search.hints.length != 0"
>
<div class="search-hints-container" v-if="search.showHints && search.hints.length != 0">
<div class="search-hints">
<button
class="search-hint text-overflow-elipsis"
v-for="hint in search.hints"
@click="search.term = hint;search.showHints = false;searchQuery(hint)"
>
<button class="search-hint text-overflow-elipsis" v-for="hint in search.hints"
@click="search.term = hint;search.showHints = false;searchQuery(hint)">
{{ hint }}
</button>
</div>
@ -435,32 +259,18 @@
</div>
</div>
</template>
<div
class="app-chrome-item full-height"
id="window-controls-container"
v-if="chrome.windowControlPosition == 'right' && !chrome.nativeControls"
>
<div class="app-chrome-item full-height" id="window-controls-container"
v-if="chrome.windowControlPosition == 'right' && !chrome.nativeControls">
<div class="window-controls">
<div class="minimize" @click="ipcRenderer.send('minimize')"></div>
<div
class="minmax restore"
v-if="chrome.maximized"
@click="ipcRenderer.send('maximize')"
></div>
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
<div class="close" @click="ipcRenderer.send('close')"></div>
</div>
</div>
<div
class="app-chrome-item full-height"
v-else-if="platform != 'darwin' && !chrome.nativeControls"
>
<button
class="app-mainmenu"
@blur="mainMenuVisibility(false)"
@click="mainMenuVisibility(true)"
:class="{active: chrome.menuOpened}"
></button>
<div class="app-chrome-item full-height" v-else-if="platform != 'darwin' && !chrome.nativeControls">
<button class="app-mainmenu" @blur="mainMenuVisibility(false, true)" @click="mainMenuVisibility(true, false)"
@contextmenu="mainMenuVisibility(true, true)" :class="{active: chrome.menuOpened}"></button>
</div>
</div>
</div>
</div>

View file

@ -13,9 +13,6 @@
<transition name="modal">
<add-to-playlist :playlists="playlists.listing" v-if="modals.addToPlaylist"></add-to-playlist>
</transition>
<transition name="modal">
<spatial-properties v-if="modals.spatialProperties"></spatial-properties>
</transition>
<transition name="modal">
<audio-controls v-if="modals.audioControls"></audio-controls>
</transition>

View file

@ -183,136 +183,6 @@
</sidebar-playlist>
</template>
</div>
<transition name="wpfade">
<div class="usermenu-container" v-if="chrome.menuOpened">
<div class="usermenu-body">
<button
class="app-sidebar-button"
style="width: 100%"
@click="appRoute('apple-account-settings')"
>
<img
class="sidebar-user-icon"
loading="lazy"
:src="getMediaItemArtwork(chrome.hideUserInfo ? 'http://localhost:9000/assets/logocut.png' : (chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : ''), 26)"
/>
<div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
<template v-if="chrome.userinfo.id || mk.isAuthorized">
<div class="fullname text-overflow-elipsis">
{{
chrome.userinfo != null &&
chrome.userinfo.attributes != null
? chrome.userinfo.attributes.name ?? ""
: ""
}}
</div>
<div class="handle-text text-overflow-elipsis">
{{
chrome.userinfo != null &&
chrome.userinfo.attributes != null
? chrome.userinfo.attributes.handle ?? ""
: ""
}}
</div>
</template>
<template v-else>
<div @click="mk.authorize()">
{{ $root.getLz("term.login") }}
</div>
</template>
</div>
<div class="sidebar-user-text" v-else>
{{ $root.getLz("app.name") }}
</div>
</button>
<!-- Use 20px SVG for usermenu icon -->
<button
class="usermenu-item"
v-if="cfg.general.privateEnabled"
@click="cfg.general.privateEnabled = false"
>
<span class="usermenu-item-icon">
<%- include("../svg/x.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.disablePrivateSession")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('remote-pair')">
<span class="usermenu-item-icon">
<%- include("../svg/smartphone.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("action.showWebRemoteQR")
}}</span>
</button>
<button
class="usermenu-item"
@click="cfg.advanced.AudioContext ? modals.castMenu = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))"
>
<span class="usermenu-item-icon">
<%- include("../svg/cast.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.cast")
}}</span>
</button>
<button
class="usermenu-item"
@click="cfg.advanced.AudioContext ? modals.audioSettings = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))"
>
<span class="usermenu-item-icon">
<%- include("../svg/headphones.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.audioSettings")
}}</span>
</button>
<button
class="usermenu-item"
v-if="pluginInstalled"
@click="modals.pluginMenu = true"
>
<span class="usermenu-item-icon">
<%- include("../svg/grid.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.plugin")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('about')">
<span class="usermenu-item-icon">
<%- include("../svg/info.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.about")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('settings')">
<span class="usermenu-item-icon">
<%- include("../svg/settings.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.settings")
}}</span>
</button>
<button class="usermenu-item" @click="unauthorize()">
<span class="usermenu-item-icon" style="right: 2.5px">
<%- include("../svg/log-out.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.logout")
}}</span>
</button>
</div>
</div>
</transition>
<div class="app-sidebar-footer collapseTab" v-if="cfg.advanced.experiments.includes('collapseSidebar')">
<button @click="chrome.sidebarCollapsed = !chrome.sidebarCollapsed">
Collapse
</button>
</div>
<div class="app-sidebar-footer display--small app-sidebar-footer--controls">
<div
class="app-playback-controls"
@ -326,6 +196,7 @@
v-if="mk.shuffleMode == 0"
@click="mk.shuffleMode = 1"
:title="$root.getLz('term.enableShuffle')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover.righttop
></button>
<button
@ -333,6 +204,7 @@
v-else
@click="mk.shuffleMode = 0"
:title="$root.getLz('term.disableShuffle')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover.righttop
></button>
</div>
@ -340,31 +212,26 @@
<button
class="playback-button previous"
@click="prevButton()"
:class="$root.isPrevDisabled() && 'disabled'"
:title="$root.getLz('term.previous')"
v-b-tooltip.hover
></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button pause"
@click="mk.pause()"
v-if="mk.isPlaying"
:title="$root.getLz('term.pause')"
v-b-tooltip.hover
></button>
<button
class="playback-button play"
@click="mk.play()"
v-else
:title="$root.getLz('term.play')"
v-b-tooltip.hover
></button>
<button class="playback-button stop" @click="$root.mk.stop()"
v-if="$root.mk.isPlaying && $root.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
<button class="playback-button pause" @click="$root.mk.pause()" v-else-if="$root.mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="$root.mk.play()" v-else :title="$root.getLz('term.play')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button next"
@click="skipToNextItem()"
:title="$root.getLz('term.next')"
:class="$root.isNextDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
</div>
@ -373,6 +240,7 @@
class="playback-button--small repeat"
v-if="mk.repeatMode == 0"
@click="mk.repeatMode = 1"
:class="$root.isDisabled() && 'disabled'"
:title="$root.getLz('term.enableRepeatOne')"
v-b-tooltip.hover
></button>
@ -381,6 +249,7 @@
@click="mk.repeatMode = 2"
v-else-if="mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
<button
@ -388,6 +257,7 @@
@click="mk.repeatMode = 0"
v-else-if="mk.repeatMode == 2"
:title="$root.getLz('term.disableRepeat')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
</div>

View file

@ -1,13 +1,15 @@
<script type="text/x-template" id="artist-chip">
<div class="artist-chip" @click.self="route" tabindex="0">
<div class="artist-chip__image">
<mediaitem-artwork v-if="artist.id != null" :url="artist.attributes.artwork.url" :size="32"></mediaitem-artwork>
<div class="artist-chip__image" v-if="image" :style="{backgroundColor: '#' + (artist.attributes.artwork?.bgColor ?? '000')}">
<mediaitem-artwork v-if="artist.id != null" :url="artist.attributes.artwork.url" :size="80"></mediaitem-artwork>
</div>
<div class="artist-chip__image" v-else>
</div>
<div class="artist-chip__name">
<span>{{ item.attributes.name }}</span>
</div>
<button @click="$root.followArtistById(artist.id, true)" title="Follow" v-if="!$root.followingArtist(artist.id)" class="artist-chip__follow codicon codicon-add"></button>
<button @click="$root.followArtistById(artist.id, false)" title="Following" v-else class="artist-chip__follow codicon codicon-check"></button>
<button @click="$root.setArtistFavorite(artist.id, true)" title="Follow" v-if="!$root.followingArtist(artist.id)" class="artist-chip__follow codicon codicon-add"></button>
<button @click="$root.setArtistFavorite(artist.id, false)" title="Following" v-else class="artist-chip__follow codicon codicon-check"></button>
</div>
</script>
@ -21,6 +23,7 @@
},
data: function() {
return {
image: false,
artist: {
id: null
}
@ -34,6 +37,7 @@
}
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artistId}`).then(response => {
this.artist = response.data.data[0];
this.image = true;
});
},
methods: {

View file

@ -1,6 +1,6 @@
<script type="text/x-template" id="artwork-material">
<div class="artworkMaterial">
<img :src="src" v-for="image in images"/>
<mediaitem-artwork :url="src" :size="500" v-for="image in images"/>
</div>
</script>

View file

@ -35,6 +35,16 @@
v-model="maxVolume"/>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.advanced')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.audio.advanced" switch/>
</label>
</div>
</div>
</div>
</div>
</div>

View file

@ -298,7 +298,7 @@
try {
for (var i = 0; i < 21; i++) {
CiderAudio.audioNodes.vibrantbassNode[i].gain.value = app.cfg.audio.maikiwiAudio.vibrantBass.gain[i] * (app.cfg.audio.equalizer.vibrantBass / 10);
} CiderAudio.intelliGainComp_h0_0();
} CiderAudio.intelliGainComp_n0_0();
}
catch (e) {
CiderAudio.hierarchical_loading();
@ -315,7 +315,7 @@
for (var i = 0; i < 10; i++) {
CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix
}
CiderAudio.intelliGainComp_h0_0();
CiderAudio.intelliGainComp_n0_0();
} catch (e) { CiderAudio.hierarchical_loading(); }
}
},
@ -323,7 +323,7 @@
if (Math.max(...app.cfg.audio.equalizer.gain) != 0) {
try {
CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix
CiderAudio.intelliGainComp_h0_0();
CiderAudio.intelliGainComp_n0_0();
}
catch (e) { CiderAudio.hierarchical_loading(); }
}
@ -413,7 +413,7 @@
CiderAudio.audioNodes.audioBands[i].frequency.value = app.cfg.audio.equalizer.frequencies[i]
CiderAudio.audioNodes.audioBands[i].Q.value = app.cfg.audio.equalizer.Q[i]
}
CiderAudio.intelliGainComp_h0_0();
CiderAudio.intelliGainComp_n0_0();
},
changePreset(id) {
let userPresets = app.cfg.audio.equalizer.presets

View file

@ -14,7 +14,9 @@
<div class="artwork" @click="app.fullscreen(false)">
<mediaitem-artwork
:size="600"
:url="(image ?? '').replace('{w}','600').replace('{h}','600') "
:video="video"
:videoPriority="true"
:url="(image ?? '').replace('{w}','600').replace('{h}','600')"
></mediaitem-artwork>
</div>
<div class="controls-parents">
@ -52,39 +54,40 @@
</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" :title="$root.getLz('term.enableShuffle')"
v-b-tooltip.hover></button>
<button class="playback-button--small shuffle active" v-else
@click="app.mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button class="playback-button previous" @click="app.prevButton()"
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button class="playback-button pause" @click="app.mk.pause()" v-if="app.mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="app.mk.play()" v-else
:title="$root.getLz('term.play')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button class="playback-button next" @click="app.skipToNextItem()"
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button class="playback-button--small repeat" v-if="app.mk.repeatMode == 0"
@click="app.mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')"
v-b-tooltip.hover></button>
<button class="playback-button--small repeat repeatOne" @click="app.mk.repeatMode = 2"
v-else-if="app.mk.repeatMode == 1" :title="$root.getLz('term.disableRepeatOne')"
v-b-tooltip.hover></button>
<button class="playback-button--small repeat active" @click="app.mk.repeatMode = 0"
v-else-if="app.mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button--small shuffle" v-if="$root.mk.shuffleMode == 0" :class="$root.isDisabled() && 'disabled'"
@click="$root.mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')" v-b-tooltip.hover></button>
<button class="playback-button--small shuffle active" v-else :class="$root.isDisabled() && 'disabled'"
@click="$root.mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button previous" @click="$root.prevButton()" :class="$root.isPrevDisabled() && 'disabled'"
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button stop" @click="$root.mk.stop()"
v-if="$root.mk.isPlaying && $root.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
<button class="playback-button pause" @click="$root.mk.pause()" v-else-if="$root.mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="$root.mk.play()" v-else :title="$root.getLz('term.play')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button next" @click="$root.skipToNextItem()" :class="$root.isNextDisabled() && 'disabled'"
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button--small repeat" v-if="$root.mk.repeatMode == 0" :class="$root.isDisabled() && 'disabled'"
@click="$root.mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
:class="$root.isDisabled() && 'disabled'" v-else-if="$root.mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat active" @click="$root.mk.repeatMode = 0"
:class="$root.isDisabled() && 'disabled'" v-else-if="$root.mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover></button>
</div>
</div>
</div>
<div class="app-chrome-item volume display--large">
<div class="input-container">
@ -149,6 +152,37 @@
return {
app: this.$root,
tabMode: "lyrics",
video: null
}
},
async mounted() {
if (app.mk.nowPlayingItem._container.type == "albums") {
try {
const result = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/${app.mk.nowPlayingItem._container.type}/${app.mk.nowPlayingItem._container.id}`, {
"fields": "editorialArtwork,editorialVideo",
})).data.data[0].attributes?.editorialVideo?.motionDetailSquare?.video
if (result) {
this.video = result
} else {
this.video = null
}
} catch (e) {
this.video = null
e = null
}
} else if (app.mk.nowPlayingItem._container.type == "library-albums") {
try {
const result = (await app.mk.api.v3.music(`/v1/me/library/albums/${app.mk.nowPlayingItem._container.id}/catalog`
, { "fields": "editorialArtwork,editorialVideo" })).data.data[0].attributes?.editorialVideo?.motionDetailSquare?.video
if (result) {
this.video = result
} else {
this.video = null
}
} catch (e) {
e = null
this.video = null
}
}
},
beforeMount() {

View file

@ -4,8 +4,7 @@
class="cd-mediaitem-list-item"
:class="{'mediaitem-selected': app.select_hasMediaItem(guid)}"
@contextmenu="contextMenu">
<template v-if="isVisible">
<div class="artwork" v-if="showArtwork == true">
<div class="artwork" v-show="isVisible" v-if="showArtwork == true">
<mediaitem-artwork
:url="getArtwork()"
size="50"
@ -17,7 +16,6 @@
{{ item.attributes.name }}
</div>
</div>
</template>
</div>
</script>

View file

@ -6,10 +6,10 @@
<h3>{{ recom.attributes.title ? recom.attributes.title.stringForDisplay : " "}}</h3>
</div>
<div class="col-auto flex-center" v-if="recom.relationships.contents.data.length >= 10">
<button class="cd-btn-seeall" @click="app.showCollection(recom.relationships.contents, recom.attributes.title ? recom.attributes.title.stringForDisplay : '', 'listen_now')" >{{app.getLz('term.seeAll')}}</button>
<button class="cd-btn-seeall" @click="showCollection(recom)" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<template v-if="recom.attributes.display.kind == 'MusicCoverShelf'">
<template v-if="recom.attributes.display.kind == 'MusicCoverShelf' || recom.attributes.display.kind == 'MusicCircleCoverShelf'">
<mediaitem-scroller-horizontal-large
:items="recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
@ -39,6 +39,10 @@
visibilityChanged: function (isVisible, entry) {
// this.isVisible = isVisible
},
showCollection: function (recom) {
console.debug(recom)
app.showCollection(recom.relationships.contents, recom.attributes.title ? recom.attributes.title.stringForDisplay : '', 'listen_now')
}
}
})
</script>

View file

@ -1,9 +1,10 @@
<script type="text/x-template" id="mediaitem-artwork">
<div class="mediaitem-artwork" @contextmenu="contextMenu" :class="[{'rounded': (type == 'artists')}, classes]" :key="url">
<div class="mediaitem-artwork" :style="awStyle" @contextmenu="contextMenu" :class="[{'rounded': (type == 'artists')}, classes]" :key="url">
<img :src="app.getMediaItemArtwork(url, size, width)"
decoding="async"
loading="lazy"
:style="{background: bgcolor}"
:style="imgStyle"
@load="imgLoaded()"
class="mediaitem-artwork--img">
<div v-if="video && getVideoPriority()" class="animatedartwork-view-box">
<animatedartwork-view :priority="getVideoPriority()" :video="video"></animatedartwork-view>
@ -50,11 +51,18 @@
},
data: function () {
return {
app:this.$root,
app: this.$root,
isVisible: false,
style: {
"box-shadow": ""
},
awStyle: {
background: this.bgcolor
},
imgStyle: {
opacity: 0,
transition: "opacity .25s linear"
},
classes: []
}
},
@ -62,6 +70,10 @@
this.getClasses()
},
methods: {
imgLoaded() {
this.imgStyle.opacity = 1
// this.awStyle.background = ""
},
contextMenu(event) {
let self = this
app.showMenuPanel({

View file

@ -16,7 +16,7 @@
@controller-click="route()"
tabindex="0"
:class="[{'mediaitem-selected': app.select_hasMediaItem(guid)}, addClasses]">
<template v-if="isVisible">
<div v-show="isVisible" class="listitem-content">
<div class="popular" v-if="!showInLibrary && item?.meta?.popularity != null && item?.meta?.popularity > 0.7"></div>
<div class="isLibrary" v-if="showLibraryStatus == true">
<div v-if="showInLibrary" :style="{display: (showInLibrary ? 'block' : 'none'), 'margin-left':'11px'}">
@ -87,7 +87,7 @@
<div class="duration" v-if="item.attributes.playCount" @dblclick="route()">
{{ item.attributes.playCount }}
</div>
</template>
</div>
</div>
</script>

View file

@ -8,8 +8,7 @@
<div v-if="reasonShown" class="reasonSP ">{{item?.meta?.reason?.stringForDisplay ?? ''}}</div>
<div style="{'--spcolor': getBgColor()}"
class="cd-mediaitem-square" :class="getClasses()" @contextmenu="getContextMenu">
<template>
<div class="artwork-container">
<div class="artwork-container" v-show="isVisible">
<div class="unavailable-overlay" v-if="unavailable">
<div class="codicon codicon-circle-slash"></div>
</div>
@ -40,6 +39,7 @@
</div>
</div>
<div class="info-rect" :class="{'info-rect-card': kind == 'card'}"
v-show="isVisible"
:style="{'--bgartwork': getArtworkUrl(size, true)}">
<div class="title"
:title="item.attributes?.name ?? (item.relationships?.contents?.data[0]?.attributes?.name ?? (item.attributes?.editorialNotes?.name ?? ''))"
@ -58,7 +58,6 @@
</div>
<div class="subtitle" v-if="getSubtitle() == '' && kind != 'card'">&nbsp;</div>
</div>
</template>
</div>
</div>
</script>
@ -88,6 +87,11 @@
default: false,
required: false
},
noScale: {
type: Boolean,
default: false,
required: false
},
'contextExt': { type: Object, required: false },
},
data: function () {
@ -252,6 +256,10 @@
},
getClasses() {
let type = []
let classes = []
if(this.noScale) {
classes.push("noscale")
}
try {
type = this.item.type
@ -264,25 +272,26 @@
}
switch (type) {
default:
return []
break;
case "editorial-elements":
case "card":
return ["mediaitem-card"]
classes.push("mediaitem-card")
break;
case "385": // editorial
return ["mediaitem-brick"]
classes.push("mediaitem-brick")
break;
case "small":
return ["mediaitem-small"]
classes.push("mediaitem-small")
break;
case "music-videos":
case "uploadedVideo":
case "uploaded-videos":
case "library-music-videos":
return "mediaitem-video";
classes.push("mediaitem-video")
break;
}
return classes
},
visibilityChanged: function (isVisible, entry) {
this.isVisible = isVisible
@ -534,19 +543,16 @@
let followActions = {
follow: {
icon: "./assets/feather/plus-circle.svg",
name: app.getLz('action.follow'),
name: app.getLz('action.favorite'),
action: () => {
self.app.cfg.home.followedArtists.push(this.item.id)
self.$root.setArtistFavorite(this.item.id, true)
}
},
unfollow: {
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.unfollow'),
name: app.getLz('action.removeFavorite'),
action: () => {
let index = self.app.cfg.home.followedArtists.indexOf(this.item.id)
if (index > -1) {
self.app.cfg.home.followedArtists.splice(index, 1)
}
self.$root.setArtistFavorite(this.item.id, false)
}
}
}

View file

@ -66,30 +66,40 @@
</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.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 repeatOne" @click="app.mk.repeatMode = 2"
v-else-if="app.mk.repeatMode == 1"></button>
<button class="playback-button--small repeat active" @click="app.mk.repeatMode = 0"
v-else-if="app.mk.repeatMode == 2"></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button--small shuffle" v-if="$root.mk.shuffleMode == 0" :class="$root.isDisabled() && 'disabled'"
@click="$root.mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')" v-b-tooltip.hover></button>
<button class="playback-button--small shuffle active" v-else :class="$root.isDisabled() && 'disabled'"
@click="$root.mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button previous" @click="$root.prevButton()" :class="$root.isPrevDisabled() && 'disabled'"
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button stop" @click="$root.mk.stop()"
v-if="$root.mk.isPlaying && $root.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
<button class="playback-button pause" @click="$root.mk.pause()" v-else-if="$root.mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="$root.mk.play()" v-else :title="$root.getLz('term.play')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button next" @click="$root.skipToNextItem()" :class="$root.isNextDisabled() && 'disabled'"
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item display--large">
<button class="playback-button--small repeat" v-if="$root.mk.repeatMode == 0" :class="$root.isDisabled() && 'disabled'"
@click="$root.mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
:class="$root.isDisabled() && 'disabled'" v-else-if="$root.mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat active" @click="$root.mk.repeatMode = 0"
:class="$root.isDisabled() && 'disabled'" v-else-if="$root.mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover></button>
</div>
</div>
</div>
<div class="app-chrome-item volume display--large">
<div class="input-container">

View file

@ -1,355 +0,0 @@
<script type="text/x-template" id="spatial-properties">
<div class="modal-fullscreen spatialproperties-panel" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window" v-if="ready">
<div class="modal-header">
<div class="modal-title">{{$root.getLz('spatial.spatialProperties')}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
</div>
<div class="modal-content">
<template v-if="roomEditType == 'dimensions'">
<div class="row">
<div class="col"><h3>{{$root.getLz('spatial.roomDimensions')}}</h3></div>
<div class="col-auto flex-center">
<button class="md-btn" @click="roomEditType = 'positions'">{{$root.getLz('spatial.setPositions')}}</button>
</div>
</div>
<div class="row">
<div class="col">
<div class="row">
<div class="col-3 flex-center">
{{$root.getLz('spatial.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">
{{$root.getLz('spatial.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">
{{$root.getLz('spatial.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">
{{$root.getLz('spatial.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>{{$root.getLz('spatial.roomPositions')}}</h3></div>
<div class="col-auto flex-center">
<button class="md-btn" @click="roomEditType = 'dimensions'">{{$root.getLz('spatial.setDimensions')}}</button>
</div>
</div>
<div class="row">
<div class="col">
<div class="row">
<div class="col-3 flex-center">
X ({{$root.getLz('spatial.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 ({{$root.getLz('spatial.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 ({{$root.getLz('spatial.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 ({{$root.getLz('spatial.audioSource')}})
</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 ({{$root.getLz('spatial.audioSource')}})
</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 ({{$root.getLz('spatial.audioSource')}})
</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>{{$root.getLz('spatial.roomMaterials')}}</h3></div>
</div>
<div class="row">
<div class="col"></div>
<div class="col flex-center">
<label>
{{$root.getLz('spatial.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>
{{$root.getLz('spatial.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>
{{$root.getLz('spatial.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>
{{$root.getLz('spatial.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>
{{$root.getLz('spatial.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>
{{$root.getLz('spatial.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>

View file

@ -58,6 +58,12 @@
#LOADER>svg {
width: 128px;
}
@media (prefers-color-scheme: light) {
#LOADER {
background-color: #eee;
}
}
</style>
</head>
@ -65,7 +71,7 @@
<div id="LOADER">
<%- include("../assets/cider-round.svg") %>
</div>
<div id="app" :class="getAppClasses()" :window-style="cfg.visual.directives.windowLayout">
<div id="app" :class="getAppClasses()" :style="getAppStyle()" :library-visbile="(chrome.sidebarCollapsed ? 0 : 1)" :window-style="cfg.visual.directives.windowLayout">
<transition name="fsModeSwitch">
<div id="app-main" v-show="appMode == 'player'">
<%- include('app/chrome-top'); %>
@ -85,6 +91,11 @@
</mini-view>
</div>
</transition>
<transition name="fsModeSwitch">
<div class="fullscreen-view-container oobe" v-if="appMode == 'oobe'">
<cider-oobe></cider-oobe>
</div>
</transition>
<%- include('app/panels'); %>
<div class="cursor" v-if="chrome.showCursor"></div>
</div>
@ -94,7 +105,8 @@
<% } %>
<script async src="https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js"></script>
<script async src="<%- (env.useV3 ? "https://js-cdn.music.apple.com/musickit/v3/amp/musickit.js" : "https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js" ) %>" data-web-components>
</script>
<script src="index.js?v=1"></script>
<script type="text/x-template" id="am-musiccovershelf">
@ -104,7 +116,7 @@
<!-- Sidebar Item -->
<script type="text/x-template" id="sidebar-library-item">
<button class="app-sidebar-item"
:class="$parent.getSidebarItemClass(page)" @click="$root.appRoute(page)">
:class="$parent.getSidebarItemClass(page)" @click="$root.setWindowHash(page)">
<div class="sidebar-icon" v-html="svgIconData" v-if="svgIconData != ''"></div>
{{ name }}
</button>

View file

@ -7,6 +7,10 @@
<div class="col nopadding">
<h3>{{app.getLz('home.followedArtists')}}</h3>
</div>
<div class="col-auto nopadding flex-center">
<button class="cd-btn-seeall" @click="syncFavorites()" v-if="!syncingFavs">{{app.getLz('home.syncFavorites')}}</button>
<div class="spinner" style="height: 26px;" v-else></div>
</div>
</div>
<vue-horizontal>
<div v-for="artist in artists" style="margin: 6px;">
@ -14,7 +18,7 @@
<button @click="unfollow(artist.id)" class="md-btn md-btn-glyph" style="display:flex;">
<div class="sidebar-icon">
<div class="svg-icon" :style="{'--url': 'url(./assets/feather/x-circle.svg)'}"></div>
</div> {{app.getLz('action.unfollow')}}
</div> {{app.getLz('action.removeFavorite')}}
</button>
</div>
</vue-horizontal>
@ -53,7 +57,8 @@
app: this.$root,
followedArtists: this.$root.cfg.home.followedArtists,
artistFeed: [],
artists: []
artists: [],
syncingFavs: false
}
},
async mounted() {
@ -61,7 +66,13 @@
await this.getArtistFeed()
},
methods: {
unfollow(id) {
async syncFavorites() {
this.syncingFavs = true
await app.syncFavorites()
await this.getArtistFeed()
this.syncingFavs = false
},
async unfollow(id) {
let index = this.followedArtists.indexOf(id)
if (index > -1) {
this.followedArtists.splice(index, 1)
@ -71,6 +82,16 @@
if (index2 > -1) {
this.artists.splice(index2, 1)
}
await app.mk.api.v3.music(`/v1/me/favorites`, {
"art[url]": "f",
"ids[artists]": id,
"l": app.mklang,
"platform": "web"
}, {
fetchOptions: {
method: "DELETE"
}
})
this.getArtistFeed()
},
async getArtistFeed() {
@ -78,7 +99,7 @@
let self = this
this.artists = []
this.artistFeed = []
// Apple limits the number of IDs we can provide in a single API call to 50.
// Divide it into groups of 50 and send parallel requests
let chunks = []

View file

@ -1,20 +1,22 @@
<script type="text/x-template" id="cider-artist">
<div class="content-inner artist-page" :class="[data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9) ? 'animated' : '']">
<div class="content-inner artist-page"
:class="[(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9) || hasHero()) ? 'animated' : '']">
<div class="artist-header" :key="data.id" v-observe-visibility="{callback: isHeaderVisible}">
<animatedartwork-view
:priority="true"
v-if="data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)"
v-if="hasAnimated()"
:video="data.attributes.editorialVideo.motionArtistWide16x9.video ?? (data.attributes.editorialVideo.motionArtistFullscreen16x9.video ?? '')">
</animatedartwork-view>
<div class="header-content" style="pointer-events: all;">
<div class="row">
<div class="col-sm" style="width: auto;">
<div class="artist-image" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
<div class="artist-image"
v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))&& !hasHero()">
<mediaitem-artwork
shadow="large"
:url="data.attributes.artwork ? data.attributes.artwork.url : ''"
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()
})" :aria-label="app.getLz('term.play')">
<%- include("../svg/play.svg") %>
@ -22,7 +24,7 @@
</div>
</div>
<div class="col flex-center artist-title"
:class="{'artist-animation-on': (data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) }"
:class="{'artist-animation-on': (data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) || hasHero() }"
>
<button class="artist-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
app.mk.play()
@ -30,15 +32,25 @@
<h1>{{ data.attributes.name }}</h1>
</div>
</div>
<button class="more-btn-round" @click="artistMenu" style="pointer-events: all;" :aria-label="app.getLz('term.more')">
<button class="more-btn-round favorite" @click="artistMenu" style="pointer-events: all;"
:aria-label="app.getLz('term.more')">
<div class="svg-icon"></div>
</button>
<button class="more-btn-round menu" @click="artistMenu" style="pointer-events: all;"
:aria-label="app.getLz('term.more')">
<div class="svg-icon"></div>
</button>
</div>
<div class="artworkContainer" v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9))">
<artwork-material :url="data.attributes.artwork.url" size="190" images="1"></artwork-material>
<div class="artworkContainer"
v-if="!(data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) && !hasHero()">
<artwork-material :url="data.attributes.artwork.url" size="190" images="1"></artwork-material>
</div>
<div class="artist-hero" v-if="hasHero() && !hasAnimated()">
<mediaitem-artwork shadow="none" :url="hasHero()" size="2048" />
</div>
</div>
<div class="floating-header" :style="{opacity: (headerVisible ? 0 : 1),'pointer-events': (headerVisible ? 'none' : '')}">
<div class="floating-header"
:style="{opacity: (headerVisible ? 0 : 1),'pointer-events': (headerVisible ? 'none' : '')}">
<div class="row">
<div class="col-auto flex-center">
<button class="artist-play" style="display:block;" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
@ -49,7 +61,7 @@
<h3>{{ data.attributes.name }}</h3>
</div>
<div class="col-auto flex-center">
<button class="more-btn-round" @click="artistMenu" :aria-label="app.getLz('term.more')">
<button class="more-btn-round menu" @click="artistMenu" :aria-label="app.getLz('term.more')">
<div class="svg-icon"></div>
</button>
</div>
@ -60,8 +72,8 @@
<div class="latestRelease" v-if="data.views['latest-release'].data.length != 0">
<h3>{{app.getLz('term.latestReleases')}}</h3>
<div style="width: auto;margin: 0 auto;">
<mediaitem-square kind="card" v-for="song in data.views['latest-release'].data"
:item="song">
<mediaitem-square kind="card" :no-scale="true" v-for="song in data.views['latest-release'].data"
:item="song">
</mediaitem-square>
</div>
</div>
@ -70,8 +82,12 @@
<div class="col" style="padding:0;">
<h3>{{app.getLz('term.topSongs')}}</h3>
</div>
<div class="col-auto flex-center" v-if="data.views['top-songs'].data.length >= 20" style="padding:0;">
<button class="cd-btn-seeall" @click="app.showArtistView(data.id, data.attributes.name + ' - Top Songs', 'top-songs')">{{app.getLz('term.seeAll')}}</button>
<div class="col-auto flex-center" v-if="data.views['top-songs'].data.length >= 20"
style="padding:0;">
<button class="cd-btn-seeall"
@click="app.showArtistView(data.id, data.attributes.name + ' - Top Songs', 'top-songs')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<div class="row">
@ -96,23 +112,27 @@
</h3>
</div>
<div class="col-auto flex-center" v-if="data.views[view].data.length >= 10">
<button class="cd-btn-seeall" @click="app.showArtistView(data.id, data.attributes.name + ' - ' + data.views[view].attributes.title, view)">{{app.getLz('term.seeAll')}}</button>
<button class="cd-btn-seeall"
@click="app.showArtistView(data.id, data.attributes.name + ' - ' + data.views[view].attributes.title, view)">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<template v-if="!((data.views[view].attributes.title ?
data.views[view].attributes.title : '???').includes('Video') || (data.views[view].attributes.title ?
data.views[view].attributes.title : '???').includes('More To See'))">
<mediaitem-scroller-horizontal-large :items="data.views[view].data.limit(10)">
</mediaitem-scroller-horizontal-large>
<mediaitem-scroller-horizontal-large :items="data.views[view].data.limit(10)">
</mediaitem-scroller-horizontal-large>
</template>
<template v-else>
<mediaitem-scroller-horizontal-mvview
:items="data.views[view].data.limit(10)"></mediaitem-scroller-horizontal-mvview>
:items="data.views[view].data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
</template>
<div class="row">
<div class="col" v-if="data.attributes.artistBio">
<h3>{{ $root.stringTemplateParser($root.getLz('term.aboutArtist'), {"artistName": data.attributes.name}) }}</h3>
<h3>{{ $root.stringTemplateParser($root.getLz('term.aboutArtist'), {"artistName":
data.attributes.name}) }}</h3>
<p v-html="data.attributes.artistBio"></p>
</div>
<div class="col">
@ -147,24 +167,40 @@
}
},
methods: {
hasAnimated() {
if(this.data.attributes?.editorialVideo && (this.data.attributes?.editorialVideo?.motionArtistWide16x9 || this.data.attributes?.editorialVideo?.motionArtistFullscreen16x9)) {
return true;
}
return false;
},
hasHero() {
if(this.data.attributes?.editorialArtwork?.centeredFullscreenBackground){
return this.data.attributes?.editorialArtwork?.centeredFullscreenBackground.url
} else if(this.data.attributes?.editorialArtwork?.bannerUber) {
return this.data.attributes?.editorialArtwork?.bannerUber.url
}else if(this.data.attributes?.editorialArtwork?.subscriptionHero){
return this.data.attributes?.editorialArtwork?.subscriptionHero.url
}
return false;
},
isHeaderVisible(visible) {
this.headerVisible = visible
},
artistMenu (event) {
async artistMenu(event) {
let self = this
let followAction = "follow"
let followActions = {
follow: {
icon: "./assets/feather/plus-circle.svg",
name: app.getLz('action.follow'),
action: ()=>{
action: () => {
self.app.cfg.home.followedArtists.push(self.data.id)
}
},
unfollow: {
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.unfollow'),
action: ()=>{
action: () => {
let index = self.app.cfg.home.followedArtists.indexOf(self.data.id)
if (index > -1) {
self.app.cfg.home.followedArtists.splice(index, 1)
@ -172,25 +208,45 @@
}
}
}
let favoriteActions = {
favorite: {
icon: "./assets/star.svg",
name: app.getLz('action.favorite'),
action: () => {
app.setArtistFavorite(app.artistPage.data.id, true)
}
},
removeFavorite: {
icon: "./assets/star.svg",
name: app.getLz('action.removeFavorite'),
action: () => {
app.setArtistFavorite(app.artistPage.data.id, false)
}
}
}
if (this.app.cfg.home.followedArtists.includes(self.data.id)) {
followAction = "unfollow"
}
const inFavorites = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${app.artistPage.data.id}`, {
"fields[artists]": "inFavorites"
})).data.data[0].attributes?.inFavorites
app.showMenuPanel({
items: [
{
icon: "./assets/feather/play.svg",
name: app.getLz('action.startRadio'),
action: ()=>{
app.mk.setStationQueue({artist:self.data.id}).then(()=>{
action: () => {
app.mk.setStationQueue({artist: self.data.id}).then(() => {
app.mk.play()
})
}
},
followActions[followAction],
favoriteActions[inFavorites ? "removeFavorite" : "favorite"],
// followActions[followAction],
{
icon: "./assets/feather/share.svg",
name: app.getLz('term.share'),
action: ()=>{
action: () => {
self.app.copyToClipboard(self.data.attributes.url)
}
}

View file

@ -35,10 +35,12 @@
<select class="md-select" style="width:180px;"
v-model="app.cfg.audio.maikiwiAudio.ciderPPE_value"
v-on:change="CiderAudio.hierarchical_loading()">
<option value="MAIKIWI">Maikiwi</option>
<option value="MAIKIWI">Maikiwi ({{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.adaptive')}})</option>
<option value="MAIKIWI_LEGACY">Maikiwi ({{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.legacy')}})</option>
<option value="NATURAL">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard')}}
</option>
<option value="LEGACY">{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.legacy')}}</option>
</select>
</div>
</div>
@ -100,7 +102,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.audio.maikiwiAudio.spatial"
v-on:change="toggleMaikiwiSpatial" switch/>
v-on:change="CiderAudio.hierarchical_loading();" switch/>
</div>
</div>
<div class="md-option-line"
@ -113,7 +115,7 @@
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:180px;"
v-model="$root.cfg.audio.maikiwiAudio.spatialProfile"
v-on:change="toggleMaikiwiSpatial">
v-on:change="CiderAudio.hierarchical_loading();">
<option v-for="profile in spprofiles" :value="profile.id">{{ getProfileLz("CTS", profile.name) }}</option>
</select>
</div>
@ -170,11 +172,10 @@
},
methods: {
getProfileLz(type, name) {
let result = "";
// Hard-coded shiz
switch (name) {
case "CRYPTO":
return "Cryptofyre";
break;
case "Maikiwi":
return "Maikiwi";
break;
@ -183,43 +184,33 @@
return "Maikiwi+";
break;
case "Minimal+":
return this.$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal') + "+";
break;
case "live":
return "LIVE";
break;
}
}
switch (type) {
case "CAR":
return this.$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.' + name);
result = this.$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.' + name);
if (result === "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode." + name) {
return name;
}
else {return result;}
break;
case "CTS":
return this.$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.' + name.toLowerCase());
result = this.$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.' + name.toLowerCase());
if (result === "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile." + name.toLowerCase()) {
return name;
}
else {return result;}
break;
default:
return name;
}
},
toggleSpatial: function () {
if (app.cfg.audio.maikiwiAudio.spatial) {
CiderAudio.spatialOn()
CiderAudio.hierarchical_loading();
} else {
CiderAudio.spatialOff()
}
},
toggleMaikiwiSpatial: function () {
if (app.cfg.audio.maikiwiAudio.spatial === true) {
CiderAudio.spatialOn()
CiderAudio.hierarchical_loading();
//let normalized = Math.pow(10, (((Math.log10(app.mk.volume) * 20) - 14) / 20));
//app.mk.volume = normalized
// -13dBFS Target
} else {
//let normalized = Math.pow(10, (((Math.log10(app.mk.volume) * 20) + 14) / 20));
//app.mk.volume = normalized
CiderAudio.spatialOn()
CiderAudio.hierarchical_loading();
}
},
}
}
})
</script>

View file

@ -0,0 +1,133 @@
<script type="text/x-template" id="cider-charts">
<div class="content-inner">
<h1 class="header-text">{{$root.getLz("term.charts")}}</h1>
<template v-if="songs != []">
<div class="row">
<div class="col">
<h3>{{ songs.name ?? ""}}</h3>
</div>
<div class="col-auto flex-center" v-if="songs.data.length > 12">
<button class="cd-btn-seeall" @click="app.showCollection((songs ?? []), songs.name ?? '', 'default')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="(songs?.data ?? []).limit(12)">
</listitem-horizontal>
</div>
</template>
<template v-if="albums != []">
<div class="row">
<div class="col">
<h3>{{ albums.name ?? ""}}</h3>
</div>
<div class="col-auto flex-center" v-if="songs.data.length > 12">
<button class="cd-btn-seeall" @click="app.showCollection((albums ?? []), albums.name ?? '', 'default')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="(albums?.data ?? []).limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="playlists != []">
<div class="row">
<div class="col">
<h3>{{ playlists.name ?? ""}}</h3>
</div>
<div class="col-auto flex-center" v-if="playlists.data.length > 12">
<button class="cd-btn-seeall" @click="app.showCollection((playlists ?? []), playlists.name ?? '', 'default')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="(playlists?.data ?? []).limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="musicvideos != []">
<div class="row">
<div class="col">
<h3>{{ musicvideos.name ?? ""}}</h3>
</div>
<div class="col-auto flex-center" v-if="musicvideos.data.length > 12">
<button class="cd-btn-seeall" @click="app.showCollection((musicvideos ?? []), musicvideos.name ?? '', 'default')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="(musicvideos?.data ?? []).limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="globalcharts != []">
<div class="row">
<div class="col">
<h3>{{ globalcharts.name ?? ""}}</h3>
</div>
<div class="col-auto flex-center" v-if="globalcharts.data.length > 12">
<button class="cd-btn-seeall" @click="app.showCollection((globalcharts ?? []), globalcharts.name ?? '', 'default')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="(globalcharts?.data ?? []).limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="citycharts != []">
<div class="row">
<div class="col">
<h3>{{ citycharts.name ?? ""}}</h3>
</div>
<div class="col-auto flex-center" v-if="citycharts.data.length > 12">
<button class="cd-btn-seeall" @click="app.showCollection((citycharts ?? []), citycharts.name ?? '', 'default')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="(citycharts?.data ?? []).limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</div>
</script>
<script>
Vue.component('cider-charts', {
template: "#cider-charts",
data: function () {
return {
app: this.$root,
songs: [],
albums: [],
playlists: [],
musicvideos: [],
citycharts: [],
globalcharts: [],
categories: [],
}
},
mounted() {
this.getData();
},
methods: {
getData() {
let self = this;
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/charts`, {
types: 'albums,songs,music-videos,playlists',
l: 'en-gb',
platform: 'auto',
limit: '50',
genre: '34',
include: 'tracks',
with: 'cityCharts,dailyGlobalTopCharts',
extend: 'artistUrl',
'fields[albums]': 'artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url',
'fields[playlists]': 'artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url,curatorName'
}).then(res => {
let page = res.data?.results ?? [];
self.songs = page.songs[0] ?? [];
self.albums = page.albums[0] ?? [];
self.playlists = page.playlists[0] ?? [];
self.musicvideos = page['music-videos'][0] ?? [];
self.citycharts = page.cityCharts[0] ?? [];
self.globalcharts = page.dailyGlobalTopCharts[0] ?? [];
})
// let self = this;
// app.mk.api.music(`/v1/catalog/${app.mk.storefrontId}/charts?types=songs%2Calbums%2Cplaylists&limit=36`).then(res => {
// let page = res.data?.results ?? [];
// self.songs = page.songs[0] ?? [];
// self.albums = page.albums[0] ?? [];
// self.playlists = page.playlists[0] ?? [];
// })
}
}
})
</script>

View file

@ -80,7 +80,7 @@
</button>
</div>
</template>
<div class="playlist-controls" v-observe-visibility="{callback: isHeaderVisible}">
<div class="playlist-controls" v-observe-visibility="{callback: isHeaderVisible}" style="z-index: 20;">
<button class="md-btn md-btn-primary md-btn-icon" style="min-width: 100px;"
@click="app.mk.shuffleMode = 0; play()"><img class="md-ico-play">
{{app.getLz('term.play')}}
@ -121,7 +121,7 @@
</div>
</div>
<div class="artworkContainer" v-if="data.attributes.artwork != null">
<artwork-material :url="data.attributes.artwork.url" size="260" images="1"></artwork-material>
<artwork-material :url="data.attributes.artwork.url" size="500" images="1"></artwork-material>
</div>
<button class="md-btn md-btn-small editTracksBtn" v-if="(data.attributes.canEdit && data.type == 'library-playlists')" @click="editing = !editing">
<span v-if="!editing">
@ -648,39 +648,11 @@
app.copyToClipboard(res.data.data[0].attributes.url)
})
}
},
"follow": {
name: app.getLz('action.follow'),
icon: "./assets/feather/plus-circle.svg",
hidden: false,
action: () => {
app.followArtistById(artistId, true)
}
},
"unfollow": {
name: app.getLz('action.unfollow'),
icon: "./assets/feather/x-circle.svg",
hidden: true,
action: () => {
app.followArtistById(artistId, false)
}
},
}
}
}
app.showMenuPanel(menuItems, event)
if (artistId != null) {
if (app.followingArtist(artistId)) {
menuItems.items.follow.hidden = true
menuItems.items.unfollow.hidden = false
} else {
menuItems.items.follow.hidden = false
menuItems.items.unfollow.hidden = true
}
} else {
menuItems.items.follow.hidden = true
menuItems.items.unfollow.hidden = true
}
try {
let rating = await app.getRating(self.data)
if (rating == 0) {

View file

@ -58,19 +58,33 @@
},
methods: {
getClasses() {
if(this.commonKind != "song") {
return "collection-list-square";
}else{
if ((this.data?.data?.length ?? 0) > 0) {
let item = this.data.data[0]
if (typeof item.kind != "undefined") {
this.commonKind = item.kind;
return item.kind
}
if (typeof item.attributes.playParams != "undefined") {
this.commonKind = item.attributes.playParams.kind
return item.attributes.playParams.kind
}
if (this.commonKind != "song") {
return "collection-list-square";
} else {
return "";
}
} else {
return "";
}
},
getKind(item) {
if (typeof item.kind != "undefined") {
this.commonKind = item.kind;
// this.commonKind = item.kind;
return item.kind
}
if (typeof item.attributes.playParams != "undefined") {
this.commonKind = item.attributes.playParams.kind
// this.commonKind = item.attributes.playParams.kind
return item.attributes.playParams.kind
}
return this.commonKind

View file

@ -61,18 +61,28 @@
};
},
async mounted() {
const queryDefaults = `?platform=web&l=en-us&extend=editorialArtwork%2CartistUrl&omit%5Bresource%3Aartists%5D=relationships&include[groupings]=curator&include[albums]=artists&include[songs]=artists&include[music-videos]=artists&fields%5Bartists%5D=name%2Curl%2Cartwork%2CeditorialArtwork%2CgenreNames%2CeditorialNotes`
const queryDefaults = {
"platform": "web",
"l" : this.$root.mklang,
"extend": "editorialArtwork,artistUrl",
"omit[resource:artists]": "relationships",
"include[groupings]": "curator",
"include[albums]": "artists",
"include[songs]": "artists",
"include[music-videos]": "artists",
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
}
const hash = window.location.hash;
// get everything after the first / character but keep everything afterwards
const query = hash.substring(hash.indexOf("/") + 1);
const query = hash.substring(hash.indexOf("/") + 1, hash.indexOf("&") > 0 ? hash.indexOf("&") : hash.length);
this.query = query;
if(!this.query.includes("?")) {
this.query += queryDefaults;
}
// if(!this.query.includes("?")) {
// this.query += queryDefaults;
// }
console.debug(query);
const result = await this.$root.mk.api.v3.music(
`/v1/editorial/${this.$root.mk.storefrontId}/groupings/${this.query}`
);
,!this.query.includes("&") ? queryDefaults : {"platform": "web"});
this.data = result.data.data[0];
console.log(this.data);

View file

@ -26,6 +26,8 @@
<h3>{{app.getLz('home.artistsFeed')}}</h3>
</div>
<div class="col-auto nopadding flex-center">
<button class="cd-btn-seeall" @click="syncFavorites()" v-if="!syncingFavs">{{app.getLz('home.syncFavorites')}}</button>
<div class="spinner" style="height: 26px;" v-else></div>
<button class="cd-btn-seeall" @click="app.appRoute('artist-feed')">{{app.getLz('term.seeAll')}}</button>
</div>
</div>
@ -113,7 +115,8 @@
page: "main",
sectionsReady: [],
year: new Date().getFullYear(),
seenReplay: localStorage.getItem('seenReplay')
seenReplay: localStorage.getItem('seenReplay'),
syncingFavs: false
}
},
async mounted() {
@ -128,6 +131,12 @@
}
},
methods: {
async syncFavorites() {
this.syncingFavs = true
await app.syncFavorites()
await this.getArtistFeed()
this.syncingFavs = false
},
async seeAllRecentlyPlayed() {
let hist = await app.mk.api.v3.music(`/v1/me/recent/played`, {
l: this.$root.mklang,
@ -188,7 +197,7 @@
async getArtistFeed() {
let artists = this.followedArtists
let self = this
this.artistFeed = []
let chunks = []
for (let artistIdx = 0; artistIdx < artists.length; artistIdx += 50) {
chunks.push(artists.slice(artistIdx, artistIdx + 50));

View file

@ -226,10 +226,10 @@
name: "Reduce Visuals",
file: "reduce_visuals.less"
})
themes.unshift({
name: "Inline Drawer",
file: "inline_drawer.less"
})
// themes.unshift({
// name: "Inline Drawer",
// file: "inline_drawer.less"
// })
themes.unshift({
name: "Dark",
file: "dark.less"

View file

@ -170,6 +170,11 @@
Vue.component('keybinds-settings', {
template: "#keybinds-settings",
props: [],
data: function () {
return {
app: this.$root
}
},
methods: {
keyBindUpdate: function (action) {
const blur = document.createElement('div');
@ -230,14 +235,14 @@
app.cfg.general.keybindings.browse = [app.platform == "darwin" ? "Command" : "Control", "B"];
app.cfg.general.keybindings.recentAdd = [app.platform == "darwin" ? "Command" : "Control", "G"];
app.cfg.general.keybindings.songs = [app.platform == "darwin" ? "Command" : "Control", "J"];
app.cfg.general.keybindings.albums = [app.platform == "darwin" ? "Command" : "Control", "S"];
app.cfg.general.keybindings.albums = [app.platform == "darwin" ? "Command" : "Control", "A"];
app.cfg.general.keybindings.artists = [app.platform == "darwin" ? "Command" : "Control", "D"];
app.cfg.general.keybindings.togglePrivateSession = [app.platform == "darwin" ? "Command" : "Control", "P"];
app.cfg.general.keybindings.webRemote = [app.platform == "darwin" ? "Command" : "Control", "W"];
app.cfg.general.keybindings.audioSettings = [app.platform == "darwin" ? "Option" : "Alt", "A"];
app.cfg.general.keybindings.pluginMenu = [app.platform == "darwin" ? "Option" : "Alt", "P"];
app.cfg.general.keybindings.castToDevices = [app.platform == "darwin" ? "Option" : "Alt", "C"];
app.cfg.general.keybindings.settings = [app.platform == "darwin" ? "Option" : "Alt", "S"];
app.cfg.general.keybindings.webRemote = [app.platform == "darwin" ? "Command" : "Control",app.platform == "darwin" ? "Option" : (app.platform == "linux" ? "Shift" : "Alt"), "W"];
app.cfg.general.keybindings.audioSettings = [app.platform == "darwin" ? "Command" : "Control",app.platform == "darwin" ? "Option" : (app.platform == "linux" ? "Shift" : "Alt"), "A"];
app.cfg.general.keybindings.pluginMenu = [app.platform == "darwin" ? "Command" : "Control",app.platform == "darwin" ? "Option" : (app.platform == "linux" ? "Shift" : "Alt"), "P"];
app.cfg.general.keybindings.castToDevices = [app.platform == "darwin" ? "Command" : "Control",app.platform == "darwin" ? "Option" : (app.platform == "linux" ? "Shift" : "Alt"), "C"];
app.cfg.general.keybindings.settings = [app.platform == "darwin" ? "Command" : "Control", ","];
app.cfg.general.keybindings.openDeveloperTools = [app.platform == "darwin" ? "Command" : "Control", app.platform == "darwin" ? "Option" : "Shift", "I"];
notyf.success(app.getLz('settings.notyf.general.keybindings.update.success'));
bootbox.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {

View file

@ -1,58 +1,70 @@
<template v-if="page == 'library-recentlyadded'">
<script type="text/x-template" id="cider-recentlyadded">
<div class="content-inner">
<div class="row">
<div class="col" style="padding:0;">
<h1 class="header-text">{{$root.getLz('term.recentlyAdded')}}</h1>
</div>
<div class="col-auto">
<button v-if="library.albums.downloadState == 2" @click="getLibraryAlbumsFull(true, 0)"
class="reload-btn" :aria-label="app.getLz('menubar.options.reload')"><%- include('../svg/redo.svg') %></button>
</div>
<h1 class="header-text">{{$root.getLz('term.recentlyAdded')}}</h1>
<div class="well itemContainer" v-if="itemSize == 'normal'">
<mediaitem-square v-for="item in items" :item="item"></mediaitem-square>
</div>
<div class="row">
<div class="col" style="padding:0;">
<div class="search-input-container" style="width:100%;margin: 16px 0;">
<div class="search-input--icon"></div>
<input type="search"
style="width:100%;"
spellcheck="false"
:placeholder="$root.getLz('term.search') + '...'"
@input="searchLibraryAlbums"
v-model="library.albums.search" class="search-input">
</div>
</div>
<div class="col-auto flex-center">
<div class="row">
<div class="col">
<select class="md-select" v-model="library.albums.sortOrder[0]"
@change="searchLibraryAlbums(0)">
<optgroup :label="$root.getLz('term.sortOrder')">
<option value="asc">{{$root.getLz('term.sortOrder.ascending')}}</option>
<option value="desc">{{$root.getLz('term.sortOrder.descending')}}</option>
</optgroup>
</select>
</div>
<div class="col">
<select class="md-select" v-model="library.albums.viewAs">
<optgroup :label="$root.getLz('term.viewAs')">
<option value="covers">{{$root.getLz('term.viewAs.coverArt')}}</option>
<option value="list">{{$root.getLz('term.viewAs.list')}}</option>
</optgroup>
</select>
</div>
</div>
</div>
<div class="well itemContainer" v-else="itemSize == 'compact'">
<mediaitem-list-item :show-meta-data="true" :show-library-status="false" v-for="item in items" :item="item"></mediaitem-list-item>
</div>
<div class="well">
<div class="albums-square-container">
<mediaitem-square v-if="library.albums.viewAs == 'covers'" :item="item"
v-for="item in library.albums.displayListing">
</mediaitem-square>
</div>
<mediaitem-list-item v-if="library.albums.viewAs == 'list'" :show-duration="false" :show-meta-data="true"
:show-library-status="false" :item="item"
v-for="item in library.albums.displayListing">
</mediaitem-list-item>
<div class="well itemContainer" v-show="loading">
<div class="spinner"></div>
</div>
<button v-if="nextUrl && !loading" style="opacity:0;height: 32px;" v-observe-visibility="{callback: visibilityChanged}">{{$root.getLz('term.showMore')}}
</button>
</div>
</template>
</script>
<script>
Vue.component("cider-recentlyadded", {
template: "#cider-recentlyadded",
computed: {
items() {
return this.$store.state.pageState['recentlyAdded'].items;
},
nextUrl() {
return this.$store.state.pageState['recentlyAdded'].nextUrl;
},
itemSize() {
return this.$store.state.pageState['recentlyAdded'].size
}
},
data: function () {
return {
loading: false,
firstRoute: `/v1/me/library/recently-added?l=${app.mklang}&platform=web&include[library-albums]=artists&include[library-artists]=catalog&fields[artists]=url&fields%5Balbums%5D=artistName%2CartistUrl%2Cartwork%2CcontentRating%2CeditorialArtwork%2Cname%2CplayParams%2CreleaseDate%2Curl&includeOnly=catalog%2Cartists&limit=25`
}
},
async mounted() {
if(this.$store.state.pageState['recentlyAdded'].items.length !== 0) return
const firstResult = await app.mk.api.v3.music(this.firstRoute)
this.$store.state.pageState["recentlyAdded"].items = firstResult.data.data
this.$store.state.pageState["recentlyAdded"].nextUrl = firstResult.data.next
},
beforeDestroy() {
// this.$store.state.pageState["recently-added"].scrollPosY = $("#app-content").scrollTop()
},
methods: {
visibilityChanged: function(isVisible, entry) {
if (isVisible && !this.loading) {
this.getNextData();
}
},
async getNextData() {
if (this.$store.state.pageState["recentlyAdded"].nextUrl) {
this.loading = true;
const nextResult = await app.mk.api.v3.music(this.$store.state.pageState["recentlyAdded"].nextUrl)
this.$store.state.pageState["recentlyAdded"].items = this.$store.state.pageState["recentlyAdded"].items.concat(nextResult.data.data)
if (nextResult.data.next) {
this.$store.state.pageState["recentlyAdded"].nextUrl = nextResult.data.next
} else {
this.$store.state.pageState["recentlyAdded"].nextUrl = null
}
this.loading = false;
}
return
}
}
});
</script>

View file

@ -0,0 +1,179 @@
<script type="text/x-template" id="cider-oobe">
<div class="content-inner oobe">
<!-- before_we_start-->
<!-- <transition name=""> -->
<div class="oobe-view" v-if="screen == 'before_we_start'">
<div class="oobe-header">
{{ getLz("oobe.amupsell.title") }}
</div>
<div class="oobe-body text">{{ getLz("oobe.amupsell.text") }}</div>
<div class="oobe-footer">
<div class="btn-group">
<div class="md-btn" @click="screen = 'welcome'">{{ getLz("oobe.next") }}</div>
</div>
</div>
</div>
<!-- </transition> -->
<!-- Welcome -->
<!-- <transition name=""> -->
<div class="oobe-view" v-if="screen == 'welcome'">
<div class="oobe-header">
{{ getLz("oobe.intro.title") }}
</div>
<div class="oobe-body text">{{ getLz("oobe.intro.text") }}</div>
<div class="oobe-footer">
<div class="btn-group">
<div class="md-btn" @click="screen = 'before_we_start'">{{ getLz("oobe.previous") }}</div>
<div class="md-btn" @click="screen = 'general'">{{ getLz("oobe.next") }}</div>
</div>
</div>
</div>
<!-- </transition> -->
<!-- General -->
<!-- <transition name=""> -->
<div class="oobe-view" v-if="screen == 'general'">
<div class="oobe-header">
{{ getLz("oobe.general.title") }}
</div>
<div class="oobe-body text">{{ getLz("oobe.general.text") }}</div>
<div class="oobe-footer">
<div class="btn-group">
<div class="md-btn" @click="screen = 'welcome'">{{ getLz("oobe.previous") }}</div>
<div class="md-btn" @click="screen = 'visual'">{{ getLz("oobe.next") }}</div>
</div>
</div>
</div>
<!-- </transition> -->
<!-- Visual -->
<!-- <transition name=""> -->
<div class="oobe-view" v-if="screen == 'visual'">
<div class="oobe-header">
{{ getLz("oobe.visual.title") }}
</div>
<div class="oobe-body visual">
<b-row>
<b-col>
<div class="card bg-dark text-white stylePicker" @click="$root.cfg.visual.directives.windowLayout = 'twopanel'" :class="{'style-active': ($root.cfg.visual.directives.windowLayout == 'twopanel')}">
<div class="card-body">
<img class="visualPreview" src="./assets/oobe/mojave.png" alt="TEMP">
</div>
<div class="card-footer">
Mojave
</div>
</div>
</b-col>
<b-col>
<div class="card bg-dark text-white stylePicker" @click="$root.cfg.visual.directives.windowLayout = 'default'" :class="{'style-active': ($root.cfg.visual.directives.windowLayout == 'default')}">
<div class="card-body">
<img class="visualPreview" src="./assets/oobe/maverick.png" alt="TEMP">
</div>
<div class="card-footer">
Maverick
</div>
</div>
</b-col>
</b-row>
<div class="blurb">{{getLz("oobe.visual.layout.text")}}</div>
</div>
<div class="oobe-footer">
<div class="btn-group">
<div class="md-btn" @click="screen = 'general'">{{ getLz("oobe.previous") }}</div>
<div class="md-btn" @click="screen = 'audio'">{{ getLz("oobe.next") }}</div>
</div>
</div>
</div>
<!-- </transition> -->
<!-- Audio -->
<!-- <transition name=""> -->
<div class="oobe-view" v-if="screen == 'audio'">
<div class="oobe-header">
{{ getLz("oobe.audio.title") }}
</div>
<div class="oobe-body">
<div class="blurb">{{ getLz("oobe.audio.text") }}</div>
<div class="md-option-container">
<div class="settings-option-body">
<div class="md-option-line">
<div class="md-option-segment">
{{getLz('settings.option.audio.enableAdvancedFunctionality')}}
<br>
<small>{{getLz('settings.option.audio.enableAdvancedFunctionality.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="$root.cfg.advanced.AudioContext"
switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="$root.cfg.advanced.AudioContext === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')}}
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="$root.cfg.audio.maikiwiAudio.ciderPPE"
switch/>
</div>
</div>
</div>
</div>
</div>
<div class="oobe-footer">
<div class="btn-group">
<div class="md-btn" @click="screen = 'visual'">{{ getLz("oobe.previous") }}</div>
<div class="md-btn" @click="signIn()">{{ getLz("oobe.next") }}</div>
</div>
</div>
</div>
<!-- </transition> -->
<div class="oobe-view" v-if="screen == 'signin'">
<div class="oobe-header">
Sign in with Apple Music
</div>
<div class="oobe-body">
<div class="blurb"></div>
</div>
<div class="oobe-footer">
</div>
</div>
<div class="oobe-titlebar">
<div class="button-group" v-if="$root.platform !== 'darwin'">
<button class="min" @click="$root.ipcRenderer.send('minimize')"></button>
<button class="close" @click="$root.ipcRenderer.send('close')"></button>
</div>
</div>
</div>
</script>
<script>
Vue.component('cider-oobe', {
template: '#cider-oobe',
data: function () {
return {
screen: "before_we_start"
}
},
async mounted() {
},
methods: {
signIn() {
if (localStorage.getItem("music.ampwebplay.media-user-token")) {
localStorage.setItem("seenOOBE", 1)
window.location.reload()
}
this.screen = "signin"
capiInit()
},
getLz() {
return this.$root.getLz.apply(this.$root, arguments);
}
}
});
</script>

View file

@ -539,7 +539,7 @@
icon: "./assets/feather/plus-circle.svg",
hidden: false,
action: () => {
app.followArtistById(artistId, true)
app.setArtistFavorite(artistId, true)
}
},
"unfollow": {
@ -547,7 +547,7 @@
icon: "./assets/feather/x-circle.svg",
hidden: true,
action: () => {
app.followArtistById(artistId, false)
app.setArtistFavorite(artistId, false)
}
},
}

View file

@ -176,7 +176,7 @@
redirect: 'follow'
};
fetch("https://api.github.com/search/repositories?q=topic:cidermusicplugin fork:true", requestOptions)
fetch("https://api.github.com/search/repositories?q=topic:cidermusicplugin fork:true&per_page=100", requestOptions)
.then(response => response.text())
.then(result => {
self.repos = JSON.parse(result).items
@ -185,4 +185,4 @@
}
}
})
</script>
</script>

View file

@ -68,7 +68,7 @@
<transition name="wpfade">
<div class="podcasts-details" v-if="selected.id != -1">
<div class="podcasts-details-header">
<button class="close-btn" @click="selected.id = -1" :aria-label="app.getLz('action.close')"></button>
<button class="close-btn" @click="selected.id = -1" :aria-label="$root.getLz('action.close')"></button>
</div>
<div class="podcast-artwork">
<mediaitem-artwork shadow="large" :url="selected.attributes.artwork.url" size="300"></mediaitem-artwork>

View file

@ -28,7 +28,7 @@
<h4>{{ loaded.attributes.uniqueSongCount }} {{$root.getLz('term.uniqueSongs')}}</h4>
</div>
<div class="col-auto replay-playlist-container">
<mediaitem-square kind="card" :force-video="true" :item="loaded.playlist"></mediaitem-square>
<mediaitem-square kind="card" :no-scale="true" :force-video="true" :item="loaded.playlist"></mediaitem-square>
</div>
</div>
<!-- Top Artists-->

View file

@ -1,82 +1,105 @@
<script type="text/x-template" id="cider-search">
<div class="content-inner search-page">
<div class="btn-group searchToggle">
<button
@click="searchType = 'catalog'"
class="md-btn md-btn-small" :class="{'md-btn-primary': searchType == 'catalog'}">{{ $root.getLz("term.appleMusic") }}</button>
<button
@click="searchType = 'library';"
class="md-btn md-btn-small" :class="{'md-btn-primary': searchType == 'library'}">{{ $root.getLz("term.library") }}</button>
</div>
<div v-if="search != null && search != [] && search.term != ''">
<h3>{{app.getLz('term.topResult')}}</h3>
<mediaitem-scroller-horizontal
:items="search.results[search.results.meta.results.order[0]]['data']"></mediaitem-scroller-horizontal>
<div class="row">
<div v-else style="text-align: center">
<h3>{{app.getLz('error.noResults')}}</h3>
<p>{{app.getLz('error.noResults.description')}}</p>
</div>
<div class="col" v-if="search.results.song">
<div class="row">
<div class="col">
<h3>{{app.getLz('term.songs')}}</h3>
<template v-if="searchType == 'catalog'">
<h3>{{app.getLz('term.topResult')}}</h3>
<mediaitem-scroller-horizontal
:items="search.results[search.results.meta.results.order[0]]['data']"></mediaitem-scroller-horizontal>
<div class="row">
<div v-else style="text-align: center">
<h3>{{app.getLz('error.noResults')}}</h3>
<p>{{app.getLz('error.noResults.description')}}</p>
</div>
<div class="col" v-if="search.results.song">
<div class="row">
<div class="col">
<h3>{{app.getLz('term.songs')}}</h3>
</div>
<div class="col-auto flex-center"
@click="app.showSearchView(app.search.term, 'song', app.friendlyTypes('song'))"
v-if="search.results.song.data.length >= 12">
<button class="cd-btn-seeall">{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<div class="col-auto flex-center"
@click="app.showSearchView(app.search.term, 'song', app.friendlyTypes('song'))"
v-if="search.results.song.data.length >= 12">
<button class="cd-btn-seeall">{{app.getLz('term.seeAll')}}</button>
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="search.results.song.data.limit(12)">
</listitem-horizontal>
</div>
</div>
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="search.results.song.data.limit(12)">
</listitem-horizontal>
</div>
</div>
</div>
<template v-if="search.results['meta'] != null">
<template
v-for="section in search.results.meta.results.order" v-if="section != 'song' && section != 'top'">
<template v-if="search.results['meta'] != null">
<template
v-for="section in search.results.meta.results.order" v-if="section != 'song' && section != 'top'">
<div class="row">
<div class="col">
<h3>{{ app.friendlyTypes(section) }}</h3>
</div>
<div class="col-auto flex-center" v-if="search.results[section].data.length >= 10">
<button class="cd-btn-seeall"
@click="app.showSearchView(app.search.term, section, app.friendlyTypes(section))">{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<template v-if="!app.friendlyTypes(section).includes('Video')">
<mediaitem-scroller-horizontal-large
:items="search.results[section].data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-else>
<mediaitem-scroller-horizontal-mvview
:items="search.results[section].data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
</template>
</template>
<template v-if="search.resultsSocial.playlist">
<div class="row">
<div class="col">
<h3>{{ app.friendlyTypes(section) }}</h3>
<h3>{{app.getLz('term.sharedPlaylists')}}</h3>
</div>
<div class="col-auto flex-center" v-if="search.results[section].data.length >= 10">
<div class="col-auto flex-center" v-if="search.resultsSocial.playlist.data.length >= 10">
<button class="cd-btn-seeall"
@click="app.showSearchView(app.search.term, section, app.friendlyTypes(section))">{{app.getLz('term.seeAll')}}
@click="app.showCollection(search.resultsSocial.playlist, 'Shared Playlists', 'default')">{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<template v-if="!app.friendlyTypes(section).includes('Video')">
<mediaitem-scroller-horizontal-large
:items="search.results[section].data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-else>
<mediaitem-scroller-horizontal-mvview
:items="search.results[section].data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<mediaitem-scroller-horizontal-large
:items="search.resultsSocial.playlist.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="search.resultsSocial.profile">
<div class="row">
<div class="col">
<h3>{{app.getLz('term.people')}}</h3>
</div>
<div class="col-auto flex-center" v-if="search.resultsSocial.profile.data.length >= 10">
<button class="cd-btn-seeall"
@click="app.showCollection(search.resultsSocial.profile, 'People', 'default')">{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="search.resultsSocial.profile.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
<template v-if="search.resultsSocial.playlist">
<div class="row">
<div class="col">
<h3>{{app.getLz('term.sharedPlaylists')}}</h3>
<template v-else>
<h1>{{ $root.getLz("term.library") }}</h1>
<div v-for="(section, key) in $root.search.resultsLibrary">
<h3>{{app.friendlyTypes(key)}}</h3>
<div class="mediaitem-list-item__grid" v-if="key.includes('songs')">
<listitem-horizontal :items="section.data"></listitem-horizontal>
</div>
<div class="col-auto flex-center" v-if="search.resultsSocial.playlist.data.length >= 10">
<button class="cd-btn-seeall"
@click="app.showCollection(search.resultsSocial.playlist, 'Shared Playlists', 'default')">{{app.getLz('term.seeAll')}}
</button>
<div class="well" v-else>
<mediaitem-scroller-horizontal-large
:items="section.data"></mediaitem-scroller-horizontal-large>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="search.resultsSocial.playlist.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="search.resultsSocial.profile">
<div class="row">
<div class="col">
<h3>{{app.getLz('term.people')}}</h3>
</div>
<div class="col-auto flex-center" v-if="search.resultsSocial.profile.data.length >= 10">
<button class="cd-btn-seeall"
@click="app.showCollection(search.resultsSocial.profile, 'People', 'default')">{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<mediaitem-scroller-horizontal-large
:items="search.resultsSocial.profile.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</div>
<div v-else>
@ -111,6 +134,7 @@
recentlyPlayed: [],
categoriesView: [],
categoriesReady: false,
searchType: "catalog",
}
},
methods: {

View file

@ -96,7 +96,8 @@
<option value="listen_now">{{$root.getLz('term.listenNow')}}</option>
<option value="browse">{{$root.getLz('term.browse')}}</option>
<option value="radio">{{$root.getLz('term.radio')}}</option>
<option value="library-recentlyadded">{{$root.getLz('term.recentlyAdded')}}</option>
<option value="library-recentlyadded">{{$root.getLz('term.recentlyAdded')}}
</option>
<option value="library-songs">{{$root.getLz('term.songs')}}</option>
<option value="library-albums">{{$root.getLz('term.albums')}}</option>
<option value="library-artists">{{$root.getLz('term.artists')}}</option>
@ -124,7 +125,8 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.sidebarItems.recentlyAdded"
<input type="checkbox"
v-model="app.cfg.general.sidebarItems.recentlyAdded"
switch/>
</label>
</div>
@ -135,7 +137,8 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.sidebarItems.songs" switch/>
<input type="checkbox" v-model="app.cfg.general.sidebarItems.songs"
switch/>
</label>
</div>
</div>
@ -185,17 +188,17 @@
</div>
</div>
</b-modal>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.general.keybindings')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="app.appRoute('keybinds-settings')" >
<button class="md-btn" @click="app.appRoute('keybinds-settings')">
{{$root.getLz('settings.option.general.keybindings.open')}}
</button>
</div>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.general.themeUpdateNotification')}}
@ -206,7 +209,7 @@
switch/>
</label>
</div>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.general.showLovedTracksInline')}}
@ -217,7 +220,7 @@
switch/>
</label>
</div>
</div>
</div>
</div>
</div>
</b-tab>
@ -241,7 +244,8 @@
<option value="HIGH">{{$root.getLz('settings.header.audio.quality.high')}}
({{$root.getLz('settings.header.audio.quality.high.description')}})
</option>
<option value="STANDARD">{{$root.getLz('settings.header.audio.quality.standard')}}
<option value="STANDARD">
{{$root.getLz('settings.header.audio.quality.standard')}}
({{$root.getLz('settings.header.audio.quality.standard.description')}})
</option>
</select>
@ -329,12 +333,13 @@
<label>
<input type="checkbox" v-model="app.cfg.audio.normalization"
v-on:change="toggleNormalization"
:disabled="app.cfg.audio.spatial === true || app.cfg.audio.maikiwiAudio.spatial === true || app.cfg.audio.maikiwiAudio.ciderPPE === true || app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true || app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true"
:disabled="app.cfg.audio.maikiwiAudio.spatial === true || app.cfg.audio.maikiwiAudio.ciderPPE === true || app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true || app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true"
switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.advanced.AudioContext && app.cfg.audio.normalization">
<div class="md-option-line"
v-show="app.cfg.advanced.AudioContext && app.cfg.audio.normalization">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.dbspl.display')}}
<br>
@ -408,13 +413,131 @@
<option value="image">
{{$root.getLz('settings.header.visual.windowBackgroundStyle.image')}}
</option>
<option value="mica">
<option value="color">
{{$root.getLz('settings.header.visual.windowBackgroundStyle.color')}}
</option>
<option v-if="$root.platform == 'win32'" value="mica">
Mica (Beta)
</option>
</select>
</label>
</div>
</div>
<div class="md-option-line child" v-if="app.cfg.visual.window_background_style == 'color'">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.windowColor')}}
</div>
<div class="md-option-segment_auto">
<input type="color" v-model="app.cfg.visual.windowColor"/>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.customAccentColor')}}
</div>
<div class="md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.visual.customAccentColor" :disabled="app.cfg.visual.purplePodcastPlaybackBar" switch/>
</div>
</div>
<div class="md-option-line child" v-if="app.cfg.visual.customAccentColor">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.accentColor')}}
</div>
<div class="md-option-segment_auto">
<input type="color" v-model="app.cfg.visual.accentColor"/>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.purplePodcastPlaybackBar')}}
</div>
<div class="md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.visual.purplePodcastPlaybackBar" :disabled="app.cfg.visual.customAccentColor" switch/>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.hardwareAcceleration')}}<br>
<small>({{$root.getLz('settings.option.visual.hardwareAcceleration.description')}})</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" style="width:180px;"
v-model="app.cfg.visual.hw_acceleration" @change="promptForRelaunch()">
<option value="default">
{{$root.getLz('settings.header.visual.hardwareAcceleration.default')}}
</option>
<option value="webgpu">
{{$root.getLz('settings.header.visual.hardwareAcceleration.webGPU')}}
</option>
<option value="disabled">{{$root.getLz('term.disabled')}}</option>
</select>
</label>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.showPersonalInfo')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.visual.showuserinfo"
v-on:change="toggleUserInfo"
switch/>
</label>
</div>
</div>
</div>
<!-- Window Settings -->
<div class="md-option-header">
<span>{{$root.getLz('settings.header.window')}}</span>
</div>
<div class="settings-option-body">
<div class="md-option-line" v-show="app.platform !== 'darwin'">
<div class="md-option-segment">
{{$root.getLz("settings.option.window.close_button_hide")}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.close_button_hide" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.platform !== 'darwin'">
<div class="md-option-segment">
{{$root.getLz("settings.option.window.useNativeTitleBar")}}<br>
<small>({{$root.getLz("settings.option.visual.hardwareAcceleration.description")}})</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.visual.nativeTitleBar" switch
@change="promptForRelaunch()"/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.platform !== 'darwin'">
<div class="md-option-segment">
{{$root.getLz("settings.option.window.windowControlStyle")}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" v-model="app.cfg.visual.windowControlPosition">
<option value="0">
{{$root.getLz("settings.option.window.windowControlStyle.right")}}
</option>
<option value="1">
{{$root.getLz("settings.option.window.windowControlStyle.left")}}
</option>
</select>
</label>
</div>
</div>
</div>
<!-- Advanced Visual -->
<div class="md-option-header">
<span>{{$root.getLz('settings.header.advanced')}}</span>
</div>
<div class="settings-option-body">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.animatedArtwork')}}
@ -483,83 +606,8 @@
</label>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.hardwareAcceleration')}}<br>
<small>({{$root.getLz('settings.option.visual.hardwareAcceleration.description')}})</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" style="width:180px;" v-model="app.cfg.visual.hw_acceleration" @change="promptForRelaunch()">
<option value="default">
{{$root.getLz('settings.header.visual.hardwareAcceleration.default')}}
</option>
<option value="webgpu">
{{$root.getLz('settings.header.visual.hardwareAcceleration.webGPU')}}
</option>
<option value="disabled">{{$root.getLz('term.disabled')}}</option>
</select>
</label>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.showPersonalInfo')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.visual.showuserinfo"
v-on:change="toggleUserInfo"
switch/>
</label>
</div>
</div>
</div>
<!-- Window Settings -->
<div class="md-option-header">
<span>{{$root.getLz('settings.header.window')}}</span>
</div>
<div class="settings-option-body">
<div class="md-option-line" v-show="app.platform !== 'darwin'">
<div class="md-option-segment">
{{$root.getLz("settings.option.window.close_button_hide")}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.close_button_hide" switch/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.platform !== 'darwin'">
<div class="md-option-segment">
{{$root.getLz("settings.option.window.useNativeTitleBar")}}<br>
<small>({{$root.getLz("settings.option.visual.hardwareAcceleration.description")}})</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.visual.nativeTitleBar" switch
@change="promptForRelaunch()"/>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.platform !== 'darwin'">
<div class="md-option-segment">
{{$root.getLz("settings.option.window.windowControlStyle")}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" v-model="app.cfg.visual.windowControlPosition">
<option value="0">
{{$root.getLz("settings.option.window.windowControlStyle.right")}}
</option>
<option value="1">
{{$root.getLz("settings.option.window.windowControlStyle.left")}}
</option>
</select>
</label>
</div>
</div>
</div>
</div>
</b-tab>
<b-tab :title="$root.getLz('settings.header.lyrics')">
@ -998,6 +1046,17 @@
</div>
</div>
<div class="md-option-line" v-show="app.cfg.general.discordrpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.reload')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="reloadDiscordRPC()">
{{$root.getLz('menubar.options.reload')}}
</button>
</div>
</div>
<!-- LastFM -->
<div class="md-option-line">
<div class="md-option-segment">
@ -1005,7 +1064,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" id="lfmConnect" ref="lfmConnect"
onclick="app.LastFMAuthenticate()">
@click="app.LastFMAuthenticate()">
{{$root.getLz('term.connect')}}
</button>
</div>
@ -1036,7 +1095,8 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lastfm.enabledRemoveFeaturingArtists" switch/>
<input type="checkbox" v-model="app.cfg.lastfm.enabledRemoveFeaturingArtists"
switch/>
</label>
</div>
</div>
@ -1125,6 +1185,20 @@
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
Use MusicKit V3
<small>Requires relaunch</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.advanced.experiments.includes('ampv3')"
@click="app.cfg.advanced.experiments.includes('ampv3') ? removeExperiment('ampv3') : addExperiment('ampv3')"
switch/>
</label>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.advanced.playlistTrackMapping')}}
@ -1138,28 +1212,16 @@
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
Collapsable Sidebar
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.advanced.experiments.includes('collapseSidebar')"
@click="app.cfg.advanced.experiments.includes('collapseSidebar') ? removeExperiment('collapseSidebar') : addExperiment('collapseSidebar')"
switch/>
</label>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.experimental.compactUI')}}
<small v-if="!!app.getThemeDirective('forceUI')">{{$root.getLz('term.themeManaged')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.advanced.experiments.includes('compactui')"
@click="app.cfg.advanced.experiments.includes('compactui') ? removeExperiment('compactui') : addExperiment('compactui')"
switch/>
switch :disabled="!!app.getThemeDirective('forceUI')"/>
</label>
</div>
</div>
@ -1187,7 +1249,8 @@
<select class="md-select" @change="$root.setLz('');$root.setLzManual()"
v-model="app.cfg.general.language">
<optgroup :label="index" v-for="(categories, index) in getLanguages()">
<option v-for="lang in categories" :value="lang.code">{{lang.nameNative}} ({{
<option v-for="lang in categories" :value="lang.code">{{lang.nameNative}}
({{
lang.nameEnglish }})
</option>
</optgroup>
@ -1211,9 +1274,10 @@
</div>
</b-tab>
<!-- Connect Settings -->
<!-- Not Prod Ready
<b-tab :title="$root.getLz('settings.header.connect')">
<div class="md-option-container">
<!-- Cider Connect / Linking Settings -->
<!!!!!-- Cider Connect / Linking Settings -!->
<div class="md-option-header">
<span>{{$root.getLz('settings.header.connect')}}</span>
</div>
@ -1295,6 +1359,7 @@
</div>
</div>
</b-tab>
-->
</b-tabs>
</div>
</script>
@ -1393,8 +1458,7 @@
if (app.cfg.audio.normalization === true) {
CiderAudio.normalizerOn()
}
if (app.cfg.audio.spatial === true) {
CiderAudio.spatialOn()
if (app.cfg.audio.maikiwiAudio.spatial === true) {
CiderAudio.hierarchical_loading();
}
}
@ -1404,8 +1468,7 @@
if (app.cfg.audio.normalization === true) {
CiderAudio.normalizerOn()
}
if (app.cfg.audio.spatial === true) {
CiderAudio.spatialOn()
if (app.cfg.audio.maikiwiAudio.spatial === true) {
CiderAudio.hierarchical_loading();
}
}
@ -1421,7 +1484,6 @@
}
},
changeAudioQuality: function () {
1
app.mk.bitrate = MusicKit.PlaybackBitrate[app.cfg.audio.quality];
},
toggleUserInfo: function () {
@ -1455,6 +1517,9 @@
logoutCC() {
ipcRenderer.send('cc-logout')
},
reloadDiscordRPC() {
ipcRenderer.send('reloadRPC')
}
}
})
</script>

View file

@ -184,7 +184,7 @@
redirect: 'follow'
};
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true", requestOptions)
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true&per_page=100", requestOptions)
.then(response => response.text())
.then(result => {
let items = JSON.parse(result).items
@ -194,4 +194,4 @@
}
}
})
</script>
</script>

View file

@ -9,6 +9,20 @@
v-if="artistLoaded"
:item="artist"
></artist-chip>
<amp-chrome-player/>
<!-- <amp-footer-player/> -->
<hr>
<amp-lcd-progress/>
<hr>
<amp-playback-controls-shuffle/>
<apple-music-playback-controls theme="dark" />
<apple-music-progress theme="dark"></apple-music-progress>
<apple-music-volume theme="dark"></apple-music-volume>
<amp-user-menu/>
<amp-tv-overlay/>
<amp-podcast-playback-controls/>
<amp-lcd/>
</div>
</script>
<script>

View 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-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>

After

Width:  |  Height:  |  Size: 388 B