CHONKY BOY

This commit is contained in:
Core 2022-08-04 05:27:29 +01:00
parent 31ed921a1a
commit c15f55d0ee
No known key found for this signature in database
GPG key ID: FE9BF1B547F8F3C6
213 changed files with 64188 additions and 55736 deletions

View file

@ -1,136 +1,136 @@
<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="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"
>
<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">{{
<span class="usermenu-item-name">{{
$root.getLz("term.disablePrivateSession")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('remote-pair')">
</button>
<button class="usermenu-item" @click="appRoute('remote-pair')">
<span class="usermenu-item-icon">
<%- include("../svg/smartphone.svg") %>
</span>
<span class="usermenu-item-name">{{
<span class="usermenu-item-name">{{
$root.getLz("action.showWebRemoteQR")
}}</span>
</button>
<button
class="usermenu-item"
@click="modals.castMenu = true"
>
</button>
<button
class="usermenu-item"
@click="modals.castMenu = true"
>
<span class="usermenu-item-icon">
<%- include("../svg/cast.svg") %>
</span>
<span class="usermenu-item-name">{{
<span class="usermenu-item-name">{{
$root.getLz("term.cast")
}}</span>
</button>
<button
class="usermenu-item"
@click="modals.audioSettings = true"
>
</button>
<button
class="usermenu-item"
@click="modals.audioSettings = true"
>
<span class="usermenu-item-icon">
<%- include("../svg/headphones.svg") %>
</span>
<span class="usermenu-item-name">{{
<span class="usermenu-item-name">{{
$root.getLz("term.audioSettings")
}}</span>
</button>
<button
class="usermenu-item"
v-if="pluginInstalled"
@click="modals.pluginMenu = true"
>
</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">{{
<span class="usermenu-item-name">{{
$root.getLz("term.plugin")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('about')">
</button>
<button class="usermenu-item" @click="appRoute('about')">
<span class="usermenu-item-icon">
<%- include("../svg/info.svg") %>
</span>
<span class="usermenu-item-name">{{
<span class="usermenu-item-name">{{
$root.getLz("term.about")
}}</span>
</button>
<button class="usermenu-item" @click="modals.settings = true">
</button>
<button class="usermenu-item" @click="modals.settings = true">
<span class="usermenu-item-icon">
<%- include("../svg/settings.svg") %>
</span>
<span class="usermenu-item-name">{{
<span class="usermenu-item-name">{{
$root.getLz("term.settings")
}}</span>
</button>
<button class="usermenu-item" @click="unauthorize()">
</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">{{
<span class="usermenu-item-name">{{
$root.getLz("term.logout")
}}</span>
</button>
<button class="usermenu-item" @click="quit()">
</button>
<button class="usermenu-item" @click="quit()">
<span class="usermenu-item-icon" style="right: 2.5px">
<%- include("../svg/x.svg") %>
</span>
<span class="usermenu-item-name">{{
<span class="usermenu-item-name">{{
$root.getLz("term.quit")
}}</span>
</button>
</button>
</div>
</div>
</div>
</transition>
<transition name="sidebartransition">
<cider-app-sidebar v-if="!chrome.sidebarCollapsed"></cider-app-sidebar>
@ -141,9 +141,12 @@
v-if="drawer.open && drawer.panel == 'lyrics' && lyrics && lyrics != [] && lyrics.length > 0">
<div class="bgArtworkMaterial">
<div class="bg-artwork-container">
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork a" :src="$store.state.artwork.playerLCD">
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork b" :src="$store.state.artwork.playerLCD">
<img v-if="!(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork no-animation" :src="$store.state.artwork.playerLCD">
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork a"
:src="$store.state.artwork.playerLCD">
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork b"
:src="$store.state.artwork.playerLCD">
<img v-if="!(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork no-animation"
:src="$store.state.artwork.playerLCD">
</div>
</div>
<lyrics-view v-if="drawer.panel == 'lyrics'" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"

View file

@ -1,4 +1,5 @@
<div class="app-chrome chrome-bottom" v-if="getThemeDirective('windowLayout') == 'twopanel'" :style="{'display': chrome.topChromeVisible ? '' : 'none'}">
<div class="app-chrome chrome-bottom" v-if="getThemeDirective('windowLayout') == 'twopanel'"
:style="{'display': chrome.topChromeVisible ? '' : 'none'}">
<div class="app-chrome--left">
<div class="app-chrome-item playback-controls">
<template v-if="mkReady()">
@ -16,15 +17,23 @@
<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>
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">{{
mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
<div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
{{(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)">{{ $root.getLz("term.miniplayer") }}</button>
<button class="md-btn md-btn-small" style="width: 100%;" @click="drawer.open = false; fullscreen(true)">{{ $root.getLz("term.fullscreenView") }}</button>
<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)">{{
$root.getLz("term.fullscreenView") }}
</button>
</div>
</div>
</b-popover>
@ -39,20 +48,26 @@
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
<div class="song-album" @click="getNowPlayingItemDetailed('album')" v-if='mk.nowPlayingItem["attributes"]["albumName"]'>
<div class="song-album" @click="getNowPlayingItemDetailed('album')"
v-if='mk.nowPlayingItem["attributes"]["albumName"]'>
{{(mk.nowPlayingItem["attributes"]["albumName"]) ?
(mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
</div>
<div class="chrome-icon-container">
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div>
<div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'" v-b-tooltip.hover
<div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'"
v-b-tooltip.hover
></div>
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover
></div>
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover
<div class="audio-type lossless-icon"
v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container"
v-b-tooltip.hover
></div>
<div class="audio-type ppe-icon"
v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')"
v-b-tooltip.hover
></div>
</div>
</div>
@ -75,7 +90,7 @@
</div>
<div class="playback-info">
<div class="song-name">
</div>
</div>
@ -86,8 +101,9 @@
</div>
<div class="app-chrome--center">
<div class="app-chrome-playback-duration-bottom">
<b-row v-if="mkReady()">
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ 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()"
@ -96,13 +112,16 @@
@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" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ 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">
<div class="app-chrome-item">
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0" :class="isDisabled() && 'disabled'"
<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'"
@ -114,7 +133,8 @@
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button class="playback-button stop" @click="mk.stop()" v-if="mk.isPlaying && mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
<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>
@ -126,13 +146,16 @@
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0" :class="isDisabled() && 'disabled'"
<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'"
<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'"
<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>
@ -149,27 +172,27 @@
v-b-tooltip.hover :title="formatVolumeTooltip()">
</div>
<div class="app-chrome-item generic">
<button class="playback-button--small cast"
<button class="playback-button--small cast"
:title="$root.getLz('term.cast')"
v-b-tooltip.hover
@click="modals.castMenu = true"></button>
</div>
<div class="app-chrome-item generic">
<button class="playback-button--small queue" :class="{'active': drawer.panel == 'queue'}"
<button class="playback-button--small queue" :class="{'active': drawer.panel == 'queue'}"
:title="$root.getLz('term.queue')"
v-b-tooltip.hover
@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"
<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"
<button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
</template>

View file

@ -1,286 +1,313 @@
<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="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" 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)"
@contextmenu="mainMenuVisibility(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="app-chrome-item">
<button class="playback-button navigation" @click="navigateBack()" :title="$root.getLz('term.navigateBack')"
v-b-tooltip.hover>
<svg-icon url="./views/svg/chevron-left.svg"></svg-icon>
</button>
</div>
<div class="app-chrome-item">
<button class="playback-button navigation" @click="navigateForward()"
:title="$root.getLz('term.navigateForward')" v-b-tooltip.hover>
<svg-icon url="./views/svg/chevron-right.svg"></svg-icon>
</button>
</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>
<div class="app-chrome--left">
<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" 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)"
@contextmenu="mainMenuVisibility(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="app-chrome-item">
<button class="playback-button navigation" @click="navigateBack()"
:title="$root.getLz('term.navigateBack')"
v-b-tooltip.hover>
<svg-icon url="./views/svg/chevron-left.svg"></svg-icon>
</button>
</div>
<div class="app-chrome-item">
<button class="playback-button navigation" @click="navigateForward()"
:title="$root.getLz('term.navigateForward')" v-b-tooltip.hover>
<svg-icon url="./views/svg/chevron-right.svg"></svg-icon>
</button>
</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 playback-control-buttons">
<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>
</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>
</div>
<div class="app-chrome-item display--large">
<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>
</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>
</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>
</div>
</div>
</template>
</div>
<div class="app-chrome--center">
<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="artwork" id="artworkLCD">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="bottom">
<div class="content">
<div class="shadow-artwork">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="popover-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`)">
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
<div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
{{
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)">
{{ $root.getLz("term.miniplayer") }}
</button>
<button class="md-btn md-btn-small" style="width: 100%" @click="drawer.open = false; fullscreen(true)">
{{ $root.getLz("term.fullscreenView") }}
</button>
</div>
</div>
</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 spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'" v-b-tooltip.hover
></div>
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover
></div>
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover
></div>
<div class="vdiv display--large" v-if="getThemeDirective('windowLayout') != 'twopanel'"></div>
</template>
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div class="app-chrome-item playback-control-buttons">
<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>
</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>
</div>
<div class="app-chrome-item display--large">
<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>
</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>
</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>
</div>
</div>
<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="
</template>
</div>
<div class="app-chrome--center">
<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="artwork" id="artworkLCD">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="bottom">
<div class="content">
<div class="shadow-artwork">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div>
<div class="popover-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`)">
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
<div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
{{
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)">
{{ $root.getLz("term.miniplayer") }}
</button>
<button class="md-btn md-btn-small" style="width: 100%"
@click="drawer.open = false; fullscreen(true)">
{{ $root.getLz("term.fullscreenView") }}
</button>
</div>
</div>
</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 spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'"
v-b-tooltip.hover
></div>
<div class="audio-type lossless-icon"
v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container"
v-b-tooltip.hover
></div>
<div class="audio-type ppe-icon"
v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')"
v-b-tooltip.hover
></div>
</div>
<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"]
: ""
}}
</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'} ]">
<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="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()" />
</div>
</div>
{{
mk.nowPlayingItem["attributes"]["albumName"]
? mk.nowPlayingItem["attributes"]["albumName"]
: ""
}}
</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>
<div class="svg-icon"></div>
</button>
</div>
</template>
</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'} ]">
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p>
<p style="width: auto">
{{ convertTime(mk.currentPlaybackDuration) }}
</p>
</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">
<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()" />
</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>
<div class="svg-icon"></div>
</button>
</div>
</template>
</div>
</div>
</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 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"
svg-icon-name="home" page="home">
</sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg"
svg-icon-name="listenNow"
page="listen_now"></sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg"
svg-icon-name="browse" page="browse">
</sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg"
svg-icon-name="radio" page="radio">
</sidebar-library-item>
</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" svg-icon-name="home" page="home">
</sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow"
page="listen_now"></sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse">
</sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" 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()"
: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()" />
</div>
<div class="app-chrome-item generic">
<button class="playback-button--small cast" :title="$root.getLz('term.cast')"
@click="modals.castMenu = true"
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>
</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>
<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()"
: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()" />
</div>
<div class="app-chrome-item generic">
<button class="playback-button--small cast" :title="$root.getLz('term.cast')"
@click="modals.castMenu = true"
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>
</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>
</template>
<template v-else>
<button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
</template>
</div>
</template>
<template v-else>
<button class="playback-button--small lyrics" :style="{'opacity': 0.3, 'pointer-events': 'none'}" ></button>
</template>
</div>
</template>
<template v-else>
<div class="app-chrome-item search">
<div class="search-input-container">
<div class="search-input--icon"></div>
<input type="search" spellcheck="false" @click="$root.appRoute('search');" @focus="search.showHints = true"
@blur="setTimeout(()=>{search.showHints = false}, 300)"
v-on:keyup.enter="searchQuery();search.showHints = false" @change="$root.appRoute('search');" @input="getSearchHints()"
:placeholder="$root.getLz('term.search') + '...'" v-model="search.term" ref="searchInput"
class="search-input" />
<div class="app-chrome-item search">
<div class="search-input-container">
<div class="search-input--icon"></div>
<input type="search" spellcheck="false" @click="$root.appRoute('search');"
@focus="search.showHints = true"
@blur="setTimeout(()=>{search.showHints = false}, 300)"
v-on:keyup.enter="searchQuery();search.showHints = false" @change="$root.appRoute('search');"
@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">
<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 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)">
{{ hint }}
</button>
</div>
</div>
</div>
</div>
</template>
<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" v-else @click="ipcRenderer.send('maximize')"></div>
<div class="close" @click="ipcRenderer.send('close')"></div>
</div>
</div>
</div>
</div>
</template>
<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" v-else @click="ipcRenderer.send('maximize')"></div>
<div class="close" @click="ipcRenderer.send('close')"></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)"
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"></button>
</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)"
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"></button>
</div>
</div>
</div>
</div>

View file

@ -50,10 +50,10 @@
<div id="apple-music-video-player-controls">
<div id="player-exit" title="Close" @click="exitMV();fullscreen(false);">
<svg fill="white" xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"
aria-role="presentation" focusable="false" @click="exitMV();fullscreen(false);">
aria-role="presentation" focusable="false" @click="exitMV();fullscreen(false);">
<path
d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z"
fill-rule="nonzero"/>
fill-rule="nonzero" />
</svg>
</div>
<div id="captions" v-if="lyricon">{{((lyricon) ? ((lyrics.length > 0 && lyrics[currentLyricsLine] &&
@ -67,7 +67,7 @@
</div>
<div class="playback-info music-player-info">
<div class="song-artist-album-content"
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap;">
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap;">
<div class="song-artist" style="display: inline-block">
{{ mk.nowPlayingItem?.attributes?.artistName ?? '' }}
</div>
@ -85,7 +85,7 @@
@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()">
<p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }}
<p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }}
</div>
<div class="app-chrome-item display--large">
<div class="app-chrome-item volume display--large">
@ -93,7 +93,8 @@
: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"
<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>
@ -102,30 +103,30 @@
<button class="playback-button play" @click="mk.play()" v-else
:title="$root.getLz('term.play')" v-b-tooltip.hover></button>
<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>
</template>
<template v-else>
<button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
</template>
</div>
<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>
</template>
<template v-else>
<button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
</template>
</div>
<div id="player-pip"
@click="pip()"
title="Picture-in-Picture"
v-b-tooltip.hover>
@click="pip()"
title="Picture-in-Picture"
v-b-tooltip.hover>
<%- include("../svg/pip.svg") %>
</div>
<div id="player-fullscreen"
@click="fullscreen(!fullscreenState)"
title="Fullscreen"
v-b-tooltip.hover>
@click="fullscreen(!fullscreenState)"
title="Fullscreen"
v-b-tooltip.hover>
<%- include("../svg/fullscreen.svg") %>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,88 +1,90 @@
<script type="text/x-template" id="add-to-playlist">
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" @contextmenu.self="app.resetState()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div>
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<button class="playlist-item"
@click="app.addSelectedToNewPlaylist()" style="width:100%;">
<div class="icon"><%- include("../svg/plus.svg") %></div>
<div class="name">{{app.getLz('action.createPlaylist')}}</div>
</button>
<sidebar-playlist :playlist-select="playlistSelect" :relate-media-items="relateItems" v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
</sidebar-playlist>
</div>
<div class="modal-search">
<div class="search-input-container" style="width:100%;margin: 16px 0;">
<div class="search-input--icon"></div>
<input type="search"
ref="searchInput"
style="width:100%;"
spellcheck="false"
:placeholder="app.getLz('term.search') + '...'"
v-model="searchQuery"
@input="search()"
class="search-input">
</div>
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()"
@contextmenu.self="app.resetState()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div>
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<button class="playlist-item"
@click="app.addSelectedToNewPlaylist()" style="width:100%;">
<div class="icon"><%- include("../svg/plus.svg") %></div>
<div class="name">{{app.getLz('action.createPlaylist')}}</div>
</button>
<sidebar-playlist :playlist-select="playlistSelect" :relate-media-items="relateItems"
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
</sidebar-playlist>
</div>
<div class="modal-search">
<div class="search-input-container" style="width:100%;margin: 16px 0;">
<div class="search-input--icon"></div>
<input type="search"
ref="searchInput"
style="width:100%;"
spellcheck="false"
:placeholder="app.getLz('term.search') + '...'"
v-model="searchQuery"
@input="search()"
class="search-input">
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('add-to-playlist', {
template: '#add-to-playlist',
data: function () {
return {
playlistSorted: [],
searchQuery: "",
focused: "",
app: this.$root,
relateItems: []
}
},
props: {
playlists: {
type: Array,
required: true
}
},
mounted() {
this.relateItems = [this.$root.selectedMediaItems[0].id]
this.search()
this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) {
if (this.focused != "") {
this.addToPlaylist(this.focused)
}
}
})
},
methods: {
playlistSelect(playlist) {
if (playlist.type != "library-playlist-folders") {
this.addToPlaylist(playlist.id)
}
},
addToPlaylist(id) {
app.addSelectedToPlaylist(id)
},
search() {
this.focused = ""
if (this.searchQuery == "") {
this.playlistSorted = this.playlists
} else {
this.playlistSorted = this.playlists.filter(playlist => {
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
if (this.playlistSorted.length == 1) {
this.focused = this.playlistSorted[0].id
}
}
},
Vue.component('add-to-playlist', {
template: '#add-to-playlist',
data: function() {
return {
playlistSorted: [],
searchQuery: "",
focused: "",
app: this.$root,
relateItems: []
}
},
props: {
playlists: {
type: Array,
required: true
}
},
mounted() {
this.relateItems = [this.$root.selectedMediaItems[0].id]
this.search()
this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) {
if (this.focused != "") {
this.addToPlaylist(this.focused)
}
}
});
</script>
})
},
methods: {
playlistSelect(playlist) {
if (playlist.type != "library-playlist-folders") {
this.addToPlaylist(playlist.id)
}
},
addToPlaylist(id) {
app.addSelectedToPlaylist(id)
},
search() {
this.focused = ""
if (this.searchQuery == "") {
this.playlistSorted = this.playlists
} else {
this.playlistSorted = this.playlists.filter(playlist => {
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
if (this.playlistSorted.length == 1) {
this.focused = this.playlistSorted[0].id
}
}
},
}
});
</script>

View file

@ -1,42 +1,45 @@
<script type="text/x-template" id="airplay-modal">
<div class="spatialproperties-panel castmenu modal-fullscreen airplay-modal" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window airplay-modal">
<div class="modal-header">
<div class="modal-title">{{'Enter password'}}</div>
<button class="close-btn" @click="close()" :aria-label="this.$root.getLz('action.close')"></button>
</div>
<div class="modal-content" style="overflow-y: overlay; padding: 3%">
<input type="text" v-model="passcode"/>
</div>
<div class="md-footer">
<div class="row">
<div class="col" >
<button style="width:100%" @click="enterPassword()" class="md-btn md-btn-block md-btn-primary">{{'OK'}}</button>
<div class="spatialproperties-panel castmenu modal-fullscreen airplay-modal" @click.self="close()"
@contextmenu.self="close()">
<div class="modal-window airplay-modal">
<div class="modal-header">
<div class="modal-title">{{'Enter password'}}</div>
<button class="close-btn" @click="close()" :aria-label="this.$root.getLz('action.close')"></button>
</div>
<div class="modal-content" style="overflow-y: overlay; padding: 3%">
<input type="text" v-model="passcode" />
</div>
<div class="md-footer">
<div class="row">
<div class="col">
<button style="width:100%" @click="enterPassword()" class="md-btn md-btn-block md-btn-primary">
{{'OK'}}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('airplay-modal', {
template: '#airplay-modal',
data: function () {
return {
passcode: '',
}
},
mounted() {
},
methods: {
close() {
this.$root.modals.airplayPW = false
},
enterPassword() {
console.log('Entered passCode: ', this.passcode)
ipcRenderer.send("setAirPlayPasscode",this.passcode)
this.close()
}
}
});
</script>
Vue.component('airplay-modal', {
template: '#airplay-modal',
data: function() {
return {
passcode: '',
}
},
mounted() {
},
methods: {
close() {
this.$root.modals.airplayPW = false
},
enterPassword() {
console.log('Entered passCode: ', this.passcode)
ipcRenderer.send("setAirPlayPasscode", this.passcode)
this.close()
}
}
});
</script>

View file

@ -5,93 +5,98 @@
</script>
<script>
Vue.component('animatedartwork-view', {
template: '#animatedartwork-view',
data: function () {
return {
app: this.$root,
hls: null,
}
},
props: {
video: {
type: String,
required: true
},
priority: {
type: Boolean,
default: false
}
},
async mounted() {
if (!this.priority && app.cfg.visual.animated_artwork == "limited") {
return
} else if (app.cfg.visual.animated_artwork == "disabled") {
return
}
if (this.video) {
this.$nextTick(function () {
var config = {
backBufferLength: 0,
enableWebVTT: false,
enableCEA708Captions: false,
emeEnabled: false,
abrEwmaDefaultEstimate: 10000,
testBandwidth: false,
};
if (this.hls) {
this.hls.detachMedia();
} else {
Vue.component('animatedartwork-view', {
template: '#animatedartwork-view',
data: function() {
return {
app: this.$root,
hls: null,
}
},
props: {
video: {
type: String,
required: true
},
priority: {
type: Boolean,
default: false
}
},
async mounted() {
if (!this.priority && app.cfg.visual.animated_artwork == "limited") {
return
} else if (app.cfg.visual.animated_artwork == "disabled") {
return
}
if (this.video) {
this.$nextTick(function() {
var config = {
backBufferLength: 0,
enableWebVTT: false,
enableCEA708Captions: false,
emeEnabled: false,
abrEwmaDefaultEstimate: 10000,
testBandwidth: false,
};
if (this.hls) {
this.hls.detachMedia();
} else {
this.hls = new CiderHls(config);
}
// bind them together
if (this.$refs.video) {
let d = "WIDEVINE_SOFTWARE"
let h = {
initDataTypes: ["cenc", "keyids"],
distinctiveIdentifier: "optional",
persistentState: "required"
}
let p = {
platformInfo: {requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h},
appData: {serviceName: "Apple Music"}
}
this.hls.attachMedia(this.$refs.video);
this.hls.loadSource(this.video, p);
let u = this.hls;
var quality = app.cfg.visual.animated_artwork_qualityLevel;
setTimeout(() => {
let levelsnum = u.levels.map((level)=>{return level.width})
if (levelsnum.length > 0) {
let qualities = []
let qualities2 = []
for (let i = 0; i < levelsnum.length; i++) {
if (qualities2.indexOf(levelsnum[i]) == -1) {
qualities.push({level: i, quality: levelsnum[i]})
qualities2.push(levelsnum[i])
}
}
let actualnum = Math.floor(qualities[qualities.length -1 ].level * (quality / 4))
if (quality != 0 ){
quality = qualities[Math.min(actualnum,qualities.length -1 )].level
}
if (quality == 4 ){
quality = qualities[qualities.length - 1].level
}
}
try{
this.hls.loadLevel = parseInt( quality || 1);} catch(e){}},200)
}
})
this.hls = new CiderHls(config);
}
// bind them together
if (this.$refs.video) {
let d = "WIDEVINE_SOFTWARE"
let h = {
initDataTypes: ["cenc", "keyids"],
distinctiveIdentifier: "optional",
persistentState: "required"
}
},
async beforeDestroy() {
if (this.hls) {
await this.hls.destroy();
this.hls = null
let p = {
platformInfo: { requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h },
appData: { serviceName: "Apple Music" }
}
}
});
</script>
this.hls.attachMedia(this.$refs.video);
this.hls.loadSource(this.video, p);
let u = this.hls;
var quality = app.cfg.visual.animated_artwork_qualityLevel;
setTimeout(() => {
let levelsnum = u.levels.map((level) => {
return level.width
})
if (levelsnum.length > 0) {
let qualities = []
let qualities2 = []
for (let i = 0; i < levelsnum.length; i++) {
if (qualities2.indexOf(levelsnum[i]) == -1) {
qualities.push({ level: i, quality: levelsnum[i] })
qualities2.push(levelsnum[i])
}
}
let actualnum = Math.floor(qualities[qualities.length - 1].level * (quality / 4))
if (quality != 0) {
quality = qualities[Math.min(actualnum, qualities.length - 1)].level
}
if (quality == 4) {
quality = qualities[qualities.length - 1].level
}
}
try {
this.hls.loadLevel = parseInt(quality || 1);
} catch (e) {
}
}, 200)
}
})
}
},
async beforeDestroy() {
if (this.hls) {
await this.hls.destroy();
this.hls = null
}
}
});
</script>

View file

@ -15,14 +15,14 @@
<transition
<% if(env.appRoutes[i].onEnter) {
%>
v-on:enter="<%- env.appRoutes[i].onEnter %>"
v-on:enter="<%- env.appRoutes[i].onEnter; %>"
<%
}
%>
:name="$root.chrome.desiredPageTransition">
<template
v-if="<%- env.appRoutes[i].condition %>">
<%- env.appRoutes[i].component %>
v-if="<%- env.appRoutes[i].condition; %>">
<%- env.appRoutes[i].component; %>
</template>
</transition>
<% } %>
@ -38,14 +38,13 @@
</script>
<script>
Vue.component('app-content-area', {
template: '#app-content-area',
data: function () {
return {
scrollPos: 0
}
},
methods: {
}
});
</script>
Vue.component('app-content-area', {
template: '#app-content-area',
data: function() {
return {
scrollPos: 0
}
},
methods: {}
});
</script>

View file

@ -1,49 +1,53 @@
<script type="text/x-template" id="artist-chip">
<div class="artist-chip" @click.self="route" tabindex="0">
<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 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.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>
<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>
<script>
Vue.component('artist-chip', {
props: {
item: {
type: Object,
required: true
}
},
data: function() {
return {
image: false,
artist: {
id: null
}
}
},
template: '#artist-chip',
async mounted() {
let artistId = this.item.id
if (typeof this.item.relationships == "object") {
artistId = this.item.relationships.catalog.data[0].id
}
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artistId}`).then(response => {
this.artist = response.data.data[0];
this.image = true;
});
},
methods: {
route() {
app.appRoute(`artist/${this.artist.id}`);
}
Vue.component('artist-chip', {
props: {
item: {
type: Object,
required: true
}
},
data: function() {
return {
image: false,
artist: {
id: null
}
});
</script>
}
},
template: '#artist-chip',
async mounted() {
let artistId = this.item.id
if (typeof this.item.relationships == "object") {
artistId = this.item.relationships.catalog.data[0].id
}
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artistId}`).then(response => {
this.artist = response.data.data[0];
this.image = true;
});
},
methods: {
route() {
app.appRoute(`artist/${this.artist.id}`);
}
}
});
</script>

View file

@ -1,37 +1,36 @@
<script type="text/x-template" id="artwork-material">
<div class="artworkMaterial">
<mediaitem-artwork :url="src" :size="500" v-for="image in images"/>
<mediaitem-artwork :url="src" :size="500" v-for="image in images" />
</div>
</script>
<script>
Vue.component('artwork-material', {
template: '#artwork-material',
data: function () {
return {
src: ""
}
},
mounted() {
this.src = app.getMediaItemArtwork(this.url, this.size)
},
props: {
url: {
type: String,
required: true
},
size: {
type: [String, Number],
required: false,
default: '32'
},
images: {
type: [String, Number],
required: false,
default: '2'
}
},
methods: {
}
});
</script>
Vue.component('artwork-material', {
template: '#artwork-material',
data: function() {
return {
src: ""
}
},
mounted() {
this.src = app.getMediaItemArtwork(this.url, this.size)
},
props: {
url: {
type: String,
required: true
},
size: {
type: [String, Number],
required: false,
default: '32'
},
images: {
type: [String, Number],
required: false,
default: '2'
}
},
methods: {}
});
</script>

View file

@ -4,7 +4,8 @@
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('term.audioControls')}}</div>
<button class="close-btn" @click="app.modals.audioControls = false" :aria-label="app.getLz('action.close')"></button>
<button class="close-btn" @click="app.modals.audioControls = false"
:aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<div class="md-option-line">
@ -14,7 +15,7 @@
<div class="md-option-segment md-option-segment_auto percent">
<input type="number"
style="width: 100%; text-align: center; margin-right: 5px;" min="0"
step="2" v-model="volume"/>
step="2" v-model="volume" />
</div>
</div>
<div class="md-option-line">
@ -23,7 +24,7 @@
</div>
<div class="md-option-segment md-option-segment_auto percent">
<input type="number" style="width: 100%; text-align: center; margin-right: 5px;" min="0"
v-model="volumeStep"/>
v-model="volumeStep" />
</div>
</div>
<div class="md-option-line">
@ -32,66 +33,66 @@
</div>
<div class="md-option-segment md-option-segment_auto percent">
<input type="number" style="width: 100%; text-align: center; margin-right: 5px;" min="0"
v-model="maxVolume"/>
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 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>
</script>
<script>
Vue.component('audio-controls', {
template: '#audio-controls',
data: function () {
return {
app: this.$root,
maxVolume: this.$root.cfg.audio.maxVolume * 100,
volumeStep: this.$root.cfg.audio.volumeStep * 100,
volume: this.$root.cfg.audio.volume * 100
}
},
watch: {
maxVolume: function (newValue, _oldValue) {
if (newValue > 100) {
newValue = 100
} else {
newValue = Math.round(newValue)
}
this.$root.cfg.audio.maxVolume = newValue / 100
this.maxVolume = newValue
console.log(newValue, _oldValue)
},
volume: function (newValue, _oldValue) {
if (newValue > this.maxVolume) {
newValue = 100
} else {
newValue = Math.round(newValue)
}
this.$root.mk.volume = newValue / 100
this.volume = newValue
console.log(newValue, _oldValue)
},
volumeStep: function (newValue, _oldValue) {
if (newValue > this.maxVolume) {
newValue = 100
} else {
newValue = Math.round(newValue)
}
this.$root.cfg.audio.volumeStep = newValue / 100
this.volumeStep = newValue
console.log(newValue, _oldValue)
}
}
});
</script>
Vue.component('audio-controls', {
template: '#audio-controls',
data: function() {
return {
app: this.$root,
maxVolume: this.$root.cfg.audio.maxVolume * 100,
volumeStep: this.$root.cfg.audio.volumeStep * 100,
volume: this.$root.cfg.audio.volume * 100
}
},
watch: {
maxVolume: function(newValue, _oldValue) {
if (newValue > 100) {
newValue = 100
} else {
newValue = Math.round(newValue)
}
this.$root.cfg.audio.maxVolume = newValue / 100
this.maxVolume = newValue
console.log(newValue, _oldValue)
},
volume: function(newValue, _oldValue) {
if (newValue > this.maxVolume) {
newValue = 100
} else {
newValue = Math.round(newValue)
}
this.$root.mk.volume = newValue / 100
this.volume = newValue
console.log(newValue, _oldValue)
},
volumeStep: function(newValue, _oldValue) {
if (newValue > this.maxVolume) {
newValue = 100
} else {
newValue = Math.round(newValue)
}
this.$root.cfg.audio.volumeStep = newValue / 100
this.volumeStep = newValue
console.log(newValue, _oldValue)
}
}
});
</script>

View file

@ -4,7 +4,8 @@
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('settings.option.audio.changePlaybackRate')}}</div>
<button class="close-btn" @click="app.modals.audioPlaybackRate = false" :aria-label="app.getLz('action.close')"></button>
<button class="close-btn" @click="app.modals.audioPlaybackRate = false"
:aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<div class="md-option-line">
@ -15,7 +16,8 @@
{{playbackRate}} ×
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="range" :step="0.05" min="0.25" :max="2" @wheel="playbackRateWheel" v-model="playbackRate">
<input type="range" :step="0.05" min="0.25" :max="2" @wheel="playbackRateWheel"
v-model="playbackRate">
</div>
</div>
</div>
@ -24,36 +26,36 @@
</script>
<script>
Vue.component('audio-playbackrate', {
template: '#audio-playbackrate',
data: function () {
return {
app: this.$root,
playbackRate: this.$root.cfg.audio.playbackRate
}
},
watch: {
playbackRate: function (newValue, _oldValue) {
this.saveValue(newValue);
}
},
methods: {
playbackRateWheel(event) {
if (app.checkScrollDirectionIsUp(event)) {
this.saveValue(this.$root.cfg.audio.playbackRate + 0.05);
} else {
this.saveValue(this.$root.cfg.audio.playbackRate - 0.05);
}
},
saveValue(newValue) {
newValue = Number(newValue);
if (newValue >= 0.25 && newValue <= 2) {
newValue = String(newValue).length > 4 ? newValue.toFixed(2) : newValue;
this.$root.mk.playbackRate = newValue;
this.$root.cfg.audio.playbackRate = newValue;
this.playbackRate = newValue;
}
}
Vue.component('audio-playbackrate', {
template: '#audio-playbackrate',
data: function() {
return {
app: this.$root,
playbackRate: this.$root.cfg.audio.playbackRate
}
},
watch: {
playbackRate: function(newValue, _oldValue) {
this.saveValue(newValue);
}
},
methods: {
playbackRateWheel(event) {
if (app.checkScrollDirectionIsUp(event)) {
this.saveValue(this.$root.cfg.audio.playbackRate + 0.05);
} else {
this.saveValue(this.$root.cfg.audio.playbackRate - 0.05);
}
});
},
saveValue(newValue) {
newValue = Number(newValue);
if (newValue >= 0.25 && newValue <= 2) {
newValue = String(newValue).length > 4 ? newValue.toFixed(2) : newValue;
this.$root.mk.playbackRate = newValue;
this.$root.cfg.audio.playbackRate = newValue;
this.playbackRate = newValue;
}
}
}
});
</script>

View file

@ -1,10 +1,12 @@
<script type="text/x-template" id="audio-settings">
<template>
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.modals.audioSettings = false" @contextmenu.self="app.modals.audioSettings = false">
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.modals.audioSettings = false"
@contextmenu.self="app.modals.audioSettings = false">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('term.audioSettings')}}</div>
<button class="close-btn" @click="app.modals.audioSettings = false" :aria-label="app.getLz('action.close')"></button>
<button class="close-btn" @click="app.modals.audioSettings = false"
:aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<button class="playlist-item"
@ -34,29 +36,30 @@
</script>
<script>
Vue.component('audio-settings', {
template: '#audio-settings',
data: function () {
return {
app: this.$root,
}
},
props: {},
mounted() {},
methods: {
openEqualizer() {
app.modals.equalizer = true
app.modals.audioSettings = false
},
openAudioControls() {
app.modals.audioControls = true
app.modals.audioSettings = false
},
openAudioPlaybackRate() {
app.modals.audioPlaybackRate = true
app.modals.audioSettings = false
},
},
Vue.component('audio-settings', {
template: '#audio-settings',
data: function() {
return {
app: this.$root,
}
);
</script>
},
props: {},
mounted() {
},
methods: {
openEqualizer() {
app.modals.equalizer = true
app.modals.audioSettings = false
},
openAudioControls() {
app.modals.audioControls = true
app.modals.audioSettings = false
},
openAudioPlaybackRate() {
app.modals.audioPlaybackRate = true
app.modals.audioSettings = false
},
},
}
);
</script>

View file

@ -1,130 +1,151 @@
<script type="text/x-template" id="castmenu">
<div class="spatialproperties-panel castmenu modal-fullscreen" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{$root.getLz('action.cast.todevices')}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
</div>
<div class="modal-content" style="overflow-y: overlay; padding: 3%">
<div class="md-labeltext">{{$root.getLz('action.cast.chromecast')}}</div>
<div class="md-option-container" style="margin-top: 12px;margin-bottom: 12px;overflow-y: scroll;">
<template v-if="!scanning">
<template v-for="(device) in devices.cast">
<div class="md-option-line" style="cursor: pointer" @click="setCast(device)">
<div class="md-option-segment">
{{ device.name }}
<br>
<small>{{ device.host }}</small>
</div>
<div class="md-option-segment_auto" style="display: flex;justify-content: center;align-items: center" v-if="activeCasts.includes(device)">
Connected
</div>
<div class="md-option-segment_auto" v-else style="display: flex;justify-content: center;align-items: center">
<svg width="20" height="20" viewBox="0 0 34 34" fill="#fff" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" class="castPlayIndicator"><path d="M28.228,18.327l-16.023,8.983c-0.99,0.555 -2.205,-0.17 -2.205,-1.318l0,-17.984c0,-1.146 1.215,-1.873 2.205,-1.317l16.023,8.982c1.029,0.577 1.029,2.077 0,2.654Z" style="fill-rule:nonzero"></path></svg>
</div>
</div>
</template>
</template>
<template v-else>
<div class="md-option-line" style="cursor: pointer">
<div class="md-option-segment">
{{$root.getLz('action.cast.scanning')}}
</div>
</div>
</template>
</div>
<div class="md-labeltext" >{{$root.getLz('action.cast.airplay')}}</div>
<div class="md-option-container" style="margin-top: 12px;margin-bottom: 12px;overflow-y: scroll;">
<div class="md-option-line">
<div class="md-option-segment">
{{'EXPERIMENTAL!!! Supports Homepods / Apple TVs / Shairport for now, AirPlay on Samsung/LG/Sony devices will be added later'}}
<!-- {{$root.getLz('action.cast.airplay.underdevelopment')}} -->
<template v-if="true" v-for="(device) in devices.airplay">
<div class="md-option-line" style="cursor: pointer" @click="setAirPlayCast(device)">
<div class="spatialproperties-panel castmenu modal-fullscreen" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{$root.getLz('action.cast.todevices')}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
</div>
<div class="modal-content" style="overflow-y: overlay; padding: 3%">
<div class="md-labeltext">{{$root.getLz('action.cast.chromecast')}}</div>
<div class="md-option-container" style="margin-top: 12px;margin-bottom: 12px;overflow-y: scroll;">
<template v-if="!scanning">
<template v-for="(device) in devices.cast">
<div class="md-option-line" style="cursor: pointer" @click="setCast(device)">
<div class="md-option-segment">
{{ device.name }}
<br>
<small>{{ device.host }}</small>
</div>
<div class="md-option-segment_auto"
style="display: flex;justify-content: center;align-items: center"
v-if="activeCasts.includes(device)">
Connected
</div>
<div class="md-option-segment_auto" v-else
style="display: flex;justify-content: center;align-items: center">
<svg width="20" height="20" viewBox="0 0 34 34" fill="#fff" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve" class="castPlayIndicator"><path
d="M28.228,18.327l-16.023,8.983c-0.99,0.555 -2.205,-0.17 -2.205,-1.318l0,-17.984c0,-1.146 1.215,-1.873 2.205,-1.317l16.023,8.982c1.029,0.577 1.029,2.077 0,2.654Z"
style="fill-rule:nonzero"></path></svg>
</div>
</div>
</template>
</template>
<template v-else>
<div class="md-option-line" style="cursor: pointer">
<div class="md-option-segment">
{{ device.name }}
<br>
<small>{{ device.host }}</small>
</div>
<div class="md-option-segment_auto" style="display: flex;justify-content: center;align-items: center" v-if="activeCasts.includes(device)">
Connected
</div>
<div class="md-option-segment_auto" v-else style="display: flex;justify-content: center;align-items: center">
<svg width="20" height="20" viewBox="0 0 34 34" fill="#fff" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" class="castPlayIndicator"><path d="M28.228,18.327l-16.023,8.983c-0.99,0.555 -2.205,-0.17 -2.205,-1.318l0,-17.984c0,-1.146 1.215,-1.873 2.205,-1.317l16.023,8.982c1.029,0.577 1.029,2.077 0,2.654Z" style="fill-rule:nonzero"></path></svg>
{{$root.getLz('action.cast.scanning')}}
</div>
</div>
</template>
</div>
<div class="md-labeltext">{{$root.getLz('action.cast.airplay')}}</div>
<div class="md-option-container" style="margin-top: 12px;margin-bottom: 12px;overflow-y: scroll;">
<div class="md-option-line">
<div class="md-option-segment">
{{'EXPERIMENTAL!!! Supports Homepods / Apple TVs / Shairport for now, AirPlay on
Samsung/LG/Sony devices will be added later'}}
<!-- {{$root.getLz('action.cast.airplay.underdevelopment')}} -->
<template v-if="true" v-for="(device) in devices.airplay">
<div class="md-option-line" style="cursor: pointer" @click="setAirPlayCast(device)">
<div class="md-option-segment">
{{ device.name }}
<br>
<small>{{ device.host }}</small>
</div>
<div class="md-option-segment_auto"
style="display: flex;justify-content: center;align-items: center"
v-if="activeCasts.includes(device)">
Connected
</div>
<div class="md-option-segment_auto" v-else
style="display: flex;justify-content: center;align-items: center">
<svg width="20" height="20" viewBox="0 0 34 34" fill="#fff" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
class="castPlayIndicator"><path
d="M28.228,18.327l-16.023,8.983c-0.99,0.555 -2.205,-0.17 -2.205,-1.318l0,-17.984c0,-1.146 1.215,-1.873 2.205,-1.317l16.023,8.982c1.029,0.577 1.029,2.077 0,2.654Z"
style="fill-rule:nonzero"></path></svg>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
<div class="md-footer">
<div class="row">
<div class="col" v-if="activeCasts.length != 0">
<button style="width:100%" @click="stopCasting()" class="md-btn md-btn-block md-btn-primary">
{{$root.getLz('action.cast.stop')}}
</button>
</div>
<div class="col">
<button style="width:100%" class="md-btn md-btn-block" @click="scan()">
{{$root.getLz('action.cast.scan')}}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="md-footer">
<div class="row">
<div class="col" v-if="activeCasts.length != 0">
<button style="width:100%" @click="stopCasting()" class="md-btn md-btn-block md-btn-primary">{{$root.getLz('action.cast.stop')}}</button>
</div>
<div class="col">
<button style="width:100%" class="md-btn md-btn-block" @click="scan()">{{$root.getLz('action.cast.scan')}}</button>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('castmenu', {
template: '#castmenu',
data: function () {
return {
devices: {
cast: [],
airplay: []
},
scanning: false,
activeCasts: this.$root.activeCasts,
Vue.component('castmenu', {
template: '#castmenu',
data: function() {
return {
devices: {
cast: [],
airplay: []
},
scanning: false,
activeCasts: this.$root.activeCasts,
}
},
mounted() {
this.scan();
},
watch:{
activeCasts: function (newVal, oldVal) {
this.$root.activeCasts = this.activeCasts;
}},
methods: {
close() {
this.$root.modals.castMenu = false
},
scan() {
let self = this;
this.scanning = true;
ipcRenderer.send('getChromeCastDevices', '');
ipcRenderer.send("getAirplayDevice","")
setTimeout(() => {
self.devices.cast = ipcRenderer.sendSync("getKnownCastDevices");
self.devices.airplay = ipcRenderer.sendSync("getKnownAirplayDevices");
self.scanning = false;
}, 2000);
console.log(this.devices);
// vm.$forceUpdate();
},
setCast(device) {
CiderAudio.sendAudio();
this.activeCasts.push(device);
ipcRenderer.send('performGCCast', device, "Cider", "Playing ...", "Test build", '');
},
setAirPlayCast(device) {
this.activeCasts.push(device);
ipcRenderer.send("performAirplayPCM",device.host,device.port,null,"","","","",device.txt,device.airplay2)
},
stopCasting() {
CiderAudio.stopAudio();
ipcRenderer.send('disconnectAirplay', '');
ipcRenderer.send('stopGCast', '');
this.activeCasts = [];
// vm.$forceUpdate();
},
}
});
</script>
}
},
mounted() {
this.scan();
},
watch: {
activeCasts: function(newVal, oldVal) {
this.$root.activeCasts = this.activeCasts;
}
},
methods: {
close() {
this.$root.modals.castMenu = false
},
scan() {
let self = this;
this.scanning = true;
ipcRenderer.send('getChromeCastDevices', '');
ipcRenderer.send("getAirplayDevice", "")
setTimeout(() => {
self.devices.cast = ipcRenderer.sendSync("getKnownCastDevices");
self.devices.airplay = ipcRenderer.sendSync("getKnownAirplayDevices");
self.scanning = false;
}, 2000);
console.log(this.devices);
// vm.$forceUpdate();
},
setCast(device) {
CiderAudio.sendAudio();
this.activeCasts.push(device);
ipcRenderer.send('performGCCast', device, "Cider", "Playing ...", "Test build", '');
},
setAirPlayCast(device) {
this.activeCasts.push(device);
ipcRenderer.send("performAirplayPCM", device.host, device.port, null, "", "", "", "", device.txt, device.airplay2)
},
stopCasting() {
CiderAudio.stopAudio();
ipcRenderer.send('disconnectAirplay', '');
ipcRenderer.send('stopGCast', '');
this.activeCasts = [];
// vm.$forceUpdate();
},
}
});
</script>

View file

@ -4,15 +4,17 @@
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('action.addToLibrary')}}</div>
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button>
<button class="close-btn" @click="app.resetState()"
:aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<button class="playlist-item"
:class="{ focused: playlist.id == focused }"
@click="addToPlaylist(playlist.id)" style="width:100%;" v-for="playlist in playlistSorted" v-if="playlist.attributes.canEdit && playlist.type != 'library-playlist-folders'">
<div class="icon"><%- include("../svg/playlist.svg") %></div>
<div class="name">{{ playlist.attributes.name }}</div>
</button>
:class="{ focused: playlist.id == focused }"
@click="addToPlaylist(playlist.id)" style="width:100%;" v-for="playlist in playlistSorted"
v-if="playlist.attributes.canEdit && playlist.type != 'library-playlist-folders'">
<div class="icon"><%- include("../svg/playlist.svg") %></div>
<div class="name">{{ playlist.attributes.name }}</div>
</button>
</div>
<div class="modal-search">
<div class="search-input-container" style="width:100%;margin: 16px 0;">
@ -33,50 +35,50 @@
</script>
<script>
Vue.component('add-to-playlist', {
template: '#add-to-playlist',
data: function () {
return {
playlistSorted: [],
searchQuery: "",
focused: "",
app: this.$root,
}
},
props: {
playlists: {
type: Array,
required: true
}
},
mounted() {
this.search()
this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) {
if (this.focused != "") {
this.addToPlaylist(this.focused)
}
}
})
},
methods: {
addToPlaylist(id) {
app.addSelectedToPlaylist(id)
},
search() {
this.focused = ""
if (this.searchQuery == "") {
this.playlistSorted = this.playlists
} else {
this.playlistSorted = this.playlists.filter(playlist => {
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
if (this.playlistSorted.length == 1) {
this.focused = this.playlistSorted[0].id
}
}
},
Vue.component('add-to-playlist', {
template: '#add-to-playlist',
data: function() {
return {
playlistSorted: [],
searchQuery: "",
focused: "",
app: this.$root,
}
},
props: {
playlists: {
type: Array,
required: true
}
},
mounted() {
this.search()
this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) {
if (this.focused != "") {
this.addToPlaylist(this.focused)
}
}
});
</script>
})
},
methods: {
addToPlaylist(id) {
app.addSelectedToPlaylist(id)
},
search() {
this.focused = ""
if (this.searchQuery == "") {
this.playlistSorted = this.playlists
} else {
this.playlistSorted = this.playlists.filter(playlist => {
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
if (this.playlistSorted.length == 1) {
this.focused = this.playlistSorted[0].id
}
}
},
}
});
</script>

View file

@ -1,434 +1,483 @@
<script type="text/x-template" id="eq-view">
<div class="modal-fullscreen equalizer-panel" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window" >
<div class="modal-header">
<div class="modal-title">{{$root.getLz('term.equalizer')}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:220px;text-align:center;margin-right:245px" v-model="$root.cfg.audio.equalizer.preset" v-on:change="changePreset($root.cfg.audio.equalizer.preset)">
<optgroup :label="$root.getLz('term.userPresets')">
<option v-for="preset in $root.cfg.audio.equalizer.presets" :value="preset.preset">{{preset.name}}</option>
</optgroup>
<optgroup :label="$root.getLz('term.defaultPresets')">
<option v-for="preset in defaultPresets" :value="preset.preset">{{preset.name}}</option>
</optgroup>
</select>
</div>
</div>
<div class="modal-content">
<!-- BANDS = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]; -->
<div class="inputs-container">
<div class="input-container mini">
{{$root.cfg.audio.equalizer.vibrantBass}}
<input tabindex="0" type="range" class="eq-slider mini" orient="vertical" min="-15" max="15" step="1" v-model="$root.cfg.audio.equalizer.vibrantBass" @change="changeVibrantBass()">
Vibrant Bass
</div>
<div class="input-container mini">
{{$root.cfg.audio.equalizer.mix}}
<input tabindex="0" type="range" class="eq-slider mini" orient="vertical" min="0" max="2" step="0.1" v-model="$root.cfg.audio.equalizer.mix" @change="changeMix()">
Mix
</div>
<div class="input-container header mini">
Gain
<input type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" >
<div class="freq-header">Freq</div>
<div>Q</div>
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[0]" @change="changeGain(0)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[0]" @change="changeGain(0)">
<input type="number" class="eq-freq" orient="vertical" min="22" max="44" step="2" v-model="$root.cfg.audio.equalizer.frequencies[0]" @change="changeFreq(0)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[0]" @change="changeQ(0)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[1]" @change="changeGain(1)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[1]" @change="changeGain(1)">
<input type="number" class="eq-freq" orient="vertical" min="44" max="88" step="4" v-model="$root.cfg.audio.equalizer.frequencies[1]" @change="changeFreq(1)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[1]" @change="changeQ(1)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[2]" @change="changeGain(2)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[2]" @change="changeGain(2)">
<input type="number" class="eq-freq" orient="vertical" min="88" max="177" step="8" v-model="$root.cfg.audio.equalizer.frequencies[2]" @change="changeFreq(2)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[2]" @change="changeQ(2)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[3]" @change="changeGain(3)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[3]" @change="changeGain(3)">
<input type="number" class="eq-freq" orient="vertical" min="177" max="355" step="16" v-model="$root.cfg.audio.equalizer.frequencies[3]" @change="changeFreq(3)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[3]" @change="changeQ(3)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[4]" @change="changeGain(4)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[4]" @change="changeGain(4)">
<input type="number" class="eq-freq" orient="vertical" min="355" max="710" step="32" v-model="$root.cfg.audio.equalizer.frequencies[4]" @change="changeFreq(4)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[4]" @change="changeQ(4)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[5]" @change="changeGain(5)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[5]" @change="changeGain(5)">
<input type="number" class="eq-freq" orient="vertical" min="710" max="1420" step="64" v-model="$root.cfg.audio.equalizer.frequencies[5]" @change="changeFreq(5)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[5]" @change="changeQ(5)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[6]" @change="changeGain(6)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[6]" @change="changeGain(6)">
<input type="number" class="eq-freq" orient="vertical" min="1420" max="2840" step="128" v-model="$root.cfg.audio.equalizer.frequencies[6]" @change="changeFreq(6)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[6]" @change="changeQ(6)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[7]" @change="changeGain(7)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[7]" @change="changeGain(7)">
<input type="number" class="eq-freq" orient="vertical" min="2840" max="5680" step="256" v-model="$root.cfg.audio.equalizer.frequencies[7]" @change="changeFreq(7)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[7]" @change="changeQ(7)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[8]" @change="changeGain(8)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[8]" @change="changeGain(8)">
<input type="number" class="eq-freq" orient="vertical" min="5680" max="11360" step="512" v-model="$root.cfg.audio.equalizer.frequencies[8]" @change="changeFreq(8)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[8]" @change="changeQ(8)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[9]" @change="changeGain(9)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[9]" @change="changeGain(9)">
<input type="number" class="eq-freq" orient="vertical" min="11360" max="22720" step="1024" v-model="$root.cfg.audio.equalizer.frequencies[9]" @change="changeFreq(9)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[9]" @change="changeQ(9)">
</div>
</div>
</div>
<div class="modal-lowercontent">
<div class="row">
<div class="col">
<button class="md-btn" style="width:100%" @click="resetGain()">{{$root.getLz('term.reset')}}</button>
</div>
<div class="col">
<button class="md-btn" style="width:100%" @click="presetOptions($event)">{{$root.getLz('term.menu')}}</button>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('eq-view', {
template: '#eq-view',
data: function () {
return {
// app: this.$root,
eqPreset: function () {
this.preset = uuidv4()
this.name = ""
this.frequencies = []
this.gain = []
this.Q = []
this.mix = 1
this.vibrantBass = 0
this.userGenerated = true
},
defaultPresets: [
{
'preset': 'default',
'name': 'Default',
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
'gain': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'Q': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'boostAiriness',
'name': 'Boost Airiness',
'frequencies': [1169, 1733, 5962, 8688, 14125, 18628, 18628, 19000, 19500, 20000],
'gain': [-1.41, 0.25, 3.33, 0.22, -0.53, 0.2, 3.64, 0, 0, 0],
'Q': [0.405, 2.102, 0.025, 2.5, 7.071, 1.768, 1.146, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'acoustic',
'name': 'Acoustic',
'frequencies': [32, 75, 125, 220, 700, 1000, 2000, 4000, 10000, 16000],
'gain': [0, -8, 0, -0.1, -3, 0, 0, 0, 4, 0],
'Q': [1, 0.2, 1, 2.0, 1.4, 1, 1, 1, 0.1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'clearVocal',
'name': 'Clear Vocal',
'frequencies': [20, 63, 125, 250, 400, 1000, 2000, 4000, 8000, 20000],
'gain': [-22, 0, 0, 0, -3, 0, 1.8, 0, 0, 3.5],
'Q': [0.3, 1, 1, 1, 2.0, 1, 0.7, 1, 1, 0.8],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'instrumentClarity',
'name': 'Instrument Clarity',
'frequencies': [20, 63, 155, 250, 500, 1000, 2000, 5000, 11000, 16000],
'gain': [-15, 0, -3, 0, 0, 0, 0, 3.1, 0, 0],
'Q': [0.5, 1, 2, 1, 1, 1, 1, 1.5, 0.1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'reduceHarshness',
'name': 'Reduce Harshness',
'frequencies': [32, 63, 125, 250, 500, 1128, 2000, 4057, 8000, 16000],
'gain': [0, 0, 0, 0, 0, 2, 0, -6.4, 0, 0],
'Q': [1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'smileyFace',
'name': 'Smiley Face',
'frequencies': [35, 63, 125, 250, 500, 800, 2000, 4000, 8000, 20000],
'gain': [5, 0, 0, 0, 0, -5, 0, 0, 0, 5],
'Q': [0.1, 1, 1, 1, 1, 0.6, 1, 1, 1, 0.2],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
},
{
'preset': 'bassBoostSurgical',
'name': 'Surgical Bass Boost',
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
'gain': [2.7, 2.2, 1.6, 1.4, 0.6, 0, 0, 0, 0, 0],
'Q': [1.4, 1.4, 1.4, 1.4, 1.4, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'bassBoostClassic',
'name': 'Classic Bass Boost',
'frequencies': [32, 63, 160, 250, 500, 1000, 2000, 3500, 8000, 20000],
'gain': [2.7, 2.2, 1.6, 1.4, 0.6, 0, 0, 0, 0, 0],
'Q': [0.7, 0.7, 0.7, 0.7, 0.7, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}]
}
},
props: ["src", "url"],
mounted() {
},
methods: {
presetOptions(event) {
let menu = {
items: {
"new": {
"icon": "./assets/feather/plus.svg",
"name": app.getLz('action.newpreset'),
action: () => {
this.addPreset()
}
},
"delete": {
"icon": "./assets/feather/x-circle.svg",
"name": app.getLz('action.deletepreset'),
action: () => {
this.deletePreset()
}
},
"import": {
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.import'),
action: () => {
this.importPreset()
}
},
"export": {
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.export'),
action: () => {
this.exportPreset()
}
},
}
}
if (!this.$root.cfg.audio.equalizer.userGenerated) {
delete menu.items.delete
}
app.showMenuPanel(menu, event)
},
sharePreset(event) {
let menu = {
items: [
{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.import'),
"action": function () {
notyf.error("Not implemented yet")
}
},
{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.export'),
"action": function () {
notyf.error("Not implemented yet")
}
},
]
}
app.showMenuPanel(menu, event)
},
deletePreset() {
let presets = this.$root.cfg.audio.equalizer.presets
app.confirm(app.getLz('term.deletepreset.warn'), (result) => {
if (result) {
this.changePreset("default")
// find the preset by id (preset) and remove it
let index = presets.findIndex(p => p.preset == this.preset)
presets.splice(index, 1)
notyf.success(app.getLz('term.deletedpreset'))
}
})
},
close() {
app.modals.equalizer = false
},
changeVibrantBass() {
if (app.cfg.audio.equalizer.vibrantBass !== '0') {
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_n0_0();
}
catch (e) {
CiderAudio.hierarchical_loading();
}
}
else {
CiderAudio.hierarchical_loading();
}
},
changeMix() {
if (Math.max(...app.cfg.audio.equalizer.gain) != 0) {
try {
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_n0_0();
} catch (e) { CiderAudio.hierarchical_loading(); }
}
},
changeGain(i) {
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_n0_0();
}
catch (e) { CiderAudio.hierarchical_loading(); }
}
else {
CiderAudio.hierarchical_loading();
}
},
changeFreq(i) {
CiderAudio.audioNodes.audioBands[i].frequency.value = app.cfg.audio.equalizer.frequencies[i]
},
changeQ(i) {
CiderAudio.audioNodes.audioBands[i].Q.value = app.cfg.audio.equalizer.Q[i]
},
resetGain() {
this.applyPreset({
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
'gain': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'Q': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
})
if (app.cfg.audio.equalizer.userGenerated) {
this.saveSelectedPreset()
}
},
addPreset() {
let self = this
app.prompt(app.getLz('term.newpreset.name'), (res) => {
if (res) {
let eqSettings = Clone(app.cfg.audio.equalizer)
let newPreset = new self.eqPreset()
newPreset.name = res
newPreset.frequencies = eqSettings.frequencies
newPreset.gain = eqSettings.gain
newPreset.Q = eqSettings.Q
newPreset.mix = eqSettings.mix
newPreset.vibrantBass = eqSettings.vibrantBass
app.cfg.audio.equalizer.presets.push(newPreset)
notyf.success(app.getLz('term.addedpreset'))
self.changePreset(newPreset.preset)
}
})
},
saveSelectedPreset() {
// Save the current settings to the selected preset
let self = this
//let preset = app.cfg.audio.equalizer.presets[app.cfg.audio.equalizer.preset]
// find the preset by its id (preset)
let preset = app.cfg.audio.equalizer.presets.find(p => p.preset == app.cfg.audio.equalizer.preset)
preset.frequencies = app.cfg.audio.equalizer.frequencies
preset.gain = app.cfg.audio.equalizer.gain
preset.Q = app.cfg.audio.equalizer.Q
preset.mix = app.cfg.audio.equalizer.mix
preset.vibrantBass = app.cfg.audio.equalizer.vibrantBass
notyf.success("Saved Preset")
},
exportPreset() {
let preset = app.cfg.audio.equalizer.presets.find(p => p.preset == app.cfg.audio.equalizer.preset)
this.$root.copyToClipboard(btoa(JSON.stringify(preset)))
},
importPreset() {
let self = this
app.prompt("Enter preset share code", (res) => {
if (res) {
let preset = JSON.parse(atob(res))
if (preset.frequencies && preset.gain && preset.Q && preset.mix && preset.vibrantBass) {
// self.applyPreset(preset)
app.cfg.audio.equalizer.presets.push(preset)
notyf.success(`${preset.name} has been imported.`)
}
else {
notyf.error("Invalid Preset")
}
}
})
},
applyPreset(preset) {
Object.assign(this.$root.cfg.audio.equalizer, preset)
this.changeVibrantBass()
for (var i = 0; i < 10; i++) {
try { CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix }
catch (e) {
CiderAudio.hierarchical_loading();
CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix
}
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_n0_0();
},
changePreset(id) {
let userPresets = app.cfg.audio.equalizer.presets
let defaultPresets = Clone(this.defaultPresets)
let presets = defaultPresets.concat(userPresets)
console.log(presets)
let preset = presets.find(p => p.preset == id)
console.log(preset)
if (preset) {
this.applyPreset(preset)
}
}
}
});
</script>
<script type="text/x-template" id="eq-view">
<div class="modal-fullscreen equalizer-panel" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{$root.getLz('term.equalizer')}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:220px;text-align:center;margin-right:245px"
v-model="$root.cfg.audio.equalizer.preset"
v-on:change="changePreset($root.cfg.audio.equalizer.preset)">
<optgroup :label="$root.getLz('term.userPresets')">
<option v-for="preset in $root.cfg.audio.equalizer.presets" :value="preset.preset">
{{preset.name}}
</option>
</optgroup>
<optgroup :label="$root.getLz('term.defaultPresets')">
<option v-for="preset in defaultPresets" :value="preset.preset">{{preset.name}}</option>
</optgroup>
</select>
</div>
</div>
<div class="modal-content">
<!-- BANDS = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]; -->
<div class="inputs-container">
<div class="input-container mini">
{{$root.cfg.audio.equalizer.vibrantBass}}
<input tabindex="0" type="range" class="eq-slider mini" orient="vertical" min="-15" max="15"
step="1" v-model="$root.cfg.audio.equalizer.vibrantBass" @change="changeVibrantBass()">
Vibrant Bass
</div>
<div class="input-container mini">
{{$root.cfg.audio.equalizer.mix}}
<input tabindex="0" type="range" class="eq-slider mini" orient="vertical" min="0" max="2"
step="0.1" v-model="$root.cfg.audio.equalizer.mix" @change="changeMix()">
Mix
</div>
<div class="input-container header mini">
Gain
<input type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1">
<div class="freq-header">Freq</div>
<div>Q</div>
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[0]" @change="changeGain(0)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[0]" @change="changeGain(0)">
<input type="number" class="eq-freq" orient="vertical" min="22" max="44" step="2"
v-model="$root.cfg.audio.equalizer.frequencies[0]" @change="changeFreq(0)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[0]" @change="changeQ(0)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[1]" @change="changeGain(1)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[1]" @change="changeGain(1)">
<input type="number" class="eq-freq" orient="vertical" min="44" max="88" step="4"
v-model="$root.cfg.audio.equalizer.frequencies[1]" @change="changeFreq(1)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[1]" @change="changeQ(1)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[2]" @change="changeGain(2)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[2]" @change="changeGain(2)">
<input type="number" class="eq-freq" orient="vertical" min="88" max="177" step="8"
v-model="$root.cfg.audio.equalizer.frequencies[2]" @change="changeFreq(2)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[2]" @change="changeQ(2)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[3]" @change="changeGain(3)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[3]" @change="changeGain(3)">
<input type="number" class="eq-freq" orient="vertical" min="177" max="355" step="16"
v-model="$root.cfg.audio.equalizer.frequencies[3]" @change="changeFreq(3)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[3]" @change="changeQ(3)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[4]" @change="changeGain(4)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[4]" @change="changeGain(4)">
<input type="number" class="eq-freq" orient="vertical" min="355" max="710" step="32"
v-model="$root.cfg.audio.equalizer.frequencies[4]" @change="changeFreq(4)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[4]" @change="changeQ(4)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[5]" @change="changeGain(5)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[5]" @change="changeGain(5)">
<input type="number" class="eq-freq" orient="vertical" min="710" max="1420" step="64"
v-model="$root.cfg.audio.equalizer.frequencies[5]" @change="changeFreq(5)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[5]" @change="changeQ(5)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[6]" @change="changeGain(6)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[6]" @change="changeGain(6)">
<input type="number" class="eq-freq" orient="vertical" min="1420" max="2840" step="128"
v-model="$root.cfg.audio.equalizer.frequencies[6]" @change="changeFreq(6)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[6]" @change="changeQ(6)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[7]" @change="changeGain(7)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[7]" @change="changeGain(7)">
<input type="number" class="eq-freq" orient="vertical" min="2840" max="5680" step="256"
v-model="$root.cfg.audio.equalizer.frequencies[7]" @change="changeFreq(7)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[7]" @change="changeQ(7)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[8]" @change="changeGain(8)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[8]" @change="changeGain(8)">
<input type="number" class="eq-freq" orient="vertical" min="5680" max="11360" step="512"
v-model="$root.cfg.audio.equalizer.frequencies[8]" @change="changeFreq(8)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[8]" @change="changeQ(8)">
</div>
<div class="input-container">
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[9]" @change="changeGain(9)">
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12"
step="0.1" v-model="$root.cfg.audio.equalizer.gain[9]" @change="changeGain(9)">
<input type="number" class="eq-freq" orient="vertical" min="11360" max="22720" step="1024"
v-model="$root.cfg.audio.equalizer.frequencies[9]" @change="changeFreq(9)">
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1"
v-model="$root.cfg.audio.equalizer.Q[9]" @change="changeQ(9)">
</div>
</div>
</div>
<div class="modal-lowercontent">
<div class="row">
<div class="col">
<button class="md-btn" style="width:100%" @click="resetGain()">{{$root.getLz('term.reset')}}
</button>
</div>
<div class="col">
<button class="md-btn" style="width:100%" @click="presetOptions($event)">
{{$root.getLz('term.menu')}}
</button>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('eq-view', {
template: '#eq-view',
data: function() {
return {
// app: this.$root,
eqPreset: function() {
this.preset = uuidv4()
this.name = ""
this.frequencies = []
this.gain = []
this.Q = []
this.mix = 1
this.vibrantBass = 0
this.userGenerated = true
},
defaultPresets: [
{
'preset': 'default',
'name': 'Default',
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
'gain': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'Q': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'boostAiriness',
'name': 'Boost Airiness',
'frequencies': [1169, 1733, 5962, 8688, 14125, 18628, 18628, 19000, 19500, 20000],
'gain': [-1.41, 0.25, 3.33, 0.22, -0.53, 0.2, 3.64, 0, 0, 0],
'Q': [0.405, 2.102, 0.025, 2.5, 7.071, 1.768, 1.146, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'acoustic',
'name': 'Acoustic',
'frequencies': [32, 75, 125, 220, 700, 1000, 2000, 4000, 10000, 16000],
'gain': [0, -8, 0, -0.1, -3, 0, 0, 0, 4, 0],
'Q': [1, 0.2, 1, 2.0, 1.4, 1, 1, 1, 0.1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'clearVocal',
'name': 'Clear Vocal',
'frequencies': [20, 63, 125, 250, 400, 1000, 2000, 4000, 8000, 20000],
'gain': [-22, 0, 0, 0, -3, 0, 1.8, 0, 0, 3.5],
'Q': [0.3, 1, 1, 1, 2.0, 1, 0.7, 1, 1, 0.8],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'instrumentClarity',
'name': 'Instrument Clarity',
'frequencies': [20, 63, 155, 250, 500, 1000, 2000, 5000, 11000, 16000],
'gain': [-15, 0, -3, 0, 0, 0, 0, 3.1, 0, 0],
'Q': [0.5, 1, 2, 1, 1, 1, 1, 1.5, 0.1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'reduceHarshness',
'name': 'Reduce Harshness',
'frequencies': [32, 63, 125, 250, 500, 1128, 2000, 4057, 8000, 16000],
'gain': [0, 0, 0, 0, 0, 2, 0, -6.4, 0, 0],
'Q': [1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'smileyFace',
'name': 'Smiley Face',
'frequencies': [35, 63, 125, 250, 500, 800, 2000, 4000, 8000, 20000],
'gain': [5, 0, 0, 0, 0, -5, 0, 0, 0, 5],
'Q': [0.1, 1, 1, 1, 1, 0.6, 1, 1, 1, 0.2],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
},
{
'preset': 'bassBoostSurgical',
'name': 'Surgical Bass Boost',
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
'gain': [2.7, 2.2, 1.6, 1.4, 0.6, 0, 0, 0, 0, 0],
'Q': [1.4, 1.4, 1.4, 1.4, 1.4, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}, {
'preset': 'bassBoostClassic',
'name': 'Classic Bass Boost',
'frequencies': [32, 63, 160, 250, 500, 1000, 2000, 3500, 8000, 20000],
'gain': [2.7, 2.2, 1.6, 1.4, 0.6, 0, 0, 0, 0, 0],
'Q': [0.7, 0.7, 0.7, 0.7, 0.7, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
'userGenerated': false
}]
}
},
props: ["src", "url"],
mounted() {
},
methods: {
presetOptions(event) {
let menu = {
items: {
"new": {
"icon": "./assets/feather/plus.svg",
"name": app.getLz('action.newpreset'),
action: () => {
this.addPreset()
}
},
"delete": {
"icon": "./assets/feather/x-circle.svg",
"name": app.getLz('action.deletepreset'),
action: () => {
this.deletePreset()
}
},
"import": {
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.import'),
action: () => {
this.importPreset()
}
},
"export": {
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.export'),
action: () => {
this.exportPreset()
}
},
}
}
if (!this.$root.cfg.audio.equalizer.userGenerated) {
delete menu.items.delete
}
app.showMenuPanel(menu, event)
},
sharePreset(event) {
let menu = {
items: [
{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.import'),
"action": function() {
notyf.error("Not implemented yet")
}
},
{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.export'),
"action": function() {
notyf.error("Not implemented yet")
}
},
]
}
app.showMenuPanel(menu, event)
},
deletePreset() {
let presets = this.$root.cfg.audio.equalizer.presets
app.confirm(app.getLz('term.deletepreset.warn'), (result) => {
if (result) {
this.changePreset("default")
// find the preset by id (preset) and remove it
let index = presets.findIndex(p => p.preset == this.preset)
presets.splice(index, 1)
notyf.success(app.getLz('term.deletedpreset'))
}
})
},
close() {
app.modals.equalizer = false
},
changeVibrantBass() {
if (app.cfg.audio.equalizer.vibrantBass !== '0') {
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_n0_0();
} catch (e) {
CiderAudio.hierarchical_loading();
}
} else {
CiderAudio.hierarchical_loading();
}
},
changeMix() {
if (Math.max(...app.cfg.audio.equalizer.gain) != 0) {
try {
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_n0_0();
} catch (e) {
CiderAudio.hierarchical_loading();
}
}
},
changeGain(i) {
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_n0_0();
} catch (e) {
CiderAudio.hierarchical_loading();
}
} else {
CiderAudio.hierarchical_loading();
}
},
changeFreq(i) {
CiderAudio.audioNodes.audioBands[i].frequency.value = app.cfg.audio.equalizer.frequencies[i]
},
changeQ(i) {
CiderAudio.audioNodes.audioBands[i].Q.value = app.cfg.audio.equalizer.Q[i]
},
resetGain() {
this.applyPreset({
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
'gain': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'Q': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'mix': 1,
'vibrantBass': 0,
})
if (app.cfg.audio.equalizer.userGenerated) {
this.saveSelectedPreset()
}
},
addPreset() {
let self = this
app.prompt(app.getLz('term.newpreset.name'), (res) => {
if (res) {
let eqSettings = Clone(app.cfg.audio.equalizer)
let newPreset = new self.eqPreset()
newPreset.name = res
newPreset.frequencies = eqSettings.frequencies
newPreset.gain = eqSettings.gain
newPreset.Q = eqSettings.Q
newPreset.mix = eqSettings.mix
newPreset.vibrantBass = eqSettings.vibrantBass
app.cfg.audio.equalizer.presets.push(newPreset)
notyf.success(app.getLz('term.addedpreset'))
self.changePreset(newPreset.preset)
}
})
},
saveSelectedPreset() {
// Save the current settings to the selected preset
let self = this
//let preset = app.cfg.audio.equalizer.presets[app.cfg.audio.equalizer.preset]
// find the preset by its id (preset)
let preset = app.cfg.audio.equalizer.presets.find(p => p.preset == app.cfg.audio.equalizer.preset)
preset.frequencies = app.cfg.audio.equalizer.frequencies
preset.gain = app.cfg.audio.equalizer.gain
preset.Q = app.cfg.audio.equalizer.Q
preset.mix = app.cfg.audio.equalizer.mix
preset.vibrantBass = app.cfg.audio.equalizer.vibrantBass
notyf.success("Saved Preset")
},
exportPreset() {
let preset = app.cfg.audio.equalizer.presets.find(p => p.preset == app.cfg.audio.equalizer.preset)
this.$root.copyToClipboard(btoa(JSON.stringify(preset)))
},
importPreset() {
let self = this
app.prompt("Enter preset share code", (res) => {
if (res) {
let preset = JSON.parse(atob(res))
if (preset.frequencies && preset.gain && preset.Q && preset.mix && preset.vibrantBass) {
// self.applyPreset(preset)
app.cfg.audio.equalizer.presets.push(preset)
notyf.success(`${preset.name} has been imported.`)
} else {
notyf.error("Invalid Preset")
}
}
})
},
applyPreset(preset) {
Object.assign(this.$root.cfg.audio.equalizer, preset)
this.changeVibrantBass()
for (var i = 0; i < 10; i++) {
try {
CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix
} catch (e) {
CiderAudio.hierarchical_loading();
CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix
}
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_n0_0();
},
changePreset(id) {
let userPresets = app.cfg.audio.equalizer.presets
let defaultPresets = Clone(this.defaultPresets)
let presets = defaultPresets.concat(userPresets)
console.log(presets)
let preset = presets.find(p => p.preset == id)
console.log(preset)
if (preset) {
this.applyPreset(preset)
}
}
}
});
</script>

View file

@ -14,19 +14,26 @@
</div>
<div class="fs-header" v-if="immersiveEnabled">
<div class="top-nav-group">
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" svg-icon-name="home" page="home">
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('home.title')"
svg-icon="./assets/feather/home.svg" svg-icon-name="home" page="home">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow"
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.listenNow')"
svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow"
page="listen_now"></sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse">
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.browse')"
svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" page="radio">
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.radio')"
svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" page="radio">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.library')" svg-icon="./assets/feather/radio.svg" svg-icon-name="library" page="library">
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.library')"
svg-icon="./assets/feather/radio.svg" svg-icon-name="library" page="library">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = ''" :name="$root.getLz('term.nowPlaying')" svg-icon="./assets/play.svg" svg-icon-name="nowPlaying" page="nowPlaying">
<sidebar-library-item @click.native="tabMode = ''" :name="$root.getLz('term.nowPlaying')"
svg-icon="./assets/play.svg" svg-icon-name="nowPlaying" page="nowPlaying">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" name="" svg-icon="./assets/search.svg" svg-icon-name="search" page="search">
<sidebar-library-item @click.native="tabMode = 'catalog'" name="" svg-icon="./assets/search.svg"
svg-icon-name="search" page="search">
</sidebar-library-item>
</div>
</div>
@ -59,7 +66,8 @@
</div>
<div class="song-artist item-navigate" style="display: inline-block;"
@click="app.getNowPlayingItemDetailed('album') && app.fullscreen(false)">
{{ (app.mk.nowPlayingItem["attributes"]["albumName"]) ? (app.mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
{{ (app.mk.nowPlayingItem["attributes"]["albumName"]) ?
(app.mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
</div>
</div>
@ -139,7 +147,7 @@
v-b-tooltip.hover :title="$root.formatVolumeTooltip()">
</div>
</div>
</div>
</div>
</template>
</div>
<div class="col right-col" v-if="tabMode != ''">
@ -175,77 +183,77 @@
</script>
<script>
Vue.component('fullscreen-view', {
template: '#fullscreen-view',
props: {
time: {
type: Number,
required: false
},
lyrics: {
type: Array,
required: false
},
richlyrics: {
type: Array,
required: false
},
image: {
type: String,
required: false
},
},
data: function () {
return {
app: this.$root,
tabMode: "lyrics",
video: null,
immersiveEnabled: app.cfg.advanced.experiments.includes("immersive-preview")
}
},
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() {
window.addEventListener('keyup', this.onEscapeKeyUp);
},
beforeDestroy() {
window.removeEventListener('keyup', this.onEscapeKeyUp)
},
methods: {
onEscapeKeyUp(event) {
if (event.which === 27) {
app.fullscreen(false);
console.log('js')
}
},
Vue.component('fullscreen-view', {
template: '#fullscreen-view',
props: {
time: {
type: Number,
required: false
},
lyrics: {
type: Array,
required: false
},
richlyrics: {
type: Array,
required: false
},
image: {
type: String,
required: false
},
},
data: function() {
return {
app: this.$root,
tabMode: "lyrics",
video: null,
immersiveEnabled: app.cfg.advanced.experiments.includes("immersive-preview")
}
},
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
}
});
</script>
} 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() {
window.addEventListener('keyup', this.onEscapeKeyUp);
},
beforeDestroy() {
window.removeEventListener('keyup', this.onEscapeKeyUp)
},
methods: {
onEscapeKeyUp(event) {
if (event.which === 27) {
app.fullscreen(false);
console.log('js')
}
},
}
});
</script>

View file

@ -3,12 +3,12 @@
</script>
<script>
var hw = Vue.component('hello-world', {
template: '#hello-world',
methods: {
sayHello: function () {
alert('Hello world!');
}
}
});
</script>
var hw = Vue.component('hello-world', {
template: '#hello-world',
methods: {
sayHello: function() {
alert('Hello world!');
}
}
});
</script>

View file

@ -6,7 +6,7 @@
<template v-if="item.type == 'artists'">
<mediaitem-square :item="item"></mediaitem-square>
</template>
<template v-else>
<template v-else>
<mediaitem-list-item
v-if="getKind(item) == 'song'"
:index="key"
@ -14,11 +14,13 @@
<mediaitem-square v-else :item="item" :type="getKind(item)"></mediaitem-square>
</template>
</template>
<button v-if="triggerEnabled" style="opacity:0;height: 32px;" v-observe-visibility="{callback: visibilityChanged}">{{this.app.getLz('term.showMore')}}
<button v-if="triggerEnabled" style="opacity:0;height: 32px;"
v-observe-visibility="{callback: visibilityChanged}">{{this.app.getLz('term.showMore')}}
</button>
</div>
<transition name="fabfade">
<button class="top-fab" v-show="showFab" @click="scrollToTop()" :aria-label="app.getLz('action.scrollToTop')">
<button class="top-fab" v-show="showFab" @click="scrollToTop()"
:aria-label="app.getLz('action.scrollToTop')">
<%- include("../svg/arrow-up.svg") %>
</button>
</transition>
@ -28,104 +30,104 @@
</div>
</script>
<script>
Vue.component('inline-collection-list', {
template: "#inline-collection-list",
props: {
data: {
required: true
},
title: {
type: String,
required: false
},
type: {
type: String,
required: false,
default: "artists"
},
parentSelector: {
type: String,
required: false,
default: null
},
},
data: function() {
return {
triggerEnabled: true,
canSeeTrigger: false,
showFab: false,
commonKind: "song",
api: this.$root.mk.api,
loading: false,
app: this.$root,
}
},
methods: {
getKind(item) {
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
}
return this.commonKind
},
scrollToTop() {
let target = document.querySelector(".header-text")
document.querySelector(this.parentSelector ?? ".collection-page").scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
})
},
getNext() {
let self = this
this.triggerEnabled = false;
if (typeof this.data.next == "undefined") {
return
}
this.loading = true
this.api.v3.music(this.data.next, app.collectionList.requestBody).then((response) => {
console.log(response)
if (!app.collectionList.response.groups) {
this.data.data = this.data.data.concat(response.data.data);
if (response.data.next) {
this.data.next = response.data.next;
this.triggerEnabled = true;
}
this.loading = false
} else {
if (!response.data.results[app.collectionList.response.groups]) {
this.loading = false
return
}
this.data.data = this.data.data.concat(response.data.results[app.collectionList.response.groups].data);
if (response.data.results[app.collectionList.response.groups].next) {
this.data.next = response.data.results[app.collectionList.response.groups].next;
this.triggerEnabled = true;
this.loading = false
}
}
})
},
headerVisibility: function(isVisible, entry) {
if (isVisible) {
this.showFab = false;
} else {
this.showFab = true;
}
},
visibilityChanged: function(isVisible, entry) {
if (isVisible) {
this.canSeeTrigger = true;
this.getNext();
} else {
this.canSeeTrigger = false;
}
}
Vue.component('inline-collection-list', {
template: "#inline-collection-list",
props: {
data: {
required: true
},
title: {
type: String,
required: false
},
type: {
type: String,
required: false,
default: "artists"
},
parentSelector: {
type: String,
required: false,
default: null
},
},
data: function() {
return {
triggerEnabled: true,
canSeeTrigger: false,
showFab: false,
commonKind: "song",
api: this.$root.mk.api,
loading: false,
app: this.$root,
}
},
methods: {
getKind(item) {
if (typeof item.kind != "undefined") {
this.commonKind = item.kind;
return item.kind
}
})
</script>
if (typeof item.attributes.playParams != "undefined") {
this.commonKind = item.attributes.playParams.kind
return item.attributes.playParams.kind
}
return this.commonKind
},
scrollToTop() {
let target = document.querySelector(".header-text")
document.querySelector(this.parentSelector ?? ".collection-page").scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
})
},
getNext() {
let self = this
this.triggerEnabled = false;
if (typeof this.data.next == "undefined") {
return
}
this.loading = true
this.api.v3.music(this.data.next, app.collectionList.requestBody).then((response) => {
console.log(response)
if (!app.collectionList.response.groups) {
this.data.data = this.data.data.concat(response.data.data);
if (response.data.next) {
this.data.next = response.data.next;
this.triggerEnabled = true;
}
this.loading = false
} else {
if (!response.data.results[app.collectionList.response.groups]) {
this.loading = false
return
}
this.data.data = this.data.data.concat(response.data.results[app.collectionList.response.groups].data);
if (response.data.results[app.collectionList.response.groups].next) {
this.data.next = response.data.results[app.collectionList.response.groups].next;
this.triggerEnabled = true;
this.loading = false
}
}
})
},
headerVisibility: function(isVisible, entry) {
if (isVisible) {
this.showFab = false;
} else {
this.showFab = true;
}
},
visibilityChanged: function(isVisible, entry) {
if (isVisible) {
this.canSeeTrigger = true;
this.getNext();
} else {
this.canSeeTrigger = false;
}
}
}
})
</script>

View file

@ -4,7 +4,7 @@
<template v-for="segment in lyrics" v-if="segmentInRange(segment.ts, segment.te, segment.x)">
<div class="verse-group active">
<template v-for="(verse, verseIndex) in segment.l"
v-if="verseInRange(segment.ts, segment.te, verse.o)">
v-if="verseInRange(segment.ts, segment.te, verse.o)">
<span class="verse verse-active">{{ verse.c }}</span>
</template>
<template v-else>
@ -17,4 +17,4 @@
</template>
</div>
</div>
</script>
</script>

View file

@ -1,192 +1,197 @@
<script type="text/x-template" id="libraryartist-item">
<div v-observe-visibility="{callback: visibilityChanged}"
<div v-observe-visibility="{callback: visibilityChanged}"
@click="select"
class="cd-mediaitem-list-item"
:class="{'mediaitem-selected': app.select_hasMediaItem(guid)}"
@contextmenu="contextMenu">
<div class="artwork" v-show="isVisible" v-if="showArtwork == true">
<mediaitem-artwork
:url="getArtwork()"
size="50"
:type="item.type"></mediaitem-artwork>
</div>
<div class="info-rect" :style="{'padding-left': (showArtwork ? '' : '16px')}"
@dblclick="app.routeView(item)">
<div class="title text-overflow-elipsis">
{{ item.attributes.name }}
</div>
<div class="artwork" v-show="isVisible" v-if="showArtwork == true">
<mediaitem-artwork
:url="getArtwork()"
size="50"
:type="item.type"></mediaitem-artwork>
</div>
<div class="info-rect" :style="{'padding-left': (showArtwork ? '' : '16px')}"
@dblclick="app.routeView(item)">
<div class="title text-overflow-elipsis">
{{ item.attributes.name }}
</div>
</div>
</div>
</script>
<script>
Vue.component('libraryartist-item', {
template: '#libraryartist-item',
data: function () {
return {
isVisible: false,
addedToLibrary: false,
guid: this.uuidv4(),
app: this.$root
}
},
props: {
'item': {type: Object, required: true},
'parent': {type: String, required: false},
'index': {type: Number, required: false, default: -1},
'show-artwork': {type: Boolean, default: true},
'show-library-status': {type: Boolean, default: true},
'show-meta-data': {type: Boolean, default: false},
'show-duration': {type: Boolean, default: true},
'contextExt': {type: Object, required: false},
},
methods: {
uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
},
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
},
getDataType() {
return this.item.type
},
async select(e) {
let u = this.item
let u1 = await app.mk.api.v3.music(`/v1/me/library/artists/${u.id}/albums`, {
"platform": "web",
"include[library-albums]": "artists,tracks",
"include[library-artists]": "catalog",
"fields[artists]": "url",
"includeOnly": "catalog,artists"
})
app.showCollection({data : Object.assign({},u1.data.data)}, u.attributes.name?? '', '');
app.select_selectMediaItem(u.id, this.getDataType(), this.index, this.guid, true)
},
getArtwork(){
let u = ""
try{
u = this.item.relationships.catalog.data[0].attributes.artwork.url}
catch (e){};
return u;
},
contextMenu(event) {
let self = this
let data_type = this.getDataType()
let item = self.item
item.attributes.artistName = item.attributes.name;
let useMenu = "normal"
if (app.selectedMediaItems.length <= 1) {
app.selectedMediaItems = []
app.select_selectMediaItem(this.item.id, data_type, this.index, this.guid, true)
} else {
useMenu = "multiple"
}
let menus = {
multiple: {
items: [] //
},
normal: {
items: [
{
"name": app.getLz('action.goToArtist'),
"icon": "./assets/feather/user.svg",
"action": function () {
app.searchAndNavigate(self.item, 'artist')
console.log(self.item)
}
},
{
"icon": "./assets/feather/radio.svg",
"name": app.getLz('action.startRadio'),
"action": function () {
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
app.mk.play()
app.selectedMediaItems = []
})
}
},
{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.share'),
"action": function () {
if (!self.item.attributes.url && self.item.relationships){
if (self.item.relationships.catalog){
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => {self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0)? u.data.data[0].attributes.url : u.data.data.attributes.url)})
}
} else {
self.app.copyToClipboard(self.item.attributes.url)}
}
},
]
}
}
if (this.contextExt) {
// if this.context-ext.normal is true append all options to the 'normal' menu which is a kvp of arrays
if (this.contextExt.normal) {
menus.normal.items = menus.normal.items.concat(this.contextExt.normal)
}
if (this.contextExt.multiple) {
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
}
}
//CiderContextMenu.Create(event, menus[useMenu]); // Depreciated Context Menu
app.showMenuPanel(menus[useMenu], event);
},
visibilityChanged: function (isVisible, entry) {
this.isVisible = isVisible
},
addToLibrary() {
let item = this.item
if (item.attributes.playParams.id) {
console.log('adding to library', item.attributes.playParams.id)
app.addToLibrary(item.attributes.playParams.id.toString())
this.addedToLibrary = true
} else if (item.id) {
console.log('adding to library', item.id)
app.addToLibrary(item.id.toString())
this.addedToLibrary = true
}
},
async removeFromLibrary() {
let item = this.item
let params = {"fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"}
let id = item.id ?? item.attributes.playParams.id
let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
id = res.relationships.library.data[0].id
}
let kind = this.item.attributes.playParams.kind ?? this.data.item ?? '';
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
if (item.attributes.playParams.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
} else if (item.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
}
},
playTrack() {
let item = this.item
let parent = this.parent
let childIndex = this.index
console.log(item, parent, childIndex)
if (parent != null && childIndex != null) {
app.queueParentandplayChild(parent, childIndex, item);
} else {
app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)
}
}
Vue.component('libraryartist-item', {
template: '#libraryartist-item',
data: function() {
return {
isVisible: false,
addedToLibrary: false,
guid: this.uuidv4(),
app: this.$root
}
},
props: {
'item': { type: Object, required: true },
'parent': { type: String, required: false },
'index': { type: Number, required: false, default: -1 },
'show-artwork': { type: Boolean, default: true },
'show-library-status': { type: Boolean, default: true },
'show-meta-data': { type: Boolean, default: false },
'show-duration': { type: Boolean, default: true },
'contextExt': { type: Object, required: false },
},
methods: {
uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
},
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
},
getDataType() {
return this.item.type
},
async select(e) {
let u = this.item
let u1 = await app.mk.api.v3.music(`/v1/me/library/artists/${u.id}/albums`, {
"platform": "web",
"include[library-albums]": "artists,tracks",
"include[library-artists]": "catalog",
"fields[artists]": "url",
"includeOnly": "catalog,artists"
})
app.showCollection({ data: Object.assign({}, u1.data.data) }, u.attributes.name ?? '', '');
app.select_selectMediaItem(u.id, this.getDataType(), this.index, this.guid, true)
},
getArtwork() {
let u = ""
try {
u = this.item.relationships.catalog.data[0].attributes.artwork.url
} catch (e) {
}
});
</script>
;
return u;
},
contextMenu(event) {
let self = this
let data_type = this.getDataType()
let item = self.item
item.attributes.artistName = item.attributes.name;
let useMenu = "normal"
if (app.selectedMediaItems.length <= 1) {
app.selectedMediaItems = []
app.select_selectMediaItem(this.item.id, data_type, this.index, this.guid, true)
} else {
useMenu = "multiple"
}
let menus = {
multiple: {
items: [] //
},
normal: {
items: [
{
"name": app.getLz('action.goToArtist'),
"icon": "./assets/feather/user.svg",
"action": function() {
app.searchAndNavigate(self.item, 'artist')
console.log(self.item)
}
},
{
"icon": "./assets/feather/radio.svg",
"name": app.getLz('action.startRadio'),
"action": function() {
app.mk.setStationQueue({ song: self.item.attributes.playParams.id ?? self.item.id }).then(() => {
app.mk.play()
app.selectedMediaItems = []
})
}
},
{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.share'),
"action": function() {
if (!self.item.attributes.url && self.item.relationships) {
if (self.item.relationships.catalog) {
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => {
self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url)
})
}
} else {
self.app.copyToClipboard(self.item.attributes.url)
}
}
},
]
}
}
if (this.contextExt) {
// if this.context-ext.normal is true append all options to the 'normal' menu which is a kvp of arrays
if (this.contextExt.normal) {
menus.normal.items = menus.normal.items.concat(this.contextExt.normal)
}
if (this.contextExt.multiple) {
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
}
}
//CiderContextMenu.Create(event, menus[useMenu]); // Depreciated Context Menu
app.showMenuPanel(menus[useMenu], event);
},
visibilityChanged: function(isVisible, entry) {
this.isVisible = isVisible
},
addToLibrary() {
let item = this.item
if (item.attributes.playParams.id) {
console.log('adding to library', item.attributes.playParams.id)
app.addToLibrary(item.attributes.playParams.id.toString())
this.addedToLibrary = true
} else if (item.id) {
console.log('adding to library', item.id)
app.addToLibrary(item.id.toString())
this.addedToLibrary = true
}
},
async removeFromLibrary() {
let item = this.item
let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" }
let id = item.id ?? item.attributes.playParams.id
let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
id = res.relationships.library.data[0].id
}
let kind = this.item.attributes.playParams.kind ?? this.data.item ?? '';
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
if (item.attributes.playParams.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
} else if (item.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
}
},
playTrack() {
let item = this.item
let parent = this.parent
let childIndex = this.index
console.log(item, parent, childIndex)
if (parent != null && childIndex != null) {
app.queueParentandplayChild(parent, childIndex, item);
} else {
app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)
}
}
}
});
</script>

View file

@ -1,92 +1,106 @@
<script type="text/x-template" id="listennow-child">
<div v-observe-visibility="{callback: visibilityChanged}">
<template v-if="isVisible && recom.attributes.display.kind != 'MusicSuperHeroShelf'">
<div class="row">
<div class="col" v-if="recom?.relationships['primary-content']?.data?.length > 0" style="display: flex; margin-block:1rem;">
<div @click="navigateContent(recom?.relationships['primary-content']?.data[0] ?? recom?.attributes?.title?.contentIds[0] ?? '')" class="listennow-chip" style="height: 40px;width: 40px;align-self: center;margin-right: 10px;" :class="{ 'circle': recom?.relationships['primary-content']?.data[0]?.type == 'artists' }">
<mediaitem-artwork v-if="recom?.relationships['primary-content']?.data[0]?.attributes?.artwork != null" :url="recom?.relationships['primary-content']?.data[0]?.attributes?.artwork?.url" :size="100"></mediaitem-artwork>
</div>
<div @click="navigateContent(recom?.relationships['primary-content']?.data[0] ?? recom?.attributes?.title?.contentIds[0] ?? '')" style="width: fit-content;" :class="{'item-navigate' : (recom?.attributes?.title?.contentIds?.length ?? 0) > 0 | recom?.relationships['primary-content']?.data?.length > 0}">
<span style="opacity: 0.5; font-weight: bold;"> {{ recom.attributes.titleWithoutName.stringForDisplay }} </span>
<h3 style="margin-block: 0"> {{ recom?.relationships['primary-content']?.data[0].attributes?.name ?? recom.attributes.title.stringForDisplay.replace(recom.attributes.titleWithoutName.stringForDisplay, '') }}</h3>
<template v-if="isVisible && recom.attributes.display.kind != 'MusicSuperHeroShelf'">
<div class="row">
<div class="col" v-if="recom?.relationships['primary-content']?.data?.length > 0"
style="display: flex; margin-block:1rem;">
<div @click="navigateContent(recom?.relationships['primary-content']?.data[0] ?? recom?.attributes?.title?.contentIds[0] ?? '')"
class="listennow-chip" style="height: 40px;width: 40px;align-self: center;margin-right: 10px;"
:class="{ 'circle': recom?.relationships['primary-content']?.data[0]?.type == 'artists' }">
<mediaitem-artwork
v-if="recom?.relationships['primary-content']?.data[0]?.attributes?.artwork != null"
:url="recom?.relationships['primary-content']?.data[0]?.attributes?.artwork?.url"
:size="100"></mediaitem-artwork>
</div>
<div @click="navigateContent(recom?.relationships['primary-content']?.data[0] ?? recom?.attributes?.title?.contentIds[0] ?? '')"
style="width: fit-content;"
:class="{'item-navigate' : (recom?.attributes?.title?.contentIds?.length ?? 0) > 0 | recom?.relationships['primary-content']?.data?.length > 0}">
<span style="opacity: 0.5; font-weight: bold;"> {{ recom.attributes.titleWithoutName.stringForDisplay }} </span>
<h3 style="margin-block: 0"> {{
recom?.relationships['primary-content']?.data[0].attributes?.name ??
recom.attributes.title.stringForDisplay.replace(recom.attributes.titleWithoutName.stringForDisplay,
'') }}</h3>
</div>
</div>
<div class="col" v-else style="display: flex; margin-block:1rem;">
<h3 @click="navigateContent(recom?.relationships['primary-content']?.data[0] ?? recom?.attributes?.title?.contentIds[0] ?? '')"
style="width: fit-content; margin-block:0;"
:class="{'item-navigate' : (recom?.attributes?.title?.contentIds?.length ?? 0) > 0 | recom?.relationships['primary-content']?.data?.length > 0}">
{{ recom.attributes.title ? recom.attributes.title.stringForDisplay : " "}}</h3>
</div>
<div class="col-auto cider-flex-center" v-if="recom.relationships.contents.data.length >= 10">
<button class="cd-btn-seeall" @click="showCollection(recom)">{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<div class="col" v-else style="display: flex; margin-block:1rem;">
<h3 @click="navigateContent(recom?.relationships['primary-content']?.data[0] ?? recom?.attributes?.title?.contentIds[0] ?? '')" style="width: fit-content; margin-block:0;" :class="{'item-navigate' : (recom?.attributes?.title?.contentIds?.length ?? 0) > 0 | recom?.relationships['primary-content']?.data?.length > 0}">
{{ recom.attributes.title ? recom.attributes.title.stringForDisplay : " "}}</h3>
</div>
<div class="col-auto cider-flex-center" v-if="recom.relationships.contents.data.length >= 10">
<button class="cd-btn-seeall" @click="showCollection(recom)" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<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
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>
<template v-else>
<mediaitem-scroller-horizontal-sp
:withReason="index==0"
:items="recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-sp>
</template>
</template>
<template v-else>
<mediaitem-scroller-horizontal-sp
:withReason="index==0"
:items="recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-sp>
<template v-else-if="recom.attributes.display.kind != 'MusicSuperHeroShelf'">
<div style="height:330px"></div>
</template>
</template>
<template v-else-if="recom.attributes.display.kind != 'MusicSuperHeroShelf'">
<div style="height:330px"> </div>
</template>
</div>
</div>
</script>
<script>
Vue.component('listennow-child', {
template: "#listennow-child",
props: ["recom","index"],
data: function () {
return {
isVisible: true,
app: this.$root
}
},
methods: {
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')
},
navigateContent: async function (id) {
Vue.component('listennow-child', {
template: "#listennow-child",
props: ["recom", "index"],
data: function() {
return {
isVisible: true,
app: this.$root
}
},
methods: {
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')
},
navigateContent: async function(id) {
if (typeof id != "string") {
app.routeView(id)
} else {
try{
let a = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[albums]=${id}`)
let q1 = a.data?.data[0]
if (q1) {
app.routeView(q1)
} else {
let b = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[artists]=${id}`)
let q2 = b.data?.data[0]
if (q2) {
app.routeView(q2)
} else {
let c = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[playlists]=${id}`)
let q3 = c.data?.data[0]
if (q3) {
app.routeView(q3)
}
}
}
} catch (e) {
let b = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[artists]=${id}`)
let q2 = b.data?.data[0]
if (q2) {
app.routeView(q2)
}
}
if (typeof id != "string") {
app.routeView(id)
} else {
try {
let a = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[albums]=${id}`)
let q1 = a.data?.data[0]
if (q1) {
app.routeView(q1)
} else {
let b = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[artists]=${id}`)
let q2 = b.data?.data[0]
if (q2) {
app.routeView(q2)
} else {
let c = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[playlists]=${id}`)
let q3 = c.data?.data[0]
if (q3) {
app.routeView(q3)
}
}
}
} catch (e) {
let b = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}?ids[artists]=${id}`)
let q2 = b.data?.data[0]
if (q2) {
app.routeView(q2)
}
}
}
})
</script>
}
}
})
</script>

View file

@ -1,69 +1,74 @@
<script type="text/x-template" id="listitem-horizontal">
<div class="listitem-horizontal">
<vue-horizontal>
<div v-for="items in itemPages">
<mediaitem-list-item
v-for="(song, index) in items" :show-library-status="showLibraryStatus" :parent="'listitem-hr' + simplifiedParent"
:index="song.index"
:item="song"></mediaitem-list-item>
</div>
</vue-horizontal>
</div>
<div class="listitem-horizontal">
<vue-horizontal>
<div v-for="items in itemPages">
<mediaitem-list-item
v-for="(song, index) in items" :show-library-status="showLibraryStatus"
:parent="'listitem-hr' + simplifiedParent"
:index="song.index"
:item="song"></mediaitem-list-item>
</div>
</vue-horizontal>
</div>
</script>
<script>
Vue.component('listitem-horizontal', {
template: '#listitem-horizontal',
name: "listitem-horizontal",
props: {
items: {
type: Array,
required: true
},
showLibraryStatus: {
type: Boolean,
default: true
}
},
data: function () {
return {
itemPages: [],
simplifiedParent : []
}
},
mounted() {
// give every item an id
this.items.forEach(function (item, index) {
item.id = index;
});
// split items into pages
this.itemPages = app.arrayToChunk(this.items, 4);
try{
this.simplifiedParent = JSON.stringify(this.items.map ( function(x){return x.attributes.playParams}));
console.debug("simplifiedParent: " + this.simplifiedParent);
}
catch (e){}
},
watch: {
items: function (items) {
// give every item an id
this.items.forEach(function (item, index) {
item.id = index;
});
// split items into pages
this.itemPages = app.arrayToChunk(this.items, 4);
try{
this.simplifiedParent = JSON.stringify(this.items.map ( function(x){return x.attributes.playParams}));
console.log("simplifiedParent: " + this.simplifiedParent);
}
catch (e){}
}
},
methods: {
sayHello: function () {
alert('Hello world!');
}
Vue.component('listitem-horizontal', {
template: '#listitem-horizontal',
name: "listitem-horizontal",
props: {
items: {
type: Array,
required: true
},
showLibraryStatus: {
type: Boolean,
default: true
}
},
data: function() {
return {
itemPages: [],
simplifiedParent: []
}
},
mounted() {
// give every item an id
this.items.forEach(function(item, index) {
item.id = index;
});
// split items into pages
this.itemPages = app.arrayToChunk(this.items, 4);
try {
this.simplifiedParent = JSON.stringify(this.items.map(function(x) {
return x.attributes.playParams
}));
console.debug("simplifiedParent: " + this.simplifiedParent);
} catch (e) {
}
},
watch: {
items: function(items) {
// give every item an id
this.items.forEach(function(item, index) {
item.id = index;
});
// split items into pages
this.itemPages = app.arrayToChunk(this.items, 4);
try {
this.simplifiedParent = JSON.stringify(this.items.map(function(x) {
return x.attributes.playParams
}));
console.log("simplifiedParent: " + this.simplifiedParent);
} catch (e) {
}
});
</script>
}
},
methods: {
sayHello: function() {
alert('Hello world!');
}
}
});
</script>

View file

@ -5,16 +5,17 @@
<h3 class="lyric-line" @click="seekTo(lyric.startTime)" :class="{unsynced : lyric.startTime == 9999999}"
v-bind:line-index="index.toString()">
<template v-if="richlyrics && richlyrics != [] && richlyrics.length > 0">
<div class="richl" >
<template v-for="verse in getVerseLine(index-1)" >
<span class="verse" :lyricstart="lyric.startTime" :versestart="verse.o" >{{ verse.c }}</span>
<div class="richl">
<template v-for="verse in getVerseLine(index-1)">
<span class="verse" :lyricstart="lyric.startTime"
:versestart="verse.o">{{ verse.c }}</span>
</template>
</div>
</template>
<template v-else>
<div class="norm" >
{{ lyric.line }}
</div>
<div class="norm">
{{ lyric.line }}
</div>
</template>
<div class="lyrics-translation" v-if="lyric.translation && lyric.translation != ''">
{{ lyric.translation }}
@ -35,173 +36,182 @@
</template>
<template v-else>
<div class="no-lyrics">
{{app.getLz('term.noLyrics')}}</div>
{{app.getLz('term.noLyrics')}}
</div>
</template>
</div>
</script>
<script>
Vue.component('lyrics-view', {
template: '#lyrics-view',
props: ["time", "lyrics", "richlyrics", "translation", "onindex", "yoffset"],
data: function () {
return {
app: this.$root,
Vue.component('lyrics-view', {
template: '#lyrics-view',
props: ["time", "lyrics", "richlyrics", "translation", "onindex", "yoffset"],
data: function() {
return {
app: this.$root,
}
},
watch: {
time: function() {
if (((app.lyricon && app.drawer.open) || app.appMode == 'fullscreen') && this.$refs.lyricsview) {
let currentLine = this.$refs.lyricsview.querySelector(`.lyric-line.active`)
if (currentLine && currentLine.getElementsByClassName('lyricWaiting').length > 0) {
let duration = currentLine.getAttribute("end") - currentLine.getAttribute("start");
let u = (this.time - currentLine.getAttribute("start")) / duration;
if (u < 0.25 && !currentLine.classList.contains('mode1')) {
try {
currentLine.classList.add('mode1');
currentLine.classList.remove('mode3');
currentLine.classList.remove('mode2');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = `dotOpacity ${0.25 * duration}s cubic-bezier(0.42, 0, 0.58, 1) forwards`;
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot3')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot2')[0].style.opacity = 0.25;
currentLine.getElementsByClassName('WaitingDot3')[0].style.opacity = 0.25;
} else if (u >= 0.25 && u < 0.5 && !currentLine.classList.contains('mode2')) {
try {
currentLine.classList.add('mode2');
currentLine.classList.remove('mode1');
currentLine.classList.remove('mode3');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = `dotOpacity ${0.25 * duration}s cubic-bezier(0.42, 0, 0.58, 1) forwards`;
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot3')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot1')[0].style.opacity = 1;
currentLine.getElementsByClassName('WaitingDot3')[0].style.opacity = 0.25;
} else if (u >= 0.5 && u < 0.75 && !currentLine.classList.contains('mode3')) {
try {
currentLine.classList.add('mode3');
currentLine.classList.remove('mode1');
currentLine.classList.remove('mode2');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot3')[0].style.animation = `dotOpacity ${0.25 * duration}s cubic-bezier(0.42, 0, 0.58, 1) forwards`;
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot1')[0].style.opacity = 1;
currentLine.getElementsByClassName('WaitingDot2')[0].style.opacity = 1;
} else if (u >= 0.75 && currentLine.classList.contains('mode3')) {
try {
currentLine.classList.remove('mode1');
currentLine.classList.remove('mode2');
currentLine.classList.remove('mode3');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot1')[0].style.opacity = 1;
currentLine.getElementsByClassName('WaitingDot2')[0].style.opacity = 1;
}
},
watch: {
time: function () {
if (((app.lyricon && app.drawer.open) || app.appMode == 'fullscreen') && this.$refs.lyricsview) {
let currentLine = this.$refs.lyricsview.querySelector(`.lyric-line.active`)
if (currentLine && currentLine.getElementsByClassName('lyricWaiting').length > 0) {
let duration = currentLine.getAttribute("end") - currentLine.getAttribute("start");
let u = (this.time - currentLine.getAttribute("start")) / duration;
if (u < 0.25 && !currentLine.classList.contains('mode1')) {
try {
currentLine.classList.add('mode1');
currentLine.classList.remove('mode3');
currentLine.classList.remove('mode2');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = `dotOpacity ${0.25 * duration}s cubic-bezier(0.42, 0, 0.58, 1) forwards`;
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot3')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot2')[0].style.opacity = 0.25;
currentLine.getElementsByClassName('WaitingDot3')[0].style.opacity = 0.25;
} else if (u >= 0.25 && u < 0.5 && !currentLine.classList.contains('mode2')) {
try {
currentLine.classList.add('mode2');
currentLine.classList.remove('mode1');
currentLine.classList.remove('mode3');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = `dotOpacity ${0.25 * duration}s cubic-bezier(0.42, 0, 0.58, 1) forwards`;
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot3')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot1')[0].style.opacity = 1;
currentLine.getElementsByClassName('WaitingDot3')[0].style.opacity = 0.25;
} else if (u >= 0.5 && u < 0.75 && !currentLine.classList.contains('mode3')) {
try {
currentLine.classList.add('mode3');
currentLine.classList.remove('mode1');
currentLine.classList.remove('mode2');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot3')[0].style.animation = `dotOpacity ${0.25 * duration}s cubic-bezier(0.42, 0, 0.58, 1) forwards`;
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot1')[0].style.opacity = 1;
currentLine.getElementsByClassName('WaitingDot2')[0].style.opacity = 1;
} else if (u >= 0.75 && currentLine.classList.contains('mode3')) {
try {
currentLine.classList.remove('mode1');
currentLine.classList.remove('mode2');
currentLine.classList.remove('mode3');
} catch (e) {
}
currentLine.getElementsByClassName('WaitingDot1')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot2')[0].style.animation = ``;
currentLine.getElementsByClassName('WaitingDot1')[0].style.opacity = 1;
currentLine.getElementsByClassName('WaitingDot2')[0].style.opacity = 1;
}
}
}
this.getActiveLyric();
}
}
},
methods: {
seekTo(startTime) {
if (startTime != 9999999) this.app.seekTo(startTime, false);
},
getActiveLyric() {
const delayfix = 0.1
const prevLine = app.currentLyricsLine;
for (var i = 0; i < this.lyrics.length; i++) {
if (this.time + delayfix >= this.lyrics[i].startTime && this.time + delayfix <= app.lyrics[i].endTime) {
if (app.currentLyricsLine != i) {
app.currentLyricsLine = i;
if (((app.lyricon && app.drawer.open) || app.appMode == 'fullscreen') && this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`)) {
if (this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`)) {
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).classList.remove("active");
}
this.getActiveLyric();
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).classList.add("active")
if (this.checkIfScrollIsStatic) {
let lyricElement = this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`)
// this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).scrollIntoView({
// behavior: "smooth",
// block: "nearest", inline: 'start'
// })
let parent = lyricElement.parentElement
let parentRect = parent.getBoundingClientRect()
let lyricElementRect = lyricElement.getBoundingClientRect()
let parentScrollTop = parent.scrollTop
let parentScrollLeft = parent.scrollLeft
let parentScrollTopDiff = parentScrollTop - parentRect.top
let parentScrollLeftDiff = parentScrollLeft - parentRect.left
let lyricElementScrollTop = lyricElementRect.top + parentScrollTopDiff
let lyricElementScrollLeft = lyricElementRect.left + parentScrollLeftDiff
let scrollTopDiff = lyricElementScrollTop - parentScrollTop
let scrollLeftDiff = lyricElementScrollLeft - parentScrollLeft
let scrollTop = parent.scrollTop + scrollTopDiff
let scrollLeft = parent.scrollLeft + scrollLeftDiff
parent.scrollTo({
top: scrollTop - (this.yoffset ?? 128),
left: scrollLeft,
behavior: 'smooth'
})
}
}
} else if (app.currentLyricsLine == 0 && app.drawer.open) {
if (this.$refs.lyricsview.querySelector(`.lyric-line[line-index="0"]`) && !this.$refs.lyricsview.querySelector(`.lyric-line[line-index="0"]`).classList.contains("active"))
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="0"]`).classList.add("active");
}
},
methods: {
seekTo(startTime) {
if (startTime != 9999999) this.app.seekTo(startTime, false);
},
getActiveLyric() {
const delayfix = 0.1
const prevLine = app.currentLyricsLine;
for (var i = 0; i < this.lyrics.length; i++) {
if (this.time + delayfix >= this.lyrics[i].startTime && this.time + delayfix <= app.lyrics[i].endTime) {
if (app.currentLyricsLine != i) {
app.currentLyricsLine = i;
if (((app.lyricon && app.drawer.open) || app.appMode == 'fullscreen') && this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`)) {
if (this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`)) {this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).classList.remove("active");}
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).classList.add("active")
if (this.checkIfScrollIsStatic) {
let lyricElement = this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`)
// this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${i}"]`).scrollIntoView({
// behavior: "smooth",
// block: "nearest", inline: 'start'
// })
let parent = lyricElement.parentElement
let parentRect = parent.getBoundingClientRect()
let lyricElementRect = lyricElement.getBoundingClientRect()
let parentScrollTop = parent.scrollTop
let parentScrollLeft = parent.scrollLeft
let parentScrollTopDiff = parentScrollTop - parentRect.top
let parentScrollLeftDiff = parentScrollLeft - parentRect.left
let lyricElementScrollTop = lyricElementRect.top + parentScrollTopDiff
let lyricElementScrollLeft = lyricElementRect.left + parentScrollLeftDiff
let scrollTopDiff = lyricElementScrollTop - parentScrollTop
let scrollLeftDiff = lyricElementScrollLeft - parentScrollLeft
let scrollTop = parent.scrollTop + scrollTopDiff
let scrollLeft = parent.scrollLeft + scrollLeftDiff
parent.scrollTo({
top: scrollTop - (this.yoffset ?? 128),
left: scrollLeft,
behavior: 'smooth'
})
}
}
} else if (app.currentLyricsLine == 0 && app.drawer.open) {
if (this.$refs.lyricsview.querySelector(`.lyric-line[line-index="0"]`) && !this.$refs.lyricsview.querySelector(`.lyric-line[line-index="0"]`).classList.contains("active"))
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="0"]`).classList.add("active");
}
break;
}
}
try{
if ((app.drawer.open) || app.appMode == 'fullscreen'){
try{this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).childNodes.classList.remove("verse-active");} catch(e){}
for (child of this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${app.currentLyricsLine}"]`).querySelectorAll(".verse")){
if (this.time + 0.1 >= child.getAttribute("lyricstart") * 1 + child.getAttribute("versestart") * 1){
child.classList.add("verse-active");
} else {child.classList.remove("verse-active");}}
}
} catch(e){}
},
getActiveVerse(timeStart, timeEnd, verseTime) {
let relativeTime = this.time - timeStart
console.log(this.time,timeEnd,timeStart,relativeTime >= verseTime && relativeTime <= timeEnd - timeStart)
return relativeTime >= verseTime && relativeTime <= timeEnd - timeStart
},
getVerseLine(index) {
if (this.richlyrics[index] != null && this.richlyrics[index].l != null) {
return this.richlyrics[index].l
}
else return []
},
qqInstrumental(lyrics) {
for(lyric of lyrics){
if (lyric.line.includes("纯音乐") && lyric.line.includes("欣赏")){
return true
}
}
return false
},
checkIfScrollIsStatic : setInterval(() => {
break;
}
}
try {
if ((app.drawer.open) || app.appMode == 'fullscreen') {
try {
if (position === this.$refs.lyricsview.scrollTop) {
clearInterval(checkIfScrollIsStatic)
// do something
}
position = this.$refs.lyricsview.scrollTop
this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${prevLine}"]`).childNodes.classList.remove("verse-active");
} catch (e) {
}
}, 50)
,
for (child of this.$refs.lyricsview.querySelector(`.lyric-line[line-index="${app.currentLyricsLine}"]`).querySelectorAll(".verse")) {
if (this.time + 0.1 >= child.getAttribute("lyricstart") * 1 + child.getAttribute("versestart") * 1) {
child.classList.add("verse-active");
} else {
child.classList.remove("verse-active");
}
}
}
} catch (e) {
}
});
</script>
},
getActiveVerse(timeStart, timeEnd, verseTime) {
let relativeTime = this.time - timeStart
console.log(this.time, timeEnd, timeStart, relativeTime >= verseTime && relativeTime <= timeEnd - timeStart)
return relativeTime >= verseTime && relativeTime <= timeEnd - timeStart
},
getVerseLine(index) {
if (this.richlyrics[index] != null && this.richlyrics[index].l != null) {
return this.richlyrics[index].l
} else return []
},
qqInstrumental(lyrics) {
for (lyric of lyrics) {
if (lyric.line.includes("纯音乐") && lyric.line.includes("欣赏")) {
return true
}
}
return false
},
checkIfScrollIsStatic: setInterval(() => {
try {
if (position === this.$refs.lyricsview.scrollTop) {
clearInterval(checkIfScrollIsStatic)
// do something
}
position = this.$refs.lyricsview.scrollTop
} catch (e) {
}
}, 50)
,
}
});
</script>

View file

@ -1,5 +1,6 @@
<script type="text/x-template" id="mediaitem-artwork">
<div class="mediaitem-artwork" :style="awStyle" @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="imgSrc"
ref="image"
decoding="async"
@ -14,138 +15,138 @@
</script>
<script>
Vue.component('mediaitem-artwork', {
template: '#mediaitem-artwork',
props: {
size: {
type: [String, Number],
default: '120'
},
width: {
type: [String, Number],
required: false
},
bgcolor: {
type: String,
default: ''
},
url: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
video: {
type: String,
required: false
},
videoPriority: {
type: Boolean,
required: false
},
shadow: {
type: String,
default: ''
},
upscaling: {
type: Boolean,
default: false
}
Vue.component('mediaitem-artwork', {
template: '#mediaitem-artwork',
props: {
size: {
type: [String, Number],
default: '120'
},
width: {
type: [String, Number],
required: false
},
bgcolor: {
type: String,
default: ''
},
url: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
video: {
type: String,
required: false
},
videoPriority: {
type: Boolean,
required: false
},
shadow: {
type: String,
default: ''
},
upscaling: {
type: Boolean,
default: false
}
},
data: function() {
return {
app: this.$root,
isVisible: false,
style: {
"box-shadow": ""
},
data: function () {
return {
app: this.$root,
isVisible: false,
style: {
"box-shadow": ""
},
awStyle: {
background: this.bgcolor
},
imgStyle: {
opacity: 0,
transition: "opacity .25s linear"
},
classes: [],
imgSrc: ""
}
awStyle: {
background: this.bgcolor
},
computed: {
windowRelativeScale: function () {
return app.$store.state.windowRelativeScale;
}
imgStyle: {
opacity: 0,
transition: "opacity .25s linear"
},
watch: {
windowRelativeScale: function (newValue, oldValue) {
this.swapImage(newValue)
},
url: function (newValue, oldValue) {
this.imgSrc = app.getMediaItemArtwork(this.url, this.size, this.width)
}
},
mounted() {
this.getClasses()
this.imgSrc = app.getMediaItemArtwork(this.url, this.size, this.width)
},
methods: {
swapImage(newValue) {
if(!this.upscaling || window.devicePixelRatio !== 1) return
if (newValue > 1.5) {
this.imgSrc = app.getMediaItemArtwork(this.url, parseInt(this.size * 2.0), parseInt(this.size * 2.0));
}
},
imgLoaded() {
this.imgStyle.opacity = 1
this.swapImage(app.$store.state.windowRelativeScale)
// this.awStyle.background = ""
},
contextMenu(event) {
let self = this
app.showMenuPanel({
items: {
"save": {
name: app.getLz('action.openArtworkInBrowser'),
action: () => {
window.open(app.getMediaItemArtwork(self.url, 1024, 1024))
}
}
}
}, event)
},
getVideoPriority() {
if (app.cfg.visual.animated_artwork == "always") {
return true;
} else if (this.videoPriority && app.cfg.visual.animated_artwork == "limited") {
return true
} else if (app.cfg.visual.animated_artwork == "disabled") {
return false
}
return this.videoPriority
},
getClasses() {
switch (this.shadow) {
case "none":
this.classes.push("no-shadow")
break;
case "large":
this.classes.push("shadow")
break;
case "subtle":
this.classes.push("subtle-shadow")
break;
default:
break;
}
return this.classes;
},
getArtworkStyle() {
return {
width: this.size + 'px',
height: this.size + 'px'
};
}
classes: [],
imgSrc: ""
}
},
computed: {
windowRelativeScale: function() {
return app.$store.state.windowRelativeScale;
}
},
watch: {
windowRelativeScale: function(newValue, oldValue) {
this.swapImage(newValue)
},
url: function(newValue, oldValue) {
this.imgSrc = app.getMediaItemArtwork(this.url, this.size, this.width)
}
},
mounted() {
this.getClasses()
this.imgSrc = app.getMediaItemArtwork(this.url, this.size, this.width)
},
methods: {
swapImage(newValue) {
if (!this.upscaling || window.devicePixelRatio !== 1) return
if (newValue > 1.5) {
this.imgSrc = app.getMediaItemArtwork(this.url, parseInt(this.size * 2.0), parseInt(this.size * 2.0));
}
});
</script>
},
imgLoaded() {
this.imgStyle.opacity = 1
this.swapImage(app.$store.state.windowRelativeScale)
// this.awStyle.background = ""
},
contextMenu(event) {
let self = this
app.showMenuPanel({
items: {
"save": {
name: app.getLz('action.openArtworkInBrowser'),
action: () => {
window.open(app.getMediaItemArtwork(self.url, 1024, 1024))
}
}
}
}, event)
},
getVideoPriority() {
if (app.cfg.visual.animated_artwork == "always") {
return true;
} else if (this.videoPriority && app.cfg.visual.animated_artwork == "limited") {
return true
} else if (app.cfg.visual.animated_artwork == "disabled") {
return false
}
return this.videoPriority
},
getClasses() {
switch (this.shadow) {
case "none":
this.classes.push("no-shadow")
break;
case "large":
this.classes.push("shadow")
break;
case "subtle":
this.classes.push("subtle-shadow")
break;
default:
break;
}
return this.classes;
},
getArtworkStyle() {
return {
width: this.size + 'px',
height: this.size + 'px'
};
}
}
});
</script>

View file

@ -24,14 +24,14 @@
</script>
<script>
Vue.component('mediaitem-hrect', {
template: '#mediaitem-hrect',
props: ['item'],
data: function () {
return {
app: this.$root,
}
},
methods: {}
});
</script>
Vue.component('mediaitem-hrect', {
template: '#mediaitem-hrect',
props: ['item'],
data: function() {
return {
app: this.$root,
}
},
methods: {}
});
</script>

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@
<div class="button" style="
border-radius: 50%;
background: rgba(50,50,50,0.7);"
:style="[(!(item.attributes?.playParams ? (item.attributes?.playParams?.kind ?? (item.type ?? '')): (item.type ?? '')).includes('radioStation') && !(item.attributes?.playParams ? (item.attributes?.playParams?.kind ?? (item.type ?? '')): (item.type ?? '')).includes('song')) ?
:style="[(!(item.attributes?.playParams ? (item.attributes?.playParams?.kind ?? (item.type ?? '')): (item.type ?? '')).includes('radioStation') && !(item.attributes?.playParams ? (item.attributes?.playParams?.kind ?? (item.type ?? '')): (item.type ?? '')).includes('song')) ?
{'margin': '205px',
'margin-left': '260px', 'margin-bottom': '140px',
width: '30px',
@ -57,18 +57,18 @@
</script>
<script>
Vue.component('mediaitem-mvview-sp', {
template: '#mediaitem-mvview-sp',
props: ['item', "imagesize", "badge"],
data: function () {
return {
app: this.$root,
}
},
methods: {
log(item) {
console.log(item);
},
}
});
</script>
Vue.component('mediaitem-mvview-sp', {
template: '#mediaitem-mvview-sp',
props: ['item', "imagesize", "badge"],
data: function() {
return {
app: this.$root,
}
},
methods: {
log(item) {
console.log(item);
},
}
});
</script>

View file

@ -27,7 +27,9 @@
<div class="title text-overflow-elipsis" @click='app.routeView(item)'>
{{ item.attributes.name ?? '' }}
</div>
<div class="subtitle text-overflow-elipsis item-navigate" v-if="item.attributes.artistName" :style = "{'z-index': ((item.attributes.editorialNotes == null) && item.attributes.artistName) ? '4' : ''}" @click="if (item.attributes.artistName)app.searchAndNavigate(item,'artist')">
<div class="subtitle text-overflow-elipsis item-navigate" v-if="item.attributes.artistName"
:style="{'z-index': ((item.attributes.editorialNotes == null) && item.attributes.artistName) ? '4' : ''}"
@click="if (item.attributes.artistName)app.searchAndNavigate(item,'artist')">
{{ item.attributes.artistName ?? '' }}
</div>
@ -51,14 +53,14 @@
</script>
<script>
Vue.component('mediaitem-mvview', {
template: '#mediaitem-mvview',
props: ['item', "imagesize"],
data: function () {
return {
app: this.$root,
}
},
methods: {}
});
</script>
Vue.component('mediaitem-mvview', {
template: '#mediaitem-mvview',
props: ['item', "imagesize"],
data: function() {
return {
app: this.$root,
}
},
methods: {}
});
</script>

View file

@ -1,14 +1,14 @@
<script type="text/x-template" id="mediaitem-scroller-horizontal-large">
<vue-horizontal>
<mediaitem-square :item="item" :key="item?.id ?? ''"
v-for="item in items"></mediaitem-square>
v-for="item in items"></mediaitem-square>
</vue-horizontal>
</script>
<script>
Vue.component('mediaitem-scroller-horizontal-large', {
template: '#mediaitem-scroller-horizontal-large',
props: ['items']
});
</script>
Vue.component('mediaitem-scroller-horizontal-large', {
template: '#mediaitem-scroller-horizontal-large',
props: ['items']
});
</script>

View file

@ -2,15 +2,15 @@
<vue-horizontal>
<template v-if="browsesp">
<mediaitem-mvview-sp
:item="
:item="
((item?.attributes?.kind != null || item?.attributes?.type == 'editorial-elements')
? item :
? item :
((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) ?? (item)"
:imagesize="imagesize"
:badge="item.attributes ?? [] " v-for="item in items"></mediaitem-mvview-sp>
</template>
<template v-else>
<mediaitem-square :kind="kind" size="600" :key="item?.id ?? ''"
<mediaitem-square :kind="kind" size="600" :key="item?.id ?? ''"
:item="item ? ((item.attributes?.kind != null || item.type == 'editorial-elements') ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"
:imagesize="imagesize"
v-for="item in items"></mediaitem-square>
@ -20,34 +20,34 @@
<script>
Vue.component('mediaitem-scroller-horizontal-mvview', {
template: '#mediaitem-scroller-horizontal-mvview',
props: {
items: {
type: Array,
required: true
},
imagesize: {
type: Number,
required: false
},
browsesp: {
type: Boolean,
required: false
},
kind: {
type: String,
required: false,
default: ""
}
},
data: function () {
return {
app: this.$root,
}
},
// mounted(){
// console.log('hes',this.items)
// }
});
</script>
Vue.component('mediaitem-scroller-horizontal-mvview', {
template: '#mediaitem-scroller-horizontal-mvview',
props: {
items: {
type: Array,
required: true
},
imagesize: {
type: Number,
required: false
},
browsesp: {
type: Boolean,
required: false
},
kind: {
type: String,
required: false,
default: ""
}
},
data: function() {
return {
app: this.$root,
}
},
// mounted(){
// console.log('hes',this.items)
// }
});
</script>

View file

@ -1,26 +1,26 @@
<script type="text/x-template" id="mediaitem-scroller-horizontal-sp">
<div class="cd-hmedia-scroller hmedia-scroller-card">
<vue-horizontal>
<template>
<mediaitem-square kind="card" :item="item" size="300" :reasonShown="withReason"
v-for="item in items"></mediaitem-square>
</template>
</vue-horizontal>
</div>
<div class="cd-hmedia-scroller hmedia-scroller-card">
<vue-horizontal>
<template>
<mediaitem-square kind="card" :item="item" size="300" :reasonShown="withReason"
v-for="item in items"></mediaitem-square>
</template>
</vue-horizontal>
</div>
</script>
<script>
Vue.component('mediaitem-scroller-horizontal-sp', {
template: '#mediaitem-scroller-horizontal-sp',
props: {
'items': { type: Array , required: false },
'withReason': { type: Boolean, required: false, default: false },
},
data: function () {
return {
app: this.$root,
}
},
methods: {}
});
Vue.component('mediaitem-scroller-horizontal-sp', {
template: '#mediaitem-scroller-horizontal-sp',
props: {
'items': { type: Array, required: false },
'withReason': { type: Boolean, required: false, default: false },
},
data: function() {
return {
app: this.$root,
}
},
methods: {}
});
</script>

View file

@ -6,27 +6,27 @@
</script>
<script>
Vue.component('mediaitem-scroller-horizontal', {
template: '#mediaitem-scroller-horizontal',
props: {
'items': {
type: Array,
required: false
},
'kind': {
type: String,
required: false,
defualt: ""
}
},
data: function() {
return {
app: this.$root,
}
},
mounted() {
// this.$refs.horizontal.refresh()
},
methods: {}
});
</script>
Vue.component('mediaitem-scroller-horizontal', {
template: '#mediaitem-scroller-horizontal',
props: {
'items': {
type: Array,
required: false
},
'kind': {
type: String,
required: false,
defualt: ""
}
},
data: function() {
return {
app: this.$root,
}
},
mounted() {
// this.$refs.horizontal.refresh()
},
methods: {}
});
</script>

File diff suppressed because it is too large Load diff

View file

@ -36,105 +36,105 @@
<script>
Vue.component('cider-menu-panel', {
template: '#cider-menu-panel',
data: function () {
return {
app: this.$root,
menuPanel: this.$root.menuPanel,
content: this.$root.menuPanel.content,
getSvgIcon: this.$root.getSvgIcon,
position: [0, 0],
size: [0, 0],
event: this.$root.menuPanel.event,
direction: "down",
elStyle: {
opacity: 0
}
}
},
mounted() {
if (this.event) {
this.position = [this.event.clientX, this.event.clientY];
}
this.$nextTick(() => {
// this.size = [document.querySelector(".menu-panel-body").offsetWidth, document.querySelector(".menu-panel-body").offsetHeight];
// ugly hack
setTimeout(this.getStyle, 1)
});
},
methods: {
getBodyClasses() {
if (this.direction == "down") {
return ["menu-panel-body-down"]
} else if (this.direction == "up") {
return ["menu-panel-body-up"]
} else {
return ["foo"]
}
},
getClasses(item) {
if (item["active"]) {
return "active";
}
},
getStyle() {
let style = {}
this.size = [this.$refs.menubody.offsetWidth, this.$refs.menubody.offsetHeight];
if (this.event) {
style["position"] = "absolute";
style["left"] = this.event.clientX + "px";
style["top"] = this.event.clientY + "px";
// make sure the menu panel isnt off the screen
if (this.event.clientX + this.size[0] > window.innerWidth) {
style["left"] = (this.event.clientX - this.size[0]) + "px";
}
if (this.event.clientY + this.size[1] > window.innerHeight) {
style["top"] = (this.event.clientY - this.size[1]) + "px";
}
// if the panel is above the mouse, set the direction to up
if (this.event.clientY < this.size[1]) {
this.direction = "up";
} else {
this.direction = "down";
}
// check if the panel is too long and goes off the screen vertically,
// if so move it upwards
if (this.event.clientY + this.size[1] > window.innerHeight) {
style["top"] = (this.event.clientY - this.size[1]) + "px";
}
}
style["opacity"] = 1
this.elStyle = style;
},
getItemStyle(item) {
let style = {}
if (item["disabled"]) {
style = Object.assign(style, {
"pointer-events": "none",
"opacity": "0.5",
});
}
return style
},
canDisplay(item) {
if (!item["hidden"]) {
return true
} else {
return false
}
},
async getActions() {
return this.content.items;
},
action(item) {
item.action()
if (!item["keepOpen"]) {
this.menuPanel.visible = false
}
}
Vue.component('cider-menu-panel', {
template: '#cider-menu-panel',
data: function() {
return {
app: this.$root,
menuPanel: this.$root.menuPanel,
content: this.$root.menuPanel.content,
getSvgIcon: this.$root.getSvgIcon,
position: [0, 0],
size: [0, 0],
event: this.$root.menuPanel.event,
direction: "down",
elStyle: {
opacity: 0
}
});
</script>
}
},
mounted() {
if (this.event) {
this.position = [this.event.clientX, this.event.clientY];
}
this.$nextTick(() => {
// this.size = [document.querySelector(".menu-panel-body").offsetWidth, document.querySelector(".menu-panel-body").offsetHeight];
// ugly hack
setTimeout(this.getStyle, 1)
});
},
methods: {
getBodyClasses() {
if (this.direction == "down") {
return ["menu-panel-body-down"]
} else if (this.direction == "up") {
return ["menu-panel-body-up"]
} else {
return ["foo"]
}
},
getClasses(item) {
if (item["active"]) {
return "active";
}
},
getStyle() {
let style = {}
this.size = [this.$refs.menubody.offsetWidth, this.$refs.menubody.offsetHeight];
if (this.event) {
style["position"] = "absolute";
style["left"] = this.event.clientX + "px";
style["top"] = this.event.clientY + "px";
// make sure the menu panel isnt off the screen
if (this.event.clientX + this.size[0] > window.innerWidth) {
style["left"] = (this.event.clientX - this.size[0]) + "px";
}
if (this.event.clientY + this.size[1] > window.innerHeight) {
style["top"] = (this.event.clientY - this.size[1]) + "px";
}
// if the panel is above the mouse, set the direction to up
if (this.event.clientY < this.size[1]) {
this.direction = "up";
} else {
this.direction = "down";
}
// check if the panel is too long and goes off the screen vertically,
// if so move it upwards
if (this.event.clientY + this.size[1] > window.innerHeight) {
style["top"] = (this.event.clientY - this.size[1]) + "px";
}
}
style["opacity"] = 1
this.elStyle = style;
},
getItemStyle(item) {
let style = {}
if (item["disabled"]) {
style = Object.assign(style, {
"pointer-events": "none",
"opacity": "0.5",
});
}
return style
},
canDisplay(item) {
if (!item["hidden"]) {
return true
} else {
return false
}
},
async getActions() {
return this.content.items;
},
action(item) {
item.action()
if (!item["keepOpen"]) {
this.menuPanel.visible = false
}
}
}
});
</script>

View file

@ -2,186 +2,206 @@
<div class="mini-view" tabindex="0">
<div class="background">
</div>
<div class="player-pin" title="Pin to Top" v-if="app.cfg.visual.miniplayer_top_toggle === false" @click="app.pinMiniPlayer()">
<div class="player-pin" title="Pin to Top" v-if="app.cfg.visual.miniplayer_top_toggle === false"
@click="app.pinMiniPlayer()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" fill="none" class="feather feather-pin">
<path d="M7.05664 16.3613C7.05664 17.1523 7.59277 17.6797 8.42773 17.6797H13.1299V21.8369C13.1299 23.0762 13.7539 24.3242 14 24.3242C14.2373 24.3242 14.8613 23.0762 14.8613 21.8369V17.6797H19.5635C20.3984 17.6797 20.9346 17.1523 20.9346 16.3613C20.9346 14.4717 19.4316 12.5293 16.9531 11.6152L16.6631 7.52832C17.9727 6.78125 19.0098 5.96387 19.4668 5.38379C19.7041 5.06738 19.8271 4.75098 19.8271 4.46973C19.8271 3.88965 19.3789 3.45898 18.7197 3.45898H9.27148C8.6123 3.45898 8.16406 3.88965 8.16406 4.46973C8.16406 4.75098 8.28711 5.06738 8.52441 5.38379C8.98145 5.96387 10.0186 6.78125 11.3281 7.52832L11.0469 11.6152C8.55957 12.5293 7.05664 14.4717 7.05664 16.3613Z" fill="#ff2654"/>
<path d="M7.05664 16.3613C7.05664 17.1523 7.59277 17.6797 8.42773 17.6797H13.1299V21.8369C13.1299 23.0762 13.7539 24.3242 14 24.3242C14.2373 24.3242 14.8613 23.0762 14.8613 21.8369V17.6797H19.5635C20.3984 17.6797 20.9346 17.1523 20.9346 16.3613C20.9346 14.4717 19.4316 12.5293 16.9531 11.6152L16.6631 7.52832C17.9727 6.78125 19.0098 5.96387 19.4668 5.38379C19.7041 5.06738 19.8271 4.75098 19.8271 4.46973C19.8271 3.88965 19.3789 3.45898 18.7197 3.45898H9.27148C8.6123 3.45898 8.16406 3.88965 8.16406 4.46973C8.16406 4.75098 8.28711 5.06738 8.52441 5.38379C8.98145 5.96387 10.0186 6.78125 11.3281 7.52832L11.0469 11.6152C8.55957 12.5293 7.05664 14.4717 7.05664 16.3613Z"
fill="#ff2654" />
</svg>
</div>
<div class="player-pin" title="Unpin to Top" v-if="app.cfg.visual.miniplayer_top_toggle === true" @click="app.pinMiniPlayer(false)">
<div class="player-pin" title="Unpin to Top" v-if="app.cfg.visual.miniplayer_top_toggle === true"
@click="app.pinMiniPlayer(false)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" fill="none" class="feather feather-pin-slashed">
<path d="M9.271 3.459c-.659 0-1.107.43-1.107 1.01 0 .282.114.59.352.897.448.59 1.494 1.415 2.777 2.162l-.07 1.02 8.99 8.991c.458-.202.722-.615.722-1.178 0-1.89-1.503-3.832-3.947-4.746l-.29-4.087c1.275-.747 2.312-1.555 2.76-2.144.246-.308.37-.633.37-.914 0-.58-.45-1.011-1.108-1.011H9.27ZM5.15 6.061l16.076 16.057c.272.281.73.273.993 0a.703.703 0 0 0 0-.984L6.15 5.076a.716.716 0 0 0-1.002 0 .711.711 0 0 0 0 .985Zm1.908 10.3c0 .791.536 1.319 1.37 1.319h4.703v4.157c0 1.24.624 2.487.861 2.487.246 0 .87-1.248.87-2.487V17.81h.413l-5.537-5.545c-1.678 1.002-2.68 2.557-2.68 4.095Z" fill="#ff2654"/>
<path d="M9.271 3.459c-.659 0-1.107.43-1.107 1.01 0 .282.114.59.352.897.448.59 1.494 1.415 2.777 2.162l-.07 1.02 8.99 8.991c.458-.202.722-.615.722-1.178 0-1.89-1.503-3.832-3.947-4.746l-.29-4.087c1.275-.747 2.312-1.555 2.76-2.144.246-.308.37-.633.37-.914 0-.58-.45-1.011-1.108-1.011H9.27ZM5.15 6.061l16.076 16.057c.272.281.73.273.993 0a.703.703 0 0 0 0-.984L6.15 5.076a.716.716 0 0 0-1.002 0 .711.711 0 0 0 0 .985Zm1.908 10.3c0 .791.536 1.319 1.37 1.319h4.703v4.157c0 1.24.624 2.487.861 2.487.246 0 .87-1.248.87-2.487V17.81h.413l-5.537-5.545c-1.678 1.002-2.68 2.557-2.68 4.095Z"
fill="#ff2654" />
</svg>
</div>
<div class="player-exit" title="Close" @click="app.miniPlayer(false)">
<svg fill="#323232e3" width="21" height="21" viewBox="0 0 21 21" aria-role="presentation" focusable="false" xmlns="http://www.w3.org/2000/svg">
<svg fill="#323232e3" width="21" height="21" viewBox="0 0 21 21" aria-role="presentation" focusable="false"
xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient gradientUnits="userSpaceOnUse" cx="10.5" cy="10.5" r="10.5" id="gradient-0">
<stop offset="0" style="stop-color: rgba(168, 163, 163, 1)"/>
<stop offset="1" style="stop-color: rgba(118, 111, 111, 1)"/>
<stop offset="0" style="stop-color: rgba(168, 163, 163, 1)" />
<stop offset="1" style="stop-color: rgba(118, 111, 111, 1)" />
</radialGradient>
</defs>
<path d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z"
fill-rule="nonzero" style="stroke-miterlimit: 11; vector-effect: non-scaling-stroke; stroke-width: 31px; fill: url(#gradient-0);"/>
fill-rule="nonzero"
style="stroke-miterlimit: 11; vector-effect: non-scaling-stroke; stroke-width: 31px; fill: url(#gradient-0);" />
</svg>
</div>
</div>
<div class="col artwork-col">
<div class="artwork" @click="app.miniPlayer(false)">
<div class="artwork" @click="app.miniPlayer(false)">
<mediaitem-artwork
:size="600"
:url="image ?? ''"
></mediaitem-artwork>
</div>
<div class="controls-parents">
<template v-if="app.mkReady()">
<div class="app-playback-controls" @mouseover="app.chrome.progresshover = true"
@mouseleave="app.chrome.progresshover = false" @contextmenu="app.nowPlayingContextMenu">
<div class="playback-info">
<div class="song-name">
{{ app.mk.nowPlayingItem["attributes"]["name"] }}
</div>
<div
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap; margin-top: 0.25vh; overflow: hidden; margin-bottom: 5px;">
<div class="item-navigate song-artist" style="display: inline-block;"
@click="app.getNowPlayingItemDetailed(`artist`)">
{{ app.mk.nowPlayingItem["attributes"]["artistName"] }}
<template v-if="app.mkReady()">
<div class="app-playback-controls" @mouseover="app.chrome.progresshover = true"
@mouseleave="app.chrome.progresshover = false" @contextmenu="app.nowPlayingContextMenu">
<div class="playback-info">
<div class="song-name">
{{ app.mk.nowPlayingItem["attributes"]["name"] }}
</div>
<div class="song-artist item-navigate" style="display: inline-block;"
@click="app.getNowPlayingItemDetailed('album')">
{{ (app.mk.nowPlayingItem["attributes"]["albumName"]) ? (" — " +
app.mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
</div>
</div>
<div class="song-progress">
<div class="song-duration" style="justify-content: space-between; height: 1px; margin-bottom: 1px;"
:style="[app.chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
<p style="width: auto">{{ app.convertTime(app.getSongProgress()) }}</p>
<p style="width: auto">{{ app.convertTime(app.mk.currentPlaybackDuration) }}</p>
<div
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap; margin-top: 0.25vh; overflow: hidden; margin-bottom: 5px;">
<div class="item-navigate song-artist" style="display: inline-block;"
@click="app.getNowPlayingItemDetailed(`artist`)">
{{ app.mk.nowPlayingItem["attributes"]["artistName"] }}
</div>
<div class="song-artist item-navigate" style="display: inline-block;"
@click="app.getNowPlayingItemDetailed('album')">
{{ (app.mk.nowPlayingItem["attributes"]["albumName"]) ? (" — " +
app.mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
</div>
</div>
<input type="range" step="0.01" min="0" :style="app.progressBarStyle()"
@input="app.playerLCD.desiredDuration = $event.target.value;app.playerLCD.userInteraction = true"
@mouseup="app.mk.seekToTime($event.target.value);app.playerLCD.desiredDuration = 0;app.playerLCD.userInteraction = false"
:max="app.mk.currentPlaybackDuration" :value="app.getSongProgress()">
<div class="song-progress">
<div class="song-duration"
style="justify-content: space-between; height: 1px; margin-bottom: 1px;"
:style="[app.chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
<p style="width: auto">{{ app.convertTime(app.getSongProgress()) }}</p>
<p style="width: auto">{{ app.convertTime(app.mk.currentPlaybackDuration) }}</p>
</div>
<input type="range" step="0.01" min="0" :style="app.progressBarStyle()"
@input="app.playerLCD.desiredDuration = $event.target.value;app.playerLCD.userInteraction = true"
@mouseup="app.mk.seekToTime($event.target.value);app.playerLCD.desiredDuration = 0;app.playerLCD.userInteraction = false"
:max="app.mk.currentPlaybackDuration" :value="app.getSongProgress()">
</div>
</div>
<div class="control-buttons">
<div class="app-chrome-item 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 class="app-chrome-item volume display--large">
<div class="input-container">
<button class="volume-button--small volume" @click="app.muteButtonPressed()"
:class="{'active': app.cfg.audio.volume == 0}"></button>
<input type="range" class="slider" @wheel="app.volumeWheel"
:step="app.cfg.audio.volumeStep" min="0" :max="app.cfg.audio.maxVolume"
v-model="app.mk.volume"
v-if="typeof app.mk.volume != 'undefined'" @change="app.checkMuteChange()">
</div>
</div>
</div>
<div class="control-buttons">
<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 class="app-chrome-item volume display--large">
<div class="input-container">
<button class="volume-button--small volume" @click="app.muteButtonPressed()" :class="{'active': app.cfg.audio.volume == 0}"></button>
<input type="range" class="slider" @wheel="app.volumeWheel" :step="app.cfg.audio.volumeStep" min="0" :max="app.cfg.audio.maxVolume" v-model="app.mk.volume"
v-if="typeof app.mk.volume != 'undefined'" @change="app.checkMuteChange()">
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
<!-- <div class="row fs-row">
<div class="col right-col" v-if="tabMode != ''">
<div class="fs-info">
<div>Name</div>
<div>Name</div>
<div>Name</div>
</div>
<div class="lyrics-col" v-if="tabMode == 'lyrics'">
<lyrics-view :yoffset="120" :time="time" :lyrics="lyrics"
:richlyrics="richlyrics"></lyrics-view>
</div>
<div class="queue-col" v-if="tabMode == 'queue'">
<cider-queue v-if="tabMode == 'queue'" ref="queue" ></cider-queue>
</div>
</div>
<!-- <div class="row fs-row">
<div class="col right-col" v-if="tabMode != ''">
<div class="fs-info">
<div>Name</div>
<div>Name</div>
<div>Name</div>
</div>
<div class="lyrics-col" v-if="tabMode == 'lyrics'">
<lyrics-view :yoffset="120" :time="time" :lyrics="lyrics"
:richlyrics="richlyrics"></lyrics-view>
</div>
<div class="queue-col" v-if="tabMode == 'queue'">
<cider-queue v-if="tabMode == 'queue'" ref="queue" ></cider-queue>
</div>
</div>
</div> -->
<!-- <div class="tab-toggles">
<div class="lyrics" :class="{active: tabMode == 'lyrics'}" @click="tabMode = (tabMode == 'lyrics') ? '' : 'lyrics'"></div>
<div class="queue" :class="{active: tabMode == 'queue'}" @click="tabMode = (tabMode == 'queue') ? '' :'queue'"></div>
</div> -->
</div> -->
<!-- <div class="tab-toggles">
<div class="lyrics" :class="{active: tabMode == 'lyrics'}" @click="tabMode = (tabMode == 'lyrics') ? '' : 'lyrics'"></div>
<div class="queue" :class="{active: tabMode == 'queue'}" @click="tabMode = (tabMode == 'queue') ? '' :'queue'"></div>
</div> -->
</div>
</script>
<script>
Vue.component('mini-view', {
template: '#mini-view',
props: {
time: {
type: Number,
required: false
},
lyrics: {
type: Array,
required: false
},
richlyrics: {
type: Array,
required: false
},
image: {
type: String,
required: false
},
},
data: function () {
return {
app: this.$root,
tabMode: "",
}
},
beforeMount() {
window.addEventListener('keyup', this.onEscapeKeyUp);
},
beforeDestroy() {
window.removeEventListener('keyup', this.onEscapeKeyUp)
},
mounted() {
app.pinMiniPlayer(true)
},
methods: {
onEscapeKeyUp(event) {
if (event.which === 27) {
app.miniPlayer(false);
console.log('js')
}
},
Vue.component('mini-view', {
template: '#mini-view',
props: {
time: {
type: Number,
required: false
},
lyrics: {
type: Array,
required: false
},
richlyrics: {
type: Array,
required: false
},
image: {
type: String,
required: false
},
},
data: function() {
return {
app: this.$root,
tabMode: "",
}
},
beforeMount() {
window.addEventListener('keyup', this.onEscapeKeyUp);
},
beforeDestroy() {
window.removeEventListener('keyup', this.onEscapeKeyUp)
},
mounted() {
app.pinMiniPlayer(true)
},
methods: {
onEscapeKeyUp(event) {
if (event.which === 27) {
app.miniPlayer(false);
console.log('js')
}
});
</script>
},
}
});
</script>

View file

@ -1,6 +1,6 @@
<script type="text/x-template" id="moreinfo-modal">
<div class="modal-fullscreen spatialproperties-panel moreinfo-modal" @click.self="if(timedelay) close()">
<div class="modal-window" >
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{data.title}}</div>
<div class="modal-subtitle modal-title">{{data.subtitle ?? ""}}</div>
@ -15,28 +15,28 @@
</script>
<script>
Vue.component('moreinfo-modal', {
template: '#moreinfo-modal',
data: function () {
return {
app: this.$root,
timedelay: false,
}
},
props: ["data"],
mounted() {
let self = this;
this.$nextTick(()=>{
setTimeout(function(){
self.timedelay = true
}, 1000);
})
Vue.component('moreinfo-modal', {
template: '#moreinfo-modal',
data: function() {
return {
app: this.$root,
timedelay: false,
}
},
props: ["data"],
mounted() {
let self = this;
this.$nextTick(() => {
setTimeout(function() {
self.timedelay = true
}, 1000);
})
},
methods: {
close() {
app.modals.moreInfo = false;
},
}
});
</script>
},
methods: {
close() {
app.modals.moreInfo = false;
},
}
});
</script>

View file

@ -1,37 +1,38 @@
<script type="text/x-template" id="pagination">
<div class="pagination-container" v-if="!isInfinite">
<button
class="md-btn page-btn"
:disabled="effectivePage === 1"
@click="goToPage(1)"
class="md-btn page-btn"
:disabled="effectivePage === 1"
@click="goToPage(1)"
>
<img class="md-ico-first"/>
<img class="md-ico-first" />
</button>
<button
class="md-btn page-btn prev"
:disabled="effectivePage === 1"
@click="goToPrevious()"
class="md-btn page-btn prev"
:disabled="effectivePage === 1"
@click="goToPrevious()"
>
<img class="md-ico-prev"/>
<img class="md-ico-prev" />
</button>
<button
:class="`md-btn page-btn${ isCurrentPage(page) ? ' md-btn-primary': ''}`"
@click="goToPage(page)"
v-for="page in pagesToShow"
>{{ page }}</button>
<button
class="md-btn page-btn next"
:disabled="effectivePage === numPages"
@click="goToNext()"
>
<img class="md-ico-next"/>
:class="`md-btn page-btn${ isCurrentPage(page) ? ' md-btn-primary': ''}`"
@click="goToPage(page)"
v-for="page in pagesToShow"
>{{ page }}
</button>
<button
class="md-btn page-btn last"
:disabled="effectivePage === numPages"
@click="goToEnd()"
class="md-btn page-btn next"
:disabled="effectivePage === numPages"
@click="goToNext()"
>
<img class="md-ico-last"/>
<img class="md-ico-next" />
</button>
<button
class="md-btn page-btn last"
:disabled="effectivePage === numPages"
@click="goToEnd()"
>
<img class="md-ico-last" />
</button>
<div class="page-btn md-input-number">
<input type="number" min="1" :max="numPages" :value="effectivePage" @change="changePage" />
@ -41,135 +42,135 @@
</script>
<script>
Vue.component('pagination', {
template: "#pagination",
props: {
'length': { type: Number, required: true },
'pageSize': { type: Number, required: true },
'scroll': { type: String, required: true },
'scrollSelector': { type: String, required: true }
},
data: function () {
return { currentPage: 1 }
},
mounted() {
document.querySelector(this.scrollSelector)
.addEventListener("scroll", this.handleScroll)
},
destroyed() {
document.querySelector(this.scrollSelector)
.removeEventListener("scroll", this.handleScroll)
},
watch: {
'length': function () {
if (this.isInfinite) {
// If a search reduces the number of things to show, we want to limit
// the number of songs shown as well. This is to prevent you scrolling
// to load your entire library, searching for one song, and then having
// th re-render the entire library
if (this.currentPage > this.numPages) {
this.currentPage = this.numPages;
this.$emit("onRangeChange", this.currentRange);
}
} else {
this.$emit("onRangeChange", this.currentRange);
}
},
'scroll': function () {
// When changing modes, set the page to 1. This is primarily to
// prevent going to a high page (e.g., 50) and then switching to infinite
// and showing 12.5k songs
this.currentPage = 1;
this.$emit("onRangeChange", this.currentRange);
}
},
computed: {
isInfinite: function () {
return this.scroll === "infinite"
},
currentRange: function () {
if (this.isInfinite) {
return [0, this.currentPage * this.pageSize];
} else {
const startingPage = Math.min(this.numPages, this.currentPage);
return [
(startingPage - 1) * this.pageSize,
startingPage * this.pageSize
];
}
},
effectivePage: function () {
return Math.min(this.currentPage, this.numPages)
},
numPages: function () {
return Math.ceil(this.length / this.pageSize) || 1;
},
pagesToShow: function () {
let start = this.currentPage - 2;
let end = this.currentPage + 2;
if (start < 1) {
end += (1 - start);
start = 1;
}
const endDifference = end - this.numPages;
if (endDifference > 0) {
end = this.numPages;
start = Math.max(1, start - endDifference);
}
const array = [];
for (let idx = start; idx <= end; idx++) {
array.push(idx);
}
return array;
}
},
methods: {
// Infinite Scrolling
handleScroll: function (event) {
if (this.isInfinite &&
this.currentPage < this.numPages &&
event.target.scrollTop >= event.target.scrollHeight - event.target.clientHeight) {
this.currentPage += 1;
this.$emit("onRangeChange", this.currentRange);
}
},
// Pagination
isCurrentPage: function (idx) {
return idx === this.currentPage ||
(idx === this.numPages && this.currentPage > this.numPages);
},
changePage: function (event) {
const value = event.target.valueAsNumber;
if (!isNaN(value) && value >= 1 && value <= this.numPages) {
this.currentPage = value;
this.$emit("onRangeChange", this.currentRange);
}
},
goToPage: function (page) {
this.currentPage = page;
this.$emit("onRangeChange", this.currentRange);
},
goToPrevious: function () {
if (this.currentPage > 1) {
this.currentPage -= 1;
this.$emit("onRangeChange", this.currentRange);
}
},
goToNext: function () {
if (this.currentPage < this.numPages) {
this.currentPage += 1;
this.$emit("onRangeChange", this.currentRange);
}
},
goToEnd: function () {
this.currentPage = this.numPages;
this.$emit("onRangeChange", this.currentRange);
}
Vue.component('pagination', {
template: "#pagination",
props: {
'length': { type: Number, required: true },
'pageSize': { type: Number, required: true },
'scroll': { type: String, required: true },
'scrollSelector': { type: String, required: true }
},
data: function() {
return { currentPage: 1 }
},
mounted() {
document.querySelector(this.scrollSelector)
.addEventListener("scroll", this.handleScroll)
},
destroyed() {
document.querySelector(this.scrollSelector)
.removeEventListener("scroll", this.handleScroll)
},
watch: {
'length': function() {
if (this.isInfinite) {
// If a search reduces the number of things to show, we want to limit
// the number of songs shown as well. This is to prevent you scrolling
// to load your entire library, searching for one song, and then having
// th re-render the entire library
if (this.currentPage > this.numPages) {
this.currentPage = this.numPages;
this.$emit("onRangeChange", this.currentRange);
}
} else {
this.$emit("onRangeChange", this.currentRange);
}
})
</script>
},
'scroll': function() {
// When changing modes, set the page to 1. This is primarily to
// prevent going to a high page (e.g., 50) and then switching to infinite
// and showing 12.5k songs
this.currentPage = 1;
this.$emit("onRangeChange", this.currentRange);
}
},
computed: {
isInfinite: function() {
return this.scroll === "infinite"
},
currentRange: function() {
if (this.isInfinite) {
return [0, this.currentPage * this.pageSize];
} else {
const startingPage = Math.min(this.numPages, this.currentPage);
return [
(startingPage - 1) * this.pageSize,
startingPage * this.pageSize
];
}
},
effectivePage: function() {
return Math.min(this.currentPage, this.numPages)
},
numPages: function() {
return Math.ceil(this.length / this.pageSize) || 1;
},
pagesToShow: function() {
let start = this.currentPage - 2;
let end = this.currentPage + 2;
if (start < 1) {
end += (1 - start);
start = 1;
}
const endDifference = end - this.numPages;
if (endDifference > 0) {
end = this.numPages;
start = Math.max(1, start - endDifference);
}
const array = [];
for (let idx = start; idx <= end; idx++) {
array.push(idx);
}
return array;
}
},
methods: {
// Infinite Scrolling
handleScroll: function(event) {
if (this.isInfinite &&
this.currentPage < this.numPages &&
event.target.scrollTop >= event.target.scrollHeight - event.target.clientHeight) {
this.currentPage += 1;
this.$emit("onRangeChange", this.currentRange);
}
},
// Pagination
isCurrentPage: function(idx) {
return idx === this.currentPage ||
(idx === this.numPages && this.currentPage > this.numPages);
},
changePage: function(event) {
const value = event.target.valueAsNumber;
if (!isNaN(value) && value >= 1 && value <= this.numPages) {
this.currentPage = value;
this.$emit("onRangeChange", this.currentRange);
}
},
goToPage: function(page) {
this.currentPage = page;
this.$emit("onRangeChange", this.currentRange);
},
goToPrevious: function() {
if (this.currentPage > 1) {
this.currentPage -= 1;
this.$emit("onRangeChange", this.currentRange);
}
},
goToNext: function() {
if (this.currentPage < this.numPages) {
this.currentPage += 1;
this.$emit("onRangeChange", this.currentRange);
}
},
goToEnd: function() {
this.currentPage = this.numPages;
this.$emit("onRangeChange", this.currentRange);
}
}
})
</script>

View file

@ -1,65 +1,66 @@
<script type="text/x-template" id="pathmenu">
<div class="spatialproperties-panel castmenu pathmenu modal-fullscreen" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{'Edit Paths'}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
</div>
<div class="modal-content">
<template v-for="folder of folders">
<div class="md-option-line">
<div class="md-option-segment">
{{folder}}
<div class="spatialproperties-panel castmenu pathmenu modal-fullscreen" @click.self="close()"
@contextmenu.self="close()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{'Edit Paths'}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
</div>
<div class="modal-content">
<template v-for="folder of folders">
<div class="md-option-line">
<div class="md-option-segment">
{{folder}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="remove(folder)">
{{'Remove'}}
</button>
</div>
</div>
</template>
<div class="md-option-line">
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="remove(folder)">
{{'Remove'}}
<button class="md-btn" @click="add()">
{{'Add Path'}}
</button>
</div>
</div>
</template>
<div class="md-option-line">
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="add()">
{{'Add Path'}}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('pathmenu', {
template: '#pathmenu',
data: function () {
return {
folders: [],
}
},
mounted() {
this.folders = this.$root.cfg.libraryPrefs.localPaths;
},
watch:{},
methods: {
close() {
this.$root.modals.pathMenu = false
},
async add(){
const result = await ipcRenderer.invoke('folderSelector')
for (i of result){
if (this.folders.findIndex(x => x.startsWith(i)) == -1){
this.folders.push(i)
}
}
this.$root.cfg.libraryPrefs.localPaths = this.folders;
ipcRenderer.invoke("scanLibrary")
},
remove(dir){
this.folders = this.folders.filter(item => item !== dir)
this.$root.cfg.libraryPrefs.localPaths = this.folders;
ipcRenderer.invoke("scanLibrary")
}
Vue.component('pathmenu', {
template: '#pathmenu',
data: function() {
return {
folders: [],
}
},
mounted() {
this.folders = this.$root.cfg.libraryPrefs.localPaths;
},
watch: {},
methods: {
close() {
this.$root.modals.pathMenu = false
},
async add() {
const result = await ipcRenderer.invoke('folderSelector')
for (i of result) {
if (this.folders.findIndex(x => x.startsWith(i)) == -1) {
this.folders.push(i)
}
}
});
</script>
this.$root.cfg.libraryPrefs.localPaths = this.folders;
ipcRenderer.invoke("scanLibrary")
},
remove(dir) {
this.folders = this.folders.filter(item => item !== dir)
this.$root.cfg.libraryPrefs.localPaths = this.folders;
ipcRenderer.invoke("scanLibrary")
}
}
});
</script>

View file

@ -1,5 +1,6 @@
<script type="text/x-template" id="plugin-menu">
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" @contextmenu.self="app.resetState()">
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()"
@contextmenu.self="app.resetState()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{$root.getLz('term.pluginMenu')}}</div>
@ -10,30 +11,32 @@
<span class="icon"><%- include("../svg/x.svg") %></span>
<span class="name" style="top: 0.5px;">{{$root.getLz('term.pluginMenu.none')}}</span>
</span>
<button class="playlist-item" @click="entry.onClick(); closeMenu();" v-for="entry in app.pluginMenuEntries">
<span class="icon"><%- include("../svg/grid.svg") %></span>
<span class="name" style="top: 0.5px;">{{ entry.name }}</span>
</button>
<button class="playlist-item" @click="entry.onClick(); closeMenu();"
v-for="entry in app.pluginMenuEntries">
<span class="icon"><%- include("../svg/grid.svg") %></span>
<span class="name" style="top: 0.5px;">{{ entry.name }}</span>
</button>
</div>
</div>
</div>
</script>
<script>
Vue.component('plugin-menu', {
template: '#plugin-menu',
data: function () {
return {
app: this.$root,
}
},
props: {},
mounted() {},
methods: {
closeMenu() {
app.modals.pluginMenu = false
},
},
Vue.component('plugin-menu', {
template: '#plugin-menu',
data: function() {
return {
app: this.$root,
}
);
</script>
},
props: {},
mounted() {
},
methods: {
closeMenu() {
app.modals.pluginMenu = false
},
},
}
);
</script>

View file

@ -1,34 +1,34 @@
<script type="text/x-template" id="qrcode-modal">
<div class="modal-fullscreen spatialproperties-panel">
<div class="modal-window" >
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{`Web Remote QR : ` + url }}</div>
<button class="close-btn" @click="close()" :aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<img class="qrimg" :src="src"/>
<img class="qrimg" :src="src" />
</div>
</div>
</div>
</script>
<script>
Vue.component('qrcode-modal', {
template: '#qrcode-modal',
data: function () {
return {
app: this.$root,
Vue.component('qrcode-modal', {
template: '#qrcode-modal',
data: function() {
return {
app: this.$root,
}
},
props: ["src","url"],
mounted() {
}
},
props: ["src", "url"],
mounted() {
},
methods: {
close() {
app.resetState()
},
}
});
</script>
},
methods: {
close() {
app.resetState()
},
}
});
</script>

View file

@ -14,7 +14,8 @@
</div>
</div>
<div class="queue-body" v-if="page == 'history'">
<mediaitem-list-item :show-library-status="false" v-for="item in history" :item="item"></mediaitem-list-item>
<mediaitem-list-item :show-library-status="false" v-for="item in history"
:item="item"></mediaitem-list-item>
</div>
<div class="queue-body" v-if="page == 'queue'">
<draggable v-model="queueItems" @start="drag=true" @end="drag=false;move()">
@ -27,18 +28,26 @@
<div class="row">
<div class="col-auto cider-flex-center">
<div class="artwork">
<mediaitem-artwork :url="queueItem.item.attributes.artwork ? queueItem.item.attributes.artwork.url : ''" :size="32"></mediaitem-artwork>
<mediaitem-artwork
:url="queueItem.item.attributes.artwork ? queueItem.item.attributes.artwork.url : ''"
:size="32"></mediaitem-artwork>
</div>
</div>
<div class="col queue-info">
<div class="queue-title text-overflow-elipsis">{{ queueItem.item.attributes.name }}</div>
<div class="queue-subtitle text-overflow-elipsis">{{ queueItem.item.attributes.artistName }} — {{ queueItem.item.attributes.albumName }}</div>
<div class="queue-title text-overflow-elipsis">{{ queueItem.item.attributes.name }}
</div>
<div class="queue-subtitle text-overflow-elipsis">{{
queueItem.item.attributes.artistName }} — {{ queueItem.item.attributes.albumName }}
</div>
</div>
<div class="queue-explicit-icon cider-flex-center" v-if="queueItem.item.attributes.contentRating == 'explicit'">
<div class="queue-explicit-icon cider-flex-center"
v-if="queueItem.item.attributes.contentRating == 'explicit'">
<div class="explicit-icon"></div>
</div>
<div class="col queue-duration-info">
<div class="queue-duration cider-flex-center">{{convertTimeToString(queueItem.item.attributes.durationInMillis)}}</div>
<div class="queue-duration cider-flex-center">
{{convertTimeToString(queueItem.item.attributes.durationInMillis)}}
</div>
</div>
</div>
</div>
@ -47,138 +56,144 @@
</div>
<div class="queue-footer">
<div class="btn-group" style="width:100%;">
<button class="md-btn md-btn-small" :class="{'md-btn-primary': (page == 'queue')}" @click="page = 'queue'">{{app.getLz('term.queue')}}</button>
<button class="md-btn md-btn-small" :class="{'md-btn-primary': (page == 'history')}" @click="getHistory();page = 'history'">{{app.getLz('term.history')}}</button>
<button class="md-btn md-btn-small" :class="{'md-btn-primary': (page == 'queue')}"
@click="page = 'queue'">{{app.getLz('term.queue')}}
</button>
<button class="md-btn md-btn-small" :class="{'md-btn-primary': (page == 'history')}"
@click="getHistory();page = 'history'">{{app.getLz('term.history')}}
</button>
</div>
<button class="md-btn md-btn-small" style="width:100%;margin-top:6px;" v-if="queueItems.length > 1" @click="app.mk.clearQueue();updateQueue()">{{app.getLz('term.clearAll')}}</button>
<button class="md-btn md-btn-small" style="width:100%;margin-top:6px;" v-if="queueItems.length > 1"
@click="app.mk.clearQueue();updateQueue()">{{app.getLz('term.clearAll')}}
</button>
</div>
</div>
</script>
<script>
Vue.component('cider-queue', {
template: '#cider-queue',
data: function () {
return {
drag: false,
queuePosition: 0,
queueItems: [],
selected: -1,
selectedItems: [],
history: [],
page: "queue",
app: this.$root
}
},
computed: {
displayQueueItems() {
const displayLimit = 50;
const lastDisplayPosition = Math.min(displayLimit + this.queuePosition, this.queueItems.length);
return this.queueItems.slice(this.queuePosition, lastDisplayPosition);
}
},
mounted() {
this.updateQueue()
},
methods: {
async getHistory() {
let history = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, { l : this.$root.mklang})
this.history = history.data.data
},
select(e, position) {
if (e.ctrlKey || e.shiftKey) {
if (this.selectedItems.indexOf(position) == -1) {
this.selectedItems.push(position)
} else {
this.selectedItems.splice(this.selectedItems.indexOf(position), 1)
}
} else {
this.selectedItems = [position]
}
},
queueContext(event, item, position) {
let self = this
let useMenu = "single"
if (this.selectedItems.length > 1) {
useMenu = "multiple"
}
let menus = {
single: {
items: [{
"name": app.getLz('action.removeFromQueue'),
"action": function () {
self.queueItems.splice(position, 1)
app.mk.queue._queueItems = self.queueItems;
app.mk.queue._reindex()
}
},
{
"name": app.getLz('action.startRadio'),
"action": function () {
app.mk.setStationQueue({
song: item.attributes.playParams.id ?? item.id
}).then(() => {
app.mk.play()
})
}
},
{
"name": app.getLz('action.goToArtist'),
"action": function () {
app.searchAndNavigate(item,'artist')
}
},
{
"name": app.getLz('action.goToAlbum'),
"action": function () {
app.searchAndNavigate(item,'album')
}
},
]
},
multiple: {
items: [{
"name": app.getLz('action.removeTracks').replace('${self.selectedItems.length}', self.selectedItems.length.toString()),
"action": function () {
// add property to items to be removed
self.selectedItems.forEach(function (item) {
self.queueItems[item].remove = true
})
// remove items
self.queueItems = self.queueItems.filter(function (item) {
return !item.remove
})
app.mk.queue._reindex()
self.selectedItems = []
}
}]
}
}
app.showMenuPanel(menus[useMenu], event);
},
playQueueItem(id) {
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(id))
},
updateQueue() {
this.selected = -1
if (app.mk.queue) {
this.queuePosition = app.mk.queue.position;
this.queueItems = app.mk.queue._queueItems;
} else {
this.queuePosition = 0;
this.queueItems = [];
}
},
move() {
this.selected = -1
app.mk.queue._queueItems = this.queueItems;
app.mk.queue._reindex()
},
convertTimeToString(timeInMilliseconds) {
var seconds = ((timeInMilliseconds % 60000) / 1000).toFixed(0);
return Math.floor(timeInMilliseconds/60000) + ":" + (seconds < 10 ? '0' : '') + seconds;
}
Vue.component('cider-queue', {
template: '#cider-queue',
data: function() {
return {
drag: false,
queuePosition: 0,
queueItems: [],
selected: -1,
selectedItems: [],
history: [],
page: "queue",
app: this.$root
}
},
computed: {
displayQueueItems() {
const displayLimit = 50;
const lastDisplayPosition = Math.min(displayLimit + this.queuePosition, this.queueItems.length);
return this.queueItems.slice(this.queuePosition, lastDisplayPosition);
}
},
mounted() {
this.updateQueue()
},
methods: {
async getHistory() {
let history = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, { l: this.$root.mklang })
this.history = history.data.data
},
select(e, position) {
if (e.ctrlKey || e.shiftKey) {
if (this.selectedItems.indexOf(position) == -1) {
this.selectedItems.push(position)
} else {
this.selectedItems.splice(this.selectedItems.indexOf(position), 1)
}
} else {
this.selectedItems = [position]
}
});
</script>
},
queueContext(event, item, position) {
let self = this
let useMenu = "single"
if (this.selectedItems.length > 1) {
useMenu = "multiple"
}
let menus = {
single: {
items: [{
"name": app.getLz('action.removeFromQueue'),
"action": function() {
self.queueItems.splice(position, 1)
app.mk.queue._queueItems = self.queueItems;
app.mk.queue._reindex()
}
},
{
"name": app.getLz('action.startRadio'),
"action": function() {
app.mk.setStationQueue({
song: item.attributes.playParams.id ?? item.id
}).then(() => {
app.mk.play()
})
}
},
{
"name": app.getLz('action.goToArtist'),
"action": function() {
app.searchAndNavigate(item, 'artist')
}
},
{
"name": app.getLz('action.goToAlbum'),
"action": function() {
app.searchAndNavigate(item, 'album')
}
},
]
},
multiple: {
items: [{
"name": app.getLz('action.removeTracks').replace('${self.selectedItems.length}', self.selectedItems.length.toString()),
"action": function() {
// add property to items to be removed
self.selectedItems.forEach(function(item) {
self.queueItems[item].remove = true
})
// remove items
self.queueItems = self.queueItems.filter(function(item) {
return !item.remove
})
app.mk.queue._reindex()
self.selectedItems = []
}
}]
}
}
app.showMenuPanel(menus[useMenu], event);
},
playQueueItem(id) {
app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(id))
},
updateQueue() {
this.selected = -1
if (app.mk.queue) {
this.queuePosition = app.mk.queue.position;
this.queueItems = app.mk.queue._queueItems;
} else {
this.queuePosition = 0;
this.queueItems = [];
}
},
move() {
this.selected = -1
app.mk.queue._queueItems = this.queueItems;
app.mk.queue._reindex()
},
convertTimeToString(timeInMilliseconds) {
var seconds = ((timeInMilliseconds % 60000) / 1000).toFixed(0);
return Math.floor(timeInMilliseconds / 60000) + ":" + (seconds < 10 ? '0' : '') + seconds;
}
}
});
</script>

View file

@ -1,8 +1,8 @@
<script type="text/x-template" id="keybinds-settings">
<div class="keybinds-page">
<div class="md-option-header">
<span>{{$root.getLz('settings.option.general.keybindings')}}</span>
</div>
<div class="md-option-header">
<span>{{$root.getLz('settings.option.general.keybindings')}}</span>
</div>
<div class="settings-option-body">
<div class="md-option-header-sub">
<span>{{$root.getLz('settings.option.general.keybindings.library')}}</span>
@ -207,7 +207,7 @@
</div>
</div>
<button class="md-btn md-btn-large md-btn-block" @click="keyBindReset()">
{{$root.getLz('term.reset')}}
{{$root.getLz('term.reset')}}
</button>
</div>
@ -215,109 +215,109 @@
</script>
<script>
Vue.component('keybinds-settings', {
template: "#keybinds-settings",
props: [],
data: function () {
return {
app: this.$root
}
},
methods: {
keyBindUpdate: function (action) {
const blur = document.createElement('div');
blur.className = 'blur';
blur.style.backgroundColor = 'rgba(0,0,0,0.25)';
blur.style.position = 'fixed';
blur.style.top = '0';
blur.style.left = '0';
blur.style.width = '100%';
blur.style.height = '100%';
blur.style.zIndex = '9999';
blur.style.display = 'flex';
blur.style.alignItems = 'center';
blur.style.justifyContent = 'center';
blur.style.fontSize = '2em';
blur.style.color = 'white';
blur.innerHTML = `<center>${app.getLz('settings.option.general.keybindings.pressCombination')}<br />${app.getLz('settings.option.general.keybindings.pressEscape')}</center>`
document.body.appendChild(blur);
Vue.component('keybinds-settings', {
template: "#keybinds-settings",
props: [],
data: function() {
return {
app: this.$root
}
},
methods: {
keyBindUpdate: function(action) {
const blur = document.createElement('div');
blur.className = 'blur';
blur.style.backgroundColor = 'rgba(0,0,0,0.25)';
blur.style.position = 'fixed';
blur.style.top = '0';
blur.style.left = '0';
blur.style.width = '100%';
blur.style.height = '100%';
blur.style.zIndex = '9999';
blur.style.display = 'flex';
blur.style.alignItems = 'center';
blur.style.justifyContent = 'center';
blur.style.fontSize = '2em';
blur.style.color = 'white';
blur.innerHTML = `<center>${app.getLz('settings.option.general.keybindings.pressCombination')}<br />${app.getLz('settings.option.general.keybindings.pressEscape')}</center>`
document.body.appendChild(blur);
let keyBind = [];
const keyBindTimeout = setTimeout(function () {
keyBind = [];
document.body.removeChild(blur);
}, 30000);
const keyBindUpdate = function (e) {
if (document.body.contains(blur)) {
if (e.key == 'Escape') {
document.body.removeChild(blur);
clearTimeout(keyBindTimeout);
return;
} else {
if (e.keyCode >= 65 && e.keyCode <= 90 && e.keyCode <= 97 && e.keyCode <= 122) {
keyBind.push(e.key.toUpperCase());
} else {
keyBind.push(e.key);
}
if (keyBind.length === 2) {
if (keyBind[0] !== keyBind[1]) {
app.cfg.general.keybindings[action] = keyBind
document.body.removeChild(blur);
clearTimeout(keyBindTimeout);
notyf.success(app.getLz('settings.notyf.general.keybindings.update.success'));
app.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {
if (ok) ipcRenderer.invoke("relaunchApp")
})
} else {
keyBind = [];
}
}
}
}
};
document.addEventListener('keydown', keyBindUpdate);
},
keyBindReset: function () {
app.cfg.general.keybindings.search = [app.platform == "darwin" ? "Command" : "Control", "F"];
app.cfg.general.keybindings.listnow = [app.platform == "darwin" ? "Command" : "Control", "L"];
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", "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",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.zoomn = [app.platform == "darwin" ? "Command" : "Control", "numadd"];
app.cfg.general.keybindings.zoomt = [app.platform == "darwin" ? "Command" : "Control", "numsub"];
app.cfg.general.keybindings.zoomrst = [app.platform == "darwin" ? "Command" : "Control", "num0"];
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'));
app.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {
let keyBind = [];
const keyBindTimeout = setTimeout(function() {
keyBind = [];
document.body.removeChild(blur);
}, 30000);
const keyBindUpdate = function(e) {
if (document.body.contains(blur)) {
if (e.key == 'Escape') {
document.body.removeChild(blur);
clearTimeout(keyBindTimeout);
return;
} else {
if (e.keyCode >= 65 && e.keyCode <= 90 && e.keyCode <= 97 && e.keyCode <= 122) {
keyBind.push(e.key.toUpperCase());
} else {
keyBind.push(e.key);
}
if (keyBind.length === 2) {
if (keyBind[0] !== keyBind[1]) {
app.cfg.general.keybindings[action] = keyBind
document.body.removeChild(blur);
clearTimeout(keyBindTimeout);
notyf.success(app.getLz('settings.notyf.general.keybindings.update.success'));
app.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {
if (ok) ipcRenderer.invoke("relaunchApp")
})
},
getLanguages: function () {
let langs = this.$root.lzListing
let categories = {
"main": [],
"fun": [],
"unsorted": []
})
} else {
keyBind = [];
}
// sort by category if category is undefined or empty put it in "unsorted"
for (let i = 0; i < langs.length; i++) {
if (langs[i].category === undefined || langs[i].category === "") {
categories.unsorted.push(langs[i])
} else {
categories[langs[i].category].push(langs[i])
}
}
// return
return categories
},
}
}
}
};
document.addEventListener('keydown', keyBindUpdate);
},
keyBindReset: function() {
app.cfg.general.keybindings.search = [app.platform == "darwin" ? "Command" : "Control", "F"];
app.cfg.general.keybindings.listnow = [app.platform == "darwin" ? "Command" : "Control", "L"];
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", "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", 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.zoomn = [app.platform == "darwin" ? "Command" : "Control", "numadd"];
app.cfg.general.keybindings.zoomt = [app.platform == "darwin" ? "Command" : "Control", "numsub"];
app.cfg.general.keybindings.zoomrst = [app.platform == "darwin" ? "Command" : "Control", "num0"];
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'));
app.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {
if (ok) ipcRenderer.invoke("relaunchApp")
})
},
getLanguages: function() {
let langs = this.$root.lzListing
let categories = {
"main": [],
"fun": [],
"unsorted": []
}
})
</script>
// sort by category if category is undefined or empty put it in "unsorted"
for (let i = 0; i < langs.length; i++) {
if (langs[i].category === undefined || langs[i].category === "") {
categories.unsorted.push(langs[i])
} else {
categories[langs[i].category].push(langs[i])
}
}
// return
return categories
},
}
})
</script>

View file

@ -21,7 +21,8 @@
<div class="row">
<div class="col cider-flex-center">
<div>
<h4 class="repo-name">{{ (repo.description != null) ? repo.description : repo.full_name }}</h4>
<h4 class="repo-name">{{ (repo.description != null) ? repo.description :
repo.full_name }}</h4>
<div>⭐ {{ repo.stargazers_count }}</div>
</div>
</div>
@ -37,7 +38,8 @@
<div>
<h3 class="repo-preview-name">{{ openRepo.description }}</h3>
<div>
<div class="svg-icon inline" :style="{'--url': 'url(\'./assets/github.svg\')'}"></div>
<div class="svg-icon inline"
:style="{'--url': 'url(\'./assets/github.svg\')'}"></div>
<a class="repo-url" target="_blank" :href="openRepo.html_url">{{ openRepo.full_name
}}</a></div>
<div>⭐ {{ openRepo.stargazers_count }}</div>
@ -62,127 +64,127 @@
</div>
</script>
<script>
Vue.component('plugins-github', {
template: "#plugins-github",
props: [],
data: function () {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
themesInstalled: []
}
Vue.component('plugins-github', {
template: "#plugins-github",
props: [],
data: function() {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
mounted() {
this.getRepos();
// this.getInstalledThemes();
},
methods: {
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "") {
self.themesInstalled.push(theme.github_repo)
}
})
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
themesInstalled: []
}
},
mounted() {
this.getRepos();
// this.getInstalledThemes();
},
methods: {
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "") {
self.themesInstalled.push(theme.github_repo)
}
})
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.plugin.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("plugin-installed", (event, arg) => {
if (arg.success) {
self.themes = []
notyf.success(app.getLz('settings.notyf.visual.plugin.install.success'));
app.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok)=>{
if (ok) {
ipcRenderer.invoke("relaunchApp")
} else {
return
}
})
} else {
notyf.error(app.getLz('settings.notyf.visual.plugin.install.error'));
}
});
ipcRenderer.invoke("get-github-plugin", repo.html_url)
}
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.plugin.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("plugin-installed", (event, arg) => {
if (arg.success) {
self.themes = []
notyf.success(app.getLz('settings.notyf.visual.plugin.install.success'));
app.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok) => {
if (ok) {
ipcRenderer.invoke("relaunchApp")
} else {
return
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.plugin.github.URL'), (result) => {
if (result) {
ipcRenderer.once("plugin-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
app.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok)=>{
if (ok) {
ipcRenderer.invoke("relaunchApp")
} else {
return
}
})
notyf.success(app.getLz('settings.notyf.visual.plugin.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.plugin.install.error'));
}
});
ipcRenderer.invoke("get-github-plugin", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
} else {
notyf.error(app.getLz('settings.notyf.visual.plugin.install.error'));
}
});
ipcRenderer.invoke("get-github-plugin", repo.html_url)
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.plugin.github.URL'), (result) => {
if (result) {
ipcRenderer.once("plugin-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
app.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok) => {
if (ok) {
ipcRenderer.invoke("relaunchApp")
} else {
return
}
})
notyf.success(app.getLz('settings.notyf.visual.plugin.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.plugin.install.error'));
}
});
ipcRenderer.invoke("get-github-plugin", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
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
})
.catch(error => console.log('error', error));
}
}
})
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
})
.catch(error => console.log('error', error));
}
}
})
</script>

View file

@ -78,120 +78,120 @@
</div>
</script>
<script>
Vue.component('themes-github', {
template: "#themes-github",
props: [],
data: function () {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
themesInstalled: [],
themes: []
}
Vue.component('themes-github', {
template: "#themes-github",
props: [],
data: function() {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
mounted() {
this.themes = ipcRenderer.sendSync("get-themes")
this.getRepos();
this.getInstalledThemes();
},
methods: {
openThemesFolder() {
ipcRenderer.invoke("open-path", "themes")
},
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "" && typeof theme.commit != "") {
self.themesInstalled.push(theme.github_repo.toLowerCase())
}
})
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
themesInstalled: [],
themes: []
}
},
mounted() {
this.themes = ipcRenderer.sendSync("get-themes")
this.getRepos();
this.getInstalledThemes();
},
methods: {
openThemesFolder() {
ipcRenderer.invoke("open-path", "themes")
},
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "" && typeof theme.commit != "") {
self.themesInstalled.push(theme.github_repo.toLowerCase())
}
})
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.theme.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
self.getInstalledThemes()
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", repo.html_url)
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.theme.github.URL'), (result) => {
if (result) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.theme.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
self.getInstalledThemes()
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", repo.html_url)
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.theme.github.URL'), (result) => {
if (result) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true&per_page=100", requestOptions)
.then(response => response.text())
.then(result => {
let items = JSON.parse(result).items
self.repos = items
})
.catch(error => console.log('error', error));
}
}
})
</script>
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
self.repos = items
})
.catch(error => console.log('error', error));
}
}
})
</script>

View file

@ -32,9 +32,9 @@
<ul class="list-group list-group-flush">
<template v-for="theme in themes">
<li @click="addStyle(theme.file)"
@contextmenu="contextMenu($event, theme)"
class="list-group-item list-group-item-dark"
:class="{'applied': $root.cfg.visual.styles.includes(theme.file)}">
@contextmenu="contextMenu($event, theme)"
class="list-group-item list-group-item-dark"
:class="{'applied': $root.cfg.visual.styles.includes(theme.file)}">
<b-row>
<b-col class="themeLabel">{{theme.name}}</b-col>
@ -51,33 +51,34 @@
<button class="themeContextMenu codicon codicon-package"></button>
</b-col>
<b-col sm="auto">
<button @click.stop="contextMenu($event, theme)" class="themeContextMenu codicon codicon-list-unordered"></button>
<button @click.stop="contextMenu($event, theme)"
class="themeContextMenu codicon codicon-list-unordered"></button>
</b-col>
</template>
</b-row>
</li>
<li @click="addStyle(packEntry.file)"
@contextmenu="contextMenu($event, theme)"
class="list-group-item list-group-item-dark addon"
v-for="packEntry in theme.pack"
:class="{'applied': $root.cfg.visual.styles.includes(packEntry.file)}"
v-if="theme.pack">
@contextmenu="contextMenu($event, theme)"
class="list-group-item list-group-item-dark addon"
v-for="packEntry in theme.pack"
:class="{'applied': $root.cfg.visual.styles.includes(packEntry.file)}"
v-if="theme.pack">
<b-row>
<b-col class="themeLabel">{{packEntry.name}}</b-col>
<template v-if="$root.cfg.visual.styles.includes(packEntry.file)">
<b-col sm="auto">
<button class="themeContextMenu codicon codicon-check"></button>
</b-col>
</template>
<template v-else>
<b-col sm="auto">
<button class="themeContextMenu codicon codicon-diff-added"></button>
</b-col>
</template>
</b-row>
</li>
<b-row>
<b-col class="themeLabel">{{packEntry.name}}</b-col>
<template v-if="$root.cfg.visual.styles.includes(packEntry.file)">
<b-col sm="auto">
<button class="themeContextMenu codicon codicon-check"></button>
</b-col>
</template>
<template v-else>
<b-col sm="auto">
<button class="themeContextMenu codicon codicon-diff-added"></button>
</b-col>
</template>
</b-row>
</li>
</template>
</ul>
</div>
@ -86,17 +87,17 @@
<div class="repo-header">
<h4>{{ $root.getLz("settings.option.visual.theme.github.applied") }} </h4>
</div>
<stylestack-editor ref="stackEditor" v-if="themes.length != 0" :themes="themes"/>
<stylestack-editor ref="stackEditor" v-if="themes.length != 0" :themes="themes" />
</div>
</div>
</div>
</script>
<script>
// do not translate
Vue.component('stylestack-editor', {
/*html*/
template: `
// do not translate
Vue.component('stylestack-editor', {
/*html*/
template: `
<div class="stylestack-editor" >
<draggable class="list-group" v-model="$root.cfg.visual.styles" @end="$root.reloadStyles()">
<b-list-group-item variant="dark" v-for="theme in $root.cfg.visual.styles" :key="theme">
@ -113,255 +114,255 @@
</draggable>
</div>
`,
props: {
themes: {
type: Array,
default: [],
required: true
}
},
data: function () {
return {
selected: null,
newTheme: null,
themeList: []
}
},
mounted() {
console.log(this.themes)
this.themeList = [...this.themes]
props: {
themes: {
type: Array,
default: [],
required: true
}
},
data: function() {
return {
selected: null,
newTheme: null,
themeList: []
}
},
mounted() {
console.log(this.themes)
this.themeList = [...this.themes]
this.themeList.forEach(theme => {
if (theme.pack) {
theme.pack.forEach(packEntry => {
packEntry.file = theme.file.replace('index.less', '') + packEntry.file
this.themeList.push(packEntry)
})
}
})
},
methods: {
gitHubExplore() {
this.$root.openSettingsPage("github-themes")
},
getThemeName(filename) {
try {
return this.themeList.find(theme => theme.file === filename).name;
} catch (e) {
return filename;
}
},
moveUp() {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(this.selected)
if (index > 0) {
styles.splice(index, 1)
styles.splice(index - 1, 0, this.selected)
}
this.$root.reloadStyles()
},
moveDown() {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(this.selected)
if (index < styles.length - 1) {
styles.splice(index, 1)
styles.splice(index + 1, 0, this.selected)
}
this.$root.reloadStyles()
},
remove(style) {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(style)
styles.splice(index, 1)
this.$root.reloadStyles()
},
addStyle(style) {
const styles = this.$root.cfg.visual.styles
styles.push(style)
this.$root.reloadStyles()
}
this.themeList.forEach(theme => {
if (theme.pack) {
theme.pack.forEach(packEntry => {
packEntry.file = theme.file.replace('index.less', '') + packEntry.file
this.themeList.push(packEntry)
})
}
})
})
},
methods: {
gitHubExplore() {
this.$root.openSettingsPage("github-themes")
},
getThemeName(filename) {
try {
return this.themeList.find(theme => theme.file === filename).name;
} catch (e) {
return filename;
}
},
moveUp() {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(this.selected)
if (index > 0) {
styles.splice(index, 1)
styles.splice(index - 1, 0, this.selected)
}
this.$root.reloadStyles()
},
moveDown() {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(this.selected)
if (index < styles.length - 1) {
styles.splice(index, 1)
styles.splice(index + 1, 0, this.selected)
}
this.$root.reloadStyles()
},
remove(style) {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(style)
styles.splice(index, 1)
this.$root.reloadStyles()
},
addStyle(style) {
const styles = this.$root.cfg.visual.styles
styles.push(style)
this.$root.reloadStyles()
}
}
})
</script>
<script>
Vue.component('installed-themes', {
template: "#installed-themes",
props: [],
data: function () {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
themesInstalled: [],
themes: []
}
Vue.component('installed-themes', {
template: "#installed-themes",
props: [],
data: function() {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
mounted() {
this.getThemesList();
},
methods: {
getThemesList() {
let self = this
let themes = ipcRenderer.sendSync("get-themes")
themes.unshift({
name: "Acrylic Grain",
file: "grain.less"
})
themes.unshift({
name: "Sweetener",
file: "sweetener.less"
})
themes.unshift({
name: "Reduce Visuals",
file: "reduce_visuals.less"
})
// themes.unshift({
// name: "Inline Drawer",
// file: "inline_drawer.less"
// })
themes.unshift({
name: "Dark",
file: "dark.less"
})
this.themes = themes
},
contextMenu(event, theme) {
let self = this
let menu = {
items: {
"uninstall": {
name: app.getLz("settings.option.visual.theme.uninstall"),
disabled: true,
action: () => {
app.confirm(app.stringTemplateParser(app.getLz("settings.prompt.visual.theme.uninstallTheme"), {
theme: theme.name ?? theme.file
}), (res) => {
if (res) {
console.debug(theme)
ipcRenderer.once("theme-uninstalled", (event, args) => {
console.debug(event, args)
self.getThemesList()
})
ipcRenderer.invoke("uninstall-theme", theme.path)
}
})
}
},
"viewInfo": {
name: app.getLz("settings.option.visual.theme.viewInfo"),
disabled: true,
action: () => {
}
}
}
}
if (theme.path) {
menu.items.uninstall.disabled = false
}
this.$root.showMenuPanel(menu, event)
},
openThemesFolder() {
ipcRenderer.invoke("open-path", "themes")
},
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "" && typeof theme.commit != "") {
self.themesInstalled.push(theme.github_repo.toLowerCase())
}
})
},
addStyle(filename) {
this.$refs.stackEditor.addStyle(filename)
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
themesInstalled: [],
themes: []
}
},
mounted() {
this.getThemesList();
},
methods: {
getThemesList() {
let self = this
let themes = ipcRenderer.sendSync("get-themes")
themes.unshift({
name: "Acrylic Grain",
file: "grain.less"
})
themes.unshift({
name: "Sweetener",
file: "sweetener.less"
})
themes.unshift({
name: "Reduce Visuals",
file: "reduce_visuals.less"
})
// themes.unshift({
// name: "Inline Drawer",
// file: "inline_drawer.less"
// })
themes.unshift({
name: "Dark",
file: "dark.less"
})
this.themes = themes
},
contextMenu(event, theme) {
let self = this
let menu = {
items: {
"uninstall": {
name: app.getLz("settings.option.visual.theme.uninstall"),
disabled: true,
action: () => {
app.confirm(app.stringTemplateParser(app.getLz("settings.prompt.visual.theme.uninstallTheme"), {
theme: theme.name ?? theme.file
}), (res) => {
if (res) {
console.debug(theme)
ipcRenderer.once("theme-uninstalled", (event, args) => {
console.debug(event, args)
self.getThemesList()
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.theme.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
self.getInstalledThemes()
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", repo.html_url)
}
ipcRenderer.invoke("uninstall-theme", theme.path)
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.theme.github.URL'), (result) => {
if (result) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true", requestOptions)
.then(response => response.text())
.then(result => {
let items = JSON.parse(result).items
self.repos = items
})
.catch(error => console.log('error', error));
}
},
"viewInfo": {
name: app.getLz("settings.option.visual.theme.viewInfo"),
disabled: true,
action: () => {
}
}
}
}
})
</script>
if (theme.path) {
menu.items.uninstall.disabled = false
}
this.$root.showMenuPanel(menu, event)
},
openThemesFolder() {
ipcRenderer.invoke("open-path", "themes")
},
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "" && typeof theme.commit != "") {
self.themesInstalled.push(theme.github_repo.toLowerCase())
}
})
},
addStyle(filename) {
this.$refs.stackEditor.addStyle(filename)
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.theme.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
self.getInstalledThemes()
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", repo.html_url)
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.theme.github.URL'), (result) => {
if (result) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true", requestOptions)
.then(response => response.text())
.then(result => {
let items = JSON.parse(result).items
self.repos = items
})
.catch(error => console.log('error', error));
}
}
})
</script>

View file

@ -11,7 +11,7 @@
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/settings.svg" classes="svg-md" name="settings-general"/>
<svg-icon url="./assets/settings.svg" classes="svg-md" name="settings-general" />
</div>
<div>
{{ $root.getLz('settings.header.general') }}
@ -61,7 +61,7 @@
<label>
<input type="checkbox" v-model="$root.cfg.general.privateEnabled"
v-on:change="$root.mk.privateEnabled = $root.cfg.general.privateEnabled"
switch/>
switch />
</label>
</div>
</div>
@ -71,7 +71,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.onStartup.enabled" switch/>
<input type="checkbox" v-model="app.cfg.general.onStartup.enabled" switch />
</label>
</div>
</div>
@ -81,7 +81,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.onStartup.hidden" switch/>
<input type="checkbox" v-model="app.cfg.general.onStartup.hidden" switch />
</label>
</div>
</div>
@ -163,7 +163,7 @@
<label>
<input type="checkbox"
v-model="app.cfg.general.sidebarItems.recentlyAdded"
switch/>
switch />
</label>
</div>
</div>
@ -175,7 +175,7 @@
<label>
<input type="checkbox"
v-model="app.cfg.general.sidebarItems.songs"
switch/>
switch />
</label>
</div>
</div>
@ -187,7 +187,7 @@
<label>
<input type="checkbox"
v-model="app.cfg.general.sidebarItems.albums"
switch/>
switch />
</label>
</div>
</div>
@ -199,7 +199,7 @@
<label>
<input type="checkbox"
v-model="app.cfg.general.sidebarItems.artists"
switch/>
switch />
</label>
</div>
</div>
@ -211,7 +211,7 @@
<label>
<input type="checkbox"
v-model="app.cfg.general.sidebarItems.videos"
switch/>
switch />
</label>
</div>
</div>
@ -223,7 +223,7 @@
<label>
<input type="checkbox"
v-model="app.cfg.general.sidebarItems.podcasts"
switch/>
switch />
</label>
</div>
</div>
@ -247,7 +247,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.themeUpdateNotification"
switch/>
switch />
</label>
</div>
</div>
@ -258,7 +258,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.showLovedTracksInline"
switch/>
switch />
</label>
</div>
</div>
@ -279,7 +279,7 @@
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/headphones.svg" classes="svg-md" name="settings-audio"/>
<svg-icon url="./assets/feather/headphones.svg" classes="svg-md" name="settings-audio" />
</div>
<div>
{{ $root.getLz('settings.header.audio') }}
@ -343,7 +343,7 @@
<label>
<input type="checkbox" v-model="app.cfg.audio.seamless_audio"
v-on:change="app.mk._bag.features['seamless-audio-transitions'] = app.cfg.audio.seamless_audio"
switch/>
switch />
</label>
</div>
</div>
@ -372,7 +372,7 @@
<input type="checkbox" v-model="app.cfg.audio.normalization"
v-on:change="toggleNormalization"
: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/>
switch />
</label>
</div>
</div>
@ -385,7 +385,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.audio.dBSPL" switch/>
<input type="checkbox" v-model="app.cfg.audio.dBSPL" switch />
</label>
</div>
</div>
@ -397,7 +397,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="number" v-model="app.cfg.audio.dBSPLcalibration"/>
<input type="number" v-model="app.cfg.audio.dBSPLcalibration" />
</label>
</div>
</div>
@ -408,33 +408,33 @@
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/zap.svg" classes="svg-md" name="settings-audiolabs"/>
<svg-icon url="./assets/feather/zap.svg" classes="svg-md" name="settings-audiolabs" />
</div>
<div>
{{ $root.getLz('settings.option.audio.audioLab') }}
</div>
</template>
<div class="settings-tab-content">
<audiolabs-page/>
<audiolabs-page />
</div>
</b-tab>
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/style.svg" classes="svg-md" name="settings-styles"/>
<svg-icon url="./assets/feather/style.svg" classes="svg-md" name="settings-styles" />
</div>
<div>
{{ $root.getLz('settings.header.visual.styles') }}
</div>
</template>
<div class="settings-tab-content">
<installed-themes/>
<installed-themes />
</div>
</b-tab>
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/pen-tool.svg" classes="svg-md" name="settings-visual"/>
<svg-icon url="./assets/feather/pen-tool.svg" classes="svg-md" name="settings-visual" />
</div>
<div>
{{ $root.getLz('settings.header.visual') }}
@ -504,7 +504,7 @@
{{$root.getLz('settings.option.visual.windowColor')}}
</div>
<div class="md-option-segment_auto">
<input type="color" v-model="app.cfg.visual.windowColor"/>
<input type="color" v-model="app.cfg.visual.windowColor" />
</div>
</div>
<div class="md-option-line">
@ -513,7 +513,7 @@
</div>
<div class="md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.visual.customAccentColor"
:disabled="app.cfg.visual.purplePodcastPlaybackBar" switch/>
:disabled="app.cfg.visual.purplePodcastPlaybackBar" switch />
</div>
</div>
<div class="md-option-line child" v-if="app.cfg.visual.customAccentColor">
@ -521,7 +521,7 @@
{{$root.getLz('settings.option.visual.accentColor')}}
</div>
<div class="md-option-segment_auto">
<input type="color" v-model="app.cfg.visual.accentColor"/>
<input type="color" v-model="app.cfg.visual.accentColor" />
</div>
</div>
<div class="md-option-line">
@ -530,7 +530,7 @@
</div>
<div class="md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.visual.purplePodcastPlaybackBar"
:disabled="app.cfg.visual.customAccentColor" switch/>
:disabled="app.cfg.visual.customAccentColor" switch />
</div>
</div>
<div class="md-option-line">
@ -538,7 +538,7 @@
{{$root.getLz('settings.option.visual.compactArtistHeader')}}
</div>
<div class="md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.visual.compactArtistHeader" switch/>
<input type="checkbox" v-model="app.cfg.visual.compactArtistHeader" switch />
</div>
</div>
<div class="md-option-line">
@ -569,7 +569,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.visual.showuserinfo"
v-on:change="toggleUserInfo" switch/>
v-on:change="toggleUserInfo" switch />
</label>
</div>
</div>
@ -604,7 +604,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.close_button_hide" switch/>
<input type="checkbox" v-model="app.cfg.general.close_button_hide" switch />
</label>
</div>
</div>
@ -616,7 +616,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.visual.nativeTitleBar" switch
@change="promptForRelaunch()"/>
@change="promptForRelaunch()" />
</label>
</div>
</div>
@ -697,7 +697,8 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" switch v-model="app.cfg.visual.bg_artwork_rotation"/>
<input type="checkbox" switch
v-model="app.cfg.visual.bg_artwork_rotation" />
</label>
</div>
</div>
@ -708,20 +709,20 @@
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/plugins.svg" classes="svg-md" name="settings-plugins"/>
<svg-icon url="./assets/feather/plugins.svg" classes="svg-md" name="settings-plugins" />
</div>
<div>
{{ $root.getLz('term.plugins') }}
</div>
</template>
<div class="settings-tab-content">
<plugins-github/>
<plugins-github />
</div>
</b-tab>
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/mic.svg" classes="svg-md" name="settings-lyrics"/>
<svg-icon url="./assets/feather/mic.svg" classes="svg-md" name="settings-lyrics" />
</div>
<div>
{{ $root.getLz('settings.header.lyrics') }}
@ -740,7 +741,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lyrics.enable_mxm" switch/>
<input type="checkbox" v-model="app.cfg.lyrics.enable_mxm" switch />
</label>
</div>
</div>
@ -750,7 +751,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lyrics.mxm_karaoke" switch/>
<input type="checkbox" v-model="app.cfg.lyrics.mxm_karaoke" switch />
</label>
</div>
</div>
@ -844,7 +845,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lyrics.enable_yt" switch/>
<input type="checkbox" v-model="app.cfg.lyrics.enable_yt" switch />
</label>
</div>
</div>
@ -854,7 +855,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.lyrics.enable_qq" switch/>
<input type="checkbox" v-model="app.cfg.lyrics.enable_qq" switch />
</label>
</div>
</div>
@ -865,7 +866,7 @@
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/radio.svg" classes="svg-md" name="settings-connectivity"/>
<svg-icon url="./assets/feather/radio.svg" classes="svg-md" name="settings-connectivity" />
</div>
<div>
{{ $root.getLz('settings.header.connectivity') }}
@ -886,7 +887,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.general.playbackNotifications"
switch/>
switch />
</label>
</div>
</div>
@ -899,7 +900,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.connectivity.discord_rpc.enabled"
switch/>
switch />
</label>
</div>
</div>
@ -926,7 +927,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox"
v-model="app.cfg.connectivity.discord_rpc.clear_on_pause" switch/>
v-model="app.cfg.connectivity.discord_rpc.clear_on_pause" switch />
</label>
</div>
</div>
@ -938,7 +939,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox"
v-model="app.cfg.connectivity.discord_rpc.hide_buttons" switch/>
v-model="app.cfg.connectivity.discord_rpc.hide_buttons" switch />
</label>
</div>
</div>
@ -950,14 +951,14 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox"
v-model="app.cfg.connectivity.discord_rpc.hide_timestamp" switch/>
v-model="app.cfg.connectivity.discord_rpc.hide_timestamp" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.detailsFormat')}}<br/>
{{$root.getLz('settings.option.connectivity.discordRPC.detailsFormat')}}<br />
<small>{{$root.getLz('term.variables')}}: {artist}, {composer}, {title},
{album},
{trackNumber}</small>
@ -965,7 +966,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text"
v-model="app.cfg.connectivity.discord_rpc.details_format"/>
v-model="app.cfg.connectivity.discord_rpc.details_format" />
</label>
</div>
</div>
@ -979,7 +980,8 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text" v-model="app.cfg.connectivity.discord_rpc.state_format"/>
<input type="text"
v-model="app.cfg.connectivity.discord_rpc.state_format" />
</label>
</div>
</div>
@ -1018,9 +1020,9 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<form @submit.prevent="submitToken">
<input type="text" autofocus id="lfmToken"/>
<input type="text" autofocus id="lfmToken" />
<input type="submit" class="md-btn"
@value="$root.getLz('action.submit')"/>
@value="$root.getLz('action.submit')" />
</form>
</label>
</div>
@ -1032,7 +1034,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="number" min="50" max="100"
v-model="app.cfg.connectivity.lastfm.scrobble_after"/>
v-model="app.cfg.connectivity.lastfm.scrobble_after" />
</label>
</div>
</div>
@ -1044,7 +1046,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.connectivity.lastfm.filter_loop"
switch/>
switch />
</label>
</div>
</div>
@ -1055,7 +1057,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.connectivity.lastfm.remove_featured"
switch/>
switch />
</label>
</div>
</div>
@ -1077,7 +1079,7 @@
<b-tab>
<template #title>
<div>
<svg-icon url="./assets/feather/hard-drive.svg" classes="svg-md" name="settings-advanced"/>
<svg-icon url="./assets/feather/hard-drive.svg" classes="svg-md" name="settings-advanced" />
</div>
<div>
{{$root.getLz('settings.header.advanced')}}
@ -1118,7 +1120,7 @@
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.advanced.disableLogging" switch/>
<input type="checkbox" v-model="app.cfg.advanced.disableLogging" switch />
</label>
</div>
</div>
@ -1161,7 +1163,7 @@
<input type="checkbox"
v-model="app.cfg.advanced.experiments.includes('immersive-preview')"
@click="app.cfg.advanced.experiments.includes('immersive-preview') ? removeExperiment('immersive-preview') : addExperiment('immersive-preview')"
switch/>
switch />
</label>
</div>
</div>
@ -1177,7 +1179,7 @@
<input type="checkbox"
v-model="app.cfg.advanced.experiments.includes('unknown-sources')"
@click="app.cfg.advanced.experiments.includes('unknown-sources') ? removeExperiment('unknown-sources') : addExperiment('unknown-sources')"
switch/>
switch />
</label>
</div>
</div>
@ -1192,7 +1194,7 @@
<input type="checkbox"
v-model="app.cfg.advanced.experiments.includes('ampv3')"
@click="app.cfg.advanced.experiments.includes('ampv3') ? removeExperiment('ampv3') : addExperiment('ampv3')"
switch/>
switch />
</label>
</div>
</div>
@ -1206,7 +1208,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.advanced.playlistTrackMapping"
switch/>
switch />
</label>
</div>
</div>
@ -1221,7 +1223,7 @@
<input type="checkbox"
v-model="app.cfg.advanced.experiments.includes('compactui')"
@click="app.cfg.advanced.experiments.includes('compactui') ? removeExperiment('compactui') : addExperiment('compactui')"
switch :disabled="!!app.getThemeDirective('forceUI')"/>
switch :disabled="!!app.getThemeDirective('forceUI')" />
</label>
</div>
</div>
@ -1235,7 +1237,7 @@
<input type="checkbox" disabled
v-model="app.cfg.advanced.experiments.includes('inline-playlists')"
@click="app.cfg.advanced.experiments.includes('inline-playlists') ? removeExperiment('inline-playlists') : addExperiment('inline-playlists')"
switch/>
switch />
</label>
</div>
</div>
@ -1248,7 +1250,7 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" v-model="app.cfg.visual.transparent" switch
@change="promptForRelaunch()"/>
@change="promptForRelaunch()" />
</label>
</div>
</div>
@ -1280,14 +1282,14 @@
<!--keybinds Settings -->
<b-tab id="hid">
<template>
<keybinds-settings/>
<keybinds-settings />
</template>
</b-tab>
<!--keybinds-settings -->
<!--Github-theme-settings -->
<b-tab id="hid">
<template>
<themes-github/>
<themes-github />
</template>
</b-tab>
<!--Github-theme-settings -->
@ -1383,164 +1385,164 @@
</div>
</script>
<script>
Vue.component("settings-window", {
template: "#settings-window",
data: function () {
return {
app: this.$root,
themes: ipcRenderer.sendSync("get-themes"),
tabIndex: 0,
canChangeHash: false,
lastfmConnecting: false
}
},
watch: {
tabIndex: function (val) {
if (this.canChangeHash) {
// window.location.hash = `#settings/${val}`
}
}
},
methods: {
sidebarVis() {
const tabIndex = app.$store.state.pageState['settings'].currentTabIndex
if (tabIndex == 3 || tabIndex == 5 || tabIndex == 10) {
return true;
}
return false
},
close() {
this.$root.modals.settings = false
},
windowBgStyleChange() {
this.$root.getNowPlayingArtworkBG(undefined, true)
if (this.$root.cfg.visual.window_background_style === "mica") {
this.$root.spawnMica()
}
},
reinstallWidevineCDM() {
app.confirm(app.getLz("settings.option.experimental.reinstallwidevine.confirm"), (ok) => {
if (ok) {
ipcRenderer.invoke("reinstall-widevine-cdm");
}
})
},
gitHubExplore() {
app.openSettingsPage("github-themes")
},
copyLogs() {
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 = {
"main": [],
"fun": [],
"unsorted": []
}
// sort by category if category is undefined or empty put it in "unsorted"
for (let i = 0; i < langs.length; i++) {
if (langs[i].category === undefined || langs[i].category === "") {
categories.unsorted.push(langs[i])
} else {
categories[langs[i].category].push(langs[i])
}
}
// return
return categories
},
addExperiment(flag) {
app.cfg.advanced.experiments.push(flag);
},
removeExperiment(flag) {
app.cfg.advanced.experiments.splice(app.cfg.advanced.experiments.indexOf(flag), 1);
},
toggleNormalization: function () {
if (app.cfg.audio.normalization) {
CiderAudio.normalizerOn()
} else {
CiderAudio.normalizerOff()
}
},
changeAudioQuality: function () {
app.mk.bitrate = MusicKit.PlaybackBitrate[app.cfg.audio.quality];
},
toggleUserInfo: function () {
app.chrome.hideUserInfo = !app.cfg.visual.showuserinfo
},
sendDataToMTT: function () {
ipcRenderer.invoke('setStoreValue', 'general.close_behavior', app.cfg.general.close_behavior);
// setStoreValue does not change plugin store values somehow
ipcRenderer.invoke('update-store-mtt', app.cfg.general.close_behavior);
},
checkIfUpdateDisabled() {
if (app.cfg.main.UPDATABLE) return;
let updateFields = document.getElementsByClassName('update-check');
for (let i = 0; i < updateFields.length; i++) {
updateFields[i].style = "opacity: 0.5; pointer-events: none;";
updateFields[i].title = "Not available on this type of build";
}
},
promptForRelaunch() {
app.confirm(app.getLz('action.relaunch.confirm'), function (result) {
if (result) {
ipcRenderer.send('relaunchApp', '');
}
});
},
authCC() {
ipcRenderer.send('cc-auth')
},
logoutCC() {
ipcRenderer.send('cc-logout')
},
reloadDiscordRPC() {
ipcRenderer.send('reloadRPC')
},
lfmDisconnect() {
this.$root.cfg.connectivity.lastfm.enabled = false;
this.$root.cfg.connectivity.lastfm.secrets.username = "";
this.$root.cfg.connectivity.lastfm.secrets.key = "";
ipcRenderer.send('lastfm:disconnect');
},
async lfmAuthorize() {
this.lastfmConnecting = true;
window.open(await ipcRenderer.invoke('lastfm:url'));
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connecting'));
/* Just a timeout for the button */
setTimeout(() => {
if (!this.$root.cfg.connectivity.lastfm.enabled) {
app.notyf.error(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectError'));
console.warn('[lastfm:authorize] Last.fm authorization timed out.');
this.lastfmConnecting = false;
}
}, 20000);
ipcRenderer.once('lastfm:authenticated', (_e, session) => {
this.$root.cfg.connectivity.lastfm.secrets.username = session.username
this.$root.cfg.connectivity.lastfm.secrets.key = session.key
this.$root.cfg.connectivity.lastfm.enabled = true
this.lastfmConnecting = false;
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectSuccess'));
})
},
filterChange(e) {
this.$root.cfg.connectivity.lastfm.filter_types[e.target.value] = e.target.checked;
},
submitToken() {
const token = document.getElementById('lfmToken').value;
ipcRenderer.send('lastfm:auth', token);
},
openLocalSongsPathMenu() {
app.modals.pathMenu = true
}
Vue.component("settings-window", {
template: "#settings-window",
data: function() {
return {
app: this.$root,
themes: ipcRenderer.sendSync("get-themes"),
tabIndex: 0,
canChangeHash: false,
lastfmConnecting: false
}
},
watch: {
tabIndex: function(val) {
if (this.canChangeHash) {
// window.location.hash = `#settings/${val}`
}
})
</script>
}
},
methods: {
sidebarVis() {
const tabIndex = app.$store.state.pageState['settings'].currentTabIndex
if (tabIndex == 3 || tabIndex == 5 || tabIndex == 10) {
return true;
}
return false
},
close() {
this.$root.modals.settings = false
},
windowBgStyleChange() {
this.$root.getNowPlayingArtworkBG(undefined, true)
if (this.$root.cfg.visual.window_background_style === "mica") {
this.$root.spawnMica()
}
},
reinstallWidevineCDM() {
app.confirm(app.getLz("settings.option.experimental.reinstallwidevine.confirm"), (ok) => {
if (ok) {
ipcRenderer.invoke("reinstall-widevine-cdm");
}
})
},
gitHubExplore() {
app.openSettingsPage("github-themes")
},
copyLogs() {
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 = {
"main": [],
"fun": [],
"unsorted": []
}
// sort by category if category is undefined or empty put it in "unsorted"
for (let i = 0; i < langs.length; i++) {
if (langs[i].category === undefined || langs[i].category === "") {
categories.unsorted.push(langs[i])
} else {
categories[langs[i].category].push(langs[i])
}
}
// return
return categories
},
addExperiment(flag) {
app.cfg.advanced.experiments.push(flag);
},
removeExperiment(flag) {
app.cfg.advanced.experiments.splice(app.cfg.advanced.experiments.indexOf(flag), 1);
},
toggleNormalization: function() {
if (app.cfg.audio.normalization) {
CiderAudio.normalizerOn()
} else {
CiderAudio.normalizerOff()
}
},
changeAudioQuality: function() {
app.mk.bitrate = MusicKit.PlaybackBitrate[app.cfg.audio.quality];
},
toggleUserInfo: function() {
app.chrome.hideUserInfo = !app.cfg.visual.showuserinfo
},
sendDataToMTT: function() {
ipcRenderer.invoke('setStoreValue', 'general.close_behavior', app.cfg.general.close_behavior);
// setStoreValue does not change plugin store values somehow
ipcRenderer.invoke('update-store-mtt', app.cfg.general.close_behavior);
},
checkIfUpdateDisabled() {
if (app.cfg.main.UPDATABLE) return;
let updateFields = document.getElementsByClassName('update-check');
for (let i = 0; i < updateFields.length; i++) {
updateFields[i].style = "opacity: 0.5; pointer-events: none;";
updateFields[i].title = "Not available on this type of build";
}
},
promptForRelaunch() {
app.confirm(app.getLz('action.relaunch.confirm'), function(result) {
if (result) {
ipcRenderer.send('relaunchApp', '');
}
});
},
authCC() {
ipcRenderer.send('cc-auth')
},
logoutCC() {
ipcRenderer.send('cc-logout')
},
reloadDiscordRPC() {
ipcRenderer.send('reloadRPC')
},
lfmDisconnect() {
this.$root.cfg.connectivity.lastfm.enabled = false;
this.$root.cfg.connectivity.lastfm.secrets.username = "";
this.$root.cfg.connectivity.lastfm.secrets.key = "";
ipcRenderer.send('lastfm:disconnect');
},
async lfmAuthorize() {
this.lastfmConnecting = true;
window.open(await ipcRenderer.invoke('lastfm:url'));
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connecting'));
/* Just a timeout for the button */
setTimeout(() => {
if (!this.$root.cfg.connectivity.lastfm.enabled) {
app.notyf.error(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectError'));
console.warn('[lastfm:authorize] Last.fm authorization timed out.');
this.lastfmConnecting = false;
}
}, 20000);
ipcRenderer.once('lastfm:authenticated', (_e, session) => {
this.$root.cfg.connectivity.lastfm.secrets.username = session.username
this.$root.cfg.connectivity.lastfm.secrets.key = session.key
this.$root.cfg.connectivity.lastfm.enabled = true
this.lastfmConnecting = false;
app.notyf.success(app.getLz('settings.notyf.connectivity.lastfmScrobble.connectSuccess'));
})
},
filterChange(e) {
this.$root.cfg.connectivity.lastfm.filter_types[e.target.value] = e.target.checked;
},
submitToken() {
const token = document.getElementById('lfmToken').value;
ipcRenderer.send('lastfm:auth', token);
},
openLocalSongsPathMenu() {
app.modals.pathMenu = true
}
}
})
</script>

View file

@ -1,10 +1,12 @@
<script type="text/x-template" id="add-to-playlist">
<template>
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" @contextmenu.self="app.resetState()">
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()"
@contextmenu.self="app.resetState()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div>
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button>
<button class="close-btn" @click="app.resetState()"
:aria-label="app.getLz('action.close')"></button>
</div>
<div class="modal-content">
<button class="playlist-item"
@ -12,7 +14,8 @@
<div class="icon"><%- include("../svg/plus.svg") %></div>
<div class="name">{{app.getLz('action.createPlaylist')}}</div>
</button>
<sidebar-playlist :playlist-select="playlistSelect" v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
<sidebar-playlist :playlist-select="playlistSelect"
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
</sidebar-playlist>
</div>
<div class="modal-search">
@ -34,55 +37,55 @@
</script>
<script>
Vue.component('add-to-playlist', {
template: '#add-to-playlist',
data: function () {
return {
playlistSorted: [],
searchQuery: "",
focused: "",
app: this.$root,
}
},
props: {
playlists: {
type: Array,
required: true
}
},
mounted() {
this.search()
this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) {
if (this.focused != "") {
this.addToPlaylist(this.focused)
}
}
})
},
methods: {
playlistSelect(playlist) {
if (playlist.type != "library-playlist-folders") {
this.addToPlaylist(playlist.id)
}
},
addToPlaylist(id) {
app.addSelectedToPlaylist(id)
},
search() {
this.focused = ""
if (this.searchQuery == "") {
this.playlistSorted = this.playlists
} else {
this.playlistSorted = this.playlists.filter(playlist => {
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
if (this.playlistSorted.length == 1) {
this.focused = this.playlistSorted[0].id
}
}
},
Vue.component('add-to-playlist', {
template: '#add-to-playlist',
data: function() {
return {
playlistSorted: [],
searchQuery: "",
focused: "",
app: this.$root,
}
},
props: {
playlists: {
type: Array,
required: true
}
},
mounted() {
this.search()
this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) {
if (this.focused != "") {
this.addToPlaylist(this.focused)
}
}
});
</script>
})
},
methods: {
playlistSelect(playlist) {
if (playlist.type != "library-playlist-folders") {
this.addToPlaylist(playlist.id)
}
},
addToPlaylist(id) {
app.addSelectedToPlaylist(id)
},
search() {
this.focused = ""
if (this.searchQuery == "") {
this.playlistSorted = this.playlists
} else {
this.playlistSorted = this.playlists.filter(playlist => {
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
if (this.playlistSorted.length == 1) {
this.focused = this.playlistSorted[0].id
}
}
},
}
});
</script>

View file

@ -1,6 +1,6 @@
<script type="text/x-template" id="sidebar-playlist">
<div class="sidebar-playlist" :key="item.id">
<button class="app-sidebar-item app-sidebar-item-playlist" :key="item.id"
<button class="app-sidebar-item app-sidebar-item-playlist" :key="item.id"
:class="item.type != 'library-playlist-folders' ? {'active': $root.page.includes(item.id)} : ['playlist-folder', {'folder-button-active': folderOpened}, isPlaylistSelected]"
@contextmenu="playlistContextMenu($event, item.id)"
@dragstart="startDrag($event, item)"
@ -9,14 +9,17 @@
:href="item.href"
@click='clickEvent()'>
<template v-if="!renaming">
<svg-icon :url="icon" name="sidebar-playlist"/> {{ item.attributes.name }}
<svg-icon :url="icon" name="sidebar-playlist" />
{{ item.attributes.name }}
<small class="presentNotice" v-if="hasRelatedMediaItems">(Track present)</small>
</template>
<input type="text" v-model="item.attributes.name" class="pl-rename-field" @blur="rename()" @keydown.enter="rename()" v-else>
<input type="text" v-model="item.attributes.name" class="pl-rename-field" @blur="rename()"
@keydown.enter="rename()" v-else>
</button>
<div class="folder-body" v-if="item.type === 'library-playlist-folders' && folderOpened">
<template v-if="children.length != 0">
<sidebar-playlist v-for="item in children" :relate-media-items="relateMediaItems" :playlist-select="playlistSelect" :item="item" :key="item.id"></sidebar-playlist>
<sidebar-playlist v-for="item in children" :relate-media-items="relateMediaItems"
:playlist-select="playlistSelect" :item="item" :key="item.id"></sidebar-playlist>
</template>
<template v-else>
<div class="spinner"></div>
@ -26,263 +29,265 @@
</script>
<script>
Vue.component('sidebar-playlist', {
template: '#sidebar-playlist',
props: {
item: {
type: Object,
required: true
},
playlistSelect: {
type: Function,
required: false
},
relateMediaItems: {
type: Array,
required: false,
default() {
return []
}
}
},
data: function () {
return {
folderOpened: false,
children: [],
playlistRoot: "p.playlistsroot",
renaming: false,
icon: "",
hasRelatedMediaItems: false
}
},
async mounted() {
if (this.item.type !== "library-playlist-folders") {
this.icon = ("./assets/feather/list.svg")
} else {
this.icon = ("./assets/feather/folder.svg")
}
let playlistMap = this.$root.playlists.trackMapping
if (this.relateMediaItems.length != 0) {
if (playlistMap[this.relateMediaItems[0]]) {
if (playlistMap[this.relateMediaItems[0]].includes(this.item.id)) {
this.hasRelatedMediaItems = true
}
}
}
},
methods: {
clickEvent() {
if (this.item.type != "library-playlist-folders") {
if (this.playlistSelect) {
this.playlistSelect(this.item)
} else {
this.openPlaylist(this.item)
}
} else {
this.getPlaylistChildren(this.item)
}
},
rename() {
this.renaming = false
if (this.item.type === "library-playlist-folders") {
this.$root.editPlaylistFolder(this.item.id, this.item.attributes.name)
} else {
this.$root.editPlaylist(this.item.id, this.item.attributes.name)
}
},
async getChildren() {
let self = this
this.children = []
this.children = this.$root.playlists.listing.filter(child => {
if (child.parent == self.item.id) {
return child
}
})
},
async move(item, sendTo) {
let self = this
console.log(sendTo)
let type = item.type.replace("library-", "")
let typeTo = sendTo.type
this.$root.mk.api.v3.music(`/v1/me/library/${type}/${item.id}/parent`, {}, {
fetchOptions: {
method: "PUT",
body: JSON.stringify({
data: [{
id: sendTo.id,
type: typeTo
}]
})
}
})
// find the item in this.$root.playlists.listing and store it in a variable
this.$root.playlists.listing.filter(playlist => {
if (playlist.id == item.id) {
console.log(playlist)
playlist.parent = sendTo.id
}
})
if (typeof this.$root.getChildren == "function") {
this.$root.getChildren()
console.log(this.$root.children)
}
await this.getChildren()
this.$root.sortPlaylists()
// await this.$root.refreshPlaylists()
},
playlistContextMenu(event, playlist_id) {
let menu = {
items: {
"moveToParent": {
name: this.$root.getLz('action.moveToTop'),
action: () => {
let self = this
this.move(this.item, {
id: this.playlistRoot,
type: "library-playlist-folders"
})
setTimeout(() => { self.getChildren() }, 2000)
}
},
"rename": {
name: this.$root.getLz('action.rename'),
action: () => {
this.renaming = true
setTimeout(() => {
document.querySelector(".pl-rename-field").focus()
document.querySelector(".pl-rename-field").select()
}, 100)
}
},
"deleteFromPlaylist": {
name: this.$root.getLz('action.removeFromLibrary'),
action: () => {
this.$root.deletePlaylist(playlist_id)
}
},
"addToFavorites": {
name: this.$root.getLz('action.addToFavorites'),
disabled: true,
hidden: true,
action: () => {
this.addFavorite(playlist_id, "library-playlists")
}
}
}
}
if (this.item.type === "library-playlist-folders") {
menu.items.addToFavorites.disabled = true
}
app.showMenuPanel(menu, event)
},
dragOver(evt) {
evt.preventDefault();
evt.dataTransfer.dropEffect = "move";
},
onDrop(evt) {
let data = JSON.parse(evt.dataTransfer.getData("text/plain"))
evt.preventDefault();
if (data.id == this.item.id) {
return;
}
console.log(data)
if (data) {
if (this.item.type == "library-playlists" || this.item.type == "library-playlist-folders") {
if (data.type == "library-playlists" && this.item.type == "library-playlists") {
return
}
this.move(data, this.item)
}
}
},
startDrag(evt) {
evt.dataTransfer.dropEffect = 'move'
evt.dataTransfer.effectAllowed = 'move'
evt.dataTransfer.setData('text/plain', JSON.stringify(this.item))
},
openPlaylist(item) {
this.$root.appRoute(`playlist_` + item.id);
this.$root.showingPlaylist = [];
if (item.id == 'ciderlocal') {
this.$root.showingPlaylist = {
"id": "ciderlocal",
"type": "library-playlists",
"href": "",
"attributes": {
"artwork": {
"width": null,
"height": null,
"url": "",
"hasP3": false
},
"dateAdded": "2021-02-16T03:39:47Z",
"name": "Local Songs",
"canDelete": true,
"hasCatalog": true,
"canEdit": true,
"playParams": {
"id": "ciderlocal",
"kind": "playlist",
"isLibrary": true,
},
"isPublic": true,
"description": {
"standard": ""
}
},
"relationships": {
"tracks": {
"href": "",
"data": this.$root.library.localsongs
}
}
}
this.$root.playlists.loadingState = 1;
} else {
this.$root.getPlaylistFromID(this.$root.page.substring(9), true)
}
},
getPlaylistChildren(item) {
let self = this
this.children = []
this.getChildren()
this.toggleFolder()
this.$root.mk.api.v3.music(`v1/me/library/playlist-folders/${item.id}/children`).then(data => {
let children = data.data.data;
children.forEach(child => {
if (!self.$root.playlists.listing.find(listing => listing.id == child.id)) {
child.parent = self.item.id
self.$root.playlists.listing.push(child)
}
})
self.$root.playlists.listing.sort((a, b) => {
if (a.type === 'library-playlist-folders' && b.type !== 'library-playlist-folders') {
return -1
} else if (a.type !== 'library-playlist-folders' && b.type === 'library-playlist-folders') {
return 1
} else {
return 0
}
})
self.getChildren()
})
},
isPlaylistSelected(item) {
if (this.$root.showingPlaylist.id == item.id) {
return ["active"]
} else {
return []
}
},
toggleFolder() {
this.folderOpened = !this.folderOpened;
}
Vue.component('sidebar-playlist', {
template: '#sidebar-playlist',
props: {
item: {
type: Object,
required: true
},
playlistSelect: {
type: Function,
required: false
},
relateMediaItems: {
type: Array,
required: false,
default() {
return []
}
});
</script>
}
},
data: function() {
return {
folderOpened: false,
children: [],
playlistRoot: "p.playlistsroot",
renaming: false,
icon: "",
hasRelatedMediaItems: false
}
},
async mounted() {
if (this.item.type !== "library-playlist-folders") {
this.icon = ("./assets/feather/list.svg")
} else {
this.icon = ("./assets/feather/folder.svg")
}
let playlistMap = this.$root.playlists.trackMapping
if (this.relateMediaItems.length != 0) {
if (playlistMap[this.relateMediaItems[0]]) {
if (playlistMap[this.relateMediaItems[0]].includes(this.item.id)) {
this.hasRelatedMediaItems = true
}
}
}
},
methods: {
clickEvent() {
if (this.item.type != "library-playlist-folders") {
if (this.playlistSelect) {
this.playlistSelect(this.item)
} else {
this.openPlaylist(this.item)
}
} else {
this.getPlaylistChildren(this.item)
}
},
rename() {
this.renaming = false
if (this.item.type === "library-playlist-folders") {
this.$root.editPlaylistFolder(this.item.id, this.item.attributes.name)
} else {
this.$root.editPlaylist(this.item.id, this.item.attributes.name)
}
},
async getChildren() {
let self = this
this.children = []
this.children = this.$root.playlists.listing.filter(child => {
if (child.parent == self.item.id) {
return child
}
})
},
async move(item, sendTo) {
let self = this
console.log(sendTo)
let type = item.type.replace("library-", "")
let typeTo = sendTo.type
this.$root.mk.api.v3.music(`/v1/me/library/${type}/${item.id}/parent`, {}, {
fetchOptions: {
method: "PUT",
body: JSON.stringify({
data: [{
id: sendTo.id,
type: typeTo
}]
})
}
})
// find the item in this.$root.playlists.listing and store it in a variable
this.$root.playlists.listing.filter(playlist => {
if (playlist.id == item.id) {
console.log(playlist)
playlist.parent = sendTo.id
}
})
if (typeof this.$root.getChildren == "function") {
this.$root.getChildren()
console.log(this.$root.children)
}
await this.getChildren()
this.$root.sortPlaylists()
// await this.$root.refreshPlaylists()
},
playlistContextMenu(event, playlist_id) {
let menu = {
items: {
"moveToParent": {
name: this.$root.getLz('action.moveToTop'),
action: () => {
let self = this
this.move(this.item, {
id: this.playlistRoot,
type: "library-playlist-folders"
})
setTimeout(() => {
self.getChildren()
}, 2000)
}
},
"rename": {
name: this.$root.getLz('action.rename'),
action: () => {
this.renaming = true
setTimeout(() => {
document.querySelector(".pl-rename-field").focus()
document.querySelector(".pl-rename-field").select()
}, 100)
}
},
"deleteFromPlaylist": {
name: this.$root.getLz('action.removeFromLibrary'),
action: () => {
this.$root.deletePlaylist(playlist_id)
}
},
"addToFavorites": {
name: this.$root.getLz('action.addToFavorites'),
disabled: true,
hidden: true,
action: () => {
this.addFavorite(playlist_id, "library-playlists")
}
}
}
}
if (this.item.type === "library-playlist-folders") {
menu.items.addToFavorites.disabled = true
}
app.showMenuPanel(menu, event)
},
dragOver(evt) {
evt.preventDefault();
evt.dataTransfer.dropEffect = "move";
},
onDrop(evt) {
let data = JSON.parse(evt.dataTransfer.getData("text/plain"))
evt.preventDefault();
if (data.id == this.item.id) {
return;
}
console.log(data)
if (data) {
if (this.item.type == "library-playlists" || this.item.type == "library-playlist-folders") {
if (data.type == "library-playlists" && this.item.type == "library-playlists") {
return
}
this.move(data, this.item)
}
}
},
startDrag(evt) {
evt.dataTransfer.dropEffect = 'move'
evt.dataTransfer.effectAllowed = 'move'
evt.dataTransfer.setData('text/plain', JSON.stringify(this.item))
},
openPlaylist(item) {
this.$root.appRoute(`playlist_` + item.id);
this.$root.showingPlaylist = [];
if (item.id == 'ciderlocal') {
this.$root.showingPlaylist = {
"id": "ciderlocal",
"type": "library-playlists",
"href": "",
"attributes": {
"artwork": {
"width": null,
"height": null,
"url": "",
"hasP3": false
},
"dateAdded": "2021-02-16T03:39:47Z",
"name": "Local Songs",
"canDelete": true,
"hasCatalog": true,
"canEdit": true,
"playParams": {
"id": "ciderlocal",
"kind": "playlist",
"isLibrary": true,
},
"isPublic": true,
"description": {
"standard": ""
}
},
"relationships": {
"tracks": {
"href": "",
"data": this.$root.library.localsongs
}
}
}
this.$root.playlists.loadingState = 1;
} else {
this.$root.getPlaylistFromID(this.$root.page.substring(9), true)
}
},
getPlaylistChildren(item) {
let self = this
this.children = []
this.getChildren()
this.toggleFolder()
this.$root.mk.api.v3.music(`v1/me/library/playlist-folders/${item.id}/children`).then(data => {
let children = data.data.data;
children.forEach(child => {
if (!self.$root.playlists.listing.find(listing => listing.id == child.id)) {
child.parent = self.item.id
self.$root.playlists.listing.push(child)
}
})
self.$root.playlists.listing.sort((a, b) => {
if (a.type === 'library-playlist-folders' && b.type !== 'library-playlist-folders') {
return -1
} else if (a.type !== 'library-playlist-folders' && b.type === 'library-playlist-folders') {
return 1
} else {
return 0
}
})
self.getChildren()
})
},
isPlaylistSelected(item) {
if (this.$root.showingPlaylist.id == item.id) {
return ["active"]
} else {
return []
}
},
toggleFolder() {
this.folderOpened = !this.folderOpened;
}
}
});
</script>

View file

@ -140,12 +140,12 @@
</template>
<template v-if="$root.cfg.libraryPrefs.localPaths.length != 0">
<div class="app-sidebar-header-text"
@click="$root.cfg.general.sidebarCollapsed.localLibrary = !$root.cfg.general.sidebarCollapsed.localLibrary"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.localLibrary}">
Local Library
@click="$root.cfg.general.sidebarCollapsed.localLibrary = !$root.cfg.general.sidebarCollapsed.localLibrary"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.localLibrary}">
Local Library
</div>
<template v-if="!$root.cfg.general.sidebarCollapsed.localLibrary">
<sidebar-playlist :item="{attributes: { name:'Songs'} , id:'ciderlocal'}"></sidebar-playlist>
<sidebar-playlist :item="{attributes: { name:'Songs'} , id:'ciderlocal'}"></sidebar-playlist>
</template>
</template>
<template v-if="$root.getPlaylistFolderChildren('p.applemusic').length != 0">
@ -310,7 +310,7 @@
</script>
<script>
Vue.component("cider-app-sidebar", {
template: "#cider-app-sidebar"
})
</script>
Vue.component("cider-app-sidebar", {
template: "#cider-app-sidebar"
})
</script>

View file

@ -15,123 +15,123 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Cider</title>
<link rel="<%- (env.dev ? " stylesheet" : "stylesheet/less" ) %>" type="text/css" href="style.<%- (env.dev ? "css"
: "less" ) %>"/>
<!-- <link rel="stylesheet/less" type="text/css" id="userTheme" href="themes/default.less"/>-->
<script src="./lib/less.js"></script>
<script src="<%- (env.dev ? " ./lib/vue.js" : "./lib/vue.dev.js" ) %>"></script>
<script src="./lib/smoothscroll.js"></script>
<link rel="manifest" href="./manifest.json?v=2">
<script src="https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js"></script>
<script src="hlscider.js"></script>
<script src="./lib/jquery-3.2.1.slim.min.js"></script>
<script src="./lib/popper.min.js"></script>
<script src="./lib/bootstrap.min.js"></script>
<script src="./lib/bootbox.min.js"></script>
<script src="./lib/notyf.min.js"></script>
<script src="./lib/marked.js"></script>
<script src="./lib/velocity.min.js"></script>
<script src="./lib/fast-plural-rules.js"></script>
<script src="./lib/resonance-audio.min.js"></script>
<script src="./lib/stackblur.min.js"></script>
<link rel="<%- (env.dev ? " stylesheet" : "stylesheet/less") %>" type="text/css" href="style.<%- (env.dev ? "css"
: "less") %>" />
<!-- <link rel="stylesheet/less" type="text/css" id="userTheme" href="themes/default.less"/>-->
<script src="./lib/less.js"></script>
<script src="<%- (env.dev ? " ./lib/vue.js" : "./lib/vue.dev.js"); %>"></script>
<script src="./lib/smoothscroll.js"></script>
<link rel="manifest" href="./manifest.json?v=2">
<script src="https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js"></script>
<script src="hlscider.js"></script>
<script src="./lib/jquery-3.2.1.slim.min.js"></script>
<script src="./lib/popper.min.js"></script>
<script src="./lib/bootstrap.min.js"></script>
<script src="./lib/bootbox.min.js"></script>
<script src="./lib/notyf.min.js"></script>
<script src="./lib/marked.js"></script>
<script src="./lib/velocity.min.js"></script>
<script src="./lib/fast-plural-rules.js"></script>
<script src="./lib/resonance-audio.min.js"></script>
<script src="./lib/stackblur.min.js"></script>
<style>
#LOADER {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #1E1E1E;
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
}
<style>
#LOADER {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #1E1E1E;
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
}
#LOADER>svg {
width: 128px;
}
#LOADER > svg {
width: 128px;
}
@media (prefers-color-scheme: light) {
#LOADER {
background-color: #eee;
}
}
</style>
@media (prefers-color-scheme: light) {
#LOADER {
background-color: #eee;
}
}
</style>
</head>
<body class="notransparency" oncontextmenu="return false;" loading="1" os-release="<%= parseInt(env.osRelease) %>"
platform="<%= env.platform %>">
<script src="<%- (env.dev ? " ./lib/vue.js" : "./lib/vue.dev.js" ) %>"></script>
<script src="./lib/vue-horizontal.js"></script>
<script src="./lib/bootstrap-vue.min.js"></script>
<script src="./lib/vuex.min.js"></script>
<script src="./lib/sortable.min.js"></script>
<script src="./lib/vue-observe-visibility.min.js"></script>
<script src="./lib/vuedraggable.umd.min.js"></script>
<script>
window.quasarConfig = {
brand: {
primary: '#fc3c44'
},
config: {
dark: true
},
loadingBar: { skipHijack: true }
}
</script>
<!-- <script src="./lib/quasar/quasar.umd.min.js"></script> -->
<script type="module" src="./main/app.js"></script>
<body class="notransparency" oncontextmenu="return false;" loading="1" os-release="<%= parseInt(env.osRelease) ;%>"
platform="<%= env.platform ;%>">
<script src="<%- (env.dev ? " ./lib/vue.js" : "./lib/vue.dev.js"); %>"></script>
<script src="./lib/vue-horizontal.js"></script>
<script src="./lib/bootstrap-vue.min.js"></script>
<script src="./lib/vuex.min.js"></script>
<script src="./lib/sortable.min.js"></script>
<script src="./lib/vue-observe-visibility.min.js"></script>
<script src="./lib/vuedraggable.umd.min.js"></script>
<script>
window.quasarConfig = {
brand: {
primary: '#fc3c44'
},
config: {
dark: true
},
loadingBar: { skipHijack: true }
}
</script>
<!-- <script src="./lib/quasar/quasar.umd.min.js"></script> -->
<script type="module" src="./main/app.js"></script>
<div id="LOADER">
<%- include("../assets/cider-round.svg") %>
</div>
<div id="app" :class="getAppClasses()" v-if="appVisible" :window-state="chrome.windowState" :style="getAppStyle()"
:library-visible="(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'); %>
<%- include('app/app-navigation'); %>
<%- include('app/chrome-bottom'); %>
</div>
</transition>
<transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'fullscreen'">
<fullscreen-view :image="currentArtUrlRaw" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"
:richlyrics="richlyrics"></fullscreen-view>
</div>
</transition>
<transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'mini'">
<mini-view :image="currentArtUrlRaw" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"
:richlyrics="richlyrics">
</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>
<div id="LOADER">
<%- include("../assets/cider-round.svg") %>
</div>
<div id="app" :class="getAppClasses()" v-if="appVisible" :window-state="chrome.windowState" :style="getAppStyle()"
:library-visible="(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'); %>
<%- include('app/app-navigation'); %>
<%- include('app/chrome-bottom'); %>
</div>
</transition>
<transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'fullscreen'">
<fullscreen-view :image="currentArtUrlRaw" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"
:richlyrics="richlyrics"></fullscreen-view>
</div>
</transition>
<transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'mini'">
<mini-view :image="currentArtUrlRaw" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"
:richlyrics="richlyrics">
</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>
<% for(var i=0; i < Object.keys(env.components).length ; i++) { %>
<%- include(env.components[i]); %>
<% } %>
<% for(var i = 0; i < Object.keys(env.components).length ; i++) { %>
<%- include(env.components[i]); %>
<% } %>
<script async
src="<%- (env.useV3 ? 'https://js-cdn.music.apple.com/musickit/v3/amp/musickit.js' : 'https://api.cider.sh/musickit.js') %>"
data-web-components>
</script>
<script src="index.js?v=1"></script>
<script async
src="<%- (env.useV3 ? "https://js-cdn.music.apple.com/musickit/v3/amp/musickit.js" : "https://api.cider.sh/musickit.js"); %>"
data-web-components>
</script>
<script src="index.js?v=1"></script>
<script type="text/x-template" id="am-musiccovershelf">
<script type="text/x-template" id="am-musiccovershelf">
<h1>{{ component.attributes.title.stringForDisplay }}</h1>
</script>
</body>
</html>
</html>

View file

@ -6,19 +6,33 @@
<p style="text-align: center" id="version">{{ $root.getLz("term.version") }} {{ $root.version }}</p>
<p style="text-align: center"> {{$root.getLz('about.thanks')}} </p>
<p style="text-align: center">"{{$root.getLz('term.appleMusic')}}" - {{$root.getLz('term.copyright')}} © 2022 <a href="https://www.apple.com/" class="dt-footer__link"
target="_blank"
rel="noopener" data-dt-link-to-exclude="">{{$root.getLz('term.appleInc')}}</a>
{{$root.getLz('term.rightsReserved')}}</p>
<p style="text-align: center">"{{$root.getLz('term.appleMusic')}}" - {{$root.getLz('term.copyright')}} ©
2022 <a href="https://www.apple.com/" class="dt-footer__link"
target="_blank"
rel="noopener" data-dt-link-to-exclude="">{{$root.getLz('term.appleInc')}}</a>
{{$root.getLz('term.rightsReserved')}}</p>
<hr>
<h3>{{$root.getLz('term.sponsor')}}</h3>
<button onclick="window.open('https://github.com/sponsors/ciderapp')" class="md-btn sponsorBtn githubBtn"><img src="./assets/github.svg"/>GitHub Sponsors</button>
<button onclick="window.open('https://ko-fi.com/cryptofyre')" class="md-btn sponsorBtn kofiBtn"><img src="./assets/ko_fi.svg"/>Ko-fi</button>
<button onclick="window.open('https://opencollective.com/ciderapp')" class="md-btn sponsorBtn opencollectiveBtn"><img src="./assets/open_collective.svg"/>Open Collective</button>
<button onclick="window.open('https://github.com/sponsors/ciderapp')"
class="md-btn sponsorBtn githubBtn"><img src="./assets/github.svg" />GitHub Sponsors
</button>
<button onclick="window.open('https://ko-fi.com/cryptofyre')" class="md-btn sponsorBtn kofiBtn"><img
src="./assets/ko_fi.svg" />Ko-fi
</button>
<button onclick="window.open('https://opencollective.com/ciderapp')"
class="md-btn sponsorBtn opencollectiveBtn"><img src="./assets/open_collective.svg" />Open
Collective
</button>
<h3>{{$root.getLz('term.socials')}}</h3>
<button onclick="window.open('https://github.com/ciderapp/Cider')" class="md-btn sponsorBtn githubBtn"><img src="./assets/github.svg"/>{{$root.getLz('term.github')}}</button>
<button onclick="window.open('https://discord.gg/applemusic')" class="md-btn sponsorBtn discordBtn"><img style="height: 26px;" src="./assets/discord.svg"/>{{$root.getLz('term.discord')}}</button>
<button onclick="window.open('https://twitter.com/UseCider')" class="md-btn sponsorBtn twitterBtn"><img src="./assets/twitter.svg"/>Twitter</button>
<button onclick="window.open('https://github.com/ciderapp/Cider')" class="md-btn sponsorBtn githubBtn">
<img src="./assets/github.svg" />{{$root.getLz('term.github')}}
</button>
<button onclick="window.open('https://discord.gg/applemusic')" class="md-btn sponsorBtn discordBtn"><img
style="height: 26px;" src="./assets/discord.svg" />{{$root.getLz('term.discord')}}
</button>
<button onclick="window.open('https://twitter.com/UseCider')" class="md-btn sponsorBtn twitterBtn"><img
src="./assets/twitter.svg" />Twitter
</button>
</div>
<div class="col">
@ -26,14 +40,18 @@
<div class="col">
<h3>{{$root.getLz('term.ciderTeam')}}</h3>
<div class="md-btn teamBtn" v-for="member in team" @click="window.open(member.link)">
<img :src="member.avatar"/>
<img :src="member.avatar" />
<div class="row" style="width:100%;">
<div class="col" style="text-align: left;">
{{ member.name }}
</div>
<div class="col" style="text-align: right">
<button @click.stop="window.open(member.twitter)" class="social-btn" v-if="member.twitter">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white" style=""><path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"></path></svg>
<button @click.stop="window.open(member.twitter)" class="social-btn"
v-if="member.twitter">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="white" style="">
<path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"></path>
</svg>
</button>
</div>
<div class="col-auto">
@ -51,105 +69,103 @@
<img class="md-contributors"
style="cursor:pointer;width:100%;"
onclick="window.open('https://github.com/ciderapp/Cider/graphs/contributors')"
src="https://contrib.rocks/image?repo=ciderapp/Cider&columns=25"/>
src="https://contrib.rocks/image?repo=ciderapp/Cider&columns=25" />
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('about-page', {
template: '#about-page',
data: function () {
return {
window: window,
version: app.version,
team: [
{
name: 'cryptofyre',
link: 'https://github.com/cryptofyre',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/33162551?v=4',
twitter: 'https://twitter.com/cryptofyre'
},
{
name: 'Core',
link: 'https://github.com/coredev-uk',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/64542347?v=4',
twitter: 'https://twitter.com/core_hdd'
},
{
name: 'Quacksire',
link: 'https://github.com/quacksire',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/19170969?v=4',
twitter: 'https://twitter.com/duckdoquack'
},
{
name: 'booploops',
link: 'https://github.com/booploops',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/49113086?v=4',
twitter: 'https://twitter.com/boopl00ps'
},
{
name: 'vapormusic',
link: 'https://github.com/vapormusic',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/27716185?v=4'
},
{
name: 'crypticplank',
link: 'https://github.com/crypticplank',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/52553007?v=4',
twitter: 'https://twitter.com/crypticplank'
},
{
name: 'Maikiwi',
link: 'https://github.com/maikirakiwi',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/74925636?v=4',
twitter: 'https://twitter.com/notmaikiwi'
},
{
name: 'yazninja',
link: 'https://github.com/yazninja',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/71800112?v=4',
twitter: 'https://twitter.com/YazNinjaa'
},
{
name: 'GamingLiamStudios',
link: 'https://github.com/GamingLiamStudios',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/58615717?v=4',
twitter: 'https://twitter.com/GLStudios_'
},
{
name: 'Amaru',
link: 'https://github.com/Amaru8',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/52407090?v=4'
},
{
name: 'Void',
link: 'https://twitter.com/MoonyVoid',
role: app.getLz('term.socialTeam'),
avatar: 'https://pbs.twimg.com/profile_images/1226463559472816129/8LScNYED_400x400.jpg'
},
{
name: 'NoseySG',
link: 'https://twitter.com/noah_grose',
role: app.getLz('term.socialTeam'),
avatar: 'https://pbs.twimg.com/profile_images/1496944907260420099/D5gl6H4J_400x400.jpg'
}
]
}
},
methods: {
}
});
</script>
Vue.component('about-page', {
template: '#about-page',
data: function() {
return {
window: window,
version: app.version,
team: [
{
name: 'cryptofyre',
link: 'https://github.com/cryptofyre',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/33162551?v=4',
twitter: 'https://twitter.com/cryptofyre'
},
{
name: 'Core',
link: 'https://github.com/coredev-uk',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/64542347?v=4',
twitter: 'https://twitter.com/core_hdd'
},
{
name: 'Quacksire',
link: 'https://github.com/quacksire',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/19170969?v=4',
twitter: 'https://twitter.com/duckdoquack'
},
{
name: 'booploops',
link: 'https://github.com/booploops',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/49113086?v=4',
twitter: 'https://twitter.com/boopl00ps'
},
{
name: 'vapormusic',
link: 'https://github.com/vapormusic',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/27716185?v=4'
},
{
name: 'crypticplank',
link: 'https://github.com/crypticplank',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/52553007?v=4',
twitter: 'https://twitter.com/crypticplank'
},
{
name: 'Maikiwi',
link: 'https://github.com/maikirakiwi',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/74925636?v=4',
twitter: 'https://twitter.com/notmaikiwi'
},
{
name: 'yazninja',
link: 'https://github.com/yazninja',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/71800112?v=4',
twitter: 'https://twitter.com/YazNinjaa'
},
{
name: 'GamingLiamStudios',
link: 'https://github.com/GamingLiamStudios',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/58615717?v=4',
twitter: 'https://twitter.com/GLStudios_'
},
{
name: 'Amaru',
link: 'https://github.com/Amaru8',
role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/52407090?v=4'
},
{
name: 'Void',
link: 'https://twitter.com/MoonyVoid',
role: app.getLz('term.socialTeam'),
avatar: 'https://pbs.twimg.com/profile_images/1226463559472816129/8LScNYED_400x400.jpg'
},
{
name: 'NoseySG',
link: 'https://twitter.com/noah_grose',
role: app.getLz('term.socialTeam'),
avatar: 'https://pbs.twimg.com/profile_images/1496944907260420099/D5gl6H4J_400x400.jpg'
}
]
}
},
methods: {}
});
</script>

View file

@ -1,18 +1,18 @@
<script type="text/x-template" id="apple-account-settings">
<div style="display:flex;width:100%;height:100%;padding-top: var(--navigationBarHeight);position:absolute;top:0;left:0;">
<webview id="foo" src="https://music.apple.com/includes/commerce/account/settings?product=music&isFullscreen=true&isModal=false" style="display:inline-flex; width:100%;"></webview>
<webview id="foo"
src="https://music.apple.com/includes/commerce/account/settings?product=music&isFullscreen=true&isModal=false"
style="display:inline-flex; width:100%;"></webview>
</div>
</script>
<script>
Vue.component('apple-account-settings', {
template: '#apple-account-settings',
mounted() {
document.querySelector("#foo").addEventListener("dom-ready", ()=>{
// document.querySelector("#foo").executeJavaScript(`document.body.innerHTML += ("<style>.header {display: none!important;} </style>")`)
})
},
methods: {
}
});
</script>
Vue.component('apple-account-settings', {
template: '#apple-account-settings',
mounted() {
document.querySelector("#foo").addEventListener("dom-ready", () => {
// document.querySelector("#foo").executeJavaScript(`document.body.innerHTML += ("<style>.header {display: none!important;} </style>")`)
})
},
methods: {}
});
</script>

View file

@ -2,41 +2,47 @@
<div class="content-inner">
<h1 class="header-text">{{ data.attributes.shortName ?? data.attributes.name}}</h1>
<template v-if="data.relationships && data.relationships.grouping">
<template v-for="(recom,index) in data.relationships.grouping.data[0].relationships.tabs.data[0].relationships.children.data">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes.name ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center" v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall" @click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')" >{{app.getLz('term.seeAll')}}</button>
</div>
<template
v-for="(recom,index) in data.relationships.grouping.data[0].relationships.tabs.data[0].relationships.children.data">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes.name ?? ""}}</h3>
</div>
<template v-if="recom.relationships && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data))">
<template v-if="(recom.attributes.name && recom.attributes.name.includes('ideo')) || index === 0">
<mediaitem-scroller-horizontal-mvview :imagesize="800" :browsesp="index == 0"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<div class="col-auto cider-flex-center"
v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall"
@click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<template
v-if="recom.relationships && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data))">
<template v-if="(recom.attributes.name && recom.attributes.name.includes('ideo')) || index === 0">
<mediaitem-scroller-horizontal-mvview :imagesize="800" :browsesp="index == 0"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
</template>
</template>
</template>
</div>
</script>
<script>
Vue.component('cider-applecurator', {
template: "#cider-applecurator",
props: ["data"],
data: function() {
return {
app: this.$root
}
}
})
</script>
Vue.component('cider-applecurator', {
template: "#cider-applecurator",
props: ["data"],
data: function() {
return {
app: this.$root
}
}
})
</script>

View file

@ -8,7 +8,9 @@
<h3>{{app.getLz('home.followedArtists')}}</h3>
</div>
<div class="col-auto nopadding cider-flex-center">
<button class="cd-btn-seeall" @click="syncFavorites()" v-if="!syncingFavs">{{app.getLz('home.syncFavorites')}}</button>
<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>
@ -17,8 +19,10 @@
<mediaitem-square :item="artist" kind="small"></mediaitem-square>
<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.removeFavorite')}}
<div class="svg-icon"
:style="{'--url': 'url(./assets/feather/x-circle.svg)'}"></div>
</div>
{{app.getLz('action.removeFavorite')}}
</button>
</div>
</vue-horizontal>
@ -50,80 +54,81 @@
</script>
<script>
Vue.component('cider-artist-feed', {
template: '#cider-artist-feed',
data: function () {
return {
app: this.$root,
followedArtists: this.$root.cfg.home.followedArtists,
artistFeed: [],
artists: [],
syncingFavs: false
}
},
async mounted() {
let self = this
await this.getArtistFeed()
},
methods: {
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)
}
let artist = this.artists.find(a => a.id == id)
let index2 = this.artists.indexOf(artist)
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() {
let artists = this.followedArtists
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 = []
for (let artistIdx = 0; artistIdx < artists.length; artistIdx += 50) {
chunks.push(artists.slice(artistIdx, artistIdx + 50))
}
try {
const chunkArtistData = await Promise.all(chunks.map(chunk =>
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${chunk.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`)))
chunkArtistData.forEach(chunkResult =>
chunkResult.data.data.forEach(item => {
self.artists.push(item)
if (item.views["latest-release"].data.length != 0) {
self.artistFeed.push(item.views["latest-release"].data[0])
}
}))
// sort artistFeed by attributes.releaseDate descending, date is formatted as "YYYY-MM-DD"
this.artistFeed.sort((a, b) => {
let dateA = new Date(a.attributes.releaseDate)
let dateB = new Date(b.attributes.releaseDate)
return dateB - dateA
})
} catch (err) { }
}
Vue.component('cider-artist-feed', {
template: '#cider-artist-feed',
data: function() {
return {
app: this.$root,
followedArtists: this.$root.cfg.home.followedArtists,
artistFeed: [],
artists: [],
syncingFavs: false
}
},
async mounted() {
let self = this
await this.getArtistFeed()
},
methods: {
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)
}
});
</script>
let artist = this.artists.find(a => a.id == id)
let index2 = this.artists.indexOf(artist)
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() {
let artists = this.followedArtists
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 = []
for (let artistIdx = 0; artistIdx < artists.length; artistIdx += 50) {
chunks.push(artists.slice(artistIdx, artistIdx + 50))
}
try {
const chunkArtistData = await Promise.all(chunks.map(chunk =>
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${chunk.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`)))
chunkArtistData.forEach(chunkResult =>
chunkResult.data.data.forEach(item => {
self.artists.push(item)
if (item.views["latest-release"].data.length != 0) {
self.artistFeed.push(item.views["latest-release"].data[0])
}
}))
// sort artistFeed by attributes.releaseDate descending, date is formatted as "YYYY-MM-DD"
this.artistFeed.sort((a, b) => {
let dateA = new Date(a.attributes.releaseDate)
let dateB = new Date(b.attributes.releaseDate)
return dateB - dateA
})
} catch (err) {
}
}
}
});
</script>

View file

@ -1,7 +1,8 @@
<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) || hasHero()) ? 'animated' : '']">
<div :class="['artist-header', { 'artist-header-compact': app.cfg.visual.compactArtistHeader }]" :key="data.id" v-observe-visibility="{callback: isHeaderVisible}">
<div :class="['artist-header', { 'artist-header-compact': app.cfg.visual.compactArtistHeader }]" :key="data.id"
v-observe-visibility="{callback: isHeaderVisible}">
<animatedartwork-view
:priority="true"
v-if="hasAnimated()"
@ -26,7 +27,7 @@
<div class="col cider-flex-center artist-title"
:class="{'artist-animation-on': (data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) || hasHero() }"
:style="{ 'color': '#' +hasHeroObject()?.textColor1 ?? ''}"
>
>
<button class="artist-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
app.mk.play()
})" :aria-label="app.getLz('term.play')"><%- include("../svg/play.svg") %></button>
@ -42,10 +43,10 @@
<div class="svg-icon"></div>
</button>
</div>
<div class="artworkContainer"
<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>
<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>
@ -157,133 +158,133 @@
</div>
</script>
<script>
Vue.component('cider-artist', {
template: "#cider-artist",
props: ['data'],
data: function () {
return {
topSongsExpanded: false,
app: this.$root,
headerVisible: true
}
},
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;
},
hasHeroObject() {
if(this.data.attributes?.editorialArtwork?.centeredFullscreenBackground){
return this.data.attributes?.editorialArtwork?.centeredFullscreenBackground
} else if(this.data.attributes?.editorialArtwork?.bannerUber) {
return this.data.attributes?.editorialArtwork?.bannerUber
} else if(this.data.attributes?.editorialArtwork?.subscriptionHero){
return this.data.attributes?.editorialArtwork?.subscriptionHero
}
return [];
},
isHeaderVisible(visible) {
this.headerVisible = visible
},
async artistMenu(event) {
let self = this
let followAction = "follow"
let followActions = {
follow: {
icon: "./assets/feather/plus-circle.svg",
name: app.getLz('action.follow'),
action: () => {
self.app.cfg.home.followedArtists.push(self.data.id)
}
},
unfollow: {
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.unfollow'),
action: () => {
let index = self.app.cfg.home.followedArtists.indexOf(self.data.id)
if (index > -1) {
self.app.cfg.home.followedArtists.splice(index, 1)
}
}
}
}
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(() => {
app.mk.play()
})
}
},
favoriteActions[inFavorites ? "removeFavorite" : "favorite"],
// followActions[followAction],
{
icon: "./assets/feather/share.svg",
name: app.getLz('term.share'),
action: () => {
self.app.copyToClipboard(self.data.attributes.url)
}
}
]
}, event)
},
getArtistPalette(artist) {
if (artist["attributes"]["artwork"]) {
return {
"background": "#" + artist["attributes"]["artwork"]["bgColor"],
"color": "#" + artist["attributes"]["artwork"]["textColor1"],
}
} else {
return {
"background": "#000000",
"color": "#ffffff",
}
}
},
getTopResult() {
if (this.search.results["meta"]) {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} else {
return false;
}
}
Vue.component('cider-artist', {
template: "#cider-artist",
props: ['data'],
data: function() {
return {
topSongsExpanded: false,
app: this.$root,
headerVisible: true
}
},
methods: {
hasAnimated() {
if (this.data.attributes?.editorialVideo && (this.data.attributes?.editorialVideo?.motionArtistWide16x9 || this.data.attributes?.editorialVideo?.motionArtistFullscreen16x9)) {
return true;
}
})
</script>
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;
},
hasHeroObject() {
if (this.data.attributes?.editorialArtwork?.centeredFullscreenBackground) {
return this.data.attributes?.editorialArtwork?.centeredFullscreenBackground
} else if (this.data.attributes?.editorialArtwork?.bannerUber) {
return this.data.attributes?.editorialArtwork?.bannerUber
} else if (this.data.attributes?.editorialArtwork?.subscriptionHero) {
return this.data.attributes?.editorialArtwork?.subscriptionHero
}
return [];
},
isHeaderVisible(visible) {
this.headerVisible = visible
},
async artistMenu(event) {
let self = this
let followAction = "follow"
let followActions = {
follow: {
icon: "./assets/feather/plus-circle.svg",
name: app.getLz('action.follow'),
action: () => {
self.app.cfg.home.followedArtists.push(self.data.id)
}
},
unfollow: {
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.unfollow'),
action: () => {
let index = self.app.cfg.home.followedArtists.indexOf(self.data.id)
if (index > -1) {
self.app.cfg.home.followedArtists.splice(index, 1)
}
}
}
}
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(() => {
app.mk.play()
})
}
},
favoriteActions[inFavorites ? "removeFavorite" : "favorite"],
// followActions[followAction],
{
icon: "./assets/feather/share.svg",
name: app.getLz('term.share'),
action: () => {
self.app.copyToClipboard(self.data.attributes.url)
}
}
]
}, event)
},
getArtistPalette(artist) {
if (artist["attributes"]["artwork"]) {
return {
"background": "#" + artist["attributes"]["artwork"]["bgColor"],
"color": "#" + artist["attributes"]["artwork"]["textColor1"],
}
} else {
return {
"background": "#000000",
"color": "#ffffff",
}
}
},
getTopResult() {
if (this.search.results["meta"]) {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} else {
return false;
}
}
}
})
</script>

View file

@ -1,183 +1,196 @@
<script type="text/x-template" id="audiolabs-page">
<div class="audiolabs-page">
<div class="md-option-container">
<div class="settings-option-body">
<div class="md-option-line">
<b-jumbotron :header="$root.getLz('settings.option.audio.audioLab')"
lead="Designed by Cider Acoustic Technologies in California"></b-jumbotron>
</div>
<div class="md-option-line">
<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="app.cfg.audio.maikiwiAudio.ciderPPE"
v-on:change="CiderAudio.hierarchical_loading();" switch/>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.audio.maikiwiAudio.ciderPPE === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength')}}
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<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 ({{$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>
<div class="md-option-line">
<div class="md-option-segment">
Cider Opportunistic Correction System
<br>
<small>Takes advantage of the sonic characteristics of a specific equipment and adapts it to be more 'Cider' oriented.</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:180px;"
v-model="app.cfg.audio.maikiwiAudio.opportunisticCorrection_state"
v-on:change="CiderAudio.hierarchical_loading()">
<option value="OFF">OFF</option>
<option value="CHU">Moondrop Chu</option>
</select>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer')}} [1]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.audio.maikiwiAudio.atmosphereRealizer1"
v-on:change="CiderAudio.hierarchical_loading();" switch/>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode')}} [1]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:230px;"
v-model="$root.cfg.audio.maikiwiAudio.atmosphereRealizer1_value"
v-on:change="CiderAudio.hierarchical_loading();">
<option v-for="profile in arprofiles" :value="profile.id">{{ $root.getProfileLz("CAR", profile.id) }}</option>
</select>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer')}} [2]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.audio.maikiwiAudio.atmosphereRealizer2"
v-on:change="CiderAudio.hierarchical_loading();" switch/>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode')}} [2]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:230px;"
v-model="$root.cfg.audio.maikiwiAudio.atmosphereRealizer2_value"
v-on:change="CiderAudio.hierarchical_loading();">
<option v-for="profile in arprofiles" :value="profile.id">{{ $root.getProfileLz("CAR", profile.id) }}</option>
</select>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization')}}
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.audio.maikiwiAudio.spatial"
v-on:change="CiderAudio.hierarchical_loading();" switch/>
</div>
</div>
<div class="md-option-line"
v-show="app.cfg.audio.maikiwiAudio.spatial === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile')}}
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.description')}}</small>
</div>
<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="CiderAudio.hierarchical_loading();">
<option v-for="profile in spprofiles" :value="profile.id">{{ $root.getProfileLz("CTS", profile.name) }}</option>
</select>
</div>
</div>
<div style="opacity: 0.5; pointer-events: none" v-if="false">
<div class="md-option-header">
<span>{{$root.getLz('settings.header.unfinished')}}</span>
</div>
<div class="md-option-line">
<div class="md-option-segment">
Cider Origami™ Vocal Enhancer/Remasterer
<br>
<small>Re-textures the vocals by carving out the frequencies and adjusts them to the
selected profile.<br>
<b>Modern:</b>
Modernizes vocals that was recorded on old microphones while preserving the artist's individualistic style.<br>
<b>Articulate:</b>
Wrapping every detail of the vocal to your ear, resulting in a more expressive voice.
</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select">
<option value="none">
{{$root.getLz('settings.header.visual.windowBackgroundStyle.none')}}
</option>
<option value="modern">
Modern
</option>
<option value="intimate">
Intimate
</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('audiolabs-page', {
template: "#audiolabs-page",
props: [],
data: function () {
return {
app: this.$root,
arprofiles: CiderAudio.atmosphereRealizerProfiles,
spprofiles: CiderAudio.spatialProfiles
}
},
mounted: function () {
},
methods: {
}
})
</script>
<script type="text/x-template" id="audiolabs-page">
<div class="audiolabs-page">
<div class="md-option-container">
<div class="settings-option-body">
<div class="md-option-line">
<b-jumbotron :header="$root.getLz('settings.option.audio.audioLab')"
lead="Designed by Cider Acoustic Technologies in California"></b-jumbotron>
</div>
<div class="md-option-line">
<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="app.cfg.audio.maikiwiAudio.ciderPPE"
v-on:change="CiderAudio.hierarchical_loading();" switch />
</div>
</div>
<div class="md-option-line" v-show="app.cfg.audio.maikiwiAudio.ciderPPE === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength')}}
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<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
({{$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>
<div class="md-option-line">
<div class="md-option-segment">
Cider Opportunistic Correction System
<br>
<small>Takes advantage of the sonic characteristics of a specific equipment and adapts it to be
more 'Cider' oriented.</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:180px;"
v-model="app.cfg.audio.maikiwiAudio.opportunisticCorrection_state"
v-on:change="CiderAudio.hierarchical_loading()">
<option value="OFF">OFF</option>
<option value="CHU">Moondrop Chu</option>
</select>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer')}} [1]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.audio.maikiwiAudio.atmosphereRealizer1"
v-on:change="CiderAudio.hierarchical_loading();" switch />
</div>
</div>
<div class="md-option-line" v-show="app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode')}} [1]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:230px;"
v-model="$root.cfg.audio.maikiwiAudio.atmosphereRealizer1_value"
v-on:change="CiderAudio.hierarchical_loading();">
<option v-for="profile in arprofiles" :value="profile.id">{{ $root.getProfileLz("CAR",
profile.id) }}
</option>
</select>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer')}} [2]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.audio.maikiwiAudio.atmosphereRealizer2"
v-on:change="CiderAudio.hierarchical_loading();" switch />
</div>
</div>
<div class="md-option-line" v-show="app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode')}} [2]
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select" style="width:230px;"
v-model="$root.cfg.audio.maikiwiAudio.atmosphereRealizer2_value"
v-on:change="CiderAudio.hierarchical_loading();">
<option v-for="profile in arprofiles" :value="profile.id">{{ $root.getProfileLz("CAR",
profile.id) }}
</option>
</select>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization')}}
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<input type="checkbox" v-model="app.cfg.audio.maikiwiAudio.spatial"
v-on:change="CiderAudio.hierarchical_loading();" switch />
</div>
</div>
<div class="md-option-line"
v-show="app.cfg.audio.maikiwiAudio.spatial === true">
<div class="md-option-segment">
{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile')}}
<br>
<small>{{$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.description')}}</small>
</div>
<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="CiderAudio.hierarchical_loading();">
<option v-for="profile in spprofiles" :value="profile.id">{{ $root.getProfileLz("CTS",
profile.name) }}
</option>
</select>
</div>
</div>
<div style="opacity: 0.5; pointer-events: none" v-if="false">
<div class="md-option-header">
<span>{{$root.getLz('settings.header.unfinished')}}</span>
</div>
<div class="md-option-line">
<div class="md-option-segment">
Cider Origami™ Vocal Enhancer/Remasterer
<br>
<small>Re-textures the vocals by carving out the frequencies and adjusts them to the
selected profile.<br>
<b>Modern:</b>
Modernizes vocals that was recorded on old microphones while preserving the artist's
individualistic style.<br>
<b>Articulate:</b>
Wrapping every detail of the vocal to your ear, resulting in a more expressive voice.
</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<select class="md-select">
<option value="none">
{{$root.getLz('settings.header.visual.windowBackgroundStyle.none')}}
</option>
<option value="modern">
Modern
</option>
<option value="intimate">
Intimate
</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('audiolabs-page', {
template: "#audiolabs-page",
props: [],
data: function() {
return {
app: this.$root,
arprofiles: CiderAudio.atmosphereRealizerProfiles,
spprofiles: CiderAudio.spatialProfiles
}
},
mounted: function() {
},
methods: {}
})
</script>

View file

@ -1,57 +1,71 @@
<script type="text/x-template" id="cider-browse">
<div class="content-inner">
<h1 class="header-text">{{$root.getLz("term.browse")}}</h1>
<template v-if="data.relationships && data.relationships.tabs">
<template v-for="(recom,index) in data.relationships.tabs.data[0].relationships.children.data">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes.name ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center" v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall" v-if="recom.relationships.room" @click="app.showRoom(recom.relationships.room?.data[0].href)" >{{app.getLz('term.seeAll')}}</button>
<button class="cd-btn-seeall" v-else @click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<template v-if="(recom.relationships != null && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data)))">
<template v-if="index === 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)">
<mediaitem-scroller-horizontal-mvview :imagesize="800" :browsesp="index == 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)" :kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="(['327']).includes(recom.attributes.editorialElementKind)">
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="recom.relationships.contents.data.limit(20)">
</listitem-horizontal>
</div>
</template>
<template v-else-if="(['385']).includes(recom.attributes.editorialElementKind)">
<mediaitem-scroller-horizontal-mvview :imagesize="800" :kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
<!-- ignored -->
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
<template v-else>
<template v-if="recom.attributes.links && recom.attributes.editorialElementKind.includes('391')">
<div class="grouping-container">
<button class="grouping-btn" @click="$root.goToGrouping(link.url)" v-for="link in recom.attributes.links">{{ link.label }}</button>
<div class="content-inner">
<h1 class="header-text">{{$root.getLz("term.browse")}}</h1>
<template v-if="data.relationships && data.relationships.tabs">
<template v-for="(recom,index) in data.relationships.tabs.data[0].relationships.children.data">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes.name ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center"
v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall" v-if="recom.relationships.room"
@click="app.showRoom(recom.relationships.room?.data[0].href)">
{{app.getLz('term.seeAll')}}
</button>
<button class="cd-btn-seeall" v-else
@click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<template
v-if="(recom.relationships != null && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data)))">
<template
v-if="index === 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)">
<mediaitem-scroller-horizontal-mvview :imagesize="800"
:browsesp="index == 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)"
:kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="(['327']).includes(recom.attributes.editorialElementKind)">
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="recom.relationships.contents.data.limit(20)">
</listitem-horizontal>
</div>
</template>
<template v-else-if="(['385']).includes(recom.attributes.editorialElementKind)">
<mediaitem-scroller-horizontal-mvview :imagesize="800"
:kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
<!-- ignored -->
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
<template v-else>
<template v-if="recom.attributes.links && recom.attributes.editorialElementKind.includes('391')">
<div class="grouping-container">
<button class="grouping-btn" @click="$root.goToGrouping(link.url)"
v-for="link in recom.attributes.links">{{ link.label }}
</button>
</div>
</template>
</template>
</template>
</template>
</template>
</template>
</div>
</template>
</div>
</script>
<script>
Vue.component("cider-browse", {
template: "#cider-browse",
props: ["data"],
data: function () {
data: function() {
return {
app: this.$root,
};
@ -59,7 +73,6 @@
mounted() {
this.$root.getBrowsePage();
},
methods: {
}
methods: {}
});
</script>

View file

@ -1,133 +1,151 @@
<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 cider-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>
<template v-if="songs != []">
<div class="row">
<div class="col">
<h3>{{ songs.name ?? ""}}</h3>
</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 cider-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 class="col-auto cider-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>
<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 cider-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>
<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>
<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 cider-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 class="col-auto cider-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>
<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 cider-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="(albums?.data ?? []).limit(10)"></mediaitem-scroller-horizontal-large>
</template>
<template v-if="playlists != []">
<div class="row">
<div class="col">
<h3>{{ playlists.name ?? ""}}</h3>
</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 cider-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 class="col-auto cider-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>
<mediaitem-scroller-horizontal-large
:items="(citycharts?.data ?? []).limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</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 cider-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 cider-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 cider-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>
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

@ -1,50 +1,57 @@
<script type="text/x-template" id="cider-multiroom">
<div class="content-inner cider-multiroom">
<div class="artworkContainer" v-if ="data.attributes?.uber?.masterArt?.url">
<artwork-material :url="data.attributes?.uber?.masterArt?.url ?? ''" size="800" images="1"></artwork-material>
<div class="artworkContainer" v-if="data.attributes?.uber?.masterArt?.url">
<artwork-material :url="data.attributes?.uber?.masterArt?.url ?? ''" size="800"
images="1"></artwork-material>
</div>
<div class="detail">
<h1 class="header-text">{{data.attributes?.title ?? ""}}</h1>
<h2 class="header-desc" v-html='data.relationships?.children?.data[0]?.attributes?.description ?? ""'></h2>
<template v-if="data.relationships">
<template v-for="(recom,index) in data.relationships.children.data">
<template v-if="(recom.relationships?.contents?.data ?? []).length > 0">
<template v-if="(recom.relationships?.contents?.data ?? []).length > 0">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes?.title ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center" v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall" @click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')" >{{app.getLz('term.seeAll')}}</button>
<div class="col-auto cider-flex-center"
v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall"
@click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<template v-if="recom.relationships && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data))">
<template v-if="(recom.attributes.name && recom.attributes.name.includes('ideo')) || index === 0">
<template
v-if="recom.relationships && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data))">
<template
v-if="(recom.attributes.name && recom.attributes.name.includes('ideo')) || index === 0">
<mediaitem-scroller-horizontal-mvview :imagesize="800" :browsesp="index == 0"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
</template>
</template>
</template>
</template>
</div>
</template>
</div>
</div>
</script>
<script>
Vue.component('cider-multiroom', {
template: "#cider-multiroom",
props: ["data"],
data: function() {
return {
app: this.$root
}
}
})
</script>
Vue.component('cider-multiroom', {
template: "#cider-multiroom",
props: ["data"],
data: function() {
return {
app: this.$root
}
}
})
</script>

File diff suppressed because it is too large Load diff

View file

@ -16,52 +16,56 @@
</div>
</div>
<div class="artist-body">
<template v-if="data.relationships && data.relationships['shared-playlists']">
<div class="row">
<div class="col">
<h3>{{ 'Shared Playlists' ?? ""}}</h3>
<template v-if="data.relationships && data.relationships['shared-playlists']">
<div class="row">
<div class="col">
<h3>{{ 'Shared Playlists' ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center"
v-if="data.relationships['shared-playlists'].data.length >= 10">
<button class="cd-btn-seeall"
@click="app.showCollection(data.relationships['shared-playlists'],'Shared Playlists' ?? '', 'default')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<div class="col-auto cider-flex-center" v-if="data.relationships['shared-playlists'].data.length >= 10">
<button class="cd-btn-seeall" @click="app.showCollection(data.relationships['shared-playlists'],'Shared Playlists' ?? '', 'default')">{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<mediaitem-square :item="item" v-for="item in data.relationships['shared-playlists'].data.limit(10)">
</mediaitem-square>
<mediaitem-square :item="item" v-for="item in data.relationships['shared-playlists'].data.limit(10)">
</mediaitem-square>
</template>
</div>
</div>
</script>
<script>
Vue.component('cider-socialprofile', {
template: "#cider-socialprofile",
props: ['data'],
data: function () {
return {
topSongsExpanded: false,
app: this.$root
}
},
methods: {
getArtistPalette(artist) {
if (artist?.attributes?.artwork != null) {
return {
"background": "#" + artist["attributes"]["artwork"]["bgColor"],
"color": "#" + artist["attributes"]["artwork"]["textColor1"],
}
} else {
return {
"background": "#000000",
"color": "#ffffff",
}
}
},
getTopResult() {
if (this.search.results["meta"]) {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} else {
return false;
}
}
Vue.component('cider-socialprofile', {
template: "#cider-socialprofile",
props: ['data'],
data: function() {
return {
topSongsExpanded: false,
app: this.$root
}
},
methods: {
getArtistPalette(artist) {
if (artist?.attributes?.artwork != null) {
return {
"background": "#" + artist["attributes"]["artwork"]["bgColor"],
"color": "#" + artist["attributes"]["artwork"]["textColor1"],
}
} else {
return {
"background": "#000000",
"color": "#ffffff",
}
}
})
</script>
},
getTopResult() {
if (this.search.results["meta"]) {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} else {
return false;
}
}
}
})
</script>

View file

@ -6,7 +6,7 @@
<template v-if="item.type == 'artists'">
<mediaitem-square :item="item"></mediaitem-square>
</template>
<template v-else>
<template v-else>
<mediaitem-list-item
v-if="getKind(item) == 'song'"
:index="key"
@ -14,11 +14,13 @@
<mediaitem-square v-else :item="item" :type="getKind(item)"></mediaitem-square>
</template>
</template>
<button v-if="triggerEnabled" style="opacity:0;height: 32px;" v-observe-visibility="{callback: visibilityChanged}">{{this.app.getLz('term.showMore')}}
<button v-if="triggerEnabled" style="opacity:0;height: 32px;"
v-observe-visibility="{callback: visibilityChanged}">{{this.app.getLz('term.showMore')}}
</button>
</div>
<transition name="fabfade">
<button class="top-fab" v-show="showFab" @click="scrollToTop()" :aria-label="app.getLz('action.scrollToTop')">
<button class="top-fab" v-show="showFab" @click="scrollToTop()"
:aria-label="app.getLz('action.scrollToTop')">
<%- include("../svg/arrow-up.svg") %>
</button>
</transition>
@ -28,121 +30,121 @@
</div>
</script>
<script>
Vue.component('cider-collection-list', {
template: "#cider-collection-list",
props: {
data: {
type: Object,
required: true
},
title: {
type: String,
required: false
},
type: {
type: String,
required: false,
default: "artists"
}
},
data: function() {
return {
triggerEnabled: true,
canSeeTrigger: false,
showFab: false,
commonKind: "song",
api: this.$root.mk.api,
loading: false,
app: this.$root,
}
},
methods: {
getClasses() {
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;
return item.kind
}
if (typeof item.attributes.playParams != "undefined") {
// this.commonKind = item.attributes.playParams.kind
return item.attributes.playParams.kind
}
return this.commonKind
},
scrollToTop() {
let target = document.querySelector(".header-text")
document.querySelector("#app-content").scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
})
},
getNext() {
let self = this
this.triggerEnabled = false;
if (typeof this.data.next == "undefined") {
return
}
this.loading = true
this.api.v3.music(this.data.next, app.collectionList.requestBody).then((response) => {
console.log(response)
if (!app.collectionList.response.groups) {
this.data.data = this.data.data.concat(response.data.data);
if (response.data.next) {
this.data.next = response.data.next;
this.triggerEnabled = true;
}
this.loading = false
} else {
if (!response.data.results[app.collectionList.response.groups]) {
this.loading = false
return
}
this.data.data = this.data.data.concat(response.data.results[app.collectionList.response.groups].data);
if (response.data.results[app.collectionList.response.groups].next) {
this.data.next = response.data.results[app.collectionList.response.groups].next;
this.triggerEnabled = true;
this.loading = false
}
}
})
},
headerVisibility: function(isVisible, entry) {
if (isVisible) {
this.showFab = false;
} else {
this.showFab = true;
}
},
visibilityChanged: function(isVisible, entry) {
if (isVisible) {
this.canSeeTrigger = true;
this.getNext();
} else {
this.canSeeTrigger = false;
}
}
Vue.component('cider-collection-list', {
template: "#cider-collection-list",
props: {
data: {
type: Object,
required: true
},
title: {
type: String,
required: false
},
type: {
type: String,
required: false,
default: "artists"
}
},
data: function() {
return {
triggerEnabled: true,
canSeeTrigger: false,
showFab: false,
commonKind: "song",
api: this.$root.mk.api,
loading: false,
app: this.$root,
}
},
methods: {
getClasses() {
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 "";
}
})
</script>
},
getKind(item) {
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
}
return this.commonKind
},
scrollToTop() {
let target = document.querySelector(".header-text")
document.querySelector("#app-content").scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
})
},
getNext() {
let self = this
this.triggerEnabled = false;
if (typeof this.data.next == "undefined") {
return
}
this.loading = true
this.api.v3.music(this.data.next, app.collectionList.requestBody).then((response) => {
console.log(response)
if (!app.collectionList.response.groups) {
this.data.data = this.data.data.concat(response.data.data);
if (response.data.next) {
this.data.next = response.data.next;
this.triggerEnabled = true;
}
this.loading = false
} else {
if (!response.data.results[app.collectionList.response.groups]) {
this.loading = false
return
}
this.data.data = this.data.data.concat(response.data.results[app.collectionList.response.groups].data);
if (response.data.results[app.collectionList.response.groups].next) {
this.data.next = response.data.results[app.collectionList.response.groups].next;
this.triggerEnabled = true;
this.loading = false
}
}
})
},
headerVisibility: function(isVisible, entry) {
if (isVisible) {
this.showFab = false;
} else {
this.showFab = true;
}
},
visibilityChanged: function(isVisible, entry) {
if (isVisible) {
this.canSeeTrigger = true;
this.getNext();
} else {
this.canSeeTrigger = false;
}
}
}
})
</script>

View file

@ -4,18 +4,16 @@
</div>
</script>
<script>
Vue.component('connected', {
template: '#connected',
async mounted() {
ipcRenderer.send('get-connected-url')
ipcRenderer.on('send-connected-url', (event, url) => {
this.url = url
app.webview.src = url
document.getElementById('foo').src = url;
})
},
methods: {
}
});
</script>
Vue.component('connected', {
template: '#connected',
async mounted() {
ipcRenderer.send('get-connected-url')
ipcRenderer.on('send-connected-url', (event, url) => {
this.url = url
app.webview.src = url
document.getElementById('foo').src = url;
})
},
methods: {}
});
</script>

View file

@ -1,69 +1,84 @@
<script type="text/x-template" id="cider-groupings">
<div class="content-inner">
<template v-if="data != null">
<h1 class="header-text">{{data.attributes?.name}}</h1>
<div class="content-inner">
<template v-if="data != null">
<h1 class="header-text">{{data.attributes?.name}}</h1>
<template v-if="data.relationships && data.relationships.tabs">
<template v-for="(recom,index) in data.relationships.tabs.data[0].relationships.children.data">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes.name ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center" v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall" v-if="recom.relationships.room" @click="app.showRoom(recom.relationships.room?.data[0].href)" >{{app.getLz('term.seeAll')}}</button>
<button class="cd-btn-seeall" v-else @click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')" >{{app.getLz('term.seeAll')}}</button>
</div>
</div>
<template v-if="(recom.relationships != null && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data)))">
<template v-if="index === 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)">
<mediaitem-scroller-horizontal-mvview :imagesize="800" :browsesp="index == 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)" :kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data : recom.relationships.contents.data"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="(['327']).includes(recom.attributes.editorialElementKind)">
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="recom.relationships.contents.data.limit(20)">
</listitem-horizontal>
</div>
</template>
<template v-else-if="(['385']).includes(recom.attributes.editorialElementKind)">
<mediaitem-scroller-horizontal-mvview :imagesize="800" :kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
<!-- ignored -->
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
<template v-else>
<template v-if="recom.attributes.links && recom.attributes.editorialElementKind.includes('391')">
<div class="grouping-container">
<button class="grouping-btn" @click="$root.goToGrouping(link.url)" v-for="link in recom.attributes.links">{{ link.label }}</button>
<template v-if="data.relationships && data.relationships.tabs">
<template v-for="(recom,index) in data.relationships.tabs.data[0].relationships.children.data">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes.name ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center"
v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall" v-if="recom.relationships.room"
@click="app.showRoom(recom.relationships.room?.data[0].href)">
{{app.getLz('term.seeAll')}}
</button>
<button class="cd-btn-seeall" v-else
@click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<template
v-if="(recom.relationships != null && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data)))">
<template
v-if="index === 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)">
<mediaitem-scroller-horizontal-mvview :imagesize="800"
:browsesp="index == 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)"
:kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data : recom.relationships.contents.data"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="(['327']).includes(recom.attributes.editorialElementKind)">
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="recom.relationships.contents.data.limit(20)">
</listitem-horizontal>
</div>
</template>
<template v-else-if="(['385']).includes(recom.attributes.editorialElementKind)">
<mediaitem-scroller-horizontal-mvview :imagesize="800"
:kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
<!-- ignored -->
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
<template v-else>
<template
v-if="recom.attributes.links && recom.attributes.editorialElementKind.includes('391')">
<div class="grouping-container">
<button class="grouping-btn" @click="$root.goToGrouping(link.url)"
v-for="link in recom.attributes.links">{{ link.label }}
</button>
</div>
</template>
</template>
</template>
</template>
</template>
</template>
</template>
</div>
</template>
</template>
</div>
</script>
<script>
Vue.component("cider-groupings", {
template: "#cider-groupings",
data: function () {
data: function() {
return {
data: null,
app: this.$root,
query: "",
};
},
async mounted() {
async mounted() {
const queryDefaults = {
"platform": "web",
"l" : this.$root.mklang,
"l": this.$root.mklang,
"extend": "editorialArtwork,artistUrl",
"omit[resource:artists]": "relationships",
"include[groupings]": "curator",
@ -82,7 +97,7 @@
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.query.includes("&") ? queryDefaults : { "platform": "web" });
this.data = result.data.data[0];
console.log(this.data);

View file

@ -8,8 +8,10 @@
<h3>{{app.getLz('home.recentlyPlayed')}}</h3>
</div>
<div class="col-auto nopadding cider-flex-center">
<button class="cd-btn-seeall" @click="seeAllHistory()">{{app.getLz('term.history')}}</button>
<button class="cd-btn-seeall" @click="seeAllRecentlyPlayed()">{{app.getLz('term.seeAll')}}</button>
<button class="cd-btn-seeall" @click="seeAllHistory()">{{app.getLz('term.history')}}
</button>
<button class="cd-btn-seeall" @click="seeAllRecentlyPlayed()">{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<div class="well artistfeed-well">
@ -26,9 +28,13 @@
<h3>{{app.getLz('home.artistsFeed')}}</h3>
</div>
<div class="col-auto nopadding cider-flex-center">
<button class="cd-btn-seeall" @click="syncFavorites()" v-if="!syncingFavs">{{app.getLz('home.syncFavorites')}}</button>
<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>
<button class="cd-btn-seeall" @click="app.appRoute('artist-feed')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<div class="well artistfeed-well" style="margin-top:0;">
@ -54,7 +60,9 @@
<!-- </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')">{{$root.getLz('term.replay')}} {{ year }}</button>
<button class="md-btn md-btn-block md-btn-replay--hero" @click="$root.appRoute('replay')">
{{$root.getLz('term.replay')}} {{ year }}
</button>
</div>
</div>
<div class="row">
@ -64,7 +72,9 @@
<h3>{{app.getLz('home.madeForYou')}}</h3>
</div>
<div class="col-auto nopadding cider-flex-center">
<button class="md-btn md-btn-replay" v-if="seenReplay" @click="$root.appRoute('replay')">{{$root.getLz('term.replay')}} {{ year }}</button>
<button class="md-btn md-btn-replay" v-if="seenReplay" @click="$root.appRoute('replay')">
{{$root.getLz('term.replay')}} {{ year }}
</button>
</div>
</div>
<div class="well">
@ -81,11 +91,14 @@
<h3>{{app.getLz('home.friendsListeningTo')}}</h3>
</div>
<div class="col-auto nopadding cider-flex-center">
<button class="cd-btn-seeall" @click="app.showSocialListeningTo()">{{app.getLz('term.seeAll')}}</button>
<button class="cd-btn-seeall" @click="app.showSocialListeningTo()">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
<div class="well">
<mediaitem-scroller-horizontal :items="friendsListeningTo" v-if="isSectionReady('friendsListeningTo')">
<mediaitem-scroller-horizontal :items="friendsListeningTo"
v-if="isSectionReady('friendsListeningTo')">
</mediaitem-scroller-horizontal>
<div class="spinner" v-else></div>
</div>
@ -96,167 +109,172 @@
</script>
<script>
Vue.component('cider-home', {
template: '#cider-home',
data: function() {
return {
app: this.$root,
followedArtists: this.$root.cfg.home.followedArtists,
favoriteItems: this.$root.cfg.home.favoriteItems,
madeForYou: [],
recentlyPlayed: [],
friendsListeningTo: [],
replayPlaylists: [],
favorites: [],
profile: {},
modify: 0,
artistFeed: [],
showingArtistFeed: false,
page: "main",
sectionsReady: [],
year: new Date().getFullYear(),
seenReplay: localStorage.getItem('seenReplay'),
syncingFavs: false
}
},
async mounted() {
let self = this
this.getListenNowData()
await this.getArtistFeed()
await this.getFavorites()
await this.getRecentlyPlayed()
if (new Date().getMonth() == 11) {
this.seenReplay = false
localStorage.setItem('seenReplay', false)
}
},
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,
include: 'tracks',
'include[albums]': 'catalog,tracks,artists',
'include[songs]': 'catalog,artists',
})
app.showCollection(hist.data, app.getLz('home.recentlyPlayed'))
},
async seeAllHistory() {
let hist = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, {
l: this.$root.mklang
})
app.showCollection(hist.data, app.getLz('term.history'))
},
isSectionReady(section) {
return this.sectionsReady.includes(section)
},
removeFavoriteContext() {
let self = this
return {
name: "Remove from Favorites",
action: function(item) {
let index = self.favoriteItems.findIndex(x => x.id == item.id)
if (index > -1) {
self.favoriteItems.splice(index, 1)
self.app.cfg.home.favoriteItems = self.favoriteItems
}
}
}
},
async getFavorites() {
let self = this
let libraryPlaylists = []
let playlists = []
for (let item of this.favoriteItems) {
if (item.type == "library-playlists") {
libraryPlaylists.push(item.id)
} else if (item.type == "playlists") {
playlists.push(item.id)
}
}
if (playlists.length != 0) {
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${playlists.toString()}`, {
l: this.$root.mklang
}).then(playlistsData => {
self.favorites.push(...playlistsData.data)
})
}
if (libraryPlaylists.length != 0) {
this.app.mk.api.v3.music(`v1/me/library/playlists/${playlists.toString()}`, {
l: this.$root.mklang
}).then(playlistsData => {
self.favorites.push(...playlistsData.data)
})
}
},
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));
}
try {
const chunkArtistData = await Promise.all(chunks.map(chunk =>
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${chunk.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`)))
chunkArtistData.forEach(chunkResult =>
chunkResult.data.data.forEach(item => {
if (item.views["latest-release"].data.length != 0) {
self.artistFeed.push(item.views["latest-release"].data[0])
}
}))
// sort artistFeed by attributes.releaseDate descending, date is formatted as "YYYY-MM-DD"
this.artistFeed.sort((a, b) => {
let dateA = new Date(a.attributes.releaseDate)
let dateB = new Date(b.attributes.releaseDate)
return dateB - dateA
})
} catch (error) { }
},
async getRecentlyPlayed() {
let hist = await app.mk.api.v3.music(`/v1/me/recent/played`, {
l: this.$root.mklang,
include: 'tracks',
'include[albums]': 'catalog,tracks,artists',
'include[songs]': 'catalog,artists',
})
this.recentlyPlayed = hist.data.data
},
async getListenNowData() {
let self = this
this.app.mk.api.v3.music(`/v1/me/recommendations?timezone=${encodeURIComponent(app.formatTimezoneOffset())}&name=listen-now&with=friendsMix,library,social&art[social-profiles:url]=c&art[url]=c,f&omit[resource]=autos&relate[editorial-items]=contents&extend=editorialCard,editorialVideo&extend[albums]=artistUrl&extend[library-albums]=artistUrl,editorialVideo&extend[playlists]=artistNames,editorialArtwork,editorialVideo&extend[library-playlists]=artistNames,editorialArtwork,editorialVideo&extend[social-profiles]=topGenreNames&include[albums]=artists&include[songs]=artists&include[music-videos]=artists&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url&fields[artists]=name,url&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&platform=web&l=${this.$root.mklang}`).then((data) => {
console.log(data.data.data[1])
try {
self.madeForYou = data.data.data.filter(section => {
if (section.meta.metrics.moduleType == "6") {
return section
};
})[0].relationships.contents.data
} catch (err) {}
self.sectionsReady.push("madeForYou")
try {
self.friendsListeningTo = data.data.data.filter(section => {
if (section.meta.metrics.moduleType == "11") {
return section
};
})[0].relationships.contents.data
} catch (err) {}
self.sectionsReady.push("recentlyPlayed")
self.sectionsReady.push("friendsListeningTo")
});
app.mk.api.v3.music("/v1/me/social/profile/").then((response) => {
self.profile = response.data.data[0]
})
Vue.component('cider-home', {
template: '#cider-home',
data: function() {
return {
app: this.$root,
followedArtists: this.$root.cfg.home.followedArtists,
favoriteItems: this.$root.cfg.home.favoriteItems,
madeForYou: [],
recentlyPlayed: [],
friendsListeningTo: [],
replayPlaylists: [],
favorites: [],
profile: {},
modify: 0,
artistFeed: [],
showingArtistFeed: false,
page: "main",
sectionsReady: [],
year: new Date().getFullYear(),
seenReplay: localStorage.getItem('seenReplay'),
syncingFavs: false
}
},
async mounted() {
let self = this
this.getListenNowData()
await this.getArtistFeed()
await this.getFavorites()
await this.getRecentlyPlayed()
if (new Date().getMonth() == 11) {
this.seenReplay = false
localStorage.setItem('seenReplay', false)
}
},
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,
include: 'tracks',
'include[albums]': 'catalog,tracks,artists',
'include[songs]': 'catalog,artists',
})
app.showCollection(hist.data, app.getLz('home.recentlyPlayed'))
},
async seeAllHistory() {
let hist = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, {
l: this.$root.mklang
})
app.showCollection(hist.data, app.getLz('term.history'))
},
isSectionReady(section) {
return this.sectionsReady.includes(section)
},
removeFavoriteContext() {
let self = this
return {
name: "Remove from Favorites",
action: function(item) {
let index = self.favoriteItems.findIndex(x => x.id == item.id)
if (index > -1) {
self.favoriteItems.splice(index, 1)
self.app.cfg.home.favoriteItems = self.favoriteItems
}
}
}
});
</script>
},
async getFavorites() {
let self = this
let libraryPlaylists = []
let playlists = []
for (let item of this.favoriteItems) {
if (item.type == "library-playlists") {
libraryPlaylists.push(item.id)
} else if (item.type == "playlists") {
playlists.push(item.id)
}
}
if (playlists.length != 0) {
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${playlists.toString()}`, {
l: this.$root.mklang
}).then(playlistsData => {
self.favorites.push(...playlistsData.data)
})
}
if (libraryPlaylists.length != 0) {
this.app.mk.api.v3.music(`v1/me/library/playlists/${playlists.toString()}`, {
l: this.$root.mklang
}).then(playlistsData => {
self.favorites.push(...playlistsData.data)
})
}
},
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));
}
try {
const chunkArtistData = await Promise.all(chunks.map(chunk =>
this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists?ids=${chunk.toString()}&views=latest-release&include[songs]=albums&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount&limit[artists:top-songs]=2&art[url]=f`)))
chunkArtistData.forEach(chunkResult =>
chunkResult.data.data.forEach(item => {
if (item.views["latest-release"].data.length != 0) {
self.artistFeed.push(item.views["latest-release"].data[0])
}
}))
// sort artistFeed by attributes.releaseDate descending, date is formatted as "YYYY-MM-DD"
this.artistFeed.sort((a, b) => {
let dateA = new Date(a.attributes.releaseDate)
let dateB = new Date(b.attributes.releaseDate)
return dateB - dateA
})
} catch (error) {
}
},
async getRecentlyPlayed() {
let hist = await app.mk.api.v3.music(`/v1/me/recent/played`, {
l: this.$root.mklang,
include: 'tracks',
'include[albums]': 'catalog,tracks,artists',
'include[songs]': 'catalog,artists',
})
this.recentlyPlayed = hist.data.data
},
async getListenNowData() {
let self = this
this.app.mk.api.v3.music(`/v1/me/recommendations?timezone=${encodeURIComponent(app.formatTimezoneOffset())}&name=listen-now&with=friendsMix,library,social&art[social-profiles:url]=c&art[url]=c,f&omit[resource]=autos&relate[editorial-items]=contents&extend=editorialCard,editorialVideo&extend[albums]=artistUrl&extend[library-albums]=artistUrl,editorialVideo&extend[playlists]=artistNames,editorialArtwork,editorialVideo&extend[library-playlists]=artistNames,editorialArtwork,editorialVideo&extend[social-profiles]=topGenreNames&include[albums]=artists&include[songs]=artists&include[music-videos]=artists&fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url&fields[artists]=name,url&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&platform=web&l=${this.$root.mklang}`).then((data) => {
console.log(data.data.data[1])
try {
self.madeForYou = data.data.data.filter(section => {
if (section.meta.metrics.moduleType == "6") {
return section
}
;
})[0].relationships.contents.data
} catch (err) {
}
self.sectionsReady.push("madeForYou")
try {
self.friendsListeningTo = data.data.data.filter(section => {
if (section.meta.metrics.moduleType == "11") {
return section
}
;
})[0].relationships.contents.data
} catch (err) {
}
self.sectionsReady.push("recentlyPlayed")
self.sectionsReady.push("friendsListeningTo")
});
app.mk.api.v3.music("/v1/me/social/profile/").then((response) => {
self.profile = response.data.data[0]
})
}
}
});
</script>

View file

@ -6,7 +6,8 @@
</div>
<div class="col-auto">
<button v-if="library.albums.downloadState == 2" @click="$root.getLibraryAlbumsFull(true, 1)"
class="reload-btn" :aria-label="app.getLz('menubar.options.reload')"><%- include('../svg/redo.svg') %></button>
class="reload-btn"
:aria-label="app.getLz('menubar.options.reload')"><%- include('../svg/redo.svg') %></button>
</div>
</div>
<div class="row">
@ -24,14 +25,18 @@
<div class="col-auto cider-flex-center">
<div class="row">
<div class="col">
<select class="md-select" v-model="prefs.sort" @change="library.albums.sorting[1] = prefs.sort; $root.searchLibraryAlbums(1)">
<select class="md-select" v-model="prefs.sort"
@change="library.albums.sorting[1] = prefs.sort; $root.searchLibraryAlbums(1)">
<optgroup :label="$root.getLz('term.sortBy')">
<option v-for="(sort, index) in library.albums.sortingOptions" :value="index">{{ sort }}</option>
<option v-for="(sort, index) in library.albums.sortingOptions" :value="index">{{ sort
}}
</option>
</optgroup>
</select>
</div>
<div class="col">
<select class="md-select" v-model="prefs.sortOrder" @change="library.albums.sortOrder[1] = prefs.sortOrder; $root.searchLibraryAlbums(1)">
<select class="md-select" v-model="prefs.sortOrder"
@change="library.albums.sortOrder[1] = prefs.sortOrder; $root.searchLibraryAlbums(1)">
<optgroup :label="$root.getLz('term.sortOrder')">
<option value="asc">{{$root.getLz('term.sortOrder.ascending')}}</option>
<option value="desc">{{$root.getLz('term.sortOrder.descending')}}</option>
@ -50,7 +55,9 @@
<select class="md-select" v-model="prefs.scroll">
<optgroup :label="app.getLz('term.scroll')">
<option value="infinite">{{app.getLz('term.scroll.infinite')}}</option>
<option value="paged">{{app.getLz('term.scroll.paged').replace("${songsPerPage}", pageSize)}}</option>
<option value="paged">{{app.getLz('term.scroll.paged').replace("${songsPerPage}",
pageSize)}}
</option>
</optgroup>
</select>
</div>
@ -58,57 +65,59 @@
</div>
</div>
<pagination
:length="app.library.albums.displayListing.length
:length="app.library.albums.displayListing.length
"
:pageSize="pageSize"
:scroll="prefs.scroll"
scrollSelector="#app-content"
@onRangeChange="onRangeChange"
:pageSize="pageSize"
:scroll="prefs.scroll"
scrollSelector="#app-content"
@onRangeChange="onRangeChange"
/>
<div class="well">
<div class="albums-square-container">
<div>
<mediaitem-square v-if="prefs.viewAs == 'covers'" :size="'300'" :item="item" v-for="item in currentSlice">
<mediaitem-square v-if="prefs.viewAs == 'covers'" :size="'300'" :item="item"
v-for="item in currentSlice">
</mediaitem-square>
</div>
</div>
<mediaitem-list-item v-if="prefs.viewAs == 'list'" :show-duration="false" :show-meta-data="true" :show-library-status="false" :item="item" v-for="item in currentSlice">
<mediaitem-list-item v-if="prefs.viewAs == 'list'" :show-duration="false" :show-meta-data="true"
:show-library-status="false" :item="item" v-for="item in currentSlice">
</mediaitem-list-item>
</div>
</div>
</script>
<script>
Vue.component('cider-library-albums', {
template: '#cider-library-albums',
data: function () {
const pageSize = this.$root.cfg.libraryPrefs.pageSize;
return {
library: this.$root.library,
mediaItemSize: "compact",
prefs: this.$root.cfg.libraryPrefs.albums,
app: this.$root,
pageSize: pageSize,
start: 0,
end: pageSize
}
},
mounted() {
this.$root.getLibraryAlbumsFull(null, 1);
this.$root.getAlbumSort();
this.$root.searchLibraryAlbums(1);
this.$root.getLibrarySongsFull();
this.$root.searchLibraryAlbums(1);
},
computed: {
currentSlice: function () {
return this.library.albums.displayListing.slice(this.start, this.end);
}
},
methods: {
onRangeChange: function (newRange) {
this.start = newRange[0];
this.end = newRange[1];
}
}
});
</script>
Vue.component('cider-library-albums', {
template: '#cider-library-albums',
data: function() {
const pageSize = this.$root.cfg.libraryPrefs.pageSize;
return {
library: this.$root.library,
mediaItemSize: "compact",
prefs: this.$root.cfg.libraryPrefs.albums,
app: this.$root,
pageSize: pageSize,
start: 0,
end: pageSize
}
},
mounted() {
this.$root.getLibraryAlbumsFull(null, 1);
this.$root.getAlbumSort();
this.$root.searchLibraryAlbums(1);
this.$root.getLibrarySongsFull();
this.$root.searchLibraryAlbums(1);
},
computed: {
currentSlice: function() {
return this.library.albums.displayListing.slice(this.start, this.end);
}
},
methods: {
onRangeChange: function(newRange) {
this.start = newRange[0];
this.end = newRange[1];
}
}
});
</script>

View file

@ -4,84 +4,88 @@
<div class="col" style="padding:0;">
<h1 class="header-text">{{$root.getLz('term.artists')}}</h1>
</div>
</div> -->
<div class="inner-container">
<div class="list-container">
<div class="col" style="padding:0;">
<div class="search-input-container" style="width:calc(100%-20px);margin: 16px 10px 10px 10px;">
<div class="search-input--icon"></div>
<input type="search" style="width:100%;" spellcheck="false" :placeholder="$root.getLz('term.search') + '...'" @input="$root.searchLibraryArtists" v-model="library.artists.search" class="search-input">
<input type="search" style="width:100%;" spellcheck="false"
:placeholder="$root.getLz('term.search') + '...'" @input="$root.searchLibraryArtists"
v-model="library.artists.search" class="search-input">
</div>
</div>
<div class="podcasts-list">
<libraryartist-item :show-duration="false" :show-meta-data="true" :show-library-status="false" :item="item" v-for="item in library.artists.displayListing">
<libraryartist-item :show-duration="false" :show-meta-data="true" :show-library-status="false"
:item="item" v-for="item in library.artists.displayListing">
</libraryartist-item>
</div>
</div>
<div class="episodes-list" >
<div class="episodes-inline-info" v-if="clready">
<inline-collection-list :parentSelector="'.episodes-list'" :data="clresponse" :type="cltype" :title="cltitle"></inline-collection-list>
</div>
<div class="episodes-list">
<div class="episodes-inline-info" v-if="clready">
<inline-collection-list :parentSelector="'.episodes-list'" :data="clresponse" :type="cltype"
:title="cltitle"></inline-collection-list>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('cider-library-artists', {
template: '#cider-library-artists',
data: function () {
return {
library: this.$root.library,
app: this.$root,
ciderPodcasts: [],
podcasts: [],
episodes: [],
search: {
term: "",
loading: false,
results: [],
resultsLibrary: [],
next: ""
},
podcastSelected: {
id: -1
},
selected: {
id: -1
},
collectionList: {
response: null, title: null, type: null, requestBody: null
},
clresponse: [],
clready: false,
cltitle: '',
cltype: "artists",
Vue.component('cider-library-artists', {
template: '#cider-library-artists',
data: function() {
return {
library: this.$root.library,
app: this.$root,
ciderPodcasts: [],
podcasts: [],
episodes: [],
search: {
term: "",
loading: false,
results: [],
resultsLibrary: [],
next: ""
},
podcastSelected: {
id: -1
},
selected: {
id: -1
},
collectionList: {
response: null, title: null, type: null, requestBody: null
},
clresponse: [],
clready: false,
cltitle: '',
cltype: "artists",
}
},
mounted() {
let self = this;
this.$root.getLibraryArtistsFull(null, 0);
this.$root.$on('ap-inlinecollection', function(e){
console.log("hey",e)
self.clready = true;
self.clresponse = e.response;
self.cltitle = e.title ?? '';
self.cltype = e.type ;
})
},
methods: {
getInlineCollection(e){
console.log("hey",e);
}
}
});
}
},
mounted() {
let self = this;
this.$root.getLibraryArtistsFull(null, 0);
this.$root.$on('ap-inlinecollection', function(e) {
console.log("hey", e)
self.clready = true;
self.clresponse = e.response;
self.cltitle = e.title ?? '';
self.cltype = e.type;
})
},
methods: {
getInlineCollection(e) {
console.log("hey", e);
}
}
});
</script>
<script type="text/x-template" id="libraryartist-item">
<div v-observe-visibility="{callback: visibilityChanged}"
<div v-observe-visibility="{callback: visibilityChanged}"
@click="select"
class="cd-mediaitem-list-item"
:class="{'mediaitem-selected': app.select_hasMediaItem(guid)}"
@ -104,179 +108,182 @@
</script>
<script>
Vue.component('libraryartist-item', {
template: '#libraryartist-item',
data: function () {
return {
isVisible: false,
addedToLibrary: false,
guid: this.uuidv4(),
app: this.$root,
}
},
props: {
'item': {type: Object, required: true},
'parent': {type: String, required: false},
'index': {type: Number, required: false, default: -1},
'show-artwork': {type: Boolean, default: true},
'show-library-status': {type: Boolean, default: true},
'show-meta-data': {type: Boolean, default: false},
'show-duration': {type: Boolean, default: true},
'contextExt': {type: Object, required: false},
},
methods: {
uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
},
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
},
getDataType() {
return this.item.type
},
async select(e) {
let u = this.item
let u1 = await app.mk.api.v3.music(`/v1/me/library/artists/${u.id}/albums`, {
"platform": "web",
"include[library-albums]": "artists,tracks",
"include[library-artists]": "catalog",
"fields[artists]": "url",
"includeOnly": "catalog,artists"
})
this.showCollection({data : Object.assign({},u1.data.data)}, u.attributes.name?? '', '');
//app.select_selectMediaItem(u.id, this.getDataType(), this.index, this.guid, true)
},
showCollection(response, title, type, requestBody = {}){
this.$root.$emit('ap-inlinecollection', {
response: response, title: title, type: type, requestBody: {}}
);
},
getArtwork(){
let u = ""
try{
u = this.item.relationships.catalog.data[0].attributes.artwork.url}
catch (e){};
return u;
},
contextMenu(event) {
let self = this
let data_type = this.getDataType()
let item = self.item
item.attributes.artistName = item.attributes.name;
let useMenu = "normal"
if (app.selectedMediaItems.length <= 1) {
app.selectedMediaItems = []
app.select_selectMediaItem(this.item.id, data_type, this.index, this.guid, true)
} else {
useMenu = "multiple"
}
let menus = {
multiple: {
items: [] //
},
normal: {
items: [
{
"name": app.getLz('action.goToArtist'),
"icon": "./assets/feather/user.svg",
"action": function () {
app.searchAndNavigate(self.item, 'artist')
console.log(self.item)
}
},
{
"icon": "./assets/feather/radio.svg",
"name": app.getLz('action.startRadio'),
"action": function () {
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
app.mk.play()
app.selectedMediaItems = []
})
}
},
// Hidden for now, as it's not implemented yet
/*{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.share'),
"action": function () {
if (!self.item.attributes.url && self.item.relationships){
if (self.item.relationships.catalog){
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => {self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0)? u.data.data[0].attributes.url : u.data.data.attributes.url)})
}
} else {
self.app.copyToClipboard(self.item.attributes.url)}
}
},*/
]
}
}
if (this.contextExt) {
// if this.context-ext.normal is true append all options to the 'normal' menu which is a kvp of arrays
if (this.contextExt.normal) {
menus.normal.items = menus.normal.items.concat(this.contextExt.normal)
}
if (this.contextExt.multiple) {
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
}
}
//CiderContextMenu.Create(event, menus[useMenu]); // Depreciated Context Menu
app.showMenuPanel(menus[useMenu], event);
},
visibilityChanged: function (isVisible, entry) {
this.isVisible = isVisible
},
addToLibrary() {
let item = this.item
if (item.attributes.playParams.id) {
console.log('adding to library', item.attributes.playParams.id)
app.addToLibrary(item.attributes.playParams.id.toString())
this.addedToLibrary = true
} else if (item.id) {
console.log('adding to library', item.id)
app.addToLibrary(item.id.toString())
this.addedToLibrary = true
}
},
async removeFromLibrary() {
let item = this.item
let params = {"fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"}
let id = item.id ?? item.attributes.playParams.id
let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
id = res.relationships.library.data[0].id
}
let kind = this.item.attributes.playParams.kind ?? this.data.item ?? '';
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
if (item.attributes.playParams.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
} else if (item.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
}
},
playTrack() {
let item = this.item
let parent = this.parent
let childIndex = this.index
console.log(item, parent, childIndex)
if (parent != null && childIndex != null) {
app.queueParentandplayChild(parent, childIndex, item);
} else {
app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)
}
}
Vue.component('libraryartist-item', {
template: '#libraryartist-item',
data: function() {
return {
isVisible: false,
addedToLibrary: false,
guid: this.uuidv4(),
app: this.$root,
}
},
props: {
'item': { type: Object, required: true },
'parent': { type: String, required: false },
'index': { type: Number, required: false, default: -1 },
'show-artwork': { type: Boolean, default: true },
'show-library-status': { type: Boolean, default: true },
'show-meta-data': { type: Boolean, default: false },
'show-duration': { type: Boolean, default: true },
'contextExt': { type: Object, required: false },
},
methods: {
uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
},
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
},
getDataType() {
return this.item.type
},
async select(e) {
let u = this.item
let u1 = await app.mk.api.v3.music(`/v1/me/library/artists/${u.id}/albums`, {
"platform": "web",
"include[library-albums]": "artists,tracks",
"include[library-artists]": "catalog",
"fields[artists]": "url",
"includeOnly": "catalog,artists"
})
this.showCollection({ data: Object.assign({}, u1.data.data) }, u.attributes.name ?? '', '');
//app.select_selectMediaItem(u.id, this.getDataType(), this.index, this.guid, true)
},
showCollection(response, title, type, requestBody = {}) {
this.$root.$emit('ap-inlinecollection', {
response: response, title: title, type: type, requestBody: {}
}
);
},
getArtwork() {
let u = ""
try {
u = this.item.relationships.catalog.data[0].attributes.artwork.url
} catch (e) {
}
});
</script>
;
return u;
},
contextMenu(event) {
let self = this
let data_type = this.getDataType()
let item = self.item
item.attributes.artistName = item.attributes.name;
let useMenu = "normal"
if (app.selectedMediaItems.length <= 1) {
app.selectedMediaItems = []
app.select_selectMediaItem(this.item.id, data_type, this.index, this.guid, true)
} else {
useMenu = "multiple"
}
let menus = {
multiple: {
items: [] //
},
normal: {
items: [
{
"name": app.getLz('action.goToArtist'),
"icon": "./assets/feather/user.svg",
"action": function() {
app.searchAndNavigate(self.item, 'artist')
console.log(self.item)
}
},
{
"icon": "./assets/feather/radio.svg",
"name": app.getLz('action.startRadio'),
"action": function() {
app.mk.setStationQueue({ song: self.item.attributes.playParams.id ?? self.item.id }).then(() => {
app.mk.play()
app.selectedMediaItems = []
})
}
},
// Hidden for now, as it's not implemented yet
/*{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.share'),
"action": function () {
if (!self.item.attributes.url && self.item.relationships){
if (self.item.relationships.catalog){
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => {self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0)? u.data.data[0].attributes.url : u.data.data.attributes.url)})
}
} else {
self.app.copyToClipboard(self.item.attributes.url)}
}
},*/
]
}
}
if (this.contextExt) {
// if this.context-ext.normal is true append all options to the 'normal' menu which is a kvp of arrays
if (this.contextExt.normal) {
menus.normal.items = menus.normal.items.concat(this.contextExt.normal)
}
if (this.contextExt.multiple) {
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
}
}
//CiderContextMenu.Create(event, menus[useMenu]); // Depreciated Context Menu
app.showMenuPanel(menus[useMenu], event);
},
visibilityChanged: function(isVisible, entry) {
this.isVisible = isVisible
},
addToLibrary() {
let item = this.item
if (item.attributes.playParams.id) {
console.log('adding to library', item.attributes.playParams.id)
app.addToLibrary(item.attributes.playParams.id.toString())
this.addedToLibrary = true
} else if (item.id) {
console.log('adding to library', item.id)
app.addToLibrary(item.id.toString())
this.addedToLibrary = true
}
},
async removeFromLibrary() {
let item = this.item
let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" }
let id = item.id ?? item.attributes.playParams.id
let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
id = res.relationships.library.data[0].id
}
let kind = this.item.attributes.playParams.kind ?? this.data.item ?? '';
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
if (item.attributes.playParams.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
} else if (item.id) {
console.log('remove from library', id)
app.removeFromLibrary(truekind, id)
this.addedToLibrary = false
}
},
playTrack() {
let item = this.item
let parent = this.parent
let childIndex = this.index
console.log(item, parent, childIndex)
if (parent != null && childIndex != null) {
app.queueParentandplayChild(parent, childIndex, item);
} else {
app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url)
}
}
}
});
</script>

View file

@ -5,66 +5,68 @@
<mediaitem-square v-for="item in items" :item="item"></mediaitem-square>
</div>
<div class="well itemContainer collection-list-square" 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>
<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 itemContainer collection-list-square" 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 v-if="nextUrl && !loading" style="opacity:0;height: 32px;"
v-observe-visibility="{callback: visibilityChanged}">{{$root.getLz('term.showMore')}}
</button>
</div>
</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
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
}
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

@ -15,18 +15,22 @@
</div>
<div class="col-auto cider-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">
<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">
<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>
</button>
<div class="col">
<select class="md-select" v-model="prefs.sort" @change="$root.searchLibrarySongs()">
<optgroup :label="app.getLz('term.sortBy')">
<option v-for="(sort, index) in library.songs.sortingOptions" :value="index">{{ sort }}</option>
<option v-for="(sort, index) in library.songs.sortingOptions" :value="index">{{ sort
}}
</option>
</optgroup>
</select>
</div>
@ -50,7 +54,9 @@
<select class="md-select" v-model="prefs.scroll">
<optgroup :label="app.getLz('term.scroll')">
<option value="infinite">{{app.getLz('term.scroll.infinite')}}</option>
<option value="paged">{{app.getLz('term.scroll.paged').replace("${songsPerPage}", pageSize)}}</option>
<option value="paged">{{app.getLz('term.scroll.paged').replace("${songsPerPage}",
pageSize)}}
</option>
</optgroup>
</select>
</div>
@ -58,81 +64,87 @@
</div>
<div class="col-auto cider-flex-center">
<button v-if="library.songs.downloadState == 2" @click="$root.getLibrarySongsFull(true)"
class="reload-btn" :aria-label="app.getLz('menubar.options.reload')"><%- include('../svg/redo.svg') %></button>
<button v-else class="reload-btn" style="opacity: 0.8;pointer-events: none" :aria-label="app.getLz('menubar.options.reload')">
class="reload-btn"
:aria-label="app.getLz('menubar.options.reload')"><%- include('../svg/redo.svg') %></button>
<button v-else class="reload-btn" style="opacity: 0.8;pointer-events: none"
:aria-label="app.getLz('menubar.options.reload')">
<div class="spinner"></div>
</button>
</div>
</div>
<pagination
:length="library.songs.displayListing.length"
:pageSize="pageSize"
:scroll="prefs.scroll"
scrollSelector="#app-content"
@onRangeChange="onRangeChange"
:length="library.songs.displayListing.length"
:pageSize="pageSize"
:scroll="prefs.scroll"
scrollSelector="#app-content"
@onRangeChange="onRangeChange"
/>
</div>
<div v-if="library.songs.downloadState == 3">Library contains no songs.</div>
<div class="well" :key="1" v-if="prefs.size == 'compact'">
<mediaitem-list-item class-list="compact" :item="item" :parent="'librarysongs'" :index="index" :show-meta-data="true" :show-library-status="false" v-for="(item, index) in currentSlice"></mediaitem-list-item>
<mediaitem-list-item class-list="compact" :item="item" :parent="'librarysongs'" :index="index"
:show-meta-data="true" :show-library-status="false"
v-for="(item, index) in currentSlice"></mediaitem-list-item>
</div>
<div class="well" :key="2" v-else>
<mediaitem-list-item :item="item" :parent="'librarysongs'" :index="index" :show-meta-data="true" :show-library-status="false" v-for="(item, index) in currentSlice"></mediaitem-list-item>
<mediaitem-list-item :item="item" :parent="'librarysongs'" :index="index" :show-meta-data="true"
:show-library-status="false"
v-for="(item, index) in currentSlice"></mediaitem-list-item>
</div>
</div>
</script>
<script>
Vue.component('cider-library-songs', {
template: '#cider-library-songs',
data: function () {
const pageSize = this.$root.cfg.libraryPrefs.pageSize;
return {
// currentPage is oneIndexed
library: this.$root.library,
mediaItemSize: "compact",
prefs: this.$root.cfg.libraryPrefs.songs,
app: this.$root,
pageSize: pageSize,
start: 0,
end: pageSize
}
},
mounted() {
this.$root.getLibrarySongsFull()
},
computed: {
currentSlice: function () {
return this.library.songs.displayListing.slice(this.start, this.end);
}
},
methods: {
onRangeChange: function (newRange) {
this.start = newRange[0];
this.end = newRange[1];
},
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)
});
}
Vue.component('cider-library-songs', {
template: '#cider-library-songs',
data: function() {
const pageSize = this.$root.cfg.libraryPrefs.pageSize;
return {
// currentPage is oneIndexed
library: this.$root.library,
mediaItemSize: "compact",
prefs: this.$root.cfg.libraryPrefs.songs,
app: this.$root,
pageSize: pageSize,
start: 0,
end: pageSize
}
},
mounted() {
this.$root.getLibrarySongsFull()
},
computed: {
currentSlice: function() {
return this.library.songs.displayListing.slice(this.start, this.end);
}
},
methods: {
onRangeChange: function(newRange) {
this.start = newRange[0];
this.end = newRange[1];
},
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;
}
}
});
</script>
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)
});
}
}
});
</script>

View file

@ -1,39 +1,38 @@
<script type="text/x-template" id="cider-library-videos">
<div class="content-inner">
<div class="row">
<div class="col" style="padding:0">
<h1 class="header-text">{{$root.getLz('term.videos')}}</h1>
</div>
</div>
<div class="madeforyou-body">
<template v-if="videos.length > 0">
<mediaitem-square :size="300" :item="item" v-for="item in videos">
</mediaitem-square>
</template>
<template v-else-if="loaded == true">
<div>{{$root.getLz('term.noVideos')}}</div>
</template>
</div>
<div class="row">
<div class="col" style="padding:0">
<h1 class="header-text">{{$root.getLz('term.videos')}}</h1>
</div>
</div>
<div class="madeforyou-body">
<template v-if="videos.length > 0">
<mediaitem-square :size="300" :item="item" v-for="item in videos">
</mediaitem-square>
</template>
<template v-else-if="loaded == true">
<div>{{$root.getLz('term.noVideos')}}</div>
</template>
</div>
</div>
</script>
<script>
Vue.component('cider-library-videos', {
template: "#cider-library-videos",
props: ["data"],
data: function(){
return {
videos: [],
loaded: false
}
},
mounted() {
this.$nextTick(async function () {
if (this.$data.videos == null || this.$data.videos.length == 0)
this.$data.videos = (await this.$root.mk.api.v3.music('/v1/me/library/music-videos')).data?.data ?? []
this.$data.loaded = true
})
}
})
Vue.component('cider-library-videos', {
template: "#cider-library-videos",
props: ["data"],
data: function() {
return {
videos: [],
loaded: false
}
},
mounted() {
this.$nextTick(async function() {
if (this.$data.videos == null || this.$data.videos.length == 0)
this.$data.videos = (await this.$root.mk.api.v3.music('/v1/me/library/music-videos')).data?.data ?? []
this.$data.loaded = true
})
}
})
</script>

View file

@ -2,22 +2,22 @@
<div class="content-inner">
<h1 class="header-text">{{app.getLz('term.listenNow')}}</h1>
<template v-for="(recom,index) in data.data">
<listennow-child :recom="recom" :index="index"></listennow-child>
<listennow-child :recom="recom" :index="index"></listennow-child>
</template>
</div>
</script>
<script>
Vue.component('cider-listen-now', {
Vue.component('cider-listen-now', {
template: "#cider-listen-now",
props: ["data"],
data: function(){
return {
app : this.$root
}
data: function() {
return {
app: this.$root
}
},
mounted() {
this.$root.getListenNow()
this.$root.getListenNow()
}
})
})
</script>

View file

@ -1,11 +1,11 @@
<div class="content-inner">
<div class="row">
<div class="col" style="padding:0">
<h1 class="header-text">{{$root.getLz('home.madeForYou')}}</h1>
</div>
</div>
<div class="madeforyou-body">
<mediaitem-square :item="item" v-for="item in madeforyou.data">
</mediaitem-square>
</div>
</div>
<div class="content-inner">
<div class="row">
<div class="col" style="padding:0">
<h1 class="header-text">{{$root.getLz('home.madeForYou')}}</h1>
</div>
</div>
<div class="madeforyou-body">
<mediaitem-square :item="item" v-for="item in madeforyou.data">
</mediaitem-square>
</div>
</div>

View file

@ -1,123 +1,127 @@
<script type="text/x-template" id="cider-oobe">
<div class="content-inner oobe">
<!-- before_we_start-->
<!-- 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 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 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 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 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>
</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 class="card-footer">
Mojave
</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>
</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">
{{$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 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">
{{$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 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 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">
@ -139,28 +143,28 @@
</div>
</script>
<script>
Vue.component('cider-oobe', {
template: '#cider-oobe',
data: function () {
return {
screen: "before_we_start"
}
},
async mounted() {
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);
}
},
methods: {
signIn() {
if (localStorage.getItem("music.ampwebplay.media-user-token")) {
localStorage.setItem("seenOOBE", 1)
window.location.reload()
}
});
</script>
this.screen = "signin"
capiInit()
},
getLz() {
return this.$root.getLz.apply(this.$root, arguments);
}
}
});
</script>

View file

@ -6,7 +6,7 @@
aria-role="presentation" focusable="false">
<path
d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z"
fill-rule="nonzero"/>
fill-rule="nonzero" />
</svg>
</div>
<template v-if="app.playlists.loadingState == 0">
@ -46,7 +46,7 @@
v-model="data.attributes.name"
@blur="editPlaylist"
@change="editPlaylist"
@keydown.enter="editPlaylist"/>
@keydown.enter="editPlaylist" />
</div>
<div class="playlist-time genre" style="margin: 0px;">{{getAlbumGenre()}}</div>
<div class="playlist-artist item-navigate"
@ -58,11 +58,15 @@
<artist-chip v-for="artist in data.relationships.artists?.data"
:item="artist"></artist-chip>
</template>
<div class="playlist-desc" v-if="(data.attributes.description && (data.attributes.description.standard || data.attributes.description.short)) || (data.attributes.editorialNotes && (data.attributes.editorialNotes.standard || data.attributes.editorialNotes.short))">
<div v-if="(data.attributes.description?.short ?? data.attributes.editorialNotes?.short) != null" class="content"
v-html="data.attributes.description?.short ?? data.attributes.editorialNotes?.short" @click="openInfoModal()"></div>
<div v-else-if="(data.attributes.description?.standard ?? data.attributes.editorialNotes?.standard) != null" class="content"
v-html="data.attributes.description?.standard ?? data.attributes.editorialNotes?.standard" ></div>
<div class="playlist-desc"
v-if="(data.attributes.description && (data.attributes.description.standard || data.attributes.description.short)) || (data.attributes.editorialNotes && (data.attributes.editorialNotes.standard || data.attributes.editorialNotes.short))">
<div v-if="(data.attributes.description?.short ?? data.attributes.editorialNotes?.short) != null"
class="content"
v-html="data.attributes.description?.short ?? data.attributes.editorialNotes?.short"
@click="openInfoModal()"></div>
<div v-else-if="(data.attributes.description?.standard ?? data.attributes.editorialNotes?.standard) != null"
class="content"
v-html="data.attributes.description?.standard ?? data.attributes.editorialNotes?.standard"></div>
<!-- <button v-if="(data.attributes.description?.short ?? data.attributes.editorialNotes?.short ) != null" class="more-btn"
@click="openInfoModal()">
{{app.getLz('term.showMore')}}
@ -101,7 +105,8 @@
<img :class="(!inLibrary) ? 'md-ico-add' : 'md-ico-remove'">
{{app.getLz('term.confirm')}}
</button>
<button class="more-btn-round" style="float:right;" @click="menu" :aria-label="app.getLz('term.more')">
<button class="more-btn-round" style="float:right;" @click="menu"
:aria-label="app.getLz('term.more')">
<div class="svg-icon"></div>
</button>
</div>
@ -158,25 +163,30 @@
<div class="">
<div style="width:100%">
<draggable :sort="data.attributes.canEdit && data.type == 'library-playlists'"
v-model="data.relationships.tracks.data" @start="drag=true" @end="drag=false;put()">
<template v-if="nestedPlaylist == [] || nestedPlaylist.length <= 1">
<mediaitem-list-item :item="item" :parent="getItemParent(data)" :index="index"
v-model="data.relationships.tracks.data" @start="drag=true"
@end="drag=false;put()">
<template v-if="nestedPlaylist == [] || nestedPlaylist.length <= 1">
<mediaitem-list-item :item="item" :parent="getItemParent(data)"
:index="index"
:showIndex="true"
:showIndexPlaylist="(data.attributes.playParams.kind ?? data.type ?? '').includes('playlist')"
:context-ext="buildContextMenu()"
v-for="(item,index) in data.relationships.tracks.data"></mediaitem-list-item>
</template>
<template v-else>
<div v-for="disc in nestedPlaylist">
<div class="playlist-time">{{($root.getLz("term.discNumber") ??
"").replace("${discNumber}",disc.disc)}}
</div>
<mediaitem-list-item :item="item" :parent="getItemParent(data)"
:index="index"
:showIndex="true"
:showIndexPlaylist="(data.attributes.playParams.kind ?? data.type ?? '').includes('playlist')"
:context-ext="buildContextMenu()"
v-for="(item,index) in data.relationships.tracks.data"></mediaitem-list-item>
</template>
<template v-else>
<div v-for="disc in nestedPlaylist">
<div class="playlist-time">{{($root.getLz("term.discNumber") ?? "").replace("${discNumber}",disc.disc)}}</div>
<mediaitem-list-item :item="item" :parent="getItemParent(data)" :index="index"
:showIndex="true"
:showIndexPlaylist="(data.attributes.playParams.kind ?? data.type ?? '').includes('playlist')"
:context-ext="buildContextMenu()"
v-for="(item,index) in disc.tracks"></mediaitem-list-item>
</div>
v-for="(item,index) in disc.tracks"></mediaitem-list-item>
</div>
</template>
</template>
</draggable>
</div>
@ -185,7 +195,8 @@
<div class="friends-info" v-if="itemBadges.length != 0">
<div class="well">
<div class="badge-container">
<div class="socialBadge" :title="`${badge.attributes.name} - @${badge.attributes.handle}`"
<div class="socialBadge"
:title="`${badge.attributes.name} - @${badge.attributes.handle}`"
v-for="badge in itemBadges">
<mediaitem-artwork
:url="badge.attributes.artwork.url"
@ -204,7 +215,8 @@
</div>
<template
v-if="(data.attributes?.playParams?.kind ?? data.type ?? '').includes('album') && data.relationships.catalog != null && data.relationships.catalog != null && data.relationships.catalog.data.length > 0">
<div class="playlist-time showExtended item-navigate" style="color:#fa586a; font-weight: bold"
<div class="playlist-time showExtended item-navigate"
style="color:#fa586a; font-weight: bold"
@click="app.routeView(data.relationships.catalog.data[0])">
{{$root.getLz("action.showAlbum")}}
</div>
@ -212,21 +224,22 @@
<hr>
</b-tab>
<template v-if="typeof data.views != 'undefined'">
<b-tab lazy :title="data.views[view].attributes.title" v-for="view in data.meta.views.order" v-if="data.views[view].data.length != 0">
<div>
<div class="row">
<div class="col">
<h3>{{ data.views[view].attributes.title }}</h3>
<b-tab lazy :title="data.views[view].attributes.title" v-for="view in data.meta.views.order"
v-if="data.views[view].data.length != 0">
<div>
<div class="row">
<div class="col">
<h3>{{ data.views[view].attributes.title }}</h3>
</div>
</div>
<div class="row">
<div class="col">
<mediaitem-scroller-horizontal
:items="data.views[view].data"></mediaitem-scroller-horizontal>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<mediaitem-scroller-horizontal
:items="data.views[view].data"></mediaitem-scroller-horizontal>
</div>
</div>
</div>
</b-tab>
</b-tab>
</template>
</b-tabs>
@ -239,7 +252,7 @@
aria-role="presentation" focusable="false">
<path
d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z"
fill-rule="nonzero"/>
fill-rule="nonzero" />
</svg>
</div>
<template v-if="app.playlists.loadingState == 0">
@ -252,408 +265,411 @@
</script>
<script>
Vue.component('playlist-inline', {
template: "#playlist-inline",
props: ["data"],
data: function () {
return {
editorialNotesExpanded: false,
drag: false,
nameEditing: false,
inLibrary: null,
confirm: false,
app: this.$root,
itemBadges: [],
badgesRequested: false,
headerVisible: true,
useArtistChip: false,
nestedPlaylist: [],
}
},
mounted: function () {
this.$nextTick(function () {
this.isInLibrary()
})
},
watch: {
data: function () {
this.nestedPlaylist = [];
this.isInLibrary()
this.getBadges()
this.generateNestedPlaylist()
}
},
methods: {
openInfoModal(){
app.moreinfodata = [];
app.moreinfodata = {
title : this.data?.attributes ? (this.data?.attributes?.name ??
(this.data?.attributes?.title ?? '') ?? '') : '',
subtitle: this.data?.attributes?.artistName ?? '',
content: ((this.data?.attributes?.editorialNotes != null ) ? (this.data?.attributes?.editorialNotes?.standard ?? (this.data?.attributes?.editorialNotes?.short ?? '') ) : (data.attributes?.description ? (this.data.attributes?.description?.standard ?? (this.data?.attributes?.description?.short ?? '')) : ''))
}
app.modals.moreInfo = true;
},
generateNestedPlaylist(){
this.nestedPlaylist = [];
if (this.data?.type?.includes("album")){
console.log(this.data.relationships.tracks.data)
let songlists = this.data.relationships.tracks.data;
let discs = songlists.map(x => {return x.attributes.discNumber}).filter((item, i, ar) => ar.indexOf(item) === i);
if (discs && discs.length > 1){
for (disc of discs){
let songs = songlists.filter(x => x.attributes.discNumber == disc);
this.nestedPlaylist.push({disc: disc, tracks: songs})
}
}
console.log(this.nestedPlaylist)
}},
isHeaderVisible(visible) {
this.headerVisible = visible
},
getBadges() {
return
if (this.badgesRequested) {
return
}
this.badgesRequested = true
this.itemBadges = []
let self = this
var id = 0
try {
id = this.data.attributes.playParams.id
} catch (e) {
id = this.data.id
}
this.$root.getSocialBadges((badges) => {
let friends = badges[id]
if (friends) {
friends.forEach(function (friend) {
self.app.mk.api.v3.music(`/v1/social/${app.mk.storefrontId}/social-profiles/${friend}`).then(data => {
self.itemBadges.push(data.data.data[0])
})
})
}
})
},
confirmButton() {
// Return button to normal state after 3 seconds
this.confirm = true
setTimeout(() => this.confirm = false, 3000);
},
getArtistName(data) {
if (data.attributes.artistName) {
this.useArtistChip = true
return data.attributes.artistName
} else if (data.attributes.artist) {
this.useArtistChip = true
return data.attributes.artist.attributes.name
} else if (data.attributes.curatorName) {
return data.attributes.curatorName
} else {
return ""
}
},
getAlbumGenre: function () {
if (this.data.type.includes('albums')) {
let date = this.data.attributes.releaseDate;
if (date == null || date === "") return "";
return `${this.data.relationships.tracks.data[0].attributes.genreNames[0]} · ${new Date(date).getFullYear()}`
}
},
async isInLibrary() {
if (this.data.type && !this.data.type.includes("library")) {
// please keep using vars here
const params = {
"fields[playlists]": "inLibrary",
"fields[albums]": "inLibrary",
"relate": "library"
};
const res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params);
this.inLibrary = (res.data.data[0] && res.data.data[0].attributes && res.data.data[0].attributes.inLibrary) ? res.data.data[0].attributes.inLibrary : false
console.log(res)
} else {
this.inLibrary = true
}
},
editPlaylist() {
this.app.editPlaylist(this.data.id, this.data.attributes.name);
this.app.playlists.listing.forEach(playlist => {
if (playlist.id === this.data.id) {
playlist.attributes.name = this.data.attributes.name
}
})
this.nameEditing = false
},
addToLibrary(id) {
app.mk.addToLibrary(id)
this.inLibrary = true
this.confirm = false
},
async removeFromLibrary(id) {
const params = {"fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library"};
var id = this.data.id ?? this.data.attributes.playParams.id
const res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params);
if (res.data.data[0] && res.data.data[0].relationships && res.data.data[0].relationships.library && res.data.data[0].relationships.library.data && res.data.data[0].relationships.library.data.length > 0) {
id = res.data.data[0].relationships.library.data[0].id
}
let kind = this.data.attributes.playParams.kind ?? this.data.type ?? '';
const truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`, {},
{
fetchOptions: {
method: "DELETE"
}
})
this.inLibrary = false
this.confirm = false
},
editPlaylistName() {
if (this.data.attributes.canEdit && this.data.type === "library-playlists") {
this.nameEditing = true
setTimeout(() => {
document.querySelector(".nameEdit").focus()
}, 100)
}
},
buildContextMenu(index) {
let self = this
if (!this.data.attributes.canEdit) {
return
}
return {
normal: [
{
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()
}
},
],
multiple: [
{
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()
}
},
]
}
},
async put() {
if (!this.data.attributes.canEdit) {
return
}
console.log('sds', this.convert())
await app.mk.api.v3.music(
`/v1/me/library/playlists/${this.data.attributes.playParams.id}/tracks`,
{},
{
fetchOptions: {
method: "PUT",
body: JSON.stringify({
data: this.convert()
})
}
}
)
},
async remove() {
if (!this.data.attributes.canEdit) {
return
}
// for each app.selectedMediaItems splice the items from the playlist
for (let i = 0; i < app.selectedMediaItems.length; i++) {
let item = app.selectedMediaItems[i]
let index = this.data.relationships.tracks.data.findIndex(x => x.id == item.id)
if (index > -1) {
this.data.relationships.tracks.data.splice(index, 1)
}
}
await this.put()
},
convert() {
let pl_tracks = []
for (let i = 0; i < this.data.relationships.tracks.data.length; i++) {
pl_tracks.push({
id: this.data.relationships.tracks.data[i].id,
type: this.data.relationships.tracks.data[i].type
})
}
return pl_tracks
},
getRecursive(url) {
app.apiCall(app.musicBaseUrl + "/v1/me/library/playlists/p.V7VYlrDso6kkYY/tracks?offset=100", res => {
this.data.relationships.tracks.data = this.data.relationships.tracks.data.concat(res.data.data)
if (res.data.next) {
this.getRecursive(res.data.next)
}
})
},
menu(event) {
let self = this
let artistId = null
if (typeof this.data.relationships.artists != "undefined") {
artistId = this.data.relationships.artists.data[0].id
if (this.data.relationships.artists.data[0].type == "library-artists") {
artistId = this.data.relationships.artists.data[0].relationships.catalog.data[0].id
}
}
let menuItems = {
items: {
"share": {
name: app.getLz('term.share'),
icon: "./assets/feather/share.svg",
action: () => {
let route = ""
switch (this.data.type) {
case 'albums':
route = `/v1/catalog/${app.mk.storefrontId}/albums/${this.data.id}`
break;
case 'playlists':
route = `/v1/catalog/${app.mk.storefrontId}/playlists/${this.data.id}`
break;
case "library-playlists":
route = `/v1/me/library/playlists/${this.data.id}/catalog`
break
case "library-albums":
route = `/v1/me/library/albums/${this.data.id}/catalog`
break
}
if (route === '') {
return
}
app.mk.api.v3.music(route).then(res => {
console.log(res.data.data[0].attributes.url)
app.copyToClipboard(res.data.data[0].attributes.url)
})
}
},
"follow": {
name: app.getLz('action.follow'),
icon: "./assets/feather/plus-circle.svg",
hidden: false,
action: () => {
app.setArtistFavorite(artistId, true)
}
},
"unfollow": {
name: app.getLz('action.unfollow'),
icon: "./assets/feather/x-circle.svg",
hidden: true,
action: () => {
app.setArtistFavorite(artistId, false)
}
},
}
}
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
}
app.showMenuPanel(menuItems, event)
},
getItemParent: function (data) {
kind = data.attributes.playParams.kind;
id = data.attributes.playParams.id;
return `${kind}:${id}`
},
getFormattedDate: function () {
let date = (this.data.attributes.releaseDate ?? (this.data.attributes.lastModifiedDate ?? (this.data.attributes.dateAdded ?? '')))
let prefix = '';
if (date == null || date === "") return "";
switch (date) {
case this.data.attributes.releaseDate:
prefix = this.app.getLz('term.time.released') + ' '
break;
case this.data.attributes.lastModifiedDate:
prefix = this.app.getLz('term.time.updated') + ' '
break;
case this.data.attributes.dateAdded:
prefix = this.app.getLz('term.time.added') + ' '
break;
}
let month, year;
try {
const releaseDate = new Date(date);
// month = new Intl.DateTimeFormat(this.app.cfg.general.language.replace('_','-'), {month: 'long'}).format(releaseDate);
// date = releaseDate.getDate();
// year = releaseDate.getFullYear();
let formatted = ''
try {
formatted = new Intl.DateTimeFormat(this.app.cfg.general.language?.replace('_', '-') ?? 'en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(releaseDate);
} catch (e) {
// use the format in json instead
if (this.app.getLz('date.format') != null) {
formatted = new this.app.getLz('date.format').replace("${d}", releaseDate.getDate()).replace("${m}", releaseDate.getMonth()).replace("${y}", releaseDate.getFullYear());
} else {
formatted = new Intl.DateTimeFormat('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(releaseDate);
}
}
return prefix + formatted
} catch (e) {
return ""
}
},
play() {
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;
}
}
const id = this.data.attributes.playParams.id ?? this.data.id;
//console.log("1")
const kind = this.data.attributes.playParams.kind ?? this.data.type ?? '';
//console.log("1")
const truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
let query = (this.data ?? app.showingPlaylist).relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
app.mk.stop().then(function () {
app.mk.setQueue({[truekind]: [id], parameters: {l: app.mklang}}).then(function () {
app.mk.play().then(function () {
if (query.length > 100) {
let u = query.slice(100);
if (app.mk.shuffleMode == 1) {
shuffleArray(u)
}
app.mk.queue.append(u)
}
})
})
})
Vue.component('playlist-inline', {
template: "#playlist-inline",
props: ["data"],
data: function() {
return {
editorialNotesExpanded: false,
drag: false,
nameEditing: false,
inLibrary: null,
confirm: false,
app: this.$root,
itemBadges: [],
badgesRequested: false,
headerVisible: true,
useArtistChip: false,
nestedPlaylist: [],
}
},
mounted: function() {
this.$nextTick(function() {
this.isInLibrary()
})
},
watch: {
data: function() {
this.nestedPlaylist = [];
this.isInLibrary()
this.getBadges()
this.generateNestedPlaylist()
}
},
methods: {
openInfoModal() {
app.moreinfodata = [];
app.moreinfodata = {
title: this.data?.attributes ? (this.data?.attributes?.name ??
(this.data?.attributes?.title ?? '') ?? '') : '',
subtitle: this.data?.attributes?.artistName ?? '',
content: ((this.data?.attributes?.editorialNotes != null) ? (this.data?.attributes?.editorialNotes?.standard ?? (this.data?.attributes?.editorialNotes?.short ?? '')) : (data.attributes?.description ? (this.data.attributes?.description?.standard ?? (this.data?.attributes?.description?.short ?? '')) : ''))
}
app.modals.moreInfo = true;
},
generateNestedPlaylist() {
this.nestedPlaylist = [];
if (this.data?.type?.includes("album")) {
console.log(this.data.relationships.tracks.data)
let songlists = this.data.relationships.tracks.data;
let discs = songlists.map(x => {
return x.attributes.discNumber
}).filter((item, i, ar) => ar.indexOf(item) === i);
if (discs && discs.length > 1) {
for (disc of discs) {
let songs = songlists.filter(x => x.attributes.discNumber == disc);
this.nestedPlaylist.push({ disc: disc, tracks: songs })
}
}
console.log(this.nestedPlaylist)
}
})
},
isHeaderVisible(visible) {
this.headerVisible = visible
},
getBadges() {
return
if (this.badgesRequested) {
return
}
this.badgesRequested = true
this.itemBadges = []
let self = this
var id = 0
try {
id = this.data.attributes.playParams.id
} catch (e) {
id = this.data.id
}
this.$root.getSocialBadges((badges) => {
let friends = badges[id]
if (friends) {
friends.forEach(function(friend) {
self.app.mk.api.v3.music(`/v1/social/${app.mk.storefrontId}/social-profiles/${friend}`).then(data => {
self.itemBadges.push(data.data.data[0])
})
})
}
})
},
confirmButton() {
// Return button to normal state after 3 seconds
this.confirm = true
setTimeout(() => this.confirm = false, 3000);
},
getArtistName(data) {
if (data.attributes.artistName) {
this.useArtistChip = true
return data.attributes.artistName
} else if (data.attributes.artist) {
this.useArtistChip = true
return data.attributes.artist.attributes.name
} else if (data.attributes.curatorName) {
return data.attributes.curatorName
} else {
return ""
}
},
getAlbumGenre: function() {
if (this.data.type.includes('albums')) {
let date = this.data.attributes.releaseDate;
if (date == null || date === "") return "";
return `${this.data.relationships.tracks.data[0].attributes.genreNames[0]} · ${new Date(date).getFullYear()}`
}
},
async isInLibrary() {
if (this.data.type && !this.data.type.includes("library")) {
// please keep using vars here
const params = {
"fields[playlists]": "inLibrary",
"fields[albums]": "inLibrary",
"relate": "library"
};
const res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params);
this.inLibrary = (res.data.data[0] && res.data.data[0].attributes && res.data.data[0].attributes.inLibrary) ? res.data.data[0].attributes.inLibrary : false
console.log(res)
} else {
this.inLibrary = true
}
},
editPlaylist() {
this.app.editPlaylist(this.data.id, this.data.attributes.name);
this.app.playlists.listing.forEach(playlist => {
if (playlist.id === this.data.id) {
playlist.attributes.name = this.data.attributes.name
}
})
this.nameEditing = false
},
addToLibrary(id) {
app.mk.addToLibrary(id)
this.inLibrary = true
this.confirm = false
},
async removeFromLibrary(id) {
const params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" };
var id = this.data.id ?? this.data.attributes.playParams.id
const res = await app.mkapi(this.data.attributes.playParams.kind ?? this.data.type, this.data.attributes.playParams.isLibrary ?? false, this.data.attributes.playParams.id ?? this.data.id, params);
if (res.data.data[0] && res.data.data[0].relationships && res.data.data[0].relationships.library && res.data.data[0].relationships.library.data && res.data.data[0].relationships.library.data.length > 0) {
id = res.data.data[0].relationships.library.data[0].id
}
let kind = this.data.attributes.playParams.kind ?? this.data.type ?? '';
const truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`, {},
{
fetchOptions: {
method: "DELETE"
}
})
this.inLibrary = false
this.confirm = false
},
editPlaylistName() {
if (this.data.attributes.canEdit && this.data.type === "library-playlists") {
this.nameEditing = true
setTimeout(() => {
document.querySelector(".nameEdit").focus()
}, 100)
}
},
buildContextMenu(index) {
let self = this
if (!this.data.attributes.canEdit) {
return
}
return {
normal: [
{
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()
}
},
],
multiple: [
{
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()
}
},
]
}
},
async put() {
if (!this.data.attributes.canEdit) {
return
}
console.log('sds', this.convert())
await app.mk.api.v3.music(
`/v1/me/library/playlists/${this.data.attributes.playParams.id}/tracks`,
{},
{
fetchOptions: {
method: "PUT",
body: JSON.stringify({
data: this.convert()
})
}
}
)
},
async remove() {
if (!this.data.attributes.canEdit) {
return
}
// for each app.selectedMediaItems splice the items from the playlist
for (let i = 0; i < app.selectedMediaItems.length; i++) {
let item = app.selectedMediaItems[i]
let index = this.data.relationships.tracks.data.findIndex(x => x.id == item.id)
if (index > -1) {
this.data.relationships.tracks.data.splice(index, 1)
}
}
await this.put()
},
convert() {
let pl_tracks = []
for (let i = 0; i < this.data.relationships.tracks.data.length; i++) {
pl_tracks.push({
id: this.data.relationships.tracks.data[i].id,
type: this.data.relationships.tracks.data[i].type
})
}
return pl_tracks
},
getRecursive(url) {
app.apiCall(app.musicBaseUrl + "/v1/me/library/playlists/p.V7VYlrDso6kkYY/tracks?offset=100", res => {
this.data.relationships.tracks.data = this.data.relationships.tracks.data.concat(res.data.data)
if (res.data.next) {
this.getRecursive(res.data.next)
}
})
},
menu(event) {
let self = this
let artistId = null
if (typeof this.data.relationships.artists != "undefined") {
artistId = this.data.relationships.artists.data[0].id
if (this.data.relationships.artists.data[0].type == "library-artists") {
artistId = this.data.relationships.artists.data[0].relationships.catalog.data[0].id
}
}
let menuItems = {
items: {
"share": {
name: app.getLz('term.share'),
icon: "./assets/feather/share.svg",
action: () => {
let route = ""
switch (this.data.type) {
case 'albums':
route = `/v1/catalog/${app.mk.storefrontId}/albums/${this.data.id}`
break;
case 'playlists':
route = `/v1/catalog/${app.mk.storefrontId}/playlists/${this.data.id}`
break;
case "library-playlists":
route = `/v1/me/library/playlists/${this.data.id}/catalog`
break
case "library-albums":
route = `/v1/me/library/albums/${this.data.id}/catalog`
break
}
if (route === '') {
return
}
app.mk.api.v3.music(route).then(res => {
console.log(res.data.data[0].attributes.url)
app.copyToClipboard(res.data.data[0].attributes.url)
})
}
},
"follow": {
name: app.getLz('action.follow'),
icon: "./assets/feather/plus-circle.svg",
hidden: false,
action: () => {
app.setArtistFavorite(artistId, true)
}
},
"unfollow": {
name: app.getLz('action.unfollow'),
icon: "./assets/feather/x-circle.svg",
hidden: true,
action: () => {
app.setArtistFavorite(artistId, false)
}
},
}
}
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
}
app.showMenuPanel(menuItems, event)
},
getItemParent: function(data) {
kind = data.attributes.playParams.kind;
id = data.attributes.playParams.id;
return `${kind}:${id}`
},
getFormattedDate: function() {
let date = (this.data.attributes.releaseDate ?? (this.data.attributes.lastModifiedDate ?? (this.data.attributes.dateAdded ?? '')))
let prefix = '';
if (date == null || date === "") return "";
switch (date) {
case this.data.attributes.releaseDate:
prefix = this.app.getLz('term.time.released') + ' '
break;
case this.data.attributes.lastModifiedDate:
prefix = this.app.getLz('term.time.updated') + ' '
break;
case this.data.attributes.dateAdded:
prefix = this.app.getLz('term.time.added') + ' '
break;
}
let month, year;
try {
const releaseDate = new Date(date);
// month = new Intl.DateTimeFormat(this.app.cfg.general.language.replace('_','-'), {month: 'long'}).format(releaseDate);
// date = releaseDate.getDate();
// year = releaseDate.getFullYear();
let formatted = ''
try {
formatted = new Intl.DateTimeFormat(this.app.cfg.general.language?.replace('_', '-') ?? 'en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(releaseDate);
} catch (e) {
// use the format in json instead
if (this.app.getLz('date.format') != null) {
formatted = new this.app.getLz('date.format').replace("${d}", releaseDate.getDate()).replace("${m}", releaseDate.getMonth()).replace("${y}", releaseDate.getFullYear());
} else {
formatted = new Intl.DateTimeFormat('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(releaseDate);
}
}
return prefix + formatted
} catch (e) {
return ""
}
},
play() {
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;
}
}
const id = this.data.attributes.playParams.id ?? this.data.id;
//console.log("1")
const kind = this.data.attributes.playParams.kind ?? this.data.type ?? '';
//console.log("1")
const truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
let query = (this.data ?? app.showingPlaylist).relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
app.mk.stop().then(function() {
app.mk.setQueue({ [truekind]: [id], parameters: { l: app.mklang } }).then(function() {
app.mk.play().then(function() {
if (query.length > 100) {
let u = query.slice(100);
if (app.mk.shuffleMode == 1) {
shuffleArray(u)
}
app.mk.queue.append(u)
}
})
})
})
}
}
})
</script>

View file

@ -1,22 +1,22 @@
<script>
Vue.component('plugin-renderer', {
/*html*/
template: `
Vue.component('plugin-renderer', {
/*html*/
template: `
<component :is="getPage()"></component>
`,
data: function () {
return {
app: this.$root,
}
},
props: {},
mounted() {
},
methods: {
getPage() {
return this.$root.pluginPages.page
}
},
data: function() {
return {
app: this.$root,
}
},
props: {},
mounted() {
},
methods: {
getPage() {
return this.$root.pluginPages.page
}
},
}
);
</script>
);
</script>

View file

@ -19,17 +19,20 @@
<div class="podcast-list-header" v-if="podcasts.length != 0">
{{$root.getLz('podcast.subscribedOnItunes')}}
</div>
<podcast-tab :isselected="podcastSelected.id == podcast.id" @click.native="selectPodcast(podcast)" v-for="podcast in podcasts" :item="podcast"></podcast-tab>
<podcast-tab :isselected="podcastSelected.id == podcast.id" @click.native="selectPodcast(podcast)"
v-for="podcast in podcasts" :item="podcast"></podcast-tab>
</div>
<div v-else>
<div class="podcast-list-header" v-if="podcasts.length != 0">
{{$root.getLz('term.library')}}
</div>
<podcast-tab :isselected="podcastSelected.id == podcast.id" @click.native="selectPodcast(podcast)" v-for="podcast in search.resultsLibrary" :item="podcast"></podcast-tab>
<podcast-tab :isselected="podcastSelected.id == podcast.id" @click.native="selectPodcast(podcast)"
v-for="podcast in search.resultsLibrary" :item="podcast"></podcast-tab>
<div class="podcast-list-header" v-if="podcasts.length != 0">
{{$root.getLz('podcast.itunesStore')}}
</div>
<podcast-tab :isselected="podcastSelected.id == podcast.id" @click.native="selectPodcast(podcast)" v-for="podcast in search.results" :item="podcast"></podcast-tab>
<podcast-tab :isselected="podcastSelected.id == podcast.id" @click.native="selectPodcast(podcast)"
v-for="podcast in search.results" :item="podcast"></podcast-tab>
</div>
</div>
<div class="episodes-list">
@ -37,13 +40,15 @@
<div class="row">
<div class="col-auto cider-flex-center">
<div class="podcast-artwork">
<mediaitem-artwork shadow="large" :url="podcastSelected.attributes.artwork.url" size="300"></mediaitem-artwork>
<mediaitem-artwork shadow="large" :url="podcastSelected.attributes.artwork.url"
size="300"></mediaitem-artwork>
</div>
</div>
<div class="col podcast-show-info">
<h1>{{ podcastSelected.attributes.name }}</h1>
<small>{{ podcastSelected.attributes.releaseFrequency }}</small>
<small>Created: {{ new Date(podcastSelected.attributes.createdDate).toLocaleDateString() }}</small>
<small>Created: {{ new Date(podcastSelected.attributes.createdDate).toLocaleDateString()
}}</small>
</div>
</div>
@ -62,32 +67,44 @@
<h3>{{$root.getLz('error.noResults')}}</h3>
<p>{{$root.getLz('error.noResults.description')}}</p>
</div>
<podcast-episode :isselected="selected.id == episode.id" @dblclick.native="playEpisode(episode)" @click.native="selectEpisode(episode)" :item="episode"
<podcast-episode :isselected="selected.id == episode.id" @dblclick.native="playEpisode(episode)"
@click.native="selectEpisode(episode)" :item="episode"
v-for="episode in episodes"></podcast-episode>
</div>
<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="$root.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>
<mediaitem-artwork shadow="large" :url="selected.attributes.artwork.url"
size="300"></mediaitem-artwork>
</div>
<h3 class="podcast-header">{{ selected.attributes.name }}</h3>
<button @click="playEpisode(selected)" class="md-btn podcast-play-btn">{{$root.getLz('podcast.playEpisode')}}</button>
<button @click="playEpisode(selected)" class="md-btn podcast-play-btn">
{{$root.getLz('podcast.playEpisode')}}
</button>
<div class="podcast-genre">
{{ selected.attributes.genreNames[0] }}
</div>
<div class="podcast-metainfo">
{{ msToMinSec(selected.attributes.durationInMilliseconds) }} • {{ new Date(selected.attributes.releaseDateTime).toLocaleString() }}
{{ msToMinSec(selected.attributes.durationInMilliseconds) }} • {{ new
Date(selected.attributes.releaseDateTime).toLocaleString() }}
</div>
<div class="well podcast-description" v-if="selected.attributes.description.standard">{{
selected.attributes.description.standard }}
</div>
<div class="well podcast-description" v-if="selected.attributes.description.standard">{{ selected.attributes.description.standard }}</div>
<div class="row">
<div class="col">
<button class="md-btn md-btn-block meta-btn" @click="openUrl(selected.attributes.websiteUrl)">{{$root.getLz('podcast.website')}}</button>
<button class="md-btn md-btn-block meta-btn" @click="openUrl(selected.attributes.websiteUrl)">
{{$root.getLz('podcast.website')}}
</button>
</div>
<div class="col">
<button class="md-btn md-btn-block meta-btn" @click="$root.share(selected.attributes.websiteUrl)">{{$root.getLz('action.share')}}</button>
<button class="md-btn md-btn-block meta-btn"
@click="$root.share(selected.attributes.websiteUrl)">{{$root.getLz('action.share')}}
</button>
</div>
</div>
</div>
@ -120,130 +137,137 @@
{{ item.attributes.description.standard }}
</div>
<div class="subtitle text-overflow-elipsis">
{{ msToMinSec(item.attributes.durationInMilliseconds) }} • {{ new Date(item.attributes.releaseDateTime).toLocaleString() }}
{{ msToMinSec(item.attributes.durationInMilliseconds) }} • {{ new
Date(item.attributes.releaseDateTime).toLocaleString() }}
</div>
</div>
</div>
</script>
<script>
Vue.component('podcast-episode', {
template: '#podcast-episode',
props: ['item', 'isselected'],
methods: {
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
}
}
});
Vue.component('podcast-tab', {
template: '#podcast-tab',
props: ['item', 'isselected'],
methods: {}
});
Vue.component('apple-podcasts', {
template: '#apple-podcasts',
data: function () {
return {
ciderPodcasts: [],
podcasts: [],
episodes: [],
search: {
term: "",
loading: false,
results: [],
resultsLibrary: [],
next: ""
},
podcastSelected: {
id: -1
},
selected: {
id: -1
}
}
Vue.component('podcast-episode', {
template: '#podcast-episode',
props: ['item', 'isselected'],
methods: {
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
}
}
});
Vue.component('podcast-tab', {
template: '#podcast-tab',
props: ['item', 'isselected'],
methods: {}
});
Vue.component('apple-podcasts', {
template: '#apple-podcasts',
data: function() {
return {
ciderPodcasts: [],
podcasts: [],
episodes: [],
search: {
term: "",
loading: false,
results: [],
resultsLibrary: [],
next: ""
},
async mounted() {
let podcastShow = await app.mk.api.v3.podcasts(`/v1/me/library/podcasts?include=episodes`)
this.podcasts = podcastShow.data.data
if (podcastShow.data.next) {
await this.getNext(podcastShow.data.next)
}
// this.episodes = podcastShow.data.data[0].relationships.episodes.data
podcastSelected: {
id: -1
},
methods: {
searchTriggerVis(visible) {
},
librarySearch() {
this.search.resultsLibrary = []
if (this.search.term.length > 2) {
this.search.resultsLibrary = this.podcasts.filter(podcast => podcast.attributes.name.toLowerCase().includes(this.search.term.toLowerCase()))
}
},
isSubscribed(id) {
return this.podcasts.filter(podcast => podcast.id == id).length > 0
},
searchPodcasts() {
let self = this
if (this.search.term == "") {
return
}
app.mk.api.v3.podcasts("/v1/catalog/us/search", {term: this.search.term, types: ["podcasts"], limit: 25}).then(response => {
console.log(response)
self.search.results = response.data.results.podcasts.data
})
},
openUrl(url) {
window.open(url)
},
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
},
playEpisode(episode) {
app.mk.setQueue({'episode': episode.id, parameters : {l : app.mklang}}).then(() => {app.mk.play()})
},
selectPodcast(podcast) {
this.podcastSelected = podcast
this.getEpisodes(podcast)
},
selectEpisode(episode) {
this.selected = Clone(episode)
},
async getEpisodes(podcast) {
this.episodes = []
let eps = await app.mk.api.v3.podcasts(`/v1/catalog/${app.mk.storefrontId}/podcasts/${podcast.id}?include=episodes`)
eps.data.data[0].relationships.episodes.data.forEach(ep => {
this.episodes.push(ep)
})
if (eps.data.data[0].relationships.episodes.next) {
await this.getNextEpisodes(eps.data.data[0].relationships.episodes.next, podcast.id)
}
},
async getNextEpisodes(next, podcastId) {
let podcastShow = await app.mk.api.v3.podcasts(next)
if (podcastId != this.podcastSelected.id) {
return
}
podcastShow.data.data.forEach(ep => {
this.episodes.push(ep)
})
if (podcastShow.data.next) {
await this.getNextEpisodes(podcastShow.data.next, podcastId)
}
},
async getNext(next) {
let podcastShow = await app.mk.api.v3.podcasts(next)
this.podcasts = this.podcasts.concat(podcastShow.data.data)
if (podcastShow.data.next) {
await this.getNext(podcastShow.data.next)
}
}
selected: {
id: -1
}
});
</script>
}
},
async mounted() {
let podcastShow = await app.mk.api.v3.podcasts(`/v1/me/library/podcasts?include=episodes`)
this.podcasts = podcastShow.data.data
if (podcastShow.data.next) {
await this.getNext(podcastShow.data.next)
}
// this.episodes = podcastShow.data.data[0].relationships.episodes.data
},
methods: {
searchTriggerVis(visible) {
},
librarySearch() {
this.search.resultsLibrary = []
if (this.search.term.length > 2) {
this.search.resultsLibrary = this.podcasts.filter(podcast => podcast.attributes.name.toLowerCase().includes(this.search.term.toLowerCase()))
}
},
isSubscribed(id) {
return this.podcasts.filter(podcast => podcast.id == id).length > 0
},
searchPodcasts() {
let self = this
if (this.search.term == "") {
return
}
app.mk.api.v3.podcasts("/v1/catalog/us/search", {
term: this.search.term,
types: ["podcasts"],
limit: 25
}).then(response => {
console.log(response)
self.search.results = response.data.results.podcasts.data
})
},
openUrl(url) {
window.open(url)
},
msToMinSec(ms) {
var minutes = Math.floor(ms / 60000);
var seconds = ((ms % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
},
playEpisode(episode) {
app.mk.setQueue({ 'episode': episode.id, parameters: { l: app.mklang } }).then(() => {
app.mk.play()
})
},
selectPodcast(podcast) {
this.podcastSelected = podcast
this.getEpisodes(podcast)
},
selectEpisode(episode) {
this.selected = Clone(episode)
},
async getEpisodes(podcast) {
this.episodes = []
let eps = await app.mk.api.v3.podcasts(`/v1/catalog/${app.mk.storefrontId}/podcasts/${podcast.id}?include=episodes`)
eps.data.data[0].relationships.episodes.data.forEach(ep => {
this.episodes.push(ep)
})
if (eps.data.data[0].relationships.episodes.next) {
await this.getNextEpisodes(eps.data.data[0].relationships.episodes.next, podcast.id)
}
},
async getNextEpisodes(next, podcastId) {
let podcastShow = await app.mk.api.v3.podcasts(next)
if (podcastId != this.podcastSelected.id) {
return
}
podcastShow.data.data.forEach(ep => {
this.episodes.push(ep)
})
if (podcastShow.data.next) {
await this.getNextEpisodes(podcastShow.data.next, podcastId)
}
},
async getNext(next) {
let podcastShow = await app.mk.api.v3.podcasts(next)
this.podcasts = this.podcasts.concat(podcastShow.data.data)
if (podcastShow.data.next) {
await this.getNext(podcastShow.data.next)
}
}
}
});
</script>

View file

@ -6,10 +6,10 @@
<h3>{{ app.getLz('term.personalStations') }}</h3>
</div>
</div>
<!-- <mediaitem-square :kind="'385'" size="600"-->
<!-- :item="item ? (item.attributes.kind ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"-->
<!-- :imagesize="800"-->
<!-- v-for="item of getFlattenedCategories()">-->
<!-- <mediaitem-square :kind="'385'" size="600"-->
<!-- :item="item ? (item.attributes.kind ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"-->
<!-- :imagesize="800"-->
<!-- v-for="item of getFlattenedCategories()">-->
<mediaitem-square :item="item" v-for="item in radio.personal"></mediaitem-square>
<div class="row">
<div class="col">
@ -27,65 +27,65 @@
</script>
<script>
Vue.component('cider-radio', {
template: "#cider-radio",
props: ["data"],
data: function () {
return {
app: this.$root,
radio: {personal: [], recent: [], am: []}
}
},
async mounted() {
this.radio.personal = await this.getPersonalStations()
this.radio.recent = await this.getRecentStations()
this.radio.am = await this.getAmStations()
console.log(this.radio)
// this.getPersonalStations();
// this.getAmStations();
},
methods: {
async getPersonalStations(attempts = 0) {
if (attempts > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/catalog/${app.mk.api.v3.storefrontId}/stations`, {
"filter[identity]": "personal",
})).data.data
} catch (e) {
console.error(`Failed to get personal stations: ${e}`)
await this.getPersonalStations(attempts + 1)
}
},
async getRecentStations(attempts = 0) {
if (attempts > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`, {
"platform": "web",
"art[url]": "f",
l: app.mklang
})).data.data
} catch (e) {
console.error(`Failed to get recent stations: ${e}`)
await this.getRecentStations(attempts + 1)
}
},
async getAmStations(attempt = 0) {
if (attempt > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/catalog/${app.mk.api.v3.storefrontId}/stations`, {
"filter[featured]": "apple-music-live-radio",
})).data.data
} catch (e) {
console.error(`Failed to get AM stations: ${e}`)
await this.getAmStations(attempt + 1)
}
},
}
})
</script>
Vue.component('cider-radio', {
template: "#cider-radio",
props: ["data"],
data: function() {
return {
app: this.$root,
radio: { personal: [], recent: [], am: [] }
}
},
async mounted() {
this.radio.personal = await this.getPersonalStations()
this.radio.recent = await this.getRecentStations()
this.radio.am = await this.getAmStations()
console.log(this.radio)
// this.getPersonalStations();
// this.getAmStations();
},
methods: {
async getPersonalStations(attempts = 0) {
if (attempts > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/catalog/${app.mk.api.v3.storefrontId}/stations`, {
"filter[identity]": "personal",
})).data.data
} catch (e) {
console.error(`Failed to get personal stations: ${e}`)
await this.getPersonalStations(attempts + 1)
}
},
async getRecentStations(attempts = 0) {
if (attempts > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`, {
"platform": "web",
"art[url]": "f",
l: app.mklang
})).data.data
} catch (e) {
console.error(`Failed to get recent stations: ${e}`)
await this.getRecentStations(attempts + 1)
}
},
async getAmStations(attempt = 0) {
if (attempt > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/catalog/${app.mk.api.v3.storefrontId}/stations`, {
"filter[featured]": "apple-music-live-radio",
})).data.data
} catch (e) {
console.error(`Failed to get AM stations: ${e}`)
await this.getAmStations(attempt + 1)
}
},
}
})
</script>

View file

@ -76,36 +76,36 @@
</div>
</script>
<script>
Vue.component('cider-recordlabel', {
template: "#cider-recordlabel",
props: ['data'],
data: function () {
return {
topSongsExpanded: false,
$root: this.$root
}
},
methods: {
getArtistPalette(artist) {
if (artist?.attributes?.artwork != null) {
return {
"background": "#" + artist["attributes"]["artwork"]["bgColor"],
"color": "#" + artist["attributes"]["artwork"]["textColor1"],
}
} else {
return {
"background": "#000000",
"color": "#ffffff",
}
}
},
getTopResult() {
if (this.search.results["meta"]) {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} else {
return false;
}
}
}
})
</script>
Vue.component('cider-recordlabel', {
template: "#cider-recordlabel",
props: ['data'],
data: function() {
return {
topSongsExpanded: false,
$root: this.$root
}
},
methods: {
getArtistPalette(artist) {
if (artist?.attributes?.artwork != null) {
return {
"background": "#" + artist["attributes"]["artwork"]["bgColor"],
"color": "#" + artist["attributes"]["artwork"]["textColor1"],
}
} else {
return {
"background": "#000000",
"color": "#ffffff",
}
}
},
getTopResult() {
if (this.search.results["meta"]) {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} else {
return false;
}
}
}
})
</script>

View file

@ -4,18 +4,16 @@
</div>
</script>
<script>
Vue.component('remote-pair', {
template: '#remote-pair',
async mounted() {
ipcRenderer.send('get-remote-pair-url')
ipcRenderer.on('send-remote-pair-url', (event, url) => {
this.url = url
app.webview.src = url
document.getElementById('foo').src = url;
})
},
methods: {
}
});
</script>
Vue.component('remote-pair', {
template: '#remote-pair',
async mounted() {
ipcRenderer.send('get-remote-pair-url')
ipcRenderer.on('send-remote-pair-url', (event, url) => {
this.url = url
app.webview.src = url
document.getElementById('foo').src = url;
})
},
methods: {}
});
</script>

View file

@ -3,158 +3,167 @@
<vue-horizontal style="height: 300px;">
<div class="replay-period" v-for="year in years" @click="getReplayYear(year.attributes.year)">
<div class="artwork-container">
<mediaitem-artwork :size="200" :url="year.relationships.playlist.data[0].attributes.artwork.url"></mediaitem-artwork>
<mediaitem-artwork :size="200"
:url="year.relationships.playlist.data[0].attributes.artwork.url"></mediaitem-artwork>
</div>
{{ year.attributes.year }}
</div>
</vue-horizontal>
<hr>
<transition name="replaycard">
<div class="replay-viewport" v-if="loaded.id != -1">
<!-- Stats -->
<div class="replay-video" v-if="false">
<mediaitem-artwork
:url="loaded.playlist.attributes.editorialVideo.motionWideVideo21x9.previewFrame.url"
:video="loaded.playlist.attributes.editorialVideo.motionWideVideo21x9.video"
:video-priority="true"></mediaitem-artwork>
</div>
<h1 class="replay-header">{{ loaded.attributes.year }} {{$root.getLz('term.replay')}}</h1>
<hr>
<div class="row">
<div class="col">
<h4>{{ convertToHours(loaded.attributes.listenTimeInMinutes) }} {{$root.getLz('term.time.hours')}}</h4>
<h4>{{ loaded.attributes.uniqueAlbumCount }} {{$root.getLz('term.uniqueAlbums')}}</h4>
<h4>{{ loaded.attributes.uniqueArtistCount }} {{$root.getLz('term.uniqueArtists')}}</h4>
<h4>{{ loaded.attributes.uniqueSongCount }} {{$root.getLz('term.uniqueSongs')}}</h4>
<div class="replay-viewport" v-if="loaded.id != -1">
<!-- Stats -->
<div class="replay-video" v-if="false">
<mediaitem-artwork
:url="loaded.playlist.attributes.editorialVideo.motionWideVideo21x9.previewFrame.url"
:video="loaded.playlist.attributes.editorialVideo.motionWideVideo21x9.video"
:video-priority="true"></mediaitem-artwork>
</div>
<div class="col-auto replay-playlist-container">
<mediaitem-square kind="card" :no-scale="true" :force-video="true" :item="loaded.playlist"></mediaitem-square>
<h1 class="replay-header">{{ loaded.attributes.year }} {{$root.getLz('term.replay')}}</h1>
<hr>
<div class="row">
<div class="col">
<h4>{{ convertToHours(loaded.attributes.listenTimeInMinutes) }}
{{$root.getLz('term.time.hours')}}</h4>
<h4>{{ loaded.attributes.uniqueAlbumCount }} {{$root.getLz('term.uniqueAlbums')}}</h4>
<h4>{{ loaded.attributes.uniqueArtistCount }} {{$root.getLz('term.uniqueArtists')}}</h4>
<h4>{{ loaded.attributes.uniqueSongCount }} {{$root.getLz('term.uniqueSongs')}}</h4>
</div>
<div class="col-auto replay-playlist-container">
<mediaitem-square kind="card" :no-scale="true" :force-video="true"
:item="loaded.playlist"></mediaitem-square>
</div>
</div>
</div>
<!-- Top Artists-->
<h3>{{$root.getLz('term.topArtists')}}</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>
<!-- Top Artists-->
<h3>{{$root.getLz('term.topArtists')}}</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">
{{ convertToHours(artistData.attributes.listenTimeInMinutes) }}
{{$root.getLz('term.time.hours', {'count':
convertToHours(artistData.attributes.listenTimeInMinutes) })}}<br>
{{$root.getLz('term.listenedTo')}} {{ artistData.attributes.playCount }}
{{$root.getLz('term.times')}}
</div>
</div>
<div class="card-footer">
{{ convertToHours(artistData.attributes.listenTimeInMinutes) }} {{$root.getLz('term.time.hours', {'count': convertToHours(artistData.attributes.listenTimeInMinutes) })}}<br>
{{$root.getLz('term.listenedTo')}} {{ artistData.attributes.playCount }} {{$root.getLz('term.times')}}
</mediaitem-scroller-horizontal>
</div>
<!-- Top Albums-->
<h3>{{$root.getLz('term.topAlbums')}}</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">
{{ convertToHours(albumData.attributes.listenTimeInMinutes) }}
{{$root.getLz('term.time.hours', {'count':
convertToHours(albumData.attributes.listenTimeInMinutes)})}}<br>
{{ albumData.attributes.playCount }} {{$root.getLz('term.plays')}}
</div>
</div>
</div>
</mediaitem-scroller-horizontal>
</div>
<!-- Top Albums-->
<h3>{{$root.getLz('term.topAlbums')}}</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>
</mediaitem-scroller-horizontal>
</div>
<!-- Top Songs-->
<h3>{{$root.getLz('term.topSongs')}}</h3>
<div class="well">
<listitem-horizontal :show-library-status="false"
:items="songsToArray(loaded.views['top-songs'].data)"></listitem-horizontal>
</div>
<h3>{{$root.getLz('term.topGenres')}}</h3>
<div class="top-genres-container">
<div v-for="genre in loaded.topGenres" class="replay-genre-display">
<div class="genre-name">
{{ genre.genre }}
</div>
<div class="card-footer">
{{ convertToHours(albumData.attributes.listenTimeInMinutes) }} {{$root.getLz('term.time.hours', {'count': convertToHours(albumData.attributes.listenTimeInMinutes)})}}<br>
{{ albumData.attributes.playCount }} {{$root.getLz('term.plays')}}
</div>
</div>
</mediaitem-scroller-horizontal>
</div>
<!-- Top Songs-->
<h3>{{$root.getLz('term.topSongs')}}</h3>
<div class="well">
<listitem-horizontal :show-library-status="false" :items="songsToArray(loaded.views['top-songs'].data)"></listitem-horizontal>
</div>
<h3>{{$root.getLz('term.topGenres')}}</h3>
<div class="top-genres-container">
<div v-for="genre in loaded.topGenres" class="replay-genre-display">
<div class="genre-name">
{{ genre.genre }}
</div>
<div class="genre-count">
<div class="genre-count-bar" :style="{'width': genre.count + '%'}">
{{ genre.count }}%
<div class="genre-count">
<div class="genre-count-bar" :style="{'width': genre.count + '%'}">
{{ genre.count }}%
</div>
</div>
</div>
</div>
</div>
</div>
</transition>
</div>
</script>
<script>
Vue.component('replay-page', {
template: '#replay-page',
data: function () {
return {
years: [],
loaded: {
id: -1
},
musicTypeGenre: ""
}
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
this.years.reverse()
localStorage.setItem("seenReplay", true)
this.getReplayYear();
const musicGenre = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/genres/34`)
this.musicTypeGenre = musicGenre.data.data[0].attributes.name
},
methods: {
songsToArray(songsData) {
let self = this
let songs = []
let topGenres = {}
let genrePlayCount = 0;
songsData.forEach(function (songData) {
let song = songData.relationships.song.data[0]
song.attributes.playCount = songData.attributes.playCount
songs.push(song)
genrePlayCount += song.attributes.playCount
song.attributes.genreNames.forEach(function (genre) {
if (genre != self.musicTypeGenre) {
if (topGenres[genre] == undefined) {
topGenres[genre] = song.attributes.playCount
} else {
topGenres[genre] += song.attributes.playCount
}
}
})
})
let topGenresArray = []
for (let genre in topGenres) {
topGenresArray.push({
genre: genre,
count: topGenres[genre]
})
}
topGenresArray.sort(function (a, b) {
return b.count - a.count
})
topGenresArray.forEach(function (genre) {
genre.count = Math.round(genre.count / genrePlayCount * 100)
})
this.loaded.topGenres = topGenresArray
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`)
let replayData = response.data.data[0]
// extended playlist
let playlist = await app.mk.api.v3.music(replayData.relationships.playlist.data[0].href, {extend: "editorialArtwork,editorialVideo"})
replayData.playlist = playlist.data.data[0]
this.loaded = replayData
},
convertToHours(minutes) {
return Math.floor(minutes / 60)
musicTypeGenre: ""
}
},
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
this.years.reverse()
localStorage.setItem("seenReplay", true)
this.getReplayYear();
const musicGenre = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/genres/34`)
this.musicTypeGenre = musicGenre.data.data[0].attributes.name
},
methods: {
songsToArray(songsData) {
let self = this
let songs = []
let topGenres = {}
let genrePlayCount = 0;
songsData.forEach(function(songData) {
let song = songData.relationships.song.data[0]
song.attributes.playCount = songData.attributes.playCount
songs.push(song)
genrePlayCount += song.attributes.playCount
song.attributes.genreNames.forEach(function(genre) {
if (genre != self.musicTypeGenre) {
if (topGenres[genre] == undefined) {
topGenres[genre] = song.attributes.playCount
} else {
topGenres[genre] += song.attributes.playCount
}
}
})
})
let topGenresArray = []
for (let genre in topGenres) {
topGenresArray.push({
genre: genre,
count: topGenres[genre]
})
}
});
</script>
topGenresArray.sort(function(a, b) {
return b.count - a.count
})
topGenresArray.forEach(function(genre) {
genre.count = Math.round(genre.count / genrePlayCount * 100)
})
this.loaded.topGenres = topGenresArray
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`)
let replayData = response.data.data[0]
// extended playlist
let playlist = await app.mk.api.v3.music(replayData.relationships.playlist.data[0].href, { extend: "editorialArtwork,editorialVideo" })
replayData.playlist = playlist.data.data[0]
this.loaded = replayData
},
convertToHours(minutes) {
return Math.floor(minutes / 60)
}
}
});
</script>

View file

@ -1,6 +1,6 @@
<script type="text/x-template" id="cider-search">
<div class="content-inner search-page">
<div class="search-input-container fs-search" v-if="$root.appMode == 'fullscreen'" >
<div class="search-input-container fs-search" v-if="$root.appMode == 'fullscreen'">
<div class="search-input--icon"></div>
<input type="search" spellcheck="false" @focus="$root.search.showHints = true"
@blur="$root.setTimeout(()=>{$root.search.showHints = false}, 300)"
@ -20,10 +20,14 @@
<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>
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>
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 != ''">
<template v-if="searchType == 'catalog'">
@ -55,14 +59,16 @@
<template v-if="search.results['meta'] != null">
<template
v-for="section in search.results.meta.results.order" v-if="section != 'song' && section != 'top'">
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 cider-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')}}
@click="app.showSearchView(app.search.term, section, app.friendlyTypes(section))">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
@ -83,7 +89,8 @@
</div>
<div class="col-auto cider-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')}}
@click="app.showCollection(search.resultsSocial.playlist, 'Shared Playlists', 'default')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
@ -97,7 +104,8 @@
</div>
<div class="col-auto cider-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')}}
@click="app.showCollection(search.resultsSocial.profile, 'People', 'default')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div>
@ -119,79 +127,83 @@
</div>
</template>
</div>
<div v-else >
<div v-if="categoriesReady || getCategories()">
<div v-else>
<div v-if="categoriesReady || getCategories()">
<div>
<div class="col" v-if="categoriesView != null && categoriesView != [] && categoriesView[0].attributes != null && categoriesView[0].attributes.title != null">
<div class="col"
v-if="categoriesView != null && categoriesView != [] && categoriesView[0].attributes != null && categoriesView[0].attributes.title != null">
<h3>{{$root.getLz('home.recentlyPlayed')}}</h3>
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="recentlyPlayed.limit(10)">
</listitem-horizontal>
</div>
<!-- <mediaitem-square :kind="'385'" size="600" v-for="item in recentlyPlayed.limit(10)" :item="item" :imagesize="800"></mediaitem-square>-->
<listitem-horizontal :items="recentlyPlayed.limit(10)">
</listitem-horizontal>
</div>
<!-- <mediaitem-square :kind="'385'" size="600" v-for="item in recentlyPlayed.limit(10)" :item="item" :imagesize="800"></mediaitem-square>-->
<h3>{{categoriesView[0].attributes.title.stringForDisplay ?? ""}}</h3>
</div>
</div>
</div>
<div class="categories">
<mediaitem-square :kind="'385'" :imageformat="'bb'" size="600"
:item="item ? (item.attributes.kind ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"
:imagesize="800"
v-for="item of getFlattenedCategories()">
:item="item ? (item.attributes.kind ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"
:imagesize="800"
v-for="item of getFlattenedCategories()">
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('cider-search', {
template: "#cider-search",
props: ['search'],
data: function () {
return {
app: this.$root,
recentlyPlayed: [],
categoriesView: [],
categoriesReady: false,
searchType: "catalog",
}
},
methods: {
getTopResult() {
try {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} catch (error) {
return false
}
},
async seeAllHistory() {
let hist = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, {
l: this.$root.mklang
})
this.recentlyPlayed = hist.data.data
},
async getCategories() {
if (this.categoriesView != [] && this.categoriesView.length > 0) { this.categoriesReady = true; return await true; } else {
await this.seeAllHistory()
let response = await this.app.mk.api.v3.music(`/v1/recommendations/${this.app.mk.storefrontId}?timezone=${encodeURIComponent(this.app.formatTimezoneOffset())}&name=search-landing&platform=web&extend=editorialArtwork&art%5Burl%5D=f%2Cc&types=editorial-items%2Capple-curators%2Cactivities&l=${this.$root.mklang}`);
this.categoriesView = response.data.data;
console.log(this.categoriesView)
this.categoriesReady = true;
return await true;
}
},
getFlattenedCategories() {
let flattened = [];
for (let i = 0; i < this.categoriesView.length; i++) {
if (this.categoriesView[i].relationships && this.categoriesView[i].relationships.contents && this.categoriesView[i].relationships.contents.data) {
for (let j = 0; j < this.categoriesView[i].relationships.contents.data.length; j++) {
if (this.categoriesView[i].relationships.contents.data[j].type != 'editorial-items')
flattened.push(this.categoriesView[i].relationships.contents.data[j])
}
}
}
return flattened;
}
Vue.component('cider-search', {
template: "#cider-search",
props: ['search'],
data: function() {
return {
app: this.$root,
recentlyPlayed: [],
categoriesView: [],
categoriesReady: false,
searchType: "catalog",
}
},
methods: {
getTopResult() {
try {
return this.search.results[this.search.results.meta.results.order[0]]["data"][0]
} catch (error) {
return false
}
})
</script>
},
async seeAllHistory() {
let hist = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, {
l: this.$root.mklang
})
this.recentlyPlayed = hist.data.data
},
async getCategories() {
if (this.categoriesView != [] && this.categoriesView.length > 0) {
this.categoriesReady = true;
return await true;
} else {
await this.seeAllHistory()
let response = await this.app.mk.api.v3.music(`/v1/recommendations/${this.app.mk.storefrontId}?timezone=${encodeURIComponent(this.app.formatTimezoneOffset())}&name=search-landing&platform=web&extend=editorialArtwork&art%5Burl%5D=f%2Cc&types=editorial-items%2Capple-curators%2Cactivities&l=${this.$root.mklang}`);
this.categoriesView = response.data.data;
console.log(this.categoriesView)
this.categoriesReady = true;
return await true;
}
},
getFlattenedCategories() {
let flattened = [];
for (let i = 0; i < this.categoriesView.length; i++) {
if (this.categoriesView[i].relationships && this.categoriesView[i].relationships.contents && this.categoriesView[i].relationships.contents.data) {
for (let j = 0; j < this.categoriesView[i].relationships.contents.data.length; j++) {
if (this.categoriesView[i].relationships.contents.data[j].type != 'editorial-items')
flattened.push(this.categoriesView[i].relationships.contents.data[j])
}
}
}
return flattened;
}
}
})
</script>

View file

@ -1,48 +1,46 @@
<script type="text/x-template" id="cider-zoo">
<div class="content-inner">
<svg-icon/>
<svg-icon />
<h3>Welcome to element park. *BERR NERR NERR NERR NERRRRR BERR NER NER NER NERRR BERRR NR NR NRRRR*</h3>
<button @click="app.playMediaItemById('1592151778', 'album')">Play Test Album</button>
{{ $store.state.test }}
<div class="spinner"></div>
<button class="md-btn">Cider Button</button>
<artist-chip
v-if="artistLoaded"
:item="artist"
v-if="artistLoaded"
:item="artist"
></artist-chip>
<amp-chrome-player/>
<amp-chrome-player />
<!-- <amp-footer-player/> -->
<hr>
<amp-lcd-progress/>
<amp-lcd-progress />
<hr>
<amp-playback-controls-shuffle/>
<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/>
<amp-user-menu />
<amp-tv-overlay />
<amp-podcast-playback-controls />
<amp-lcd />
</div>
</script>
<script>
Vue.component('cider-zoo', {
template: '#cider-zoo',
data: function () {
return {
artistLoaded: false,
artist: {}
}
},
async mounted() {
app.mk.api.v3.music("/v1/catalog/us/artists/669831761").then(response => {
this.artist = response.data.data[0];
this.artistLoaded = true;
});
},
methods: {
}
});
</script>
Vue.component('cider-zoo', {
template: '#cider-zoo',
data: function() {
return {
artistLoaded: false,
artist: {}
}
},
async mounted() {
app.mk.api.v3.music("/v1/catalog/us/artists/669831761").then(response => {
this.artist = response.data.data[0];
this.artistLoaded = true;
});
},
methods: {}
});
</script>

View file

@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" fill="currentColor"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"
fill="currentColor"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<path
d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 531 B

After

Width:  |  Height:  |  Size: 545 B

Before After
Before After

View file

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cast"><path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"/><line x1="2" y1="20" x2="2.01" y2="20"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cast">
<path
d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"/>
<line x1="2" y1="20" x2="2.01" y2="20"/>
</svg>

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 392 B

Before After
Before After

View file

@ -1 +1,4 @@
<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-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
<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-check">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 272 B

Before After
Before After

View file

@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 320 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 320 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<path
d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"/>
</svg>

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 532 B

Before After
Before After

View file

@ -1 +1,6 @@
<svg aria-hidden="true" data-prefix="fas" data-icon="chevron-right" fill="currentColor" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" class="svg-inline--fa fa-chevron-right fa-w-10 fa-7x"><path fill="currentColor" d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z" class=""/></svg>
<svg aria-hidden="true" data-prefix="fas" data-icon="chevron-right" fill="currentColor" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" class="svg-inline--fa fa-chevron-right fa-w-10 fa-7x">
<path fill="currentColor"
d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"
class=""/>
</svg>

Before

Width:  |  Height:  |  Size: 538 B

After

Width:  |  Height:  |  Size: 564 B

Before After
Before After

View file

@ -1 +1,4 @@
<svg width="9" height="9" viewBox="0 0 9 9" xmlns="http://www.w3.org/2000/svg" class="glyph" aria-hidden="true"><path d="M1.59 8.991h5.82c1.043 0 1.582-.538 1.582-1.566v-5.85C8.992.547 8.453.008 7.41.008H1.59C.552.008.008.542.008 1.575v5.85c0 1.028.544 1.566 1.582 1.566zm1.812-2.273c-.332 0-.505-.211-.505-.553V2.753c0-.341.173-.553.505-.553h2.264c.245 0 .408.14.408.385 0 .235-.163.384-.408.384H3.854v1.106h1.71c.226 0 .38.125.38.355 0 .221-.154.346-.38.346h-1.71V5.95h1.812c.245 0 .408.14.408.385 0 .235-.163.384-.408.384H3.402z"></path></svg>
<svg width="9" height="9" viewBox="0 0 9 9" xmlns="http://www.w3.org/2000/svg" class="glyph" aria-hidden="true">
<path
d="M1.59 8.991h5.82c1.043 0 1.582-.538 1.582-1.566v-5.85C8.992.547 8.453.008 7.41.008H1.59C.552.008.008.542.008 1.575v5.85c0 1.028.544 1.566 1.582 1.566zm1.812-2.273c-.332 0-.505-.211-.505-.553V2.753c0-.341.173-.553.505-.553h2.264c.245 0 .408.14.408.385 0 .235-.163.384-.408.384H3.854v1.106h1.71c.226 0 .38.125.38.355 0 .221-.154.346-.38.346h-1.71V5.95h1.812c.245 0 .408.14.408.385 0 .235-.163.384-.408.384H3.402z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 555 B

Before After
Before After

View file

@ -1 +1,6 @@
<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>
<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>

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 404 B

Before After
Before After

View file

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="white">
<path d="M0 0h36v36h-36z" fill="none"/>
<path d="M10 21h-3v8h8v-3h-5v-5zm-3-6h3v-5h5v-3h-8v8zm19 11h-5v3h8v-8h-3v5zm-5-19v3h5v5h3v-8h-8z"/>
<path d="M0 0h36v36h-36z" fill="none"/>
<path d="M10 21h-3v8h8v-3h-5v-5zm-3-6h3v-5h5v-3h-8v8zm19 11h-5v3h8v-8h-3v5zm-5-19v3h5v5h3v-8h-8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 248 B

Before After
Before After

View file

@ -1 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-grid"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-grid">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 423 B

Before After
Before After

View file

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-headphones"><path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-headphones">
<path d="M3 18v-6a9 9 0 0 1 18 0v6"></path>
<path
d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 412 B

Before After
Before After

View file

@ -1,13 +1,21 @@
<?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 214 214" 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;">
<svg width="100%" height="100%" viewBox="0 0 214 214" version="1.1" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-79.4309,9.53659)">
<path d="M250.163,76.146C250.163,52.589 231.037,33.463 207.48,33.463L122.114,33.463C98.556,33.463 79.431,52.589 79.431,76.146L79.431,161.512C79.431,185.07 98.556,204.195 122.114,204.195L207.48,204.195C231.037,204.195 250.163,185.07 250.163,161.512L250.163,76.146ZM235.163,83.646C235.163,64.228 219.398,48.463 199.98,48.463L129.614,48.463C110.196,48.463 94.431,64.228 94.431,83.646L94.431,154.012C94.431,173.43 110.196,189.195 129.614,189.195L199.98,189.195C219.398,189.195 235.163,173.43 235.163,154.012L235.163,83.646Z" style="fill:currentColor;"/>
<path
d="M250.163,76.146C250.163,52.589 231.037,33.463 207.48,33.463L122.114,33.463C98.556,33.463 79.431,52.589 79.431,76.146L79.431,161.512C79.431,185.07 98.556,204.195 122.114,204.195L207.48,204.195C231.037,204.195 250.163,185.07 250.163,161.512L250.163,76.146ZM235.163,83.646C235.163,64.228 219.398,48.463 199.98,48.463L129.614,48.463C110.196,48.463 94.431,64.228 94.431,83.646L94.431,154.012C94.431,173.43 110.196,189.195 129.614,189.195L199.98,189.195C219.398,189.195 235.163,173.43 235.163,154.012L235.163,83.646Z"
style="fill:currentColor;"/>
</g>
<g transform="matrix(0.906286,0,0,0.906286,-20.9871,-22.3274)">
<path d="M250.163,76.146C250.163,52.589 231.037,33.463 207.48,33.463L122.114,33.463C98.556,33.463 79.431,52.589 79.431,76.146L79.431,161.512C79.431,185.07 98.556,204.195 122.114,204.195L207.48,204.195C231.037,204.195 250.163,185.07 250.163,161.512L250.163,76.146Z" style="fill:white;"/>
<g transform="matrix(0.906286,0,0,0.906286,-20.9871,-22.3274)">
<path
d="M250.163,76.146C250.163,52.589 231.037,33.463 207.48,33.463L122.114,33.463C98.556,33.463 79.431,52.589 79.431,76.146L79.431,161.512C79.431,185.07 98.556,204.195 122.114,204.195L207.48,204.195C231.037,204.195 250.163,185.07 250.163,161.512L250.163,76.146Z"
style="fill:white;"/>
</g>
<g transform="matrix(0.333526,0,0,0.333526,43.0167,0)">
<path d="M511.8,130.7C511.8,115 510.5,99.3 506.7,84C500,56 484,34.7 460.2,19C448,11 434.5,6.1 420.1,3.5C409.1,1.5 397.9,0.6 386.8,0.3L384.1,0L127.6,0C124.4,0.3 121.1,0.4 117.9,0.6C102,1.5 86.2,3.2 71.1,9.2C42.7,20.4 22.1,40.1 10.1,68.4C5.9,78 3.8,88.2 2.3,98.5C1.1,106.8 0.4,115.3 0.1,123.7L-0.1,125.7L-0.1,386.4C0.1,389.4 0.2,392.4 0.4,395.4C1.5,412.8 3.7,430.1 11.1,446.1C24.9,476.4 48.2,496.3 80.1,505.8C89,508.6 98.3,509.8 107.7,510.6C119.5,511.8 131.4,511.9 143.2,511.9L378.5,511.9C389.7,511.9 400.8,511.2 412,509.7C429.6,507.5 446.1,502.3 461,492.5C478.9,480.7 492.4,465 501.1,445.4C505.1,436.4 507.3,426.8 509,417.2C511.4,402.8 511.9,388.2 511.9,373.6C511.8,292.6 511.9,211.6 511.8,130.6L511.8,130.7ZM374.8,93.4L374.8,337.6C374.8,346.5 373.6,355.3 369.6,363.3C363.4,375.9 353.4,383.8 340,387.6C332.6,389.8 324.9,390.9 317.2,391.3C297,392.3 279.4,378.6 275.8,358.6C272.7,342.1 280.6,323.9 298,315.4C304.8,312.1 312.2,310.1 319.7,308.6C327.8,306.9 335.9,305.3 343.9,303.4C349.8,302.1 353.6,298.5 354.8,292.4L355.2,288.3L355.2,172.1L354.6,168.2C353.8,165 351.4,163 348.1,163.2C344.7,163.4 341.4,163.9 338,164.6C321.7,167.8 305.5,171 289.3,174.3L210.4,190.2L209.3,190.5C203.4,192.2 201.3,194.8 201,201L201,203.7C200.9,259.2 201,314.7 200.9,370.2C200.9,379.2 199.9,388 196.3,396.4C190.4,410.1 179.9,418.7 165.7,422.7C158.2,424.9 150.5,426.1 142.7,426.4C122.3,427.2 105.3,413.6 101.8,393.5C98.8,376.2 106.7,357.5 126.4,349.2C134.1,346 142,344.3 150.1,342.6C156.2,341.4 162.4,340.1 168.4,338.9C176.6,337.2 180.8,332 181.2,323.7L181.2,131C181.2,128.3 181.5,125.7 182.1,123.1C183.6,117 187.9,113.5 193.8,112.1C199.2,110.7 204.8,109.7 210.3,108.5C226,105.3 241.5,102.2 257.2,99.1L305.6,89.3C319.9,86.5 334.2,83.6 348.5,80.7C353.2,79.8 357.9,78.8 362.7,78.4C369.3,77.8 373.9,82 374.5,88.7C374.7,90.3 374.8,91.9 374.8,93.4Z" style="fill:currentColor;fill-rule:nonzero;"/>
<g transform="matrix(0.333526,0,0,0.333526,43.0167,0)">
<path
d="M511.8,130.7C511.8,115 510.5,99.3 506.7,84C500,56 484,34.7 460.2,19C448,11 434.5,6.1 420.1,3.5C409.1,1.5 397.9,0.6 386.8,0.3L384.1,0L127.6,0C124.4,0.3 121.1,0.4 117.9,0.6C102,1.5 86.2,3.2 71.1,9.2C42.7,20.4 22.1,40.1 10.1,68.4C5.9,78 3.8,88.2 2.3,98.5C1.1,106.8 0.4,115.3 0.1,123.7L-0.1,125.7L-0.1,386.4C0.1,389.4 0.2,392.4 0.4,395.4C1.5,412.8 3.7,430.1 11.1,446.1C24.9,476.4 48.2,496.3 80.1,505.8C89,508.6 98.3,509.8 107.7,510.6C119.5,511.8 131.4,511.9 143.2,511.9L378.5,511.9C389.7,511.9 400.8,511.2 412,509.7C429.6,507.5 446.1,502.3 461,492.5C478.9,480.7 492.4,465 501.1,445.4C505.1,436.4 507.3,426.8 509,417.2C511.4,402.8 511.9,388.2 511.9,373.6C511.8,292.6 511.9,211.6 511.8,130.6L511.8,130.7ZM374.8,93.4L374.8,337.6C374.8,346.5 373.6,355.3 369.6,363.3C363.4,375.9 353.4,383.8 340,387.6C332.6,389.8 324.9,390.9 317.2,391.3C297,392.3 279.4,378.6 275.8,358.6C272.7,342.1 280.6,323.9 298,315.4C304.8,312.1 312.2,310.1 319.7,308.6C327.8,306.9 335.9,305.3 343.9,303.4C349.8,302.1 353.6,298.5 354.8,292.4L355.2,288.3L355.2,172.1L354.6,168.2C353.8,165 351.4,163 348.1,163.2C344.7,163.4 341.4,163.9 338,164.6C321.7,167.8 305.5,171 289.3,174.3L210.4,190.2L209.3,190.5C203.4,192.2 201.3,194.8 201,201L201,203.7C200.9,259.2 201,314.7 200.9,370.2C200.9,379.2 199.9,388 196.3,396.4C190.4,410.1 179.9,418.7 165.7,422.7C158.2,424.9 150.5,426.1 142.7,426.4C122.3,427.2 105.3,413.6 101.8,393.5C98.8,376.2 106.7,357.5 126.4,349.2C134.1,346 142,344.3 150.1,342.6C156.2,341.4 162.4,340.1 168.4,338.9C176.6,337.2 180.8,332 181.2,323.7L181.2,131C181.2,128.3 181.5,125.7 182.1,123.1C183.6,117 187.9,113.5 193.8,112.1C199.2,110.7 204.8,109.7 210.3,108.5C226,105.3 241.5,102.2 257.2,99.1L305.6,89.3C319.9,86.5 334.2,83.6 348.5,80.7C353.2,79.8 357.9,78.8 362.7,78.4C369.3,77.8 373.9,82 374.5,88.7C374.7,90.3 374.8,91.9 374.8,93.4Z"
style="fill:currentColor;fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before After
Before After

View file

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 363 B

Before After
Before After

View file

@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="white" d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<path fill="white"
d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"/>
</svg>

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 849 B

Before After
Before After

View file

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 383 B

Before After
Before After

View file

@ -1,3 +1,4 @@
<svg fill="white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" class="glyph">
<path d="M8.89547677 13.5330073c0-1.1057457-.97371638-2.0959657-2.09596577-2.0959657-1.13875305 0-2.0794621.99022-2.0794621 2.0959657 0 1.1222494.94070905 2.0794621 2.0794621 2.0794621 1.12224939 0 2.09596577-.9572127 2.09596577-2.0794621zm6.68398533 0c0-1.1057457-.9572127-2.0959657-2.0794621-2.0959657-1.1222494 0-2.0794621.99022-2.0794621 2.0959657 0 1.1222494.9572127 2.0794621 2.0794621 2.0794621 1.1222494 0 2.0794621-.9572127 2.0794621-2.0794621zm6.700489 0c0-1.1057457-.940709-2.0959657-2.0794621-2.0959657-1.1222494 0-2.0959658.99022-2.0959658 2.0959657 0 1.1222494.9737164 2.0794621 2.0959658 2.0794621 1.1387531 0 2.0794621-.9572127 2.0794621-2.0794621z"></path>
</svg>
<path
d="M8.89547677 13.5330073c0-1.1057457-.97371638-2.0959657-2.09596577-2.0959657-1.13875305 0-2.0794621.99022-2.0794621 2.0959657 0 1.1222494.94070905 2.0794621 2.0794621 2.0794621 1.12224939 0 2.09596577-.9572127 2.09596577-2.0794621zm6.68398533 0c0-1.1057457-.9572127-2.0959657-2.0794621-2.0959657-1.1222494 0-2.0794621.99022-2.0794621 2.0959657 0 1.1222494.9572127 2.0794621 2.0794621 2.0794621 1.1222494 0 2.0794621-.9572127 2.0794621-2.0794621zm6.700489 0c0-1.1057457-.940709-2.0959657-2.0794621-2.0959657-1.1222494 0-2.0959658.99022-2.0959658 2.0959657 0 1.1222494.9737164 2.0794621 2.0959658 2.0794621 1.1387531 0 2.0794621-.9572127 2.0794621-2.0794621z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 775 B

Before After
Before After

View file

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
fill="white" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px"
y="0px"
fill="white" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path id="XMLID_11_" d="M418.5,139.4H232.4v139.8h186.1V139.4z M464.8,46.7H46.3C20.5,46.7,0,68.1,0,93.1v325.9
c0,25.8,21.4,46.3,46.3,46.3h419.4c25.8,0,46.3-20.5,46.3-46.3V93.1C512,67.2,490.6,46.7,464.8,46.7z M464.8,418.9H46.3V92.2h419.4
v326.8H464.8z"/>

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 592 B

Before After
Before After

View file

@ -1,3 +1,4 @@
<svg fill="white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" class="glyph">
<path d="M11.3545232,18.4180929 L18.4676039,14.242665 C19.0452323,13.9290954 19.0122249,13.1204156 18.4676039,12.806846 L11.3545232,8.63141809 C10.7603912,8.26833741 9.98471883,8.54889976 9.98471883,9.19254279 L9.98471883,17.8404645 C9.98471883,18.5006112 10.7108802,18.7976773 11.3545232,18.4180929 Z"></path>
</svg>
<path
d="M11.3545232,18.4180929 L18.4676039,14.242665 C19.0452323,13.9290954 19.0122249,13.1204156 18.4676039,12.806846 L11.3545232,8.63141809 C10.7603912,8.26833741 9.98471883,8.54889976 9.98471883,9.19254279 L9.98471883,17.8404645 C9.98471883,18.5006112 10.7108802,18.7976773 11.3545232,18.4180929 Z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 412 B

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more