Merge branch 'develop' of https://github.com/Apple-Music-Electron/Cider into develop

This commit is contained in:
cryptofyre 2022-02-17 16:10:43 -06:00
commit 90b289e3df
17 changed files with 392 additions and 84 deletions

View file

@ -122,3 +122,9 @@ Update 16/02/2022 21:45 UTC
* `term.audioControls`: Added for `en_US`.
* `settings.option.audio.volumeStep`: Added for `en_US`.
* `settings.option.audio.maxVolume`: Added for `en_US`.`
Update 17/02/2022 10:00 UTC
+ `settings.header.debug`: Added for `en_US`.
+ `settings.option.debug.copy_log`: Replaces `settings.option.experimental.copy_log`.
+ `settings.option.debug.openAppData`: Added for `en_US`
+ `action.open`: Added for `en_US`

View file

@ -231,7 +231,7 @@
"action.copy": "Copy",
"action.newpreset": "New Preset...", // Equalizer Preset
"action.deletepreset": "Delete Preset",
"action.open": "Open",
// Settings - General
"settings.header.general": "General",
"settings.header.general.description": "Adjust the general settings for Cider.",
@ -340,12 +340,18 @@
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.fm)",
// Refer to term.connect for the connect button
// Settings - Debug
"settings.header.debug": "Debug",
"settings.option.debug.copy_log": "Copy logs to clipboard",
"settings.option.debug.openAppData": "Open Cider Folder",
// Settings - Experimental
"settings.header.experimental": "Experimental",
"settings.header.experimental.description": "Adjust the experimental settings for Cider.",
"settings.option.experimental.compactUI": "Compact UI", // Toggle
"settings.option.experimental.close_button_hide": "Close Button Should Hide the Application",
"settings.option.experimental.copy_log": "Copy logs to clipboard",
"settings.option.experimental.inline_playlists": "Inline Playlists and Albums",
// Refer to term.disabled & term.enabled

View file

@ -47,6 +47,7 @@ export class BrowserWindow {
"pages/library-videos",
"pages/remote-pair",
"pages/themes-github",
"pages/replay",
"components/mediaitem-artwork",
"components/artwork-material",
"components/menu-panel",
@ -125,6 +126,16 @@ export class BrowserWindow {
this.options.width = windowState.width;
this.options.height = windowState.height;
switch(process.platform) {
default:
break;
case "linux":
this.options.autoHideMenuBar = true
this.options.frame = true
break;
}
// Start the webserver for the browser window to load
this.startWebServer();
@ -699,7 +710,9 @@ export class BrowserWindow {
ipcMain.on('get-version', (_event) => {
_event.returnValue = app.getVersion()
});
ipcMain.on('open-appdata', (_event) => {
shell.openPath(app.getPath('userData'));
});
/* *********************************************************************************************
* Window Events
* **********************************************************************************************/

View file

@ -27,7 +27,7 @@ export class Store {
},
"audio": {
"volume": 1,
"volumeStep": 0.1,
"volumeStep": 0.02,
"maxVolume": 1,
"lastVolume": 1,
"muted": false,

View file

@ -32,7 +32,7 @@ const MusicKitInterop = {
}
// LastFM's Custom Call
// await MusicKitInterop.modifyNamesOnLocale();
await MusicKitInterop.modifyNamesOnLocale();
if (trackFilter || !app.cfg.lastfm.filterLoop) {
global.ipcRenderer.send('nowPlayingItemDidChangeLastFM', attributes);
}

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

View file

@ -1354,8 +1354,15 @@ const app = new Vue({
}
},
async getNowPlayingItemDetailed(target) {
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"], {"include[songs]": "albums,artists", l : app.mklang});
app.searchAndNavigate(u.data.data[0], target)
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"],
{ "include[songs]": "albums,artists", l: app.mklang });
app.searchAndNavigate(u.data.data[0], target)
} catch (e) {
app.searchAndNavigate(app.mk.nowPlayingItem, target)
}
},
async searchAndNavigate(item, target) {
let self = this
@ -3037,7 +3044,7 @@ const app = new Vue({
type += "s"
}
type = type.replace("library-", "")
let id = item.attributes.playParams.catalogId ?? item.id
let id = item.attributes.playParams.catalogId ?? item.attributes.playParams.id ?? item.id
let index = types.findIndex(function (type) {
return type.type == this

View file

@ -0,0 +1,6 @@
// Linux
body[platform="linux"] {
#window-controls-container {
display: none;
}
}

View file

@ -819,3 +819,133 @@
margin: 16px;
}
}
//Home
.home-page {
top: 0;
padding-top: var(--navbarHeight);
.md-btn-replay {
background: var(--replayGradient);
border: 0px;
box-shadow: inset 0px 0px 0px 1px rgba(200, 200, 200, 0.2);
text-transform: uppercase;
font-weight: bold;
}
.md-btn-replay--hero {
font-size: 1em;
padding: 16px;
background: var(--replayGradient);
border: 0px;
box-shadow: inset 0px 0px 0px 1px rgb(200 200 200 / 20%);
margin-top: 1em;
font-size: 0.9em;
text-transform: uppercase;
font-weight: bold;
}
.artist-feed-card {
position: absolute;
bottom: 0;
left: 10%;
z-index: 1;
background: black;
width: 80%;
height: 96%;
overflow: scroll;
border-radius: 10px;
}
.col.madeforyou-col {
width: 420px;
min-width: 0px;
max-width: 420px;
}
.well.artistfeed-well {
margin-top: 0px;
height: 392px;
align-content: flex-start;
}
.hint-text {
font-size: 0.9rem;
color: rgb(200 200 200 / 70%);
}
.user-icon {
border-radius: 100%;
width: 128px;
height: 128px;
overflow: hidden;
box-shadow: var(--mediaItemShadow-Shadow);
margin: 16px;
}
.well.profile-well {
flex-direction: column;
justify-content: center;
align-items: center;
.name {
margin: 4px;
font-weight: 500;
}
.handle {
margin: 4px;
opacity: 0.7;
font-weight: 500;
}
}
}
// Replay
.replay-page {
--replayTextShadow: 0px 3px 2px #6f3f52;
.cd-mediaitem-square {
.mediaitem-artwork {
animation: replayFadeIn .5s var(--appleEase);
}
transition: transform .2s var(--appleEase);
transition-delay: .1s;
&:hover {
transform: scale(1.1);
transition-delay: 0s;
}
}
@keyframes replayFadeIn {
0% {
//border-radius: 100%;
transform: translateY(10px) scale(0.9);
opacity: 0;
}
100% {
//border-radius: var(--mediaItemRadius);
transform: scale(1);
opacity: 1;
}
}
.replay-viewport {
background: var(--replayGradient);
padding: 16px 40px;
border-radius: 10px;
box-shadow: var(--mediaItemShadow), var(--mediaItemShadow-Shadow);
color: rgb(238 238 238 / 86%);
.replay-header {
text-align: center;
font-size: 3em;
text-shadow: var(--replayTextShadow);
}
}
.replay-card {
background: transparent;
border:0px;
}
}

View file

@ -38,6 +38,7 @@
--songProgressColor: var(--keyColor);
--songProgressBackground: #333;
--textColor: #eee;
--replayGradient: linear-gradient(45deg, hsl(248deg 58% 29%), hsl(13deg 41% 42%));
}
html,
@ -1085,6 +1086,7 @@ body[platform="darwin"] .app-chrome .app-chrome-item > .window-controls > div.cl
max-width: 500px;
border-left: 1px solid rgb(200 200 200 / 8%);
border-right: 1px solid rgb(200 200 200 / 8%);
-webkit-app-region: drag;
}
.app-chrome .app-chrome-item > .app-playback-controls {
@ -1092,6 +1094,7 @@ body[platform="darwin"] .app-chrome .app-chrome-item > .window-controls > div.cl
justify-content: center;
align-content: center;
width: 100%;
-webkit-app-region: no-drag;
}
.app-chrome .app-chrome-item > .app-playback-controls {
@ -1220,6 +1223,7 @@ body[platform="darwin"] .app-chrome .app-chrome-item > .window-controls > div.cl
height: 4px;
background-color: rgb(200 200 200 / 10%);
border-radius: 2px;
margin: 0;
&::-webkit-slider-thumb {
opacity: 0;
@ -1645,66 +1649,6 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
height: 100%;
}
.home-page {
top: 0;
padding-top: var(--navbarHeight);
.artist-feed-card {
position: absolute;
bottom: 0;
left: 10%;
z-index: 1;
background: black;
width: 80%;
height: 96%;
overflow: scroll;
border-radius: 10px;
}
.col.madeforyou-col {
width: 420px;
min-width: 0px;
max-width: 420px;
}
.well.artistfeed-well {
margin-top: 0px;
height: 392px;
align-content: flex-start;
}
.hint-text {
font-size: 0.9rem;
color: rgb(200 200 200 / 70%);
}
.user-icon {
border-radius: 100%;
width: 128px;
height: 128px;
overflow: hidden;
box-shadow: var(--mediaItemShadow-Shadow);
margin: 16px;
}
.well.profile-well {
flex-direction: column;
justify-content: center;
align-items: center;
.name {
margin: 4px;
font-weight: 500;
}
.handle {
margin: 4px;
opacity: 0.7;
font-weight: 500;
}
}
}
/* Cider */
.more-btn-round {
@ -2756,6 +2700,17 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb {
/* Transitions */
.replaycard-enter-active,
.replaycard-leave-active {
transition: opacity .5s var(--appleEase), transform .5s var(--appleEase);
}
.replaycard-enter,
.replaycard-leave-to {
opacity: 0;
transform: translateY(20px);
}
.modal-enter-active,
.modal-leave-active {
transition: opacity .1s var(--appleEase), transform .1s var(--appleEase);
@ -3155,4 +3110,5 @@ body[platform='darwin'] {
}
@import url("less/linux.less");
@import url("less/compact.less");

View file

@ -172,4 +172,11 @@
</template>
</transition>
<!-- Replay -->
<transition name="wpfade">
<template v-if="page == 'replay'">
<replay-page></replay-page>
</template>
</transition>
</div>

View file

@ -3,7 +3,7 @@
<vue-horizontal>
<div v-for="items in itemPages">
<mediaitem-list-item
v-for="(song, index) in items" :parent="'listitem-hr' + simplifiedParent"
v-for="(song, index) in items" :show-library-status="showLibraryStatus" :parent="'listitem-hr' + simplifiedParent"
:index="song.index"
:item="song"></mediaitem-list-item>
</div>
@ -19,6 +19,10 @@
items: {
type: Array,
required: true
},
showLibraryStatus: {
type: Boolean,
default: true
}
},
data: function () {

View file

@ -74,6 +74,9 @@
<div class="duration" v-if="displayDuration" @dblclick="route()">
{{ msToMinSec(item.attributes.durationInMillis ?? 0) }}
</div>
<div class="duration" v-if="item.attributes.playCount" @dblclick="route()">
{{ item.attributes.playCount }}
</div>
</template>
</div>
</script>

View file

@ -49,9 +49,21 @@
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class="row" v-if="!seenReplay">
<div class="col">
<button class="md-btn md-btn-block md-btn-replay--hero" @click="$root.appRoute('replay')">Replay {{ year }}</button>
</div>
</div>
<div class="row">
<div class="col">
<h3>{{app.getLz('home.madeForYou')}}</h3>
<div class="row">
<div class="col nopadding">
<h3>{{app.getLz('home.madeForYou')}}</h3>
</div>
<div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-replay" v-if="seenReplay" @click="$root.appRoute('replay')">Replay {{ year }}</button>
</div>
</div>
<div class="well">
<mediaitem-scroller-horizontal :items="madeForYou" v-if="isSectionReady('madeForYou')">
</mediaitem-scroller-horizontal>
@ -98,7 +110,9 @@
artistFeed: [],
showingArtistFeed: false,
page: "main",
sectionsReady: []
sectionsReady: [],
year: new Date().getFullYear(),
seenReplay: localStorage.getItem('seenReplay')
}
},
async mounted() {
@ -106,6 +120,10 @@
this.getListenNowData()
await this.getArtistFeed()
await this.getFavorites()
if (new Date().getMonth() == 11) {
this.seenReplay = false
localStorage.setItem('seenReplay', false)
}
},
methods: {
async seeAllHistory() {

View file

@ -17,6 +17,14 @@
</div>
<div class="col-auto flex-center">
<div class="row">
<button class="col md-btn md-btn-primary md-btn-icon" style="min-width: 100px;margin-right: 3px;"
@click="app.mk.shuffleMode = 0; play()"> <img class="md-ico-play">
{{app.getLz('term.play')}}
</button>
<button class="col md-btn md-btn-primary md-btn-icon" style="min-width: 100px;margin-right: 3px;"
@click="app.mk.shuffleMode = 1;play()"> <img class="md-ico-shuffle">
{{app.getLz('term.shuffle')}}
</button>
<div class="col">
<select class="md-select" v-model="prefs.sort" @change="$root.searchLibrarySongs()">
<optgroup :label="app.getLz('term.sortBy')">
@ -75,6 +83,28 @@
methods: {
sayHello: function () {
alert('Hello world!');
},
play: function () {
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
let query = this.app.library.songs.displayListing.map(item => new MusicKit.MediaItem(item));
if (!app.mk.queue.isEmpty)
app.mk.queue.splice(0, app.mk.queue._itemIDs.length);
app.mk.stop().then(() => {
if (app.mk.shuffleMode == 1) {
shuffleArray(query)
}
app.mk.queue.append(query)
app.mk.changeToMediaAtIndex(0)
});
}
}
});

View file

@ -0,0 +1,98 @@
<script type="text/x-template" id="replay-page">
<div class="content-inner replay-page">
<h1 class="header-text">Replay</h1>
<button v-for="year in years" class="md-btn md-btn-primary" @click="getReplayYear(year.attributes.year)">{{ year.attributes.year }}</button>
<button class="md-btn md-btn-primary" @click="getReplayYear()">This Year</button>
<hr>
<transition name="replaycard">
<div class="replay-viewport" v-if="loaded.id != -1">
<!-- Stats -->
<h1 class="replay-header">{{ loaded.attributes.year }} Replay</h1>
<hr>
<div class="row">
<div class="col">
<h4>{{ loaded.attributes.listenTimeInMinutes }} minutes</h4>
<h4>{{ loaded.attributes.uniqueAlbumCount }} Unique Albums</h4>
<h4>{{ loaded.attributes.uniqueArtistCount }} Unique Artists</h4>
<h4>{{ loaded.attributes.uniqueSongCount }} Unique Songs</h4>
</div>
<div class="col-auto">
<mediaitem-square kind="card" :item="loaded.relationships.playlist.data[0]"></mediaitem-square>
</div>
</div>
<!-- Top Artists-->
<h3>Top Artists</h3>
<div class="well">
<mediaitem-scroller-horizontal>
<div class="card replay-card" v-for="artistData in loaded.views['top-artists'].data">
<div class="card-body">
<mediaitem-square :item="artistData.relationships.artist.data[0]"></mediaitem-square>
</div>
<div class="card-footer">
{{ artistData.attributes.listenTimeInMinutes }} minutes<br>
Listened to: {{ artistData.attributes.playCount }} times
</div>
</div>
</mediaitem-scroller-horizontal>
</div>
<!-- Top Albums-->
<h3>Top Albums</h3>
<div class="well">
<mediaitem-scroller-horizontal>
<div class="card replay-card" v-for="albumData in loaded.views['top-albums'].data">
<div class="card-body">
<mediaitem-square :item="albumData.relationships.album.data[0]"></mediaitem-square>
</div>
<div class="card-footer">
{{ albumData.attributes.listenTimeInMinutes }} minutes<br>
{{ albumData.attributes.playCount }} plays
</div>
</div>
</mediaitem-scroller-horizontal>
</div>
<!-- Top Songs-->
<h3>Top Songs</h3>
<div class="well">
<listitem-horizontal :show-library-status="false" :items="songsToArray(loaded.views['top-songs'].data)"></listitem-horizontal>
</div>
</div>
</transition>
</div>
</script>
<script>
Vue.component('replay-page', {
template: '#replay-page',
data: function () {
return {
years: [],
loaded: {
id: -1
}
}
},
async mounted() {
// Get available years
let year = await app.mk.api.v3.music("/v1/me/music-summaries/search?extend=inLibrary&period=year&fields[music-summaries]=period%2Cyear&include[music-summaries]=playlist")
this.years = year.data.data
localStorage.setItem("seenReplay", true)
this.getReplayYear();
},
methods: {
songsToArray(songsData) {
let songs = []
songsData.forEach(function (songData) {
let song = songData.relationships.song.data[0]
song.attributes.playCount = songData.attributes.playCount
songs.push(song)
})
return songs
},
async getReplayYear(year = new Date().getFullYear()) {
this.loaded.id = -1
let response = await app.mk.api.v3.music(`/v1/me/music-summaries/year-${year}?extend=inLibrary&views=top-artists%2Ctop-albums%2Ctop-songs&include[music-summaries]=playlist&include[playlists]=tracks&includeOnly=playlist%2Ctracks%2Csong%2Cartist%2Calbum`)
console.warn(response.data.data[0])
this.loaded = response.data.data[0]
}
}
});
</script>

View file

@ -620,6 +620,36 @@
</div>
</div>
</div>
<div class="md-option-header">
<span>{{$root.getLz('settings.header.debug')}}</span>
</div>
<div class="settings-option-body">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.debug.copy_log')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="copyLogs">
{{$root.getLz('action.copy')}}
</button>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.debug.openAppData')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="openAppData">
{{$root.getLz('action.open')}}
</button>
</div>
</div>
</div>
<div class="md-option-header">
<span>{{$root.getLz('settings.header.experimental')}}</span>
</div>
@ -692,16 +722,6 @@
</select>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.experimental.copy_log')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="copyLogs">
{{$root.getLz('action.copy')}}
</button>
</div>
</div>
</div>
<div style="opacity: 0.5; pointer-events: none">
<div class="md-option-header">
@ -791,6 +811,9 @@
ipcRenderer.send('fetch-log')
notyf.success(app.getLz('term.share.success'));
},
openAppData() {
ipcRenderer.send('open-appdata')
},
getLanguages: function () {
let langs = this.$root.lzListing
let categories = {