[web] Change to Pinia store

This commit is contained in:
Alain Nussbaumer 2024-08-22 21:31:59 +02:00
parent ed16cc7928
commit 8b586728b6
69 changed files with 1171 additions and 1417 deletions

1092
web-src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
"bulma-switch": "^2.0.4", "bulma-switch": "^2.0.4",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"mdi-vue": "^3.0.13", "mdi-vue": "^3.0.13",
"pinia": "^2.1.7",
"reconnectingwebsocket": "^1.0.0", "reconnectingwebsocket": "^1.0.0",
"spotify-web-api-js": "^1.5.2", "spotify-web-api-js": "^1.5.2",
"vue": "^3.4.23", "vue": "^3.4.23",
@ -27,8 +28,7 @@
"vue-router": "^4.3.2", "vue-router": "^4.3.2",
"vue3-click-away": "^1.2.4", "vue3-click-away": "^1.2.4",
"vue3-lazyload": "^0.3.8", "vue3-lazyload": "^0.3.8",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0"
"vuex": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0", "@intlify/unplugin-vue-i18n": "^4.0.0",

View File

@ -25,13 +25,23 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ModalDialogRemotePairing from '@/components/ModalDialogRemotePairing.vue' import ModalDialogRemotePairing from '@/components/ModalDialogRemotePairing.vue'
import ModalDialogUpdate from '@/components/ModalDialogUpdate.vue' import ModalDialogUpdate from '@/components/ModalDialogUpdate.vue'
import NavbarBottom from '@/components/NavbarBottom.vue' import NavbarBottom from '@/components/NavbarBottom.vue'
import NavbarTop from '@/components/NavbarTop.vue' import NavbarTop from '@/components/NavbarTop.vue'
import NotificationList from '@/components/NotificationList.vue' import NotificationList from '@/components/NotificationList.vue'
import ReconnectingWebSocket from 'reconnectingwebsocket' import ReconnectingWebSocket from 'reconnectingwebsocket'
import { useConfigurationStore } from '@/stores/configuration'
import { useLibraryStore } from '@/stores/library'
import { useLyricsStore } from '@/stores/lyrics'
import { useNotificationsStore } from '@/stores/notifications'
import { useOutputsStore } from './stores/outputs'
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
import { useRemotesStore } from './stores/remotes'
import { useServicesStore } from '@/stores/services'
import { useSettingsStore } from '@/stores/settings'
import { useUIStore } from './stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -44,6 +54,22 @@ export default {
NotificationList NotificationList
}, },
setup() {
return {
configurationStore: useConfigurationStore(),
libraryStore: useLibraryStore(),
lyricsStore: useLyricsStore(),
notificationsStore: useNotificationsStore(),
outputsStore: useOutputsStore(),
playerStore: usePlayerStore(),
queueStore: useQueueStore(),
remotesStore: useRemotesStore(),
servicesStore: useServicesStore(),
settingsStore: useSettingsStore(),
uiStore: useUIStore()
}
},
data() { data() {
return { return {
pairing_active: false, pairing_active: false,
@ -55,26 +81,26 @@ export default {
computed: { computed: {
show_burger_menu: { show_burger_menu: {
get() { get() {
return this.$store.state.show_burger_menu return this.uiStore.show_burger_menu
}, },
set(value) { set(value) {
this.$store.commit(types.SHOW_BURGER_MENU, value) this.uiStore.show_burger_menu = value
} }
}, },
show_player_menu: { show_player_menu: {
get() { get() {
return this.$store.state.show_player_menu return this.uiStore.show_player_menu
}, },
set(value) { set(value) {
this.$store.commit(types.SHOW_PLAYER_MENU, value) this.uiStore.show_player_menu = value
} }
}, },
show_update_dialog: { show_update_dialog: {
get() { get() {
return this.$store.state.show_update_dialog return this.uiStore.show_update_dialog
}, },
set(value) { set(value) {
this.$store.commit(types.SHOW_UPDATE_DIALOG, value) this.uiStore.show_update_dialog = value
} }
} }
}, },
@ -115,15 +141,14 @@ export default {
webapi webapi
.config() .config()
.then(({ data }) => { .then(({ data }) => {
this.$store.commit(types.UPDATE_CONFIG, data) this.configurationStore.$state = data
this.$store.commit(types.HIDE_SINGLES, data.hide_singles) this.uiStore.hide_singles = data.hide_singles
document.title = data.library_name document.title = data.library_name
this.open_ws() this.open_ws()
this.$Progress.finish() this.$Progress.finish()
}) })
.catch(() => { .catch(() => {
this.$store.dispatch('add_notification', { this.notificationsStore.add({
text: this.$t('server.connection-failed'), text: this.$t('server.connection-failed'),
topic: 'connection', topic: 'connection',
type: 'danger' type: 'danger'
@ -131,8 +156,8 @@ export default {
}) })
}, },
open_ws() { open_ws() {
if (this.$store.state.config.websocket_port <= 0) { if (this.configurationStore.websocket_port <= 0) {
this.$store.dispatch('add_notification', { this.notificationsStore.add({
text: this.$t('server.missing-port'), text: this.$t('server.missing-port'),
type: 'danger' type: 'danger'
}) })
@ -144,7 +169,7 @@ export default {
protocol = 'wss://' protocol = 'wss://'
} }
let wsUrl = `${protocol}${window.location.hostname}:${this.$store.state.config.websocket_port}` let wsUrl = `${protocol}${window.location.hostname}:${this.configurationStore.websocket_port}`
if (import.meta.env.DEV && import.meta.env.VITE_OWNTONE_URL) { if (import.meta.env.DEV && import.meta.env.VITE_OWNTONE_URL) {
/* /*
@ -152,7 +177,7 @@ export default {
* url from the host of the environment variable VITE_OWNTONE_URL * url from the host of the environment variable VITE_OWNTONE_URL
*/ */
const url = new URL(import.meta.env.VITE_OWNTONE_URL) const url = new URL(import.meta.env.VITE_OWNTONE_URL)
wsUrl = `${protocol}${url.hostname}:${this.$store.state.config.websocket_port}` wsUrl = `${protocol}${url.hostname}:${this.configurationStore.websocket_port}`
} }
const socket = new ReconnectingWebSocket(wsUrl, 'notify', { const socket = new ReconnectingWebSocket(wsUrl, 'notify', {
@ -267,59 +292,58 @@ export default {
}, },
update_lastfm() { update_lastfm() {
webapi.lastfm().then(({ data }) => { webapi.lastfm().then(({ data }) => {
this.$store.commit(types.UPDATE_LASTFM, data) this.servicesStore.lastfm = data
}) })
}, },
update_library_stats() { update_library_stats() {
webapi.library_stats().then(({ data }) => { webapi.library_stats().then(({ data }) => {
this.$store.commit(types.UPDATE_LIBRARY_STATS, data) this.libraryStore.$state = data
}) })
webapi.library_count('scan_kind is rss').then(({ data }) => { webapi.library_count('scan_kind is rss').then(({ data }) => {
this.$store.commit(types.UPDATE_LIBRARY_RSS_COUNT, data) this.libraryStore.rss = data
}) })
}, },
update_lyrics() { update_lyrics() {
const track = this.$store.getters.now_playing const track = this.queueStore.current
if (track && track.track_id) { if (track?.track_id) {
webapi.library_track(track.track_id).then(({ data }) => { webapi.library_track(track.track_id).then(({ data }) => {
this.$store.commit(types.UPDATE_LYRICS, data.lyrics) this.lyricsStore.lyrics = data.lyrics
}) })
} else { } else {
this.$store.commit(types.UPDATE_LYRICS) this.lyricsStore.$reset()
} }
}, },
update_outputs() { update_outputs() {
webapi.outputs().then(({ data }) => { webapi.outputs().then(({ data }) => {
this.$store.commit(types.UPDATE_OUTPUTS, data.outputs) this.outputsStore.outputs = data.outputs
}) })
}, },
update_pairing() { update_pairing() {
webapi.pairing().then(({ data }) => { webapi.pairing().then(({ data }) => {
this.$store.commit(types.UPDATE_PAIRING, data) this.remotesStore.$state = data
this.pairing_active = data.active this.pairing_active = data.active
}) })
}, },
update_player_status() { update_player_status() {
webapi.player_status().then(({ data }) => { webapi.player_status().then(({ data }) => {
this.$store.commit(types.UPDATE_PLAYER_STATUS, data) this.playerStore.$state = data
this.update_lyrics() this.update_lyrics()
}) })
}, },
update_queue() { update_queue() {
webapi.queue().then(({ data }) => { webapi.queue().then(({ data }) => {
this.$store.commit(types.UPDATE_QUEUE, data) this.queueStore.$state = data
this.update_lyrics() this.update_lyrics()
}) })
}, },
update_settings() { update_settings() {
webapi.settings().then(({ data }) => { webapi.settings().then(({ data }) => {
this.$store.commit(types.UPDATE_SETTINGS, data) this.settingsStore.$state = data
}) })
}, },
update_spotify() { update_spotify() {
webapi.spotify().then(({ data }) => { webapi.spotify().then(({ data }) => {
this.$store.commit(types.UPDATE_SPOTIFY, data) this.servicesStore.spotify = data
if (this.token_timer_id > 0) { if (this.token_timer_id > 0) {
window.clearTimeout(this.token_timer_id) window.clearTimeout(this.token_timer_id)
this.token_timer_id = 0 this.token_timer_id = 0

View File

@ -8,7 +8,10 @@
/> />
</div> </div>
<div v-else class="media is-align-items-center" @click="open(item.item)"> <div v-else class="media is-align-items-center" @click="open(item.item)">
<div v-if="show_artwork" class="media-left"> <div
v-if="settingsStore.show_cover_artwork_in_album_lists"
class="media-left"
>
<cover-artwork <cover-artwork
:url="item.item.artwork_url" :url="item.item.artwork_url"
:artist="item.item.artist" :artist="item.item.artist"
@ -69,6 +72,7 @@
import CoverArtwork from '@/components/CoverArtwork.vue' import CoverArtwork from '@/components/CoverArtwork.vue'
import ModalDialog from '@/components/ModalDialog.vue' import ModalDialog from '@/components/ModalDialog.vue'
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue' import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
import { useSettingsStore } from '@/stores/settings'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -80,6 +84,10 @@ export default {
}, },
emits: ['play-count-changed', 'podcast-deleted'], emits: ['play-count-changed', 'podcast-deleted'],
setup() {
return { settingsStore: useSettingsStore() }
},
data() { data() {
return { return {
rss_playlist_to_remove: {}, rss_playlist_to_remove: {},
@ -92,12 +100,6 @@ export default {
computed: { computed: {
media_kind_resolved() { media_kind_resolved() {
return this.media_kind || this.selected_item.media_kind return this.media_kind || this.selected_item.media_kind
},
show_artwork() {
return this.$store.getters.setting(
'webinterface',
'show_cover_artwork_in_album_lists'
).value
} }
}, },

View File

@ -1,7 +1,10 @@
<template> <template>
<template v-for="item in items" :key="item.id"> <template v-for="item in items" :key="item.id">
<div class="media is-align-items-center" @click="open(item)"> <div class="media is-align-items-center" @click="open(item)">
<div v-if="show_artwork" class="media-left is-clickable"> <div
v-if="settingsStore.show_cover_artwork_in_album_lists"
class="media-left is-clickable"
>
<cover-artwork <cover-artwork
:url="artwork_url(item)" :url="artwork_url(item)"
:artist="item.artist" :artist="item.artist"
@ -41,23 +44,19 @@
<script> <script>
import CoverArtwork from '@/components/CoverArtwork.vue' import CoverArtwork from '@/components/CoverArtwork.vue'
import ModalDialogAlbumSpotify from '@/components/ModalDialogAlbumSpotify.vue' import ModalDialogAlbumSpotify from '@/components/ModalDialogAlbumSpotify.vue'
import { useSettingsStore } from '@/stores/settings'
export default { export default {
name: 'ListAlbumsSpotify', name: 'ListAlbumsSpotify',
components: { CoverArtwork, ModalDialogAlbumSpotify }, components: { CoverArtwork, ModalDialogAlbumSpotify },
props: { items: { required: true, type: Object } }, props: { items: { required: true, type: Object } },
data() { setup() {
return { selected_item: {}, show_details_modal: false } return { settingsStore: useSettingsStore() }
}, },
computed: { data() {
show_artwork() { return { selected_item: {}, show_details_modal: false }
return this.$store.getters.setting(
'webinterface',
'show_cover_artwork_in_album_lists'
).value
}
}, },
methods: { methods: {

View File

@ -14,7 +14,7 @@
<h1 <h1
class="title is-6" class="title is-6"
:class="{ :class="{
'has-text-primary': item.id === state.item_id, 'has-text-primary': item.id === player.item_id,
'has-text-grey-light': !is_next 'has-text-grey-light': !is_next
}" }"
v-text="item.title" v-text="item.title"
@ -22,18 +22,18 @@
<h2 <h2
class="subtitle is-7 has-text-weight-bold" class="subtitle is-7 has-text-weight-bold"
:class="{ :class="{
'has-text-primary': item.id === state.item_id, 'has-text-primary': item.id === player.item_id,
'has-text-grey-light': !is_next, 'has-text-grey-light': !is_next,
'has-text-grey': is_next && item.id !== state.item_id 'has-text-grey': is_next && item.id !== player.item_id
}" }"
v-text="item.artist" v-text="item.artist"
/> />
<h2 <h2
class="subtitle is-7" class="subtitle is-7"
:class="{ :class="{
'has-text-primary': item.id === state.item_id, 'has-text-primary': item.id === player.item_id,
'has-text-grey-light': !is_next, 'has-text-grey-light': !is_next,
'has-text-grey': is_next && item.id !== state.item_id 'has-text-grey': is_next && item.id !== player.item_id
}" }"
v-text="item.album" v-text="item.album"
/> />
@ -45,6 +45,7 @@
</template> </template>
<script> <script>
import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -57,12 +58,18 @@ export default {
show_only_next_items: Boolean show_only_next_items: Boolean
}, },
setup() {
return {
playerStore: usePlayerStore()
}
},
computed: { computed: {
is_next() { is_next() {
return this.current_position < 0 || this.position >= this.current_position return this.current_position < 0 || this.position >= this.current_position
}, },
state() { player() {
return this.$store.state.player return this.playerStore
} }
}, },

View File

@ -26,7 +26,7 @@
<h2 v-if="!item.is_playable" class="subtitle is-7"> <h2 v-if="!item.is_playable" class="subtitle is-7">
(<span v-text="$t('list.spotify.not-playable-track')" /> (<span v-text="$t('list.spotify.not-playable-track')" />
<span <span
v-if="item.restrictions && item.restrictions.reason" v-if="item.restrictions?.reason"
v-text=" v-text="
$t('list.spotify.restriction-reason', { $t('list.spotify.restriction-reason', {
reason: item.restrictions.reason reason: item.restrictions.reason

View File

@ -31,8 +31,16 @@
</template> </template>
<script> <script>
import { useLyricsStore } from '@/stores/lyrics'
import { usePlayerStore } from '@/stores/player'
export default { export default {
name: 'LyricsPane', name: 'LyricsPane',
setup() {
return { lyricsStore: useLyricsStore(), playerStore: usePlayerStore() }
},
data() { data() {
/* /*
* Non reactive. Used as a cache to speed up the finding of lyrics * Non reactive. Used as a cache to speed up the finding of lyrics
@ -46,14 +54,15 @@ export default {
autoScrolling: true autoScrolling: true
} }
}, },
computed: { computed: {
is_playing() { is_playing() {
return this.player.state === 'play' return this.playerStore.state === 'play'
}, },
lyrics() { lyrics() {
const raw = this.$store.state.lyrics.content const raw = this.lyricsStore.content
const parsed = [] const parsed = []
if (raw) { if (raw.length > 0) {
// Parse the lyrics // Parse the lyrics
const regex = const regex =
/\[(?<minutes>\d+):(?<seconds>\d+)(?:\.(?<hundredths>\d+))?\] ?(?<text>.*)/u /\[(?<minutes>\d+):(?<seconds>\d+)(?:\.(?<hundredths>\d+))?\] ?(?<text>.*)/u
@ -82,14 +91,11 @@ export default {
} }
return parsed return parsed
}, },
player() {
return this.$store.state.player
},
verse_index() { verse_index() {
if (this.lyrics.length && this.lyrics[0].time) { if (this.lyrics.length && this.lyrics[0].time) {
const currentTime = this.player.item_progress_ms / 1000, const currentTime = this.playerStore.item_progress_ms / 1000,
la = this.lyrics, la = this.lyrics,
trackChanged = this.player.item_id !== this.lastItemId, trackChanged = this.playerStore.item_id !== this.lastItemId,
trackSeeked = trackSeeked =
this.lastIndex >= 0 && this.lastIndex >= 0 &&
this.lastIndex < la.length && this.lastIndex < la.length &&
@ -149,10 +155,10 @@ export default {
methods: { methods: {
reset_scrolling() { reset_scrolling() {
// Scroll to the start of the lyrics in all cases // Scroll to the start of the lyrics in all cases
if (this.player.item_id !== this.lastItemId && this.$refs.lyrics) { if (this.playerStore.item_id !== this.lastItemId && this.$refs.lyrics) {
this.$refs.lyrics.scrollTo(0, 0) this.$refs.lyrics.scrollTo(0, 0)
} }
this.lastItemId = this.player.item_id this.lastItemId = this.playerStore.item_id
this.lastIndex = -1 this.lastIndex = -1
}, },
scroll_to_verse() { scroll_to_verse() {

View File

@ -136,6 +136,7 @@
<script> <script>
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -143,6 +144,10 @@ export default {
props: { item: { required: true, type: Object }, show: Boolean }, props: { item: { required: true, type: Object }, show: Boolean },
emits: ['close'], emits: ['close'],
setup() {
return { servicesStore: useServicesStore() }
},
data() { data() {
return { return {
spotify_track: {} spotify_track: {}
@ -151,9 +156,9 @@ export default {
watch: { watch: {
item() { item() {
if (this.item && this.item.data_kind === 'spotify') { if (this.item?.data_kind === 'spotify') {
const spotifyApi = new SpotifyWebApi() const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token) spotifyApi.setAccessToken(this.servicesStore.spotify.webapi_token)
spotifyApi spotifyApi
.getTrack(this.item.path.slice(this.item.path.lastIndexOf(':') + 1)) .getTrack(this.item.path.slice(this.item.path.lastIndexOf(':') + 1))
.then((response) => { .then((response) => {

View File

@ -53,6 +53,7 @@
</template> </template>
<script> <script>
import { useRemotesStore } from '@/stores/remotes'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -60,6 +61,10 @@ export default {
props: { show: Boolean }, props: { show: Boolean },
emits: ['close'], emits: ['close'],
setup() {
return { remoteStore: useRemotesStore() }
},
data() { data() {
return { return {
pairing_req: { pin: '' } pairing_req: { pin: '' }
@ -68,7 +73,7 @@ export default {
computed: { computed: {
pairing() { pairing() {
return this.$store.state.pairing return this.remoteStore.pairing
} }
}, },

View File

@ -172,6 +172,7 @@
<script> <script>
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -179,6 +180,10 @@ export default {
props: { item: { required: true, type: Object }, show: Boolean }, props: { item: { required: true, type: Object }, show: Boolean },
emits: ['close', 'play-count-changed'], emits: ['close', 'play-count-changed'],
setup() {
return { servicesStore: useServicesStore() }
},
data() { data() {
return { return {
spotify_track: {} spotify_track: {}
@ -193,7 +198,7 @@ export default {
this.item.media_kind !== 'podcast' this.item.media_kind !== 'podcast'
) { ) {
const spotifyApi = new SpotifyWebApi() const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token) spotifyApi.setAccessToken(this.servicesStore.spotify.webapi_token)
spotifyApi spotifyApi
.getTrack(this.item.path.slice(this.item.path.lastIndexOf(':') + 1)) .getTrack(this.item.path.slice(this.item.path.lastIndexOf(':') + 1))
.then((response) => { .then((response) => {

View File

@ -48,8 +48,9 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ModalDialog from '@/components/ModalDialog.vue' import ModalDialog from '@/components/ModalDialog.vue'
import { useLibraryStore } from '@/stores/library'
import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -58,6 +59,13 @@ export default {
props: { show: Boolean }, props: { show: Boolean },
emits: ['close'], emits: ['close'],
setup() {
return {
libraryStore: useLibraryStore(),
servicesStore: useServicesStore()
}
},
data() { data() {
return { return {
rescan_metadata: false rescan_metadata: false
@ -66,23 +74,23 @@ export default {
computed: { computed: {
library() { library() {
return this.$store.state.library return this.libraryStore.$state
}, },
rss() { rss() {
return this.$store.state.rss_count return this.libraryStore.rss
}, },
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
update_dialog_scan_kind: { update_dialog_scan_kind: {
get() { get() {
return this.$store.state.update_dialog_scan_kind return this.library.update_dialog_scan_kind
}, },
set(value) { set(value) {
this.$store.commit(types.UPDATE_DIALOG_SCAN_KIND, value) this.library.update_dialog_scan_kind = value
} }
} }
}, },

View File

@ -113,12 +113,12 @@
class="is-expanded is-clipped is-size-7" class="is-expanded is-clipped is-size-7"
> >
<div class="fd-is-text-clipped"> <div class="fd-is-text-clipped">
<strong v-text="now_playing.title" /> <strong v-text="current.title" />
<br /> <br />
<span v-text="now_playing.artist" /> <span v-text="current.artist" />
<span <span
v-if="now_playing.album" v-if="current.album"
v-text="$t('navigation.now-playing', { album: now_playing.album })" v-text="$t('navigation.now-playing', { album: current.album })"
/> />
</div> </div>
</navbar-item-link> </navbar-item-link>
@ -255,7 +255,6 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ControlSlider from '@/components/ControlSlider.vue' import ControlSlider from '@/components/ControlSlider.vue'
import NavbarItemLink from '@/components/NavbarItemLink.vue' import NavbarItemLink from '@/components/NavbarItemLink.vue'
import NavbarItemOutput from '@/components/NavbarItemOutput.vue' import NavbarItemOutput from '@/components/NavbarItemOutput.vue'
@ -270,6 +269,11 @@ import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue'
import PlayerButtonShuffle from '@/components/PlayerButtonShuffle.vue' import PlayerButtonShuffle from '@/components/PlayerButtonShuffle.vue'
import audio from '@/lib/Audio' import audio from '@/lib/Audio'
import { mdiCancel } from '@mdi/js' import { mdiCancel } from '@mdi/js'
import { useNotificationsStore } from '@/stores/notifications'
import { useOutputsStore } from '@/stores/outputs'
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -289,6 +293,16 @@ export default {
PlayerButtonShuffle PlayerButtonShuffle
}, },
setup() {
return {
notificationsStore: useNotificationsStore(),
outputsStore: useOutputsStore(),
playerStore: usePlayerStore(),
queueStore: useQueueStore(),
uiStore: useUIStore()
}
},
data() { data() {
return { return {
cursor: mdiCancel, cursor: mdiCancel,
@ -302,33 +316,30 @@ export default {
}, },
computed: { computed: {
config() {
return this.$store.state.config
},
is_now_playing_page() { is_now_playing_page() {
return this.$route.name === 'now-playing' return this.$route.name === 'now-playing'
}, },
now_playing() { current() {
return this.$store.getters.now_playing return this.queueStore.current
}, },
outputs() { outputs() {
return this.$store.state.outputs return this.outputsStore.outputs
}, },
player() { player() {
return this.$store.state.player return this.playerStore
}, },
show_player_menu: { show_player_menu: {
get() { get() {
return this.$store.state.show_player_menu return this.uiStore.show_player_menu
}, },
set(value) { set(value) {
this.$store.commit(types.SHOW_PLAYER_MENU, value) this.uiStore.show_player_menu = value
} }
} }
}, },
watch: { watch: {
'$store.state.player.volume'() { 'playerStore.volume'() {
if (this.player.volume > 0) { if (this.player.volume > 0) {
this.old_volume = this.player.volume this.old_volume = this.player.volume
} }
@ -383,7 +394,7 @@ export default {
}) })
a.addEventListener('error', () => { a.addEventListener('error', () => {
this.closeAudio() this.closeAudio()
this.$store.dispatch('add_notification', { this.notificationsStore.add({
text: this.$t('navigation.stream-error'), text: this.$t('navigation.stream-error'),
type: 'danger' type: 'danger'
}) })

View File

@ -5,7 +5,7 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types' import { useUIStore } from '@/stores/ui'
export default { export default {
name: 'NavbarItemLink', name: 'NavbarItemLink',
@ -13,6 +13,10 @@ export default {
to: { required: true, type: Object } to: { required: true, type: Object }
}, },
setup() {
return { uiStore: useUIStore() }
},
computed: { computed: {
href() { href() {
return this.$router.resolve(this.to).href return this.$router.resolve(this.to).href
@ -21,11 +25,11 @@ export default {
methods: { methods: {
open() { open() {
if (this.$store.state.show_burger_menu) { if (this.uiStore.show_burger_menu) {
this.$store.commit(types.SHOW_BURGER_MENU, false) this.uiStore.show_burger_menu = false
} }
if (this.$store.state.show_player_menu) { if (this.uiStore.show_player_menu) {
this.$store.commit(types.SHOW_PLAYER_MENU, false) this.uiStore.show_player_menu = false
} }
this.$router.push(this.to) this.$router.push(this.to)
} }

View File

@ -6,25 +6,46 @@
aria-label="main navigation" aria-label="main navigation"
> >
<div class="navbar-brand"> <div class="navbar-brand">
<navbar-item-link v-if="show_playlists" :to="{ name: 'playlists' }"> <navbar-item-link
v-if="settingsStore.show_menu_item_playlists"
:to="{ name: 'playlists' }"
>
<mdicon class="icon" name="music-box-multiple" size="16" /> <mdicon class="icon" name="music-box-multiple" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="show_music" :to="{ name: 'music' }"> <navbar-item-link
v-if="settingsStore.show_menu_item_music"
:to="{ name: 'music' }"
>
<mdicon class="icon" name="music" size="16" /> <mdicon class="icon" name="music" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="show_podcasts" :to="{ name: 'podcasts' }"> <navbar-item-link
v-if="settingsStore.show_menu_item_podcasts"
:to="{ name: 'podcasts' }"
>
<mdicon class="icon" name="microphone" size="16" /> <mdicon class="icon" name="microphone" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="show_audiobooks" :to="{ name: 'audiobooks' }"> <navbar-item-link
v-if="settingsStore.show_menu_item_audiobooks"
:to="{ name: 'audiobooks' }"
>
<mdicon class="icon" name="book-open-variant" size="16" /> <mdicon class="icon" name="book-open-variant" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="show_radio" :to="{ name: 'radio' }"> <navbar-item-link
v-if="settingsStore.show_menu_item_radio"
:to="{ name: 'radio' }"
>
<mdicon class="icon" name="radio" size="16" /> <mdicon class="icon" name="radio" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="show_files" :to="{ name: 'files' }"> <navbar-item-link
v-if="settingsStore.show_menu_item_files"
:to="{ name: 'files' }"
>
<mdicon class="icon" name="folder-open" size="16" /> <mdicon class="icon" name="folder-open" size="16" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link v-if="show_search" :to="{ name: search_name }"> <navbar-item-link
v-if="settingsStore.show_menu_item_search"
:to="{ name: searchStore.search_source }"
>
<mdicon class="icon" name="magnify" size="16" /> <mdicon class="icon" name="magnify" size="16" />
</navbar-item-link> </navbar-item-link>
<div <div
@ -89,7 +110,7 @@
<mdicon class="icon" name="folder-open" size="16" /> <mdicon class="icon" name="folder-open" size="16" />
<b v-text="$t('navigation.files')" /> <b v-text="$t('navigation.files')" />
</navbar-item-link> </navbar-item-link>
<navbar-item-link :to="{ name: search_name }"> <navbar-item-link :to="{ name: searchStore.search_source }">
<mdicon class="icon" name="magnify" size="16" /> <mdicon class="icon" name="magnify" size="16" />
<b v-text="$t('navigation.search')" /> <b v-text="$t('navigation.search')" />
</navbar-item-link> </navbar-item-link>
@ -118,13 +139,25 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import NavbarItemLink from '@/components/NavbarItemLink.vue' import NavbarItemLink from '@/components/NavbarItemLink.vue'
import { useSearchStore } from '@/stores/search'
import { useServicesStore } from '@/stores/services'
import { useSettingsStore } from '@/stores/settings'
import { useUIStore } from '@/stores/ui'
export default { export default {
name: 'NavbarTop', name: 'NavbarTop',
components: { NavbarItemLink }, components: { NavbarItemLink },
setup() {
return {
searchStore: useSearchStore(),
servicesStore: useServicesStore(),
settingsStore: useSettingsStore(),
uiStore: useUIStore()
}
},
data() { data() {
return { return {
show_settings_menu: false show_settings_menu: false
@ -132,71 +165,27 @@ export default {
}, },
computed: { computed: {
search_name: {
get() {
return this.$store.state.search_source
}
},
show_audiobooks() {
return this.$store.getters.setting(
'webinterface',
'show_menu_item_audiobooks'
).value
},
show_burger_menu: { show_burger_menu: {
get() { get() {
return this.$store.state.show_burger_menu return this.uiStore.show_burger_menu
}, },
set(value) { set(value) {
this.$store.commit(types.SHOW_BURGER_MENU, value) this.uiStore.show_burger_menu = value
} }
}, },
show_files() {
return this.$store.getters.setting('webinterface', 'show_menu_item_files')
.value
},
show_music() {
return this.$store.getters.setting('webinterface', 'show_menu_item_music')
.value
},
show_player_menu() {
return this.$store.state.show_player_menu
},
show_playlists() {
return this.$store.getters.setting(
'webinterface',
'show_menu_item_playlists'
).value
},
show_podcasts() {
return this.$store.getters.setting(
'webinterface',
'show_menu_item_podcasts'
).value
},
show_radio() {
return this.$store.getters.setting('webinterface', 'show_menu_item_radio')
.value
},
show_search() {
return this.$store.getters.setting(
'webinterface',
'show_menu_item_search'
).value
},
show_update_dialog: { show_update_dialog: {
get() { get() {
return this.$store.state.show_update_dialog return this.uiStore.show_update_dialog
}, },
set(value) { set(value) {
this.$store.commit(types.SHOW_UPDATE_DIALOG, value) this.uiStore.show_update_dialog = value
} }
}, },
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
zindex() { zindex() {
if (this.show_player_menu) { if (this.uiStore.show_player_menu) {
return 'z-index: 21' return 'z-index: 21'
} }
return '' return ''

View File

@ -17,18 +17,26 @@
</template> </template>
<script> <script>
import { useNotificationsStore } from '@/stores/notifications'
export default { export default {
name: 'NotificationList', name: 'NotificationList',
setup() {
return {
notificationsStore: useNotificationsStore()
}
},
computed: { computed: {
notifications() { notifications() {
return this.$store.state.notifications.list return this.notificationsStore.list
} }
}, },
methods: { methods: {
remove(notification) { remove(notification) {
this.$store.dispatch('delete_notification', notification) this.notificationsStore.remove(notification)
} }
} }
} }

View File

@ -10,6 +10,7 @@
</template> </template>
<script> <script>
import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -18,9 +19,15 @@ export default {
icon_size: { default: 16, type: Number } icon_size: { default: 16, type: Number }
}, },
setup() {
return {
playerStore: usePlayerStore()
}
},
computed: { computed: {
is_consume() { is_consume() {
return this.$store.state.player.consume return this.playerStore.consume
} }
}, },

View File

@ -10,24 +10,32 @@
</template> </template>
<script> <script>
import { useLyricsStore } from '@/stores/lyrics'
export default { export default {
name: 'PlayerButtonLyrics', name: 'PlayerButtonLyrics',
props: { props: {
icon_size: { default: 16, type: Number } icon_size: { default: 16, type: Number }
}, },
setup() {
return {
lyricsStore: useLyricsStore()
}
},
computed: { computed: {
icon_name() { icon_name() {
return this.is_active ? 'script-text-play' : 'script-text-outline' return this.is_active ? 'script-text-play' : 'script-text-outline'
}, },
is_active() { is_active() {
return this.$store.state.lyrics.pane return this.lyricsStore.pane
} }
}, },
methods: { methods: {
toggle_lyrics() { toggle_lyrics() {
this.$store.state.lyrics.pane = !this.$store.state.lyrics.pane this.lyricsStore.pane = !this.lyricsStore.pane
} }
} }
} }

View File

@ -9,6 +9,7 @@
</template> </template>
<script> <script>
import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -19,7 +20,7 @@ export default {
computed: { computed: {
disabled() { disabled() {
return !this.$store.state.queue || this.$store.state.queue.count <= 0 return useQueueStore()?.count <= 0
} }
}, },

View File

@ -9,6 +9,9 @@
</template> </template>
<script> <script>
import { useNotificationsStore } from '@/stores/notifications'
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -18,9 +21,17 @@ export default {
show_disabled_message: Boolean show_disabled_message: Boolean
}, },
setup() {
return {
notificationsStore: useNotificationsStore(),
playerStore: usePlayerStore(),
queueStore: useQueueStore()
}
},
computed: { computed: {
disabled() { disabled() {
return !this.$store.state.queue || this.$store.state.queue.count <= 0 return this.queueStore?.count <= 0
}, },
icon_name() { icon_name() {
if (!this.is_playing) { if (!this.is_playing) {
@ -31,13 +42,11 @@ export default {
return 'stop' return 'stop'
}, },
is_pause_allowed() { is_pause_allowed() {
return ( const { current } = this.queueStore
this.$store.getters.now_playing && return current && current.data_kind !== 'pipe'
this.$store.getters.now_playing.data_kind !== 'pipe'
)
}, },
is_playing() { is_playing() {
return this.$store.state.player.state === 'play' return this.playerStore.state === 'play'
} }
}, },
@ -45,7 +54,7 @@ export default {
toggle_play_pause() { toggle_play_pause() {
if (this.disabled) { if (this.disabled) {
if (this.show_disabled_message) { if (this.show_disabled_message) {
this.$store.dispatch('add_notification', { this.notificationsStore.add({
text: this.$t('server.empty-queue'), text: this.$t('server.empty-queue'),
timeout: 2000, timeout: 2000,
topic: 'connection', topic: 'connection',
@ -54,7 +63,6 @@ export default {
} }
return return
} }
if (this.is_playing && this.is_pause_allowed) { if (this.is_playing && this.is_pause_allowed) {
webapi.player_pause() webapi.player_pause()
} else if (this.is_playing && !this.is_pause_allowed) { } else if (this.is_playing && !this.is_pause_allowed) {

View File

@ -9,6 +9,7 @@
</template> </template>
<script> <script>
import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -17,9 +18,15 @@ export default {
icon_size: { default: 16, type: Number } icon_size: { default: 16, type: Number }
}, },
setup() {
return {
queueStore: useQueueStore()
}
},
computed: { computed: {
disabled() { disabled() {
return !this.$store.state.queue || this.$store.state.queue.count <= 0 return this.queueStore.count <= 0
} }
}, },
@ -28,7 +35,6 @@ export default {
if (this.disabled) { if (this.disabled) {
return return
} }
webapi.player_previous() webapi.player_previous()
} }
} }

View File

@ -10,6 +10,7 @@
</template> </template>
<script> <script>
import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -18,6 +19,12 @@ export default {
icon_size: { default: 16, type: Number } icon_size: { default: 16, type: Number }
}, },
setup() {
return {
playerStore: usePlayerStore()
}
},
computed: { computed: {
icon_name() { icon_name() {
if (this.is_repeat_all) { if (this.is_repeat_all) {
@ -28,13 +35,13 @@ export default {
return 'repeat-off' return 'repeat-off'
}, },
is_repeat_all() { is_repeat_all() {
return this.$store.state.player.repeat === 'all' return this.playerStore.repeat === 'all'
}, },
is_repeat_off() { is_repeat_off() {
return !this.is_repeat_all && !this.is_repeat_single return !this.is_repeat_all && !this.is_repeat_single
}, },
is_repeat_single() { is_repeat_single() {
return this.$store.state.player.repeat === 'single' return this.playerStore.repeat === 'single'
} }
}, },

View File

@ -9,6 +9,8 @@
</template> </template>
<script> <script>
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -18,23 +20,32 @@ export default {
seek_ms: { required: true, type: Number } seek_ms: { required: true, type: Number }
}, },
setup() {
return {
playerStore: usePlayerStore(),
queueStore: useQueueStore()
}
},
computed: { computed: {
disabled() { disabled() {
return ( return (
!this.$store.state.queue || this.queueStore?.count <= 0 ||
this.$store.state.queue.count <= 0 ||
this.is_stopped || this.is_stopped ||
this.now_playing.data_kind === 'pipe' this.current.data_kind === 'pipe'
) )
}, },
is_stopped() { is_stopped() {
return this.$store.state.player.state === 'stop' return this.player.state === 'stop'
}, },
now_playing() { current() {
return this.$store.getters.now_playing return this.queueStore.current
},
player() {
return this.playerStore
}, },
visible() { visible() {
return ['podcast', 'audiobook'].includes(this.now_playing.media_kind) return ['podcast', 'audiobook'].includes(this.current.media_kind)
} }
}, },

View File

@ -9,6 +9,8 @@
</template> </template>
<script> <script>
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -18,23 +20,32 @@ export default {
seek_ms: { required: true, type: Number } seek_ms: { required: true, type: Number }
}, },
setup() {
return {
playerStore: usePlayerStore(),
queueStore: useQueueStore()
}
},
computed: { computed: {
disabled() { disabled() {
return ( return (
!this.$store.state.queue || this.queueStore?.count <= 0 ||
this.$store.state.queue.count <= 0 ||
this.is_stopped || this.is_stopped ||
this.now_playing.data_kind === 'pipe' this.current.data_kind === 'pipe'
) )
}, },
is_stopped() { is_stopped() {
return this.$store.state.player.state === 'stop' return this.player.state === 'stop'
}, },
now_playing() { current() {
return this.$store.getters.now_playing return this.queueStore.current
},
player() {
return this.playerStore
}, },
visible() { visible() {
return ['podcast', 'audiobook'].includes(this.now_playing.media_kind) return ['podcast', 'audiobook'].includes(this.current.media_kind)
} }
}, },

View File

@ -10,6 +10,7 @@
</template> </template>
<script> <script>
import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -19,6 +20,12 @@ export default {
icon_size: { default: 16, type: Number } icon_size: { default: 16, type: Number }
}, },
setup() {
return {
playerStore: usePlayerStore()
}
},
computed: { computed: {
icon_name() { icon_name() {
if (this.is_shuffle) { if (this.is_shuffle) {
@ -27,7 +34,7 @@ export default {
return 'shuffle-disabled' return 'shuffle-disabled'
}, },
is_shuffle() { is_shuffle() {
return this.$store.state.player.shuffle return this.playerStore.shuffle
} }
}, },

View File

@ -22,6 +22,7 @@
</template> </template>
<script> <script>
import { useSettingsStore } from '@/stores/settings'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -31,6 +32,12 @@ export default {
name: { required: true, type: String } name: { required: true, type: String }
}, },
setup() {
return {
settingsStore: useSettingsStore()
}
},
data() { data() {
return { return {
statusUpdate: '', statusUpdate: '',
@ -55,7 +62,7 @@ export default {
return this.statusUpdate === 'success' return this.statusUpdate === 'success'
}, },
setting() { setting() {
const setting = this.$store.getters.setting(this.category, this.name) const setting = this.settingsStore.setting(this.category, this.name)
if (!setting) { if (!setting) {
return { return {
category: this.category, category: this.category,
@ -84,7 +91,7 @@ export default {
webapi webapi
.settings_update(this.category, setting) .settings_update(this.category, setting)
.then(() => { .then(() => {
this.$store.dispatch('update_setting', setting) this.settingsStore.update(setting)
this.statusUpdate = 'success' this.statusUpdate = 'success'
}) })
.catch(() => { .catch(() => {

View File

@ -28,6 +28,7 @@
</template> </template>
<script> <script>
import { useSettingsStore } from '@/stores/settings'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -39,6 +40,12 @@ export default {
placeholder: { default: '', type: String } placeholder: { default: '', type: String }
}, },
setup() {
return {
settingsStore: useSettingsStore()
}
},
data() { data() {
return { return {
statusUpdate: '', statusUpdate: '',
@ -63,7 +70,7 @@ export default {
return this.statusUpdate === 'success' return this.statusUpdate === 'success'
}, },
setting() { setting() {
return this.$store.getters.setting(this.category, this.name) return this.settingsStore.setting(this.category, this.name)
} }
}, },
@ -95,7 +102,7 @@ export default {
webapi webapi
.settings_update(this.category, setting) .settings_update(this.category, setting)
.then(() => { .then(() => {
this.$store.dispatch('update_setting', setting) this.settingsStore.update(setting)
this.statusUpdate = 'success' this.statusUpdate = 'success'
}) })
.catch(() => { .catch(() => {

View File

@ -27,6 +27,7 @@
</template> </template>
<script> <script>
import { useSettingsStore } from '@/stores/settings'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -38,6 +39,12 @@ export default {
placeholder: { default: '', type: String } placeholder: { default: '', type: String }
}, },
setup() {
return {
settingsStore: useSettingsStore()
}
},
data() { data() {
return { return {
statusUpdate: '', statusUpdate: '',
@ -62,7 +69,7 @@ export default {
return this.statusUpdate === 'success' return this.statusUpdate === 'success'
}, },
setting() { setting() {
return this.$store.getters.setting(this.category, this.name) return this.settingsStore.setting(this.category, this.name)
} }
}, },
@ -96,7 +103,7 @@ export default {
webapi webapi
.settings_update(this.category, setting) .settings_update(this.category, setting)
.then(() => { .then(() => {
this.$store.dispatch('update_setting', setting) this.settingsStore.update(setting)
this.statusUpdate = 'success' this.statusUpdate = 'success'
}) })
.catch(() => { .catch(() => {

View File

@ -113,12 +113,18 @@
</template> </template>
<script> <script>
import { useServicesStore } from '@/stores/services'
export default { export default {
name: 'TabsMusic', name: 'TabsMusic',
setup() {
return { servicesStore: useServicesStore() }
},
computed: { computed: {
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }
} }
} }

View File

@ -34,13 +34,21 @@
</template> </template>
<script> <script>
import { useServicesStore } from '@/stores/services'
export default { export default {
name: 'TabsSearch', name: 'TabsSearch',
emits: ['search-library', 'search-spotify'], emits: ['search-library', 'search-spotify'],
setup() {
return {
servicesStore: useServicesStore()
}
},
computed: { computed: {
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }
} }
} }

View File

@ -4,15 +4,15 @@ import VueClickAway from 'vue3-click-away'
import VueLazyLoad from 'vue3-lazyload' import VueLazyLoad from 'vue3-lazyload'
import VueProgressBar from '@aacassandra/vue3-progressbar' import VueProgressBar from '@aacassandra/vue3-progressbar'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { filters } from './filter' import { filters } from './filter'
import i18n from './i18n' import i18n from './i18n'
import { icons } from './icons' import { icons } from './icons'
import mdiVue from 'mdi-vue/v3' import mdiVue from 'mdi-vue/v3'
import { router } from './router' import { router } from './router'
import store from './store'
const app = createApp(App) const app = createApp(App)
.use(store) .use(createPinia())
.use(router) .use(router)
.use(VueProgressBar) .use(VueProgressBar)
.use(VueClickAway) .use(VueClickAway)

View File

@ -4,7 +4,7 @@
<div class="container"> <div class="container">
<div class="columns is-centered"> <div class="columns is-centered">
<div class="column is-four-fifths has-text-centered-mobile"> <div class="column is-four-fifths has-text-centered-mobile">
<h1 class="title is-4" v-text="config.library_name" /> <h1 class="title is-4" v-text="configuration.library_name" />
</div> </div>
</div> </div>
</div> </div>
@ -130,13 +130,15 @@
<div class="content has-text-centered-mobile"> <div class="content has-text-centered-mobile">
<p <p
class="is-size-7" class="is-size-7"
v-text="$t('page.about.version', { version: config.version })" v-text="
$t('page.about.version', { version: configuration.version })
"
/> />
<p <p
class="is-size-7" class="is-size-7"
v-text=" v-text="
$t('page.about.compiled-with', { $t('page.about.compiled-with', {
options: config.buildoptions.join(', ') options: configuration.buildoptions.join(', ')
}) })
" "
/> />
@ -176,23 +178,33 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types' import { useConfigurationStore } from '@/stores/configuration'
import { useLibraryStore } from '@/stores/library'
import { useUIStore } from '@/stores/ui'
export default { export default {
name: 'PageAbout', name: 'PageAbout',
setup() {
return {
configurationStore: useConfigurationStore(),
libraryStore: useLibraryStore(),
uiStore: useUIStore()
}
},
computed: { computed: {
config() { configuration() {
return this.$store.state.config return this.configurationStore.$state
}, },
library() { library() {
return this.$store.state.library return this.libraryStore.$state
} }
}, },
methods: { methods: {
showUpdateDialog() { showUpdateDialog() {
this.$store.commit(types.SHOW_UPDATE_DIALOG, true) this.uiStore.show_update_dialog = true
} }
} }
} }

View File

@ -56,15 +56,15 @@ import CoverArtwork from '@/components/CoverArtwork.vue'
import ListTracksSpotify from '@/components/ListTracksSpotify.vue' import ListTracksSpotify from '@/components/ListTracksSpotify.vue'
import ModalDialogAlbumSpotify from '@/components/ModalDialogAlbumSpotify.vue' import ModalDialogAlbumSpotify from '@/components/ModalDialogAlbumSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import store from '@/store' import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
load(to) { load(to) {
const spotifyApi = new SpotifyWebApi() const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(store.state.spotify.webapi_token) spotifyApi.setAccessToken(useServicesStore().spotify.webapi_token)
return spotifyApi.getAlbum(to.params.id, { return spotifyApi.getAlbum(to.params.id, {
market: store.state.spotify.webapi_country market: useServicesStore().spotify.webapi_country
}) })
}, },
@ -88,6 +88,12 @@ export default {
}) })
}, },
setup() {
return {
servicesStore: useServicesStore()
}
},
data() { data() {
return { return {
album: { artists: [{}], tracks: {} }, album: { artists: [{}], tracks: {} },

View File

@ -63,13 +63,14 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
import { useServicesStore } from '@/stores/services'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -98,6 +99,10 @@ export default {
}) })
}, },
setup() {
return { uiStore: useUIStore(), servicesStore: useServicesStore() }
},
data() { data() {
return { return {
albums_list: new GroupedList(), albums_list: new GroupedList(),
@ -162,30 +167,30 @@ export default {
}, },
hide_singles: { hide_singles: {
get() { get() {
return this.$store.state.hide_singles return this.uiStore.hide_singles
}, },
set(value) { set(value) {
this.$store.commit(types.HIDE_SINGLES, value) this.uiStore.hide_singles = value
} }
}, },
hide_spotify: { hide_spotify: {
get() { get() {
return this.$store.state.hide_spotify return this.uiStore.hide_spotify
}, },
set(value) { set(value) {
this.$store.commit(types.HIDE_SPOTIFY, value) this.uiStore.hide_spotify = value
} }
}, },
selected_grouping_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.albums_sort return this.uiStore.albums_sort
}, },
set(value) { set(value) {
this.$store.commit(types.ALBUMS_SORT, value) this.uiStore.albums_sort = value
} }
}, },
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }
} }
} }

View File

@ -71,12 +71,13 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue' import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
import { useServicesStore } from '@/stores/services'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -108,6 +109,10 @@ export default {
}) })
}, },
setup() {
return { servicesStore: useServicesStore(), uiStore: useUIStore() }
},
data() { data() {
return { return {
albums_list: new GroupedList(), albums_list: new GroupedList(),
@ -140,22 +145,22 @@ export default {
}, },
hide_spotify: { hide_spotify: {
get() { get() {
return this.$store.state.hide_spotify return this.uiStore.hide_spotify
}, },
set(value) { set(value) {
this.$store.commit(types.HIDE_SPOTIFY, value) this.uiStore.hide_spotify = value
} }
}, },
selected_grouping_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.artist_albums_sort return this.uiStore.artist_albums_sort
}, },
set(value) { set(value) {
this.$store.commit(types.ARTIST_ALBUMS_SORT, value) this.uiStore.artist_albums_sort = value
} }
}, },
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
track_count() { track_count() {
// The count of tracks is incorrect when albums have Spotify tracks. // The count of tracks is incorrect when albums have Spotify tracks.

View File

@ -52,7 +52,7 @@ import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue'
import ModalDialogArtistSpotify from '@/components/ModalDialogArtistSpotify.vue' import ModalDialogArtistSpotify from '@/components/ModalDialogArtistSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading' import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
import store from '@/store' import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
const PAGE_SIZE = 50 const PAGE_SIZE = 50
@ -60,13 +60,13 @@ const PAGE_SIZE = 50
const dataObject = { const dataObject = {
load(to) { load(to) {
const spotifyApi = new SpotifyWebApi() const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(store.state.spotify.webapi_token) spotifyApi.setAccessToken(useServicesStore().spotify.webapi_token)
return Promise.all([ return Promise.all([
spotifyApi.getArtist(to.params.id), spotifyApi.getArtist(to.params.id),
spotifyApi.getArtistAlbums(to.params.id, { spotifyApi.getArtistAlbums(to.params.id, {
include_groups: 'album,single', include_groups: 'album,single',
limit: PAGE_SIZE, limit: PAGE_SIZE,
market: store.state.spotify.webapi_country, market: useServicesStore().spotify.webapi_country,
offset: 0 offset: 0
}) })
]) ])
@ -96,6 +96,10 @@ export default {
}) })
}, },
setup() {
return { servicesStore: useServicesStore() }
},
data() { data() {
return { return {
albums: [], albums: [],
@ -114,7 +118,7 @@ export default {
}, },
load_next({ loaded }) { load_next({ loaded }) {
const spotifyApi = new SpotifyWebApi() const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token) spotifyApi.setAccessToken(this.servicesStore.spotify.webapi_token)
spotifyApi spotifyApi
.getArtistAlbums(this.artist.id, { .getArtistAlbums(this.artist.id, {
include_groups: 'album,single', include_groups: 'album,single',

View File

@ -72,13 +72,14 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue' import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
import { useServicesStore } from '@/stores/services'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -111,6 +112,10 @@ export default {
}) })
}, },
setup() {
return { servicesStore: useServicesStore(), uiStore: useUIStore() }
},
data() { data() {
return { return {
artist: {}, artist: {},
@ -144,22 +149,22 @@ export default {
}, },
hide_spotify: { hide_spotify: {
get() { get() {
return this.$store.state.hide_spotify return this.uiStore.hide_spotify
}, },
set(value) { set(value) {
this.$store.commit(types.HIDE_SPOTIFY, value) this.uiStore.hide_spotify = value
} }
}, },
selected_grouping_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.artist_tracks_sort return this.uiStore.artist_tracks_sort
}, },
set(value) { set(value) {
this.$store.commit(types.ARTIST_TRACKS_SORT, value) this.uiStore.artist_tracks_sort = value
} }
}, },
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
track_uris() { track_uris() {
return this.tracks_list.items.map((item) => item.uri).join() return this.tracks_list.items.map((item) => item.uri).join()

View File

@ -63,13 +63,14 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListArtists from '@/components/ListArtists.vue' import ListArtists from '@/components/ListArtists.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
import { useServicesStore } from '@/stores/services'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -98,6 +99,10 @@ export default {
}) })
}, },
setup() {
return { servicesStore: useServicesStore(), uiStore: useUIStore() }
},
data() { data() {
return { return {
artists_list: new GroupedList(), artists_list: new GroupedList(),
@ -134,30 +139,30 @@ export default {
}, },
hide_singles: { hide_singles: {
get() { get() {
return this.$store.state.hide_singles return this.uiStore.hide_singles
}, },
set(value) { set(value) {
this.$store.commit(types.HIDE_SINGLES, value) this.uiStore.hide_singles = value
} }
}, },
hide_spotify: { hide_spotify: {
get() { get() {
return this.$store.state.hide_spotify return this.uiStore.hide_spotify
}, },
set(value) { set(value) {
this.$store.commit(types.HIDE_SPOTIFY, value) this.uiStore.hide_spotify = value
} }
}, },
selected_grouping_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.artists_sort return this.uiStore.artists_sort
}, },
set(value) { set(value) {
this.$store.commit(types.ARTISTS_SORT, value) this.uiStore.artists_sort = value
} }
}, },
spotify_enabled() { spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }
} }
} }

View File

@ -60,13 +60,13 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogComposer from '@/components/ModalDialogComposer.vue' import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -99,6 +99,10 @@ export default {
}) })
}, },
setup() {
return { uiStore: useUIStore() }
},
data() { data() {
return { return {
composer: {}, composer: {},
@ -128,10 +132,10 @@ export default {
}, },
selected_grouping_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.composer_tracks_sort return this.uiStore.composer_tracks_sort
}, },
set(value) { set(value) {
this.$store.commit(types.COMPOSER_TRACKS_SORT, value) this.uiStore.composer_tracks_sort = value
} }
}, },
tracks() { tracks() {

View File

@ -43,6 +43,7 @@ import ListDirectories from '@/components/ListDirectories.vue'
import ListPlaylists from '@/components/ListPlaylists.vue' import ListPlaylists from '@/components/ListPlaylists.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogDirectory from '@/components/ModalDialogDirectory.vue' import ModalDialogDirectory from '@/components/ModalDialogDirectory.vue'
import { useConfigurationStore } from '@/stores/configuration'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -57,8 +58,8 @@ const dataObject = {
vm.directories = response.data.directories.map((directory) => vm.directories = response.data.directories.map((directory) =>
vm.transform(directory.path) vm.transform(directory.path)
) )
} else if (vm.$store.state.config.directories) { } else if (useConfigurationStore().directories) {
vm.directories = vm.$store.state.config.directories.map((path) => vm.directories = useConfigurationStore().directories.map((path) =>
vm.transform(path) vm.transform(path)
) )
} else { } else {
@ -96,6 +97,12 @@ export default {
}) })
}, },
setup() {
return {
configurationStore: useConfigurationStore()
}
},
data() { data() {
return { return {
directories: [], directories: [],

View File

@ -55,13 +55,13 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogGenre from '@/components/ModalDialogGenre.vue' import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -94,6 +94,10 @@ export default {
}) })
}, },
setup() {
return { uiStore: useUIStore() }
},
data() { data() {
return { return {
genre: {}, genre: {},
@ -124,10 +128,10 @@ export default {
}, },
selected_grouping_id: { selected_grouping_id: {
get() { get() {
return this.$store.state.genre_tracks_sort return this.uiStore.genre_tracks_sort
}, },
set(value) { set(value) {
this.$store.commit(types.GENRE_TRACKS_SORT, value) this.uiStore.genre_tracks_sort = value
} }
}, },
tracks() { tracks() {

View File

@ -17,12 +17,12 @@ import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import TabsMusic from '@/components/TabsMusic.vue' import TabsMusic from '@/components/TabsMusic.vue'
import store from '@/store' import { useSettingsStore } from '@/stores/settings'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
load(to) { load(to) {
const limit = store.getters.setting_recently_added_limit const limit = useSettingsStore().recently_added_limit
return webapi.search({ return webapi.search({
expression: expression:
'media_kind is music having track_count > 3 order by time_added desc', 'media_kind is music having track_count > 3 order by time_added desc',
@ -49,6 +49,12 @@ export default {
}) })
}, },
setup() {
return {
settingsStore: useSettingsStore()
}
},
data() { data() {
return { return {
albums: new GroupedList() albums: new GroupedList()

View File

@ -7,10 +7,10 @@
:artist="track.artist" :artist="track.artist"
:album="track.album" :album="track.album"
class="is-clickable fd-has-shadow fd-cover-big-image" class="is-clickable fd-has-shadow fd-cover-big-image"
:class="{ 'is-masked': lyrics_visible }" :class="{ 'is-masked': lyricsStore.pane }"
@click="open_dialog(track)" @click="open_dialog(track)"
/> />
<lyrics-pane v-if="lyrics_visible" /> <lyrics-pane v-if="lyricsStore.pane" />
<control-slider <control-slider
v-model:value="track_progress" v-model:value="track_progress"
class="mt-5" class="mt-5"
@ -34,9 +34,9 @@
/> />
<p v-if="track.album" class="subtitle is-6" v-text="track.album" /> <p v-if="track.album" class="subtitle is-6" v-text="track.album" />
<p <p
v-if="filepath" v-if="settingsStore.show_filepath_now_playing"
class="subtitle is-6 has-text-grey" class="subtitle is-6 has-text-grey"
v-text="filepath" v-text="track.path"
/> />
</div> </div>
</div> </div>
@ -55,12 +55,15 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ControlSlider from '@/components/ControlSlider.vue' import ControlSlider from '@/components/ControlSlider.vue'
import CoverArtwork from '@/components/CoverArtwork.vue' import CoverArtwork from '@/components/CoverArtwork.vue'
import LyricsPane from '@/components/LyricsPane.vue' import LyricsPane from '@/components/LyricsPane.vue'
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue' import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
import { mdiCancel } from '@mdi/js' import { mdiCancel } from '@mdi/js'
import { useLyricsStore } from '@/stores/lyrics'
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
import { useSettingsStore } from '@/stores/settings'
import webapi from '@/webapi' import webapi from '@/webapi'
const INTERVAL = 1000 const INTERVAL = 1000
@ -74,6 +77,15 @@ export default {
ModalDialogQueueItem ModalDialogQueueItem
}, },
setup() {
return {
lyricsStore: useLyricsStore(),
playerStore: usePlayerStore(),
queueStore: useQueueStore(),
settingsStore: useSettingsStore()
}
},
data() { data() {
return { return {
INTERVAL, INTERVAL,
@ -87,11 +99,12 @@ export default {
computed: { computed: {
composer() { composer() {
if (this.setting_show_composer_now_playing) { if (this.settingsStore.show_composer_now_playing) {
const genres = this.settingsStore.show_composer_for_genre
if ( if (
!this.setting_show_composer_for_genre || !genres ||
(this.track.genre && (this.track.genre &&
this.setting_show_composer_for_genre genres
.toLowerCase() .toLowerCase()
.split(',') .split(',')
.findIndex( .findIndex(
@ -104,42 +117,21 @@ export default {
} }
return null return null
}, },
filepath() {
if (this.setting_show_filepath_now_playing) {
return this.track.path
}
return null
},
is_live() { is_live() {
return this.track.length_ms === 0 return this.track.length_ms === 0
}, },
lyrics_visible() {
return this.$store.state.lyrics.pane
},
player() {
return this.$store.state.player
},
setting_show_composer_for_genre() {
return this.$store.getters.setting_show_composer_for_genre
},
setting_show_composer_now_playing() {
return this.$store.getters.setting_show_composer_now_playing
},
setting_show_filepath_now_playing() {
return this.$store.getters.setting_show_filepath_now_playing
},
track() { track() {
return this.$store.getters.now_playing return this.queueStore.current
}, },
track_elapsed_time() { track_elapsed_time() {
return this.$filters.durationInHours(this.track_progress * INTERVAL) return this.$filters.durationInHours(this.track_progress * INTERVAL)
}, },
track_progress: { track_progress: {
get() { get() {
return Math.floor(this.player.item_progress_ms / INTERVAL) return Math.floor(this.playerStore.item_progress_ms / INTERVAL)
}, },
set(value) { set(value) {
this.player.item_progress_ms = value * INTERVAL this.playerStore.item_progress_ms = value * INTERVAL
} }
}, },
track_progress_max() { track_progress_max() {
@ -153,12 +145,12 @@ export default {
}, },
watch: { watch: {
player() { playerStore() {
if (this.interval_id > 0) { if (this.interval_id > 0) {
window.clearTimeout(this.interval_id) window.clearTimeout(this.interval_id)
this.interval_id = 0 this.interval_id = 0
} }
if (this.player.state === 'play') { if (this.playerStore.state === 'play') {
this.interval_id = window.setInterval(this.tick, INTERVAL) this.interval_id = window.setInterval(this.tick, INTERVAL)
} }
} }
@ -166,8 +158,8 @@ export default {
created() { created() {
webapi.player_status().then(({ data }) => { webapi.player_status().then(({ data }) => {
this.$store.commit(types.UPDATE_PLAYER_STATUS, data) this.playerStore.$state = data
if (this.player.state === 'play') { if (this.playerStore.state === 'play') {
this.interval_id = window.setInterval(this.tick, INTERVAL) this.interval_id = window.setInterval(this.tick, INTERVAL)
} }
}) })

View File

@ -24,6 +24,7 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import ListPlaylists from '@/components/ListPlaylists.vue' import ListPlaylists from '@/components/ListPlaylists.vue'
import { useConfigurationStore } from '@/stores/configuration'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -57,6 +58,12 @@ export default {
}) })
}, },
setup() {
return {
configurationStore: useConfigurationStore()
}
},
data() { data() {
return { return {
playlist: {}, playlist: {},
@ -77,7 +84,7 @@ export default {
}) })
}, },
radio_playlists() { radio_playlists() {
return this.$store.state.config.radio_playlists return this.configurationStore.radio_playlists
} }
} }
} }

View File

@ -54,7 +54,7 @@ import ListTracksSpotify from '@/components/ListTracksSpotify.vue'
import ModalDialogPlaylistSpotify from '@/components/ModalDialogPlaylistSpotify.vue' import ModalDialogPlaylistSpotify from '@/components/ModalDialogPlaylistSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading' import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
import store from '@/store' import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
const PAGE_SIZE = 50 const PAGE_SIZE = 50
@ -62,12 +62,12 @@ const PAGE_SIZE = 50
const dataObject = { const dataObject = {
load(to) { load(to) {
const spotifyApi = new SpotifyWebApi() const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(store.state.spotify.webapi_token) spotifyApi.setAccessToken(useServicesStore().spotify.webapi_token)
return Promise.all([ return Promise.all([
spotifyApi.getPlaylist(to.params.id), spotifyApi.getPlaylist(to.params.id),
spotifyApi.getPlaylistTracks(to.params.id, { spotifyApi.getPlaylistTracks(to.params.id, {
limit: PAGE_SIZE, limit: PAGE_SIZE,
market: store.state.spotify.webapi_country, market: useServicesStore().state.spotify.webapi_country,
offset: 0 offset: 0
}) })
]) ])
@ -97,6 +97,10 @@ export default {
}) })
}, },
setup() {
return { servicesStore: useServicesStore() }
},
data() { data() {
return { return {
offset: 0, offset: 0,
@ -128,11 +132,11 @@ export default {
}, },
load_next({ loaded }) { load_next({ loaded }) {
const spotifyApi = new SpotifyWebApi() const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token) spotifyApi.setAccessToken(this.servicesStore.spotify.webapi_token)
spotifyApi spotifyApi
.getPlaylistTracks(this.playlist.id, { .getPlaylistTracks(this.playlist.id, {
limit: PAGE_SIZE, limit: PAGE_SIZE,
market: store.state.spotify.webapi_country, market: this.servicesStore.spotify.webapi_country,
offset: this.offset offset: this.offset
}) })
.then((data) => { .then((data) => {

View File

@ -57,12 +57,13 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import ModalDialogAddRss from '@/components/ModalDialogAddRss.vue' import ModalDialogAddRss from '@/components/ModalDialogAddRss.vue'
import { useLibraryStore } from '@/stores/library'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
const dataObject = { const dataObject = {
@ -94,6 +95,10 @@ export default {
}) })
}, },
setup() {
return { libraryStore: useLibraryStore(), uiStore: useUIStore() }
},
data() { data() {
return { return {
albums: [], albums: [],
@ -104,7 +109,7 @@ export default {
computed: { computed: {
rss() { rss() {
return this.$store.state.rss_count return this.libraryStore.rss
} }
}, },
@ -134,8 +139,8 @@ export default {
}, },
update_rss() { update_rss() {
this.$store.commit(types.UPDATE_DIALOG_SCAN_KIND, 'rss') this.libraryStore.update_dialog_scan_kind = 'rss'
this.$store.commit(types.SHOW_UPDATE_DIALOG, true) this.uiStore.show_update_dialog = true
} }
} }
} }

View File

@ -74,7 +74,7 @@
/> />
</a> </a>
<a <a
v-if="element.id !== state.item_id && edit_mode" v-if="element.id !== player.item_id && edit_mode"
@click.prevent.stop="remove(element)" @click.prevent.stop="remove(element)"
> >
<mdicon class="icon has-text-grey" name="delete" size="18" /> <mdicon class="icon has-text-grey" name="delete" size="18" />
@ -103,13 +103,16 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListItemQueueItem from '@/components/ListItemQueueItem.vue' import ListItemQueueItem from '@/components/ListItemQueueItem.vue'
import ModalDialogAddUrlStream from '@/components/ModalDialogAddUrlStream.vue' import ModalDialogAddUrlStream from '@/components/ModalDialogAddUrlStream.vue'
import ModalDialogPlaylistSave from '@/components/ModalDialogPlaylistSave.vue' import ModalDialogPlaylistSave from '@/components/ModalDialogPlaylistSave.vue'
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue' import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { useConfigurationStore } from '@/stores/configuration'
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
@ -123,6 +126,15 @@ export default {
draggable draggable
}, },
setup() {
return {
configurationStore: useConfigurationStore(),
playerStore: usePlayerStore(),
queueStore: useQueueStore(),
uiStore: useUIStore()
}
},
data() { data() {
return { return {
edit_mode: false, edit_mode: false,
@ -135,30 +147,30 @@ export default {
computed: { computed: {
current_position() { current_position() {
return this.$store.getters.now_playing?.position ?? -1 return this.queue.current?.position ?? -1
}, },
is_queue_save_allowed() { is_queue_save_allowed() {
return ( return (
this.$store.state.config.allow_modifying_stored_playlists && this.configurationStore.allow_modifying_stored_playlists &&
this.$store.state.config.default_playlist_directory this.configurationStore.default_playlist_directory
) )
}, },
queue() { queue() {
return this.$store.state.queue return this.queueStore
}, },
queue_items: { queue_items: {
get() { get() {
return this.$store.state.queue.items return this.queue.items
}, },
set() { set() {
/* Do nothing? Send move request in @end event */ /* Do nothing? Send move request in @end event */
} }
}, },
show_only_next_items() { show_only_next_items() {
return this.$store.state.show_only_next_items return this.uiStore.show_only_next_items
}, },
state() { player() {
return this.$store.state.player return this.playerStore
} }
}, },
@ -191,7 +203,7 @@ export default {
} }
}, },
update_show_next_items() { update_show_next_items() {
this.$store.commit(types.SHOW_ONLY_NEXT_ITEMS, !this.show_only_next_items) this.uiStore.show_only_next_items = !this.uiStore.show_only_next_items
} }
} }
} }

View File

@ -79,7 +79,6 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
@ -88,6 +87,7 @@ import ListComposers from '@/components/ListComposers.vue'
import ListPlaylists from '@/components/ListPlaylists.vue' import ListPlaylists from '@/components/ListPlaylists.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
import TabsSearch from '@/components/TabsSearch.vue' import TabsSearch from '@/components/TabsSearch.vue'
import { useSearchStore } from '@/stores/search'
import webapi from '@/webapi' import webapi from '@/webapi'
const PAGE_SIZE = 3, const PAGE_SIZE = 3,
@ -113,6 +113,10 @@ export default {
TabsSearch TabsSearch
}, },
setup() {
return { searchStore: useSearchStore() }
},
data() { data() {
return { return {
components: { components: {
@ -136,26 +140,26 @@ export default {
return this.search_types.length === 1 return this.search_types.length === 1
}, },
recent_searches() { recent_searches() {
return this.$store.state.recent_searches return this.searchStore.recent_searches
} }
}, },
watch: { watch: {
search_query() { search_query() {
this.$store.commit(types.SEARCH_QUERY, this.search_query) this.searchStore.search_query = this.search_query
} }
}, },
mounted() { mounted() {
this.$store.commit(types.SEARCH_SOURCE, this.$route.name) this.searchStore.search_source = this.$route.name
this.search_query = this.$store.state.search_query this.search_query = this.searchStore.search_query
this.search_limit = PAGE_SIZE this.search_limit = PAGE_SIZE
this.search() this.search()
}, },
methods: { methods: {
expand(type) { expand(type) {
this.search_query = this.$store.state.search_query this.search_query = this.searchStore.search_query
this.search_types = [type] this.search_types = [type]
this.search_limit = -1 this.search_limit = -1
this.search() this.search()
@ -167,7 +171,7 @@ export default {
this.search() this.search()
}, },
remove_search(query) { remove_search(query) {
this.$store.dispatch('remove_recent_search', query) this.searchStore.remove(query)
}, },
reset() { reset() {
this.results.clear() this.results.clear()
@ -189,7 +193,7 @@ export default {
this.search_types.forEach((type) => { this.search_types.forEach((type) => {
this.search_items(type) this.search_items(type)
}) })
this.$store.dispatch('add_recent_search', this.search_query) this.searchStore.add(this.search_query)
}, },
search_items(type) { search_items(type) {
const music = type !== 'audiobook' && type !== 'podcast' const music = type !== 'audiobook' && type !== 'podcast'

View File

@ -74,7 +74,6 @@
</template> </template>
<script> <script>
import * as types from '@/store/mutation_types'
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue' import ListAlbumsSpotify from '@/components/ListAlbumsSpotify.vue'
import ListArtistsSpotify from '@/components/ListArtistsSpotify.vue' import ListArtistsSpotify from '@/components/ListArtistsSpotify.vue'
@ -83,6 +82,7 @@ import ListTracksSpotify from '@/components/ListTracksSpotify.vue'
import SpotifyWebApi from 'spotify-web-api-js' import SpotifyWebApi from 'spotify-web-api-js'
import TabsSearch from '@/components/TabsSearch.vue' import TabsSearch from '@/components/TabsSearch.vue'
import { VueEternalLoading } from '@ts-pro/vue-eternal-loading' import { VueEternalLoading } from '@ts-pro/vue-eternal-loading'
import { useSearchStore } from '@/stores/search'
import webapi from '@/webapi' import webapi from '@/webapi'
const PAGE_SIZE = 3, const PAGE_SIZE = 3,
@ -101,6 +101,10 @@ export default {
VueEternalLoading VueEternalLoading
}, },
setup() {
return { searchStore: useSearchStore() }
},
data() { data() {
return { return {
components: { components: {
@ -121,7 +125,7 @@ export default {
return this.search_types.length === 1 return this.search_types.length === 1
}, },
recent_searches() { recent_searches() {
return this.$store.state.recent_searches.filter( return this.searchStore.recent_searches.filter(
(query) => !query.startsWith('query:') (query) => !query.startsWith('query:')
) )
} }
@ -129,20 +133,20 @@ export default {
watch: { watch: {
search_query() { search_query() {
this.$store.commit(types.SEARCH_QUERY, this.search_query) this.searchStore.search_query = this.search_query
} }
}, },
mounted() { mounted() {
this.$store.commit(types.SEARCH_SOURCE, this.$route.name) this.searchStore.search_source = this.$route.name
this.search_query = this.$store.state.search_query this.search_query = this.searchStore.search_query
this.search_parameters.limit = PAGE_SIZE this.search_parameters.limit = PAGE_SIZE
this.search() this.search()
}, },
methods: { methods: {
expand(type) { expand(type) {
this.search_query = this.$store.state.search_query this.search_query = this.searchStore.search_query
this.search_types = [type] this.search_types = [type]
this.search_parameters.limit = PAGE_SIZE_EXPANDED this.search_parameters.limit = PAGE_SIZE_EXPANDED
this.search_parameters.offset = 0 this.search_parameters.offset = 0
@ -156,7 +160,7 @@ export default {
this.search() this.search()
}, },
remove_search(query) { remove_search(query) {
this.$store.dispatch('remove_recent_search', query) this.searchStore.remove(query)
}, },
reset() { reset() {
this.results.clear() this.results.clear()
@ -180,7 +184,7 @@ export default {
this.results.set(type, data[`${type}s`]) this.results.set(type, data[`${type}s`])
}) })
}) })
this.$store.dispatch('add_recent_search', this.search_query) this.searchStore.add(this.search_query)
}, },
search_items() { search_items() {
return webapi.spotify().then(({ data }) => { return webapi.spotify().then(({ data }) => {

View File

@ -54,14 +54,19 @@
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import SettingsCheckbox from '@/components/SettingsCheckbox.vue' import SettingsCheckbox from '@/components/SettingsCheckbox.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useServicesStore } from '@/stores/services'
export default { export default {
name: 'PageSettingsArtwork', name: 'PageSettingsArtwork',
components: { ContentWithHeading, SettingsCheckbox, TabsSettings }, components: { ContentWithHeading, SettingsCheckbox, TabsSettings },
setup() {
return { servicesStore: useServicesStore() }
},
computed: { computed: {
spotify() { spotify() {
return this.$store.state.spotify return this.servicesStore.spotify
} }
} }
} }

View File

@ -131,12 +131,17 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useServicesStore } from '@/stores/services'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PageSettingsOnlineServices', name: 'PageSettingsOnlineServices',
components: { ContentWithHeading, TabsSettings }, components: { ContentWithHeading, TabsSettings },
setup() {
return { servicesStore: useServicesStore() }
},
data() { data() {
return { return {
lastfm_login: { lastfm_login: {
@ -149,10 +154,10 @@ export default {
computed: { computed: {
lastfm() { lastfm() {
return this.$store.state.lastfm return this.servicesStore.lastfm
}, },
spotify() { spotify() {
return this.$store.state.spotify return this.servicesStore.spotify
}, },
spotify_missing_scope() { spotify_missing_scope() {
if ( if (

View File

@ -95,12 +95,18 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useOutputsStore } from '@/stores/outputs'
import { useRemotesStore } from '@/stores/remotes'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PageSettingsRemotesOutputs', name: 'PageSettingsRemotesOutputs',
components: { ContentWithHeading, TabsSettings }, components: { ContentWithHeading, TabsSettings },
setup() {
return { outputsStore: useOutputsStore(), remotesStore: useRemotesStore() }
},
data() { data() {
return { return {
pairing_req: { pin: '' }, pairing_req: { pin: '' },
@ -110,10 +116,10 @@ export default {
computed: { computed: {
outputs() { outputs() {
return this.$store.state.outputs return this.outputsStore.outputs
}, },
pairing() { pairing() {
return this.$store.state.pairing return this.remotesStore.pairing
} }
}, },

View File

@ -111,7 +111,7 @@
<settings-textfield <settings-textfield
category="webinterface" category="webinterface"
name="show_composer_for_genre" name="show_composer_for_genre"
:disabled="!setting_show_composer_now_playing" :disabled="!settingsStore.show_composer_now_playing"
:placeholder="$t('page.settings.general.genres')" :placeholder="$t('page.settings.general.genres')"
> >
<template #label> <template #label>
@ -169,6 +169,7 @@ import SettingsCheckbox from '@/components/SettingsCheckbox.vue'
import SettingsIntfield from '@/components/SettingsIntfield.vue' import SettingsIntfield from '@/components/SettingsIntfield.vue'
import SettingsTextfield from '@/components/SettingsTextfield.vue' import SettingsTextfield from '@/components/SettingsTextfield.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useSettingsStore } from '@/stores/settings'
export default { export default {
name: 'PageSettingsWebinterface', name: 'PageSettingsWebinterface',
@ -181,6 +182,10 @@ export default {
TabsSettings TabsSettings
}, },
setup() {
return { settingsStore: useSettingsStore() }
},
computed: { computed: {
locale: { locale: {
get() { get() {
@ -209,12 +214,6 @@ export default {
name: this.$t(`language.${item}`) name: this.$t(`language.${item}`)
})) }))
} }
},
setting_show_composer_now_playing() {
return this.$store.getters.setting_show_composer_now_playing
},
setting_show_filepath_now_playing() {
return this.$store.getters.setting_show_filepath_now_playing
} }
} }
} }

View File

@ -1,4 +1,3 @@
import * as types from '@/store/mutation_types'
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import PageAbout from '@/pages/PageAbout.vue' import PageAbout from '@/pages/PageAbout.vue'
import PageAlbum from '@/pages/PageAlbum.vue' import PageAlbum from '@/pages/PageAlbum.vue'
@ -40,7 +39,7 @@ import PageSettingsArtwork from '@/pages/PageSettingsArtwork.vue'
import PageSettingsOnlineServices from '@/pages/PageSettingsOnlineServices.vue' import PageSettingsOnlineServices from '@/pages/PageSettingsOnlineServices.vue'
import PageSettingsRemotesOutputs from '@/pages/PageSettingsRemotesOutputs.vue' import PageSettingsRemotesOutputs from '@/pages/PageSettingsRemotesOutputs.vue'
import PageSettingsWebinterface from '@/pages/PageSettingsWebinterface.vue' import PageSettingsWebinterface from '@/pages/PageSettingsWebinterface.vue'
import store from '@/store' import { useUIStore } from '@/stores/ui'
const TOP_WITH_TABS = 140 const TOP_WITH_TABS = 140
const TOP_WITHOUT_TABS = 110 const TOP_WITHOUT_TABS = 110
@ -343,13 +342,14 @@ export const router = createRouter({
}) })
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (store.state.show_burger_menu) { const uiStore = useUIStore()
store.commit(types.SHOW_BURGER_MENU, false) if (uiStore.show_burger_menu) {
uiStore.show_burger_menu = false
next(false) next(false)
return return
} }
if (store.state.show_player_menu) { if (uiStore.show_player_menu) {
store.commit(types.SHOW_PLAYER_MENU, false) uiStore.show_player_menu = false
next(false) next(false)
return return
} }

View File

@ -1,238 +0,0 @@
import * as types from './mutation_types'
import { createStore } from 'vuex'
export default createStore({
state() {
return {
albums_sort: 1,
artist_albums_sort: 1,
artist_tracks_sort: 1,
artists_sort: 1,
composer_tracks_sort: 1,
config: {
buildoptions: [],
version: '',
websocket_port: 0
},
genre_tracks_sort: 1,
hide_singles: false,
hide_spotify: false,
lastfm: {},
library: {
albums: 0,
artists: 0,
db_playtime: 0,
songs: 0,
started_at: '01',
updated_at: '01',
updating: false
},
lyrics: {
content: [],
pane: false
},
notifications: {
list: [],
next_id: 1
},
outputs: [],
pairing: {},
player: {
consume: false,
item_id: 0,
item_length_ms: 0,
item_progress_ms: 0,
repeat: 'off',
shuffle: false,
state: 'stop',
volume: 0
},
queue: {
count: 0,
items: [],
version: 0
},
recent_searches: [],
rss_count: {},
search_query: '',
search_source: 'search-library',
settings: {
categories: []
},
show_burger_menu: false,
show_only_next_items: false,
show_player_menu: false,
show_update_dialog: false,
spotify: {},
update_dialog_scan_kind: ''
}
},
getters: {
now_playing: (state) =>
state.queue.items.find((item) => item.id === state.player.item_id) ?? {},
setting: (state) => (categoryName, optionName) =>
state.settings.categories
.find((category) => category.name === categoryName)
?.options.find((option) => option.name === optionName) ?? {},
setting_recently_added_limit: (state, getters) =>
getters.settings_webinterface?.options.find(
(option) => option.name === 'recently_added_limit'
)?.value ?? 100,
setting_show_composer_for_genre: (state, getters) =>
getters.settings_webinterface?.options.find(
(option) => option.name === 'show_composer_for_genre'
)?.value ?? null,
setting_show_composer_now_playing: (state, getters) =>
getters.settings_webinterface?.options.find(
(option) => option.name === 'show_composer_now_playing'
)?.value ?? false,
setting_show_filepath_now_playing: (state, getters) =>
getters.settings_webinterface?.options.find(
(option) => option.name === 'show_filepath_now_playing'
)?.value ?? false,
settings_webinterface: (state) =>
state.settings?.categories.find(
(category) => category.name === 'webinterface'
) ?? null
},
mutations: {
[types.UPDATE_CONFIG](state, config) {
state.config = config
},
[types.UPDATE_SETTINGS](state, settings) {
state.settings = settings
},
[types.UPDATE_LIBRARY_STATS](state, libraryStats) {
state.library = libraryStats
},
[types.UPDATE_LIBRARY_RSS_COUNT](state, count) {
state.rss_count = count
},
[types.UPDATE_OUTPUTS](state, outputs) {
state.outputs = outputs
},
[types.UPDATE_PLAYER_STATUS](state, playerStatus) {
state.player = playerStatus
},
[types.UPDATE_QUEUE](state, queue) {
state.queue = queue
},
[types.UPDATE_LYRICS](state, lyrics) {
state.lyrics.content = lyrics
},
[types.UPDATE_LASTFM](state, lastfm) {
state.lastfm = lastfm
},
[types.UPDATE_SPOTIFY](state, spotify) {
state.spotify = spotify
},
[types.UPDATE_PAIRING](state, pairing) {
state.pairing = pairing
},
[types.SEARCH_QUERY](state, query) {
state.search_query = query
},
[types.SEARCH_SOURCE](state, searchSource) {
state.search_source = searchSource
},
[types.COMPOSER_TRACKS_SORT](state, sort) {
state.composer_tracks_sort = sort
},
[types.GENRE_TRACKS_SORT](state, sort) {
state.genre_tracks_sort = sort
},
[types.HIDE_SINGLES](state, hideSingles) {
state.hide_singles = hideSingles
},
[types.HIDE_SPOTIFY](state, hideSpotify) {
state.hide_spotify = hideSpotify
},
[types.ARTISTS_SORT](state, sort) {
state.artists_sort = sort
},
[types.ARTIST_ALBUMS_SORT](state, sort) {
state.artist_albums_sort = sort
},
[types.ARTIST_TRACKS_SORT](state, sort) {
state.artist_tracks_sort = sort
},
[types.ALBUMS_SORT](state, sort) {
state.albums_sort = sort
},
[types.SHOW_ONLY_NEXT_ITEMS](state, showOnlyNextItems) {
state.show_only_next_items = showOnlyNextItems
},
[types.SHOW_BURGER_MENU](state, showBurgerMenu) {
state.show_burger_menu = showBurgerMenu
},
[types.SHOW_PLAYER_MENU](state, showPlayerMenu) {
state.show_player_menu = showPlayerMenu
},
[types.SHOW_UPDATE_DIALOG](state, showUpdateDialog) {
state.show_update_dialog = showUpdateDialog
},
[types.UPDATE_DIALOG_SCAN_KIND](state, scanKind) {
state.update_dialog_scan_kind = scanKind
}
},
actions: {
add_notification({ state }, notification) {
const newNotification = {
id: state.notifications.next_id++,
text: notification.text,
timeout: notification.timeout,
topic: notification.topic,
type: notification.type
}
if (newNotification.topic) {
const index = state.notifications.list.findIndex(
(elem) => elem.topic === newNotification.topic
)
if (index >= 0) {
state.notifications.list.splice(index, 1, newNotification)
return
}
}
state.notifications.list.push(newNotification)
if (notification.timeout > 0) {
setTimeout(() => {
this.dispatch('delete_notification', newNotification)
}, notification.timeout)
}
},
add_recent_search({ state }, query) {
const index = state.recent_searches.indexOf(query)
if (index !== -1) {
state.recent_searches.splice(index, 1)
}
state.recent_searches.splice(0, 0, query)
if (state.recent_searches.length > 5) {
state.recent_searches.pop()
}
},
delete_notification({ state }, notification) {
const index = state.notifications.list.indexOf(notification)
if (index !== -1) {
state.notifications.list.splice(index, 1)
}
},
remove_recent_search({ state }, query) {
const index = state.recent_searches.indexOf(query)
if (index !== -1) {
state.recent_searches.splice(index, 1)
}
},
update_setting({ state }, option) {
const settingCategory = state.settings.categories.find(
(category) => category.name === option.category
),
settingOption = settingCategory.options.find(
(setting) => setting.name === option.name
)
settingOption.value = option.value
}
}
})

View File

@ -1,26 +0,0 @@
export const ALBUMS_SORT = 'ALBUMS_SORT',
ARTISTS_SORT = 'ARTISTS_SORT',
ARTIST_ALBUMS_SORT = 'ARTIST_ALBUMS_SORT',
ARTIST_TRACKS_SORT = 'ARTIST_TRACKS_SORT',
COMPOSER_TRACKS_SORT = 'COMPOSER_TRACKS_SORT',
GENRE_TRACKS_SORT = 'GENRE_TRACKS_SORT',
HIDE_SINGLES = 'HIDE_SINGLES',
HIDE_SPOTIFY = 'HIDE_SPOTIFY',
SEARCH_QUERY = 'SEARCH_QUERY',
SEARCH_SOURCE = 'SEARCH_SOURCE',
SHOW_BURGER_MENU = 'SHOW_BURGER_MENU',
SHOW_ONLY_NEXT_ITEMS = 'SHOW_ONLY_NEXT_ITEMS',
SHOW_PLAYER_MENU = 'SHOW_PLAYER_MENU',
SHOW_UPDATE_DIALOG = 'SHOW_UPDATE_DIALOG',
UPDATE_CONFIG = 'UPDATE_CONFIG',
UPDATE_DIALOG_SCAN_KIND = 'UPDATE_DIALOG_SCAN_KIND',
UPDATE_LASTFM = 'UPDATE_LASTFM',
UPDATE_LIBRARY_RSS_COUNT = 'UPDATE_LIBRARY_RSS_COUNT',
UPDATE_LIBRARY_STATS = 'UPDATE_LIBRARY_STATS',
UPDATE_LYRICS = 'UPDATE_LYRICS',
UPDATE_OUTPUTS = 'UPDATE_OUTPUTS',
UPDATE_PAIRING = 'UPDATE_PAIRING',
UPDATE_PLAYER_STATUS = 'UPDATE_PLAYER_STATUS',
UPDATE_QUEUE = 'UPDATE_QUEUE',
UPDATE_SETTINGS = 'UPDATE_SETTINGS',
UPDATE_SPOTIFY = 'UPDATE_SPOTIFY'

View File

@ -0,0 +1,9 @@
import { defineStore } from 'pinia'
export const useConfigurationStore = defineStore('ConfigurationStore', {
state: () => ({
buildoptions: [],
version: '',
websocket_port: 0
})
})

View File

@ -0,0 +1,15 @@
import { defineStore } from 'pinia'
export const useLibraryStore = defineStore('LibraryStore', {
state: () => ({
albums: 0,
artists: 0,
db_playtime: 0,
songs: 0,
rss: {},
started_at: '01',
updated_at: '01',
update_dialog_scan_kind: '',
updating: false
})
})

View File

@ -0,0 +1,8 @@
import { defineStore } from 'pinia'
export const useLyricsStore = defineStore('LyricsStore', {
state: () => ({
content: [],
pane: false
})
})

View File

@ -0,0 +1,40 @@
import { defineStore } from 'pinia'
export const useNotificationsStore = defineStore('NotificationsStore', {
state: () => ({
list: [],
next_id: 1
}),
actions: {
add(notification) {
const newNotification = {
id: this.next_id++,
text: notification.text,
timeout: notification.timeout,
topic: notification.topic,
type: notification.type
}
if (newNotification.topic) {
const index = this.list.findIndex(
(elem) => elem.topic === newNotification.topic
)
if (index >= 0) {
this.list.splice(index, 1, newNotification)
return
}
}
this.list.push(newNotification)
if (notification.timeout > 0) {
setTimeout(() => {
this.remove(newNotification)
}, notification.timeout)
}
},
remove(notification) {
const index = this.list.indexOf(notification)
if (index !== -1) {
this.list.splice(index, 1)
}
}
}
})

View File

@ -0,0 +1,7 @@
import { defineStore } from 'pinia'
export const useOutputsStore = defineStore('OutputsStore', {
state: () => ({
outputs: []
})
})

View File

@ -0,0 +1,14 @@
import { defineStore } from 'pinia'
export const usePlayerStore = defineStore('PlayerStore', {
state: () => ({
consume: false,
item_id: 0,
item_length_ms: 0,
item_progress_ms: 0,
repeat: 'off',
shuffle: false,
state: 'stop',
volume: 0
})
})

View File

@ -0,0 +1,16 @@
import { defineStore } from 'pinia'
import { usePlayerStore } from '@/stores/player'
export const useQueueStore = defineStore('QueueStore', {
state: () => ({
count: 0,
items: [],
version: 0
}),
getters: {
current(state) {
const player = usePlayerStore()
return state.items.find((item) => item.id === player.item_id) ?? {}
}
}
})

View File

@ -0,0 +1,7 @@
import { defineStore } from 'pinia'
export const useRemotesStore = defineStore('RemotesStore', {
state: () => ({
pairing: {}
})
})

View File

@ -0,0 +1,27 @@
import { defineStore } from 'pinia'
export const useSearchStore = defineStore('SearchStore', {
state: () => ({
recent_searches: [],
search_query: '',
search_source: 'search-library'
}),
actions: {
add(query) {
const index = this.recent_searches.indexOf(query)
if (index !== -1) {
this.recent_searches.splice(index, 1)
}
this.recent_searches.unshift(query)
if (this.recent_searches.length > 5) {
this.recent_searches.pop()
}
},
remove(query) {
const index = this.recent_searches.indexOf(query)
if (index !== -1) {
this.recent_searches.splice(index, 1)
}
}
}
})

View File

@ -0,0 +1,8 @@
import { defineStore } from 'pinia'
export const useServicesStore = defineStore('ServicesStore', {
state: () => ({
lastfm: {},
spotify: {}
})
})

View File

@ -0,0 +1,60 @@
import { defineStore } from 'pinia'
export const useSettingsStore = defineStore('SettingsStore', {
state: () => ({
categories: []
}),
getters: {
recently_added_limit: (state) =>
state.setting('webinterface', 'recently_added_limit')?.value ?? 100,
show_composer_for_genre: (state) =>
state.setting('webinterface', 'show_composer_for_genre')?.value ?? null,
show_composer_now_playing: (state) =>
state.setting('webinterface', 'show_composer_now_playing')?.value ??
false,
show_cover_artwork_in_album_lists: (state) =>
state.setting('webinterface', 'show_cover_artwork_in_album_lists')
?.value ?? false,
show_filepath_now_playing: (state) =>
state.setting('webinterface', 'show_filepath_now_playing')?.value ??
false,
show_menu_item_audiobooks: (state) =>
state.setting('webinterface', 'show_menu_item_audiobooks')?.value ??
false,
show_menu_item_files: (state) =>
state.setting('webinterface', 'show_menu_item_files')?.value ?? false,
show_menu_item_music: (state) =>
state.setting('webinterface', 'show_menu_item_music')?.value ?? false,
show_menu_item_playlists: (state) =>
state.setting('webinterface', 'show_menu_item_playlists')?.value ?? false,
show_menu_item_podcasts: (state) =>
state.setting('webinterface', 'show_menu_item_podcasts')?.value ?? false,
show_menu_item_radio: (state) =>
state.setting('webinterface', 'show_menu_item_radio')?.value ?? false,
show_menu_item_search: (state) =>
state.setting('webinterface', 'show_menu_item_search')?.value ?? false
},
actions: {
update(option) {
const settingCategory = this.categories.find(
(category) => category.name === option.category
)
if (!settingCategory) {
return
}
const settingOption = settingCategory.options.find(
(setting) => setting.name === option.name
)
if (settingOption) {
settingOption.value = option.value
}
},
setting(categoryName, optionName) {
return (
this.categories
.find((category) => category.name === categoryName)
?.options.find((option) => option.name === optionName) ?? {}
)
}
}
})

18
web-src/src/stores/ui.js Normal file
View File

@ -0,0 +1,18 @@
import { defineStore } from 'pinia'
export const useUIStore = defineStore('UIStore', {
state: () => ({
albums_sort: 1,
artist_albums_sort: 1,
artist_tracks_sort: 1,
artists_sort: 1,
composer_tracks_sort: 1,
genre_tracks_sort: 1,
hide_singles: false,
hide_spotify: false,
show_burger_menu: false,
show_only_next_items: false,
show_player_menu: false,
show_update_dialog: false
})
})

View File

@ -1,6 +1,7 @@
import axios from 'axios' import axios from 'axios'
import i18n from '@/i18n' import i18n from '@/i18n'
import store from '@/store' import { useNotificationsStore } from '@/stores/notifications'
import { useQueueStore } from '@/stores/queue'
const { t } = i18n.global const { t } = i18n.global
@ -8,7 +9,7 @@ axios.interceptors.response.use(
(response) => response, (response) => response,
(error) => { (error) => {
if (error.request.status && error.request.responseURL) { if (error.request.status && error.request.responseURL) {
store.dispatch('add_notification', { useNotificationsStore().add({
text: t('server.request-failed', { text: t('server.request-failed', {
cause: error.request.statusText, cause: error.request.statusText,
status: error.request.status, status: error.request.status,
@ -316,7 +317,7 @@ export default {
queue_add(uri) { queue_add(uri) {
return axios.post(`./api/queue/items/add?uris=${uri}`).then((response) => { return axios.post(`./api/queue/items/add?uris=${uri}`).then((response) => {
store.dispatch('add_notification', { useNotificationsStore().add({
text: t('server.appended-tracks', { count: response.data.count }), text: t('server.appended-tracks', { count: response.data.count }),
timeout: 2000, timeout: 2000,
type: 'info' type: 'info'
@ -327,16 +328,15 @@ export default {
queue_add_next(uri) { queue_add_next(uri) {
let position = 0 let position = 0
if (store.getters.now_playing && store.getters.now_playing.id) { const { current } = useQueueStore()
position = store.getters.now_playing.position + 1 if (current?.id) {
position = current.position + 1
} }
return axios return axios
.post(`./api/queue/items/add?uris=${uri}&position=${position}`) .post(`./api/queue/items/add?uris=${uri}&position=${position}`)
.then((response) => { .then((response) => {
store.dispatch('add_notification', { useNotificationsStore().add({
text: t('server.appended-tracks', { text: t('server.appended-tracks', { count: response.data.count }),
count: response.data.count
}),
timeout: 2000, timeout: 2000,
type: 'info' type: 'info'
}) })
@ -352,10 +352,8 @@ export default {
return axios return axios
.post('./api/queue/items/add', null, { params: { expression } }) .post('./api/queue/items/add', null, { params: { expression } })
.then((response) => { .then((response) => {
store.dispatch('add_notification', { useNotificationsStore().add({
text: t('server.appended-tracks', { text: t('server.appended-tracks', { count: response.data.count }),
count: response.data.count
}),
timeout: 2000, timeout: 2000,
type: 'info' type: 'info'
}) })
@ -367,16 +365,15 @@ export default {
const params = {} const params = {}
params.expression = expression params.expression = expression
params.position = 0 params.position = 0
if (store.getters.now_playing && store.getters.now_playing.id) { const { current } = useQueueStore()
params.position = store.getters.now_playing.position + 1 if (current?.id) {
params.position = current.position + 1
} }
return axios return axios
.post('./api/queue/items/add', null, { params }) .post('./api/queue/items/add', null, { params })
.then((response) => { .then((response) => {
store.dispatch('add_notification', { useNotificationsStore().add({
text: t('server.appended-tracks', { text: t('server.appended-tracks', { count: response.data.count }),
count: response.data.count
}),
timeout: 2000, timeout: 2000,
type: 'info' type: 'info'
}) })
@ -396,7 +393,7 @@ export default {
return axios return axios
.post('./api/queue/save', null, { params: { name } }) .post('./api/queue/save', null, { params: { name } })
.then((response) => { .then((response) => {
store.dispatch('add_notification', { useNotificationsStore().add({
text: t('server.queue-saved', { name }), text: t('server.queue-saved', { name }),
timeout: 2000, timeout: 2000,
type: 'info' type: 'info'