mirror of
https://github.com/owntone/owntone-server.git
synced 2025-03-03 15:20:09 -05:00
[web] Refactor lists to improve performance
Reduces the number of Vue components that need to be created/managed. Instead of a Vue component for each item, we now only have one Vue component for the whole list of items.
This commit is contained in:
parent
a24cb11e17
commit
27e2274d8a
@ -1,7 +1,5 @@
|
|||||||
# Vue 3 + Vite Migration
|
# Vue 3 + Vite Migration
|
||||||
|
|
||||||
- Vue Dev Tools required in version 6 (currently only released as beta versions): <https://devtools.vuejs.org/guide/installation.html#beta>
|
|
||||||
|
|
||||||
- [ ] vite does not support env vars in `vite.config.js` from `.env` files
|
- [ ] vite does not support env vars in `vite.config.js` from `.env` files
|
||||||
|
|
||||||
- <https://stackoverflow.com/questions/66389043/how-can-i-use-vite-env-variables-in-vite-config-js>
|
- <https://stackoverflow.com/questions/66389043/how-can-i-use-vite-env-variables-in-vite-config-js>
|
||||||
@ -9,7 +7,7 @@
|
|||||||
|
|
||||||
- [ ] Documentation update
|
- [ ] Documentation update
|
||||||
|
|
||||||
- [ ] Add linting (ESLint) ?
|
- [x] Add linting (ESLint)
|
||||||
|
|
||||||
- [x] Update dialog is missing scan options
|
- [x] Update dialog is missing scan options
|
||||||
|
|
||||||
@ -17,22 +15,22 @@
|
|||||||
|
|
||||||
- [ ] Do not reload data, if using the index-nav
|
- [ ] Do not reload data, if using the index-nav
|
||||||
- [x] PageAlbums
|
- [x] PageAlbums
|
||||||
- [ ] PageArtists
|
- [x] PageArtists
|
||||||
|
- [ ] PageGenres
|
||||||
- [ ] ...
|
- [ ] ...
|
||||||
- [ ] Albums page is slow to load (because of the number of vue components?)
|
- [ ] Albums page is slow to load (because of the number of vue components - ListItem+CoverArtwork)
|
||||||
- [ ] Evaluate virtual scroller <https://github.com/Akryum/vue-virtual-scroller/tree/next/packages/vue-virtual-scroller>
|
- [ ] Evaluate removing ListItem and CoverArtwork component
|
||||||
|
|
||||||
- [x] JS error on Podacst page
|
- [x] JS error on Podacst page
|
||||||
|
|
||||||
- Problem caused by the Slider component
|
- Problem caused by the Slider component
|
||||||
- Replace with plain html
|
- Replace with plain html
|
||||||
|
|
||||||
- [ ] vue-router scroll-behavior
|
- [x] vue-router scroll-behavior
|
||||||
|
|
||||||
- [x] Index list not always hidden
|
- [x] Index list not always hidden
|
||||||
- [x] Check transitions
|
- [x] Check transitions
|
||||||
- [ ] Page display is "jumpy"
|
- [x] Page display is "jumpy" - "fixed" by removing the page transition effect
|
||||||
- Workaround is removing the page transition effect
|
|
||||||
|
|
||||||
- [x] Index navigation "scroll up/down" button does not scroll down, if index is visible
|
- [x] Index navigation "scroll up/down" button does not scroll down, if index is visible
|
||||||
|
|
||||||
@ -54,7 +52,7 @@
|
|||||||
|
|
||||||
- [x] vue-router does not support navigation guards in mixins: <https://github.com/vuejs/vue-router-next/issues/454>
|
- [x] vue-router does not support navigation guards in mixins: <https://github.com/vuejs/vue-router-next/issues/454>
|
||||||
|
|
||||||
- replace mixin with composition api? <https://next.router.vuejs.org/guide/advanced/composition-api.html#navigation-guards>
|
- Replace mixin with composition api? <https://next.router.vuejs.org/guide/advanced/composition-api.html#navigation-guards>
|
||||||
- Copied nav guards into each component
|
- Copied nav guards into each component
|
||||||
|
|
||||||
- [x] vue-router link does not support `tag` and `active-class` properties: <https://next.router.vuejs.org/guide/migration/index.html#removal-of-event-and-tag-props-in-router-link>
|
- [x] vue-router link does not support `tag` and `active-class` properties: <https://next.router.vuejs.org/guide/migration/index.html#removal-of-event-and-tag-props-in-router-link>
|
||||||
|
@ -20,6 +20,9 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
filtered_index() {
|
filtered_index() {
|
||||||
|
if (!this.index) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
const specialChars = '!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
|
const specialChars = '!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
|
||||||
return this.index.filter((c) => !specialChars.includes(c))
|
return this.index.filter((c) => !specialChars.includes(c))
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,53 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<template v-for="album in albums" :key="album.itemId">
|
||||||
<div v-if="is_grouped">
|
<div v-if="!album.isItem && !hide_group_title" class="mt-6 mb-5 py-2">
|
||||||
<div v-for="idx in albums.indexList" :key="idx" class="mb-6">
|
<span
|
||||||
<span
|
:id="'index_' + album.groupKey"
|
||||||
:id="'index_' + idx"
|
class="tag is-info is-light is-small has-text-weight-bold"
|
||||||
class="tag is-info is-light is-small has-text-weight-bold"
|
>{{ album.groupKey }}</span
|
||||||
>{{ idx }}</span
|
>
|
||||||
>
|
</div>
|
||||||
|
<div v-else-if="album.isItem" class="media" @click="open_album(album.item)">
|
||||||
<div
|
<div v-if="is_visible_artwork" class="media-left fd-has-action">
|
||||||
v-for="album in albums.grouped[idx]"
|
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
||||||
:key="album.id"
|
<figure>
|
||||||
class="media"
|
<img
|
||||||
:album="album"
|
v-lazy="{
|
||||||
@click="open_album(album)"
|
src: artwork_url_with_size(album.item.artwork_url),
|
||||||
>
|
lifecycle: artwork_options.lazy_lifecycle
|
||||||
<div v-if="is_visible_artwork" class="media-left fd-has-action">
|
}"
|
||||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
:album="album.item.name"
|
||||||
<cover-artwork
|
:artist="album.item.artist"
|
||||||
:artwork_url="album.artwork_url"
|
/>
|
||||||
:artist="album.artist"
|
</figure>
|
||||||
:album="album.name"
|
</p>
|
||||||
:maxwidth="64"
|
</div>
|
||||||
:maxheight="64"
|
<div class="media-content fd-has-action is-clipped">
|
||||||
/>
|
<div style="margin-top: 0.7rem">
|
||||||
</p>
|
<h1 class="title is-6">
|
||||||
</div>
|
{{ album.item.name }}
|
||||||
<div class="media-content fd-has-action is-clipped">
|
</h1>
|
||||||
<div style="margin-top: 0.7rem">
|
<h2 class="subtitle is-7 has-text-grey">
|
||||||
<h1 class="title is-6">
|
<b>{{ album.item.artist }}</b>
|
||||||
{{ album.name }}
|
</h2>
|
||||||
</h1>
|
<h2
|
||||||
<h2 class="subtitle is-7 has-text-grey">
|
v-if="album.item.date_released && album.item.media_kind === 'music'"
|
||||||
<b>{{ album.artist }}</b>
|
class="subtitle is-7 has-text-grey has-text-weight-normal"
|
||||||
</h2>
|
>
|
||||||
<h2
|
{{ $filters.time(album.item.date_released, 'L') }}
|
||||||
v-if="album.date_released && album.media_kind === 'music'"
|
</h2>
|
||||||
class="subtitle is-7 has-text-grey has-text-weight-normal"
|
|
||||||
>
|
|
||||||
{{ $filters.time(album.date_released, 'L') }}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media-right" style="padding-top: 0.7rem">
|
|
||||||
<a @click.prevent.stop="open_dialog(album)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="media-right" style="padding-top: 0.7rem">
|
||||||
|
<a @click.prevent.stop="open_dialog(album.item)">
|
||||||
|
<span class="icon has-text-dark"
|
||||||
|
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||||
|
/></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
</template>
|
||||||
<list-item-album
|
<teleport to="#app">
|
||||||
v-for="album in albums_list"
|
|
||||||
:key="album.id"
|
|
||||||
:album="album"
|
|
||||||
@click="open_album(album)"
|
|
||||||
>
|
|
||||||
<template v-if="is_visible_artwork" #artwork>
|
|
||||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
|
||||||
<cover-artwork
|
|
||||||
:artwork_url="album.artwork_url"
|
|
||||||
:artist="album.artist"
|
|
||||||
:album="album.name"
|
|
||||||
:maxwidth="64"
|
|
||||||
:maxheight="64"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<a @click.prevent.stop="open_dialog(album)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-album>
|
|
||||||
</div>
|
|
||||||
<modal-dialog-album
|
<modal-dialog-album
|
||||||
:show="show_details_modal"
|
:show="show_details_modal"
|
||||||
:album="selected_album"
|
:album="selected_album"
|
||||||
@ -103,22 +72,20 @@
|
|||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</modal-dialog>
|
</modal-dialog>
|
||||||
</div>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListItemAlbum from '@/components/ListItemAlbum.vue'
|
|
||||||
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
|
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
|
||||||
import ModalDialog from '@/components/ModalDialog.vue'
|
import ModalDialog from '@/components/ModalDialog.vue'
|
||||||
import CoverArtwork from '@/components/CoverArtwork.vue'
|
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import Albums from '@/lib/Albums'
|
import { renderSVG } from '@/lib/SVGRenderer'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListAlbums',
|
name: 'ListAlbums',
|
||||||
components: { ListItemAlbum, ModalDialogAlbum, ModalDialog, CoverArtwork },
|
components: { ModalDialogAlbum, ModalDialog },
|
||||||
|
|
||||||
props: ['albums', 'media_kind'],
|
props: ['albums', 'media_kind', 'hide_group_title'],
|
||||||
emits: ['play-count-changed', 'podcast-deleted'],
|
emits: ['play-count-changed', 'podcast-deleted'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -127,7 +94,23 @@ export default {
|
|||||||
selected_album: {},
|
selected_album: {},
|
||||||
|
|
||||||
show_remove_podcast_modal: false,
|
show_remove_podcast_modal: false,
|
||||||
rss_playlist_to_remove: {}
|
rss_playlist_to_remove: {},
|
||||||
|
|
||||||
|
artwork_options: {
|
||||||
|
width: 600,
|
||||||
|
height: 600,
|
||||||
|
font_family: 'sans-serif',
|
||||||
|
font_size: 200,
|
||||||
|
font_weight: 600,
|
||||||
|
lazy_lifecycle: {
|
||||||
|
error: (el) => {
|
||||||
|
el.src = this.dataURI(
|
||||||
|
el.attributes.album.value,
|
||||||
|
el.attributes.artist.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -141,20 +124,6 @@ export default {
|
|||||||
|
|
||||||
media_kind_resolved: function () {
|
media_kind_resolved: function () {
|
||||||
return this.media_kind ? this.media_kind : this.selected_album.media_kind
|
return this.media_kind ? this.media_kind : this.selected_album.media_kind
|
||||||
},
|
|
||||||
|
|
||||||
albums_list: function () {
|
|
||||||
if (Array.isArray(this.albums)) {
|
|
||||||
return this.albums
|
|
||||||
}
|
|
||||||
if (this.albums) {
|
|
||||||
return this.albums.sortedAndFiltered
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
|
|
||||||
is_grouped: function () {
|
|
||||||
return this.albums instanceof Albums && this.albums.options.group
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -207,6 +176,43 @@ export default {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.$emit('podcast-deleted')
|
this.$emit('podcast-deleted')
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
artwork_url_with_size: function (artwork_url) {
|
||||||
|
if (this.artwork_options.width > 0 && this.artwork_options.height > 0) {
|
||||||
|
return webapi.artwork_url_append_size_params(
|
||||||
|
artwork_url,
|
||||||
|
this.artwork_options.width,
|
||||||
|
this.artwork_options.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return webapi.artwork_url_append_size_params(artwork_url)
|
||||||
|
},
|
||||||
|
|
||||||
|
alt_text(album, artist) {
|
||||||
|
return artist + ' - ' + album
|
||||||
|
},
|
||||||
|
|
||||||
|
caption(album, artist) {
|
||||||
|
if (album) {
|
||||||
|
return album.substring(0, 2)
|
||||||
|
}
|
||||||
|
if (artist) {
|
||||||
|
return artist.substring(0, 2)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
dataURI: function (album, artist) {
|
||||||
|
const caption = this.caption(album, artist)
|
||||||
|
const alt_text = this.alt_text(album, artist)
|
||||||
|
return renderSVG(caption, alt_text, {
|
||||||
|
width: this.artwork_options.width,
|
||||||
|
height: this.artwork_options.height,
|
||||||
|
font_family: this.artwork_options.font_family,
|
||||||
|
font_size: this.artwork_options.font_size,
|
||||||
|
font_weight: this.artwork_options.font_weight
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<template v-for="artist in artists" :key="artist.itemId">
|
||||||
<div v-if="is_grouped">
|
<div v-if="!artist.isItem && !hide_group_title" class="mt-6 mb-5 py-2">
|
||||||
<div v-for="idx in artists.indexList" :key="idx" class="mb-6">
|
<div class="media-content is-clipped">
|
||||||
<span
|
<span
|
||||||
:id="'index_' + idx"
|
:id="'index_' + artist.groupKey"
|
||||||
class="tag is-info is-light is-small has-text-weight-bold"
|
class="tag is-info is-light is-small has-text-weight-bold"
|
||||||
>{{ idx }}</span
|
>{{ artist.groupKey }}</span
|
||||||
>
|
>
|
||||||
<list-item-artist
|
|
||||||
v-for="artist in artists.grouped[idx]"
|
|
||||||
:key="artist.id"
|
|
||||||
:artist="artist"
|
|
||||||
@click="open_artist(artist)"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<a @click.prevent.stop="open_dialog(artist)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-artist>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div
|
||||||
<list-item-artist
|
v-else-if="artist.isItem"
|
||||||
v-for="artist in artists_list"
|
class="media"
|
||||||
:key="artist.id"
|
@click="open_artist(artist.item)"
|
||||||
:artist="artist"
|
>
|
||||||
@click="open_artist(artist)"
|
<div class="media-content fd-has-action is-clipped">
|
||||||
>
|
<h1 class="title is-6">
|
||||||
<template #actions>
|
{{ artist.item.name }}
|
||||||
<a @click.prevent.stop="open_dialog(artist)">
|
</h1>
|
||||||
<span class="icon has-text-dark"
|
</div>
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
<div class="media-right">
|
||||||
/></span>
|
<a @click.prevent.stop="open_dialog(artist.item)">
|
||||||
</a>
|
<span class="icon has-text-dark"
|
||||||
</template>
|
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||||
</list-item-artist>
|
/></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<teleport to="#app">
|
||||||
<modal-dialog-artist
|
<modal-dialog-artist
|
||||||
:show="show_details_modal"
|
:show="show_details_modal"
|
||||||
:artist="selected_artist"
|
:artist="selected_artist"
|
||||||
:media_kind="media_kind"
|
:media_kind="media_kind"
|
||||||
@close="show_details_modal = false"
|
@close="show_details_modal = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListItemArtist from '@/components/ListItemArtist.vue'
|
|
||||||
import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
|
import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
|
||||||
import Artists from '@/lib/Artists'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListArtists',
|
name: 'ListArtists',
|
||||||
components: { ListItemArtist, ModalDialogArtist },
|
components: { ModalDialogArtist },
|
||||||
|
|
||||||
props: ['artists', 'media_kind'],
|
props: ['artists', 'media_kind', 'hide_group_title'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -69,17 +57,6 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
media_kind_resolved: function () {
|
media_kind_resolved: function () {
|
||||||
return this.media_kind ? this.media_kind : this.selected_artist.media_kind
|
return this.media_kind ? this.media_kind : this.selected_artist.media_kind
|
||||||
},
|
|
||||||
|
|
||||||
artists_list: function () {
|
|
||||||
if (Array.isArray(this.artists)) {
|
|
||||||
return this.artists
|
|
||||||
}
|
|
||||||
return this.artists.sortedAndFiltered
|
|
||||||
},
|
|
||||||
|
|
||||||
is_grouped: function () {
|
|
||||||
return this.artists instanceof Artists && this.artists.options.group
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,63 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<template v-for="composer in composers" :key="composer.itemId">
|
||||||
<div v-if="is_grouped">
|
<div v-if="!composer.isItem && !hide_group_title" class="mt-6 mb-5 py-2">
|
||||||
<div v-for="idx in composers.indexList" :key="idx" class="mb-6">
|
<div class="media-content is-clipped">
|
||||||
<span
|
<span
|
||||||
:id="'index_' + idx"
|
:id="'index_' + composer.groupKey"
|
||||||
class="tag is-info is-light is-small has-text-weight-bold"
|
class="tag is-info is-light is-small has-text-weight-bold"
|
||||||
>{{ idx }}</span
|
>{{ composer.groupKey }}</span
|
||||||
>
|
>
|
||||||
<list-item-composer
|
|
||||||
v-for="composer in composers.grouped[idx]"
|
|
||||||
:key="composer.id"
|
|
||||||
:composer="composer"
|
|
||||||
@click="open_composer(composer)"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<a @click.prevent.stop="open_dialog(composer)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-composer>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div
|
||||||
<list-item-composer
|
v-else-if="composer.isItem"
|
||||||
v-for="composer in composers_list"
|
class="media"
|
||||||
:key="composer.id"
|
@click="open_composer(composer.item)"
|
||||||
:composer="composer"
|
>
|
||||||
@click="open_composer(composer)"
|
<div class="media-content fd-has-action is-clipped">
|
||||||
>
|
<h1 class="title is-6">
|
||||||
<template #actions>
|
{{ composer.item.name }}
|
||||||
<a @click.prevent.stop="open_dialog(composer)">
|
</h1>
|
||||||
<span class="icon has-text-dark"
|
</div>
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
<div class="media-right">
|
||||||
/></span>
|
<a @click.prevent.stop="open_dialog(composer.item)">
|
||||||
</a>
|
<span class="icon has-text-dark"
|
||||||
</template>
|
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||||
</list-item-composer>
|
/></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<teleport to="#app">
|
||||||
<modal-dialog-composer
|
<modal-dialog-composer
|
||||||
:show="show_details_modal"
|
:show="show_details_modal"
|
||||||
:composer="selected_composer"
|
:composer="selected_composer"
|
||||||
:media_kind="media_kind"
|
:media_kind="media_kind"
|
||||||
@close="show_details_modal = false"
|
@close="show_details_modal = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListItemComposer from '@/components/ListItemComposer.vue'
|
|
||||||
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
|
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
|
||||||
import Composers from '@/lib/Composers'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListComposers',
|
name: 'ListComposers',
|
||||||
components: { ListItemComposer, ModalDialogComposer },
|
components: { ModalDialogComposer },
|
||||||
|
|
||||||
props: ['composers', 'media_kind'],
|
props: ['composers', 'media_kind', 'hide_group_title'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -71,17 +59,6 @@ export default {
|
|||||||
return this.media_kind
|
return this.media_kind
|
||||||
? this.media_kind
|
? this.media_kind
|
||||||
: this.selected_composer.media_kind
|
: this.selected_composer.media_kind
|
||||||
},
|
|
||||||
|
|
||||||
composers_list: function () {
|
|
||||||
if (Array.isArray(this.composers)) {
|
|
||||||
return this.composers
|
|
||||||
}
|
|
||||||
return this.composers.sortedAndFiltered
|
|
||||||
},
|
|
||||||
|
|
||||||
is_grouped: function () {
|
|
||||||
return this.composers instanceof Composers && this.composers.options.group
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
116
web-src/src/components/ListDirectories.vue
Normal file
116
web-src/src/components/ListDirectories.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="$route.query.directory"
|
||||||
|
class="media"
|
||||||
|
@click="open_parent_directory()"
|
||||||
|
>
|
||||||
|
<figure class="media-left fd-has-action">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="mdi mdi-subdirectory-arrow-left" />
|
||||||
|
</span>
|
||||||
|
</figure>
|
||||||
|
<div class="media-content fd-has-action is-clipped">
|
||||||
|
<h1 class="title is-6">..</h1>
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-for="directory in directories" :key="directory.path">
|
||||||
|
<div class="media" @click="open_directory(directory)">
|
||||||
|
<figure class="media-left fd-has-action">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="mdi mdi-folder" />
|
||||||
|
</span>
|
||||||
|
</figure>
|
||||||
|
<div class="media-content fd-has-action is-clipped">
|
||||||
|
<h1 class="title is-6">
|
||||||
|
{{ directory.path.substring(directory.path.lastIndexOf('/') + 1) }}
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle is-7 has-text-grey-light">
|
||||||
|
{{ directory.path }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<a @click.prevent.stop="open_dialog(directory)">
|
||||||
|
<span class="icon has-text-dark"
|
||||||
|
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||||
|
/></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<teleport to="#app">
|
||||||
|
<modal-dialog-directory
|
||||||
|
:show="show_details_modal"
|
||||||
|
:directory="selected_directory"
|
||||||
|
@close="show_details_modal = false"
|
||||||
|
/>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ModalDialogDirectory from '@/components/ModalDialogDirectory.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ListDirectories',
|
||||||
|
components: { ModalDialogDirectory },
|
||||||
|
|
||||||
|
props: ['directories'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show_details_modal: false,
|
||||||
|
selected_directory: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
current_directory() {
|
||||||
|
if (this.$route.query && this.$route.query.directory) {
|
||||||
|
return this.$route.query.directory
|
||||||
|
}
|
||||||
|
return '/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
open_parent_directory: function () {
|
||||||
|
const parent = this.current_directory.slice(
|
||||||
|
0,
|
||||||
|
this.current_directory.lastIndexOf('/')
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
parent === '' ||
|
||||||
|
this.$store.state.config.directories.includes(this.current_directory)
|
||||||
|
) {
|
||||||
|
this.$router.push({ path: '/files' })
|
||||||
|
} else {
|
||||||
|
this.$router.push({
|
||||||
|
path: '/files',
|
||||||
|
query: {
|
||||||
|
directory: this.current_directory.slice(
|
||||||
|
0,
|
||||||
|
this.current_directory.lastIndexOf('/')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
open_directory: function (directory) {
|
||||||
|
this.$router.push({
|
||||||
|
path: '/files',
|
||||||
|
query: { directory: directory.path }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
open_dialog: function (directory) {
|
||||||
|
this.selected_directory = directory
|
||||||
|
this.show_details_modal = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
71
web-src/src/components/ListGenres.vue
Normal file
71
web-src/src/components/ListGenres.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<template v-for="genre in genres" :key="genre.itemId">
|
||||||
|
<div v-if="!genre.isItem && !hide_group_title" class="mt-6 mb-5 py-2">
|
||||||
|
<div class="media-content is-clipped">
|
||||||
|
<span
|
||||||
|
:id="'index_' + genre.groupKey"
|
||||||
|
class="tag is-info is-light is-small has-text-weight-bold"
|
||||||
|
>{{ genre.groupKey }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="genre.isItem" class="media" @click="open_genre(genre.item)">
|
||||||
|
<div class="media-content fd-has-action is-clipped">
|
||||||
|
<h1 class="title is-6">
|
||||||
|
{{ genre.item.name }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<a @click.prevent.stop="open_dialog(genre.item)">
|
||||||
|
<span class="icon has-text-dark"
|
||||||
|
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||||
|
/></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<teleport to="#app">
|
||||||
|
<modal-dialog-genre
|
||||||
|
:show="show_details_modal"
|
||||||
|
:genre="selected_genre"
|
||||||
|
@close="show_details_modal = false"
|
||||||
|
/>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ListGenres',
|
||||||
|
components: { ModalDialogGenre },
|
||||||
|
|
||||||
|
props: ['genres', 'media_kind', 'hide_group_title'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show_details_modal: false,
|
||||||
|
selected_genre: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
media_kind_resolved: function () {
|
||||||
|
return this.media_kind ? this.media_kind : this.selected_genre.media_kind
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
open_genre: function (genre) {
|
||||||
|
this.$router.push({ name: 'Genre', params: { genre: genre.name } })
|
||||||
|
},
|
||||||
|
|
||||||
|
open_dialog: function (genre) {
|
||||||
|
this.selected_genre = genre
|
||||||
|
this.show_details_modal = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :id="'index_' + album.name_sort.charAt(0).toUpperCase()" class="media">
|
|
||||||
<div v-if="$slots['artwork']" class="media-left fd-has-action">
|
|
||||||
<slot name="artwork" />
|
|
||||||
</div>
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<div style="margin-top: 0.7rem">
|
|
||||||
<h1 class="title is-6">
|
|
||||||
{{ album.name }}
|
|
||||||
</h1>
|
|
||||||
<h2 class="subtitle is-7 has-text-grey">
|
|
||||||
<b>{{ album.artist }}</b>
|
|
||||||
</h2>
|
|
||||||
<h2
|
|
||||||
v-if="album.date_released && album.media_kind === 'music'"
|
|
||||||
class="subtitle is-7 has-text-grey has-text-weight-normal"
|
|
||||||
>
|
|
||||||
{{ $filters.time(album.date_released, 'L') }}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media-right" style="padding-top: 0.7rem">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ListItemAlbum',
|
|
||||||
props: ['album', 'media_kind']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<h1 class="title is-6">
|
|
||||||
{{ artist.name }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ListItemArtist',
|
|
||||||
props: ['artist']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :id="'index_' + composer.name.charAt(0).toUpperCase()" class="media">
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<h1 class="title is-6">
|
|
||||||
{{ composer.name }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ListItemComposer',
|
|
||||||
props: ['composer']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="media">
|
|
||||||
<figure class="media-left fd-has-action">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="mdi mdi-folder" />
|
|
||||||
</span>
|
|
||||||
</figure>
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<h1 class="title is-6">
|
|
||||||
{{ directory.path.substring(directory.path.lastIndexOf('/') + 1) }}
|
|
||||||
</h1>
|
|
||||||
<h2 class="subtitle is-7 has-text-grey-light">
|
|
||||||
{{ directory.path }}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ListItemDirectory',
|
|
||||||
props: ['directory']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :id="'index_' + genre.name.charAt(0).toUpperCase()" class="media">
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<h1 class="title is-6">
|
|
||||||
{{ genre.name }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ListItemGenre',
|
|
||||||
props: ['genre']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@ -1,24 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="media">
|
|
||||||
<figure v-if="$slots.icon" class="media-left fd-has-action">
|
|
||||||
<slot name="icon" />
|
|
||||||
</figure>
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<h1 class="title is-6">
|
|
||||||
{{ playlist.name }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ListItemPlaylist',
|
|
||||||
props: ['playlist']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
:id="'index_' + track.title_sort.charAt(0).toUpperCase()"
|
|
||||||
class="media"
|
|
||||||
:class="{ 'with-progress': $slots.progress }"
|
|
||||||
>
|
|
||||||
<figure v-if="$slots.icon" class="media-left fd-has-action">
|
|
||||||
<slot name="icon" />
|
|
||||||
</figure>
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<h1
|
|
||||||
class="title is-6"
|
|
||||||
:class="{
|
|
||||||
'has-text-grey':
|
|
||||||
track.media_kind === 'podcast' && track.play_count > 0
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ track.title }}
|
|
||||||
</h1>
|
|
||||||
<h2 class="subtitle is-7 has-text-grey">
|
|
||||||
<b>{{ track.artist }}</b>
|
|
||||||
</h2>
|
|
||||||
<h2 class="subtitle is-7 has-text-grey">
|
|
||||||
{{ track.album }}
|
|
||||||
</h2>
|
|
||||||
<slot name="progress" />
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ListItemTrack',
|
|
||||||
props: ['track']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@ -1,46 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div
|
||||||
<list-item-playlist
|
v-for="playlist in playlists"
|
||||||
v-for="playlist in playlists"
|
:key="playlist.id"
|
||||||
:key="playlist.id"
|
class="media"
|
||||||
:playlist="playlist"
|
:playlist="playlist"
|
||||||
@click="open_playlist(playlist)"
|
@click="open_playlist(playlist)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<figure class="media-left fd-has-action">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i
|
<i
|
||||||
class="mdi"
|
class="mdi"
|
||||||
:class="{
|
:class="{
|
||||||
'mdi-library-music': playlist.type !== 'folder',
|
'mdi-library-music': playlist.type !== 'folder',
|
||||||
'mdi-rss': playlist.type === 'rss',
|
'mdi-rss': playlist.type === 'rss',
|
||||||
'mdi-folder': playlist.type === 'folder'
|
'mdi-folder': playlist.type === 'folder'
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</figure>
|
||||||
<template #actions>
|
<div class="media-content fd-has-action is-clipped">
|
||||||
<a @click.prevent.stop="open_dialog(playlist)">
|
<h1 class="title is-6">
|
||||||
<span class="icon has-text-dark"
|
{{ playlist.name }}
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
</h1>
|
||||||
/></span>
|
</div>
|
||||||
</a>
|
<div class="media-right">
|
||||||
</template>
|
<a @click.prevent.stop="open_dialog(playlist)">
|
||||||
</list-item-playlist>
|
<span class="icon has-text-dark"
|
||||||
|
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||||
|
/></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<teleport to="#app">
|
||||||
<modal-dialog-playlist
|
<modal-dialog-playlist
|
||||||
:show="show_details_modal"
|
:show="show_details_modal"
|
||||||
:playlist="selected_playlist"
|
:playlist="selected_playlist"
|
||||||
@close="show_details_modal = false"
|
@close="show_details_modal = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListItemPlaylist from '@/components/ListItemPlaylist.vue'
|
|
||||||
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue'
|
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListPlaylists',
|
name: 'ListPlaylists',
|
||||||
components: { ListItemPlaylist, ModalDialogPlaylist },
|
components: { ModalDialogPlaylist },
|
||||||
|
|
||||||
props: ['playlists'],
|
props: ['playlists'],
|
||||||
|
|
||||||
|
@ -1,37 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div
|
||||||
<list-item-track
|
v-for="(track, index) in tracks"
|
||||||
v-for="(track, index) in tracks"
|
:id="'index_' + track.title_sort.charAt(0).toUpperCase()"
|
||||||
:key="track.id"
|
:key="track.id"
|
||||||
:track="track"
|
class="media"
|
||||||
@click="play_track(index, track)"
|
:class="{ 'with-progress': show_progress }"
|
||||||
>
|
@click="play_track(index, track)"
|
||||||
<template #actions>
|
>
|
||||||
<a @click.prevent.stop="open_dialog(track)">
|
<figure v-if="show_icon" class="media-left fd-has-action">
|
||||||
<span class="icon has-text-dark"
|
<span class="icon">
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
<i class="mdi mdi-file-outline" />
|
||||||
/></span>
|
</span>
|
||||||
</a>
|
</figure>
|
||||||
</template>
|
<div class="media-content fd-has-action is-clipped">
|
||||||
</list-item-track>
|
<h1
|
||||||
|
class="title is-6"
|
||||||
|
:class="{
|
||||||
|
'has-text-grey':
|
||||||
|
track.media_kind === 'podcast' && track.play_count > 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ track.title }}
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle is-7 has-text-grey">
|
||||||
|
<b>{{ track.artist }}</b>
|
||||||
|
</h2>
|
||||||
|
<h2 class="subtitle is-7 has-text-grey">
|
||||||
|
{{ track.album }}
|
||||||
|
</h2>
|
||||||
|
<progress-bar
|
||||||
|
v-if="show_progress"
|
||||||
|
:max="track.length_ms"
|
||||||
|
:value="track.seek_ms"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<a @click.prevent.stop="open_dialog(track)">
|
||||||
|
<span class="icon has-text-dark"
|
||||||
|
><i class="mdi mdi-dots-vertical mdi-18px"
|
||||||
|
/></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<teleport to="#app">
|
||||||
<modal-dialog-track
|
<modal-dialog-track
|
||||||
:show="show_details_modal"
|
:show="show_details_modal"
|
||||||
:track="selected_track"
|
:track="selected_track"
|
||||||
@close="show_details_modal = false"
|
@close="show_details_modal = false"
|
||||||
|
@play-count-changed="$emit('play-count-changed')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListItemTrack from '@/components/ListItemTrack.vue'
|
|
||||||
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
|
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
|
||||||
|
import ProgressBar from '@/components/ProgressBar.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListTracks',
|
name: 'ListTracks',
|
||||||
components: { ListItemTrack, ModalDialogTrack },
|
components: { ModalDialogTrack, ProgressBar },
|
||||||
|
|
||||||
props: ['tracks', 'uris', 'expression'],
|
props: ['tracks', 'uris', 'expression', 'show_progress', 'show_icon'],
|
||||||
|
emits: ['play-count-changed'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
export default class Albums {
|
|
||||||
constructor(
|
|
||||||
items,
|
|
||||||
options = {
|
|
||||||
hideSingles: false,
|
|
||||||
hideSpotify: false,
|
|
||||||
sort: 'Name',
|
|
||||||
group: false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
this.items = items
|
|
||||||
this.options = options
|
|
||||||
this.grouped = {}
|
|
||||||
this.sortedAndFiltered = []
|
|
||||||
this.indexList = []
|
|
||||||
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.createSortedAndFilteredList()
|
|
||||||
this.createGroupedList()
|
|
||||||
this.createIndexList()
|
|
||||||
}
|
|
||||||
|
|
||||||
getAlbumIndex(album) {
|
|
||||||
if (this.options.sort === 'Recently added') {
|
|
||||||
return album.time_added.substring(0, 4)
|
|
||||||
} else if (this.options.sort === 'Recently added (browse)') {
|
|
||||||
return this.getRecentlyAddedBrowseIndex(album.time_added)
|
|
||||||
} else if (this.options.sort === 'Recently released') {
|
|
||||||
return album.date_released ? album.date_released.substring(0, 4) : '0000'
|
|
||||||
} else if (this.options.sort === 'Release date') {
|
|
||||||
return album.date_released ? album.date_released.substring(0, 4) : '0000'
|
|
||||||
}
|
|
||||||
return album.name_sort.charAt(0).toUpperCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
getRecentlyAddedBrowseIndex(recentlyAdded) {
|
|
||||||
if (!recentlyAdded) {
|
|
||||||
return '0000'
|
|
||||||
}
|
|
||||||
|
|
||||||
const diff = new Date().getTime() - new Date(recentlyAdded).getTime()
|
|
||||||
|
|
||||||
if (diff < 86400000) {
|
|
||||||
// 24h
|
|
||||||
return 'Today'
|
|
||||||
} else if (diff < 604800000) {
|
|
||||||
// 7 days
|
|
||||||
return 'Last week'
|
|
||||||
} else if (diff < 2592000000) {
|
|
||||||
// 30 days
|
|
||||||
return 'Last month'
|
|
||||||
}
|
|
||||||
return recentlyAdded.substring(0, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
isAlbumVisible(album) {
|
|
||||||
if (this.options.hideSingles && album.track_count <= 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (this.options.hideSpotify && album.data_kind === 'spotify') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
createIndexList() {
|
|
||||||
this.indexList = [
|
|
||||||
...new Set(
|
|
||||||
this.sortedAndFiltered.map((album) => this.getAlbumIndex(album))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
createSortedAndFilteredList() {
|
|
||||||
let albumsSorted = this.items
|
|
||||||
if (
|
|
||||||
this.options.hideSingles ||
|
|
||||||
this.options.hideSpotify ||
|
|
||||||
this.options.hideOther
|
|
||||||
) {
|
|
||||||
albumsSorted = albumsSorted.filter((album) => this.isAlbumVisible(album))
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.options.sort === 'Recently added' ||
|
|
||||||
this.options.sort === 'Recently added (browse)'
|
|
||||||
) {
|
|
||||||
albumsSorted = [...albumsSorted].sort((a, b) =>
|
|
||||||
b.time_added.localeCompare(a.time_added)
|
|
||||||
)
|
|
||||||
} else if (this.options.sort === 'Recently released') {
|
|
||||||
albumsSorted = [...albumsSorted].sort((a, b) => {
|
|
||||||
if (!a.date_released) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if (!b.date_released) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return b.date_released.localeCompare(a.date_released)
|
|
||||||
})
|
|
||||||
} else if (this.options.sort === 'Release date') {
|
|
||||||
albumsSorted = [...albumsSorted].sort((a, b) => {
|
|
||||||
if (!a.date_released) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if (!b.date_released) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return a.date_released.localeCompare(b.date_released)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.sortedAndFiltered = albumsSorted
|
|
||||||
}
|
|
||||||
|
|
||||||
createGroupedList() {
|
|
||||||
if (!this.options.group) {
|
|
||||||
this.grouped = {}
|
|
||||||
}
|
|
||||||
this.grouped = this.sortedAndFiltered.reduce((r, album) => {
|
|
||||||
const idx = this.getAlbumIndex(album)
|
|
||||||
r[idx] = [...(r[idx] || []), album]
|
|
||||||
return r
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
export default class Artists {
|
|
||||||
constructor(
|
|
||||||
items,
|
|
||||||
options = {
|
|
||||||
hideSingles: false,
|
|
||||||
hideSpotify: false,
|
|
||||||
sort: 'Name',
|
|
||||||
group: false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
this.items = items
|
|
||||||
this.options = options
|
|
||||||
this.grouped = {}
|
|
||||||
this.sortedAndFiltered = []
|
|
||||||
this.indexList = []
|
|
||||||
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.createSortedAndFilteredList()
|
|
||||||
this.createGroupedList()
|
|
||||||
this.createIndexList()
|
|
||||||
}
|
|
||||||
|
|
||||||
getArtistIndex(artist) {
|
|
||||||
if (this.options.sort === 'Name') {
|
|
||||||
return artist.name_sort.charAt(0).toUpperCase()
|
|
||||||
}
|
|
||||||
return artist.time_added.substring(0, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
isArtistVisible(artist) {
|
|
||||||
if (
|
|
||||||
this.options.hideSingles &&
|
|
||||||
artist.track_count <= artist.album_count * 2
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (this.options.hideSpotify && artist.data_kind === 'spotify') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
createIndexList() {
|
|
||||||
this.indexList = [
|
|
||||||
...new Set(
|
|
||||||
this.sortedAndFiltered.map((artist) => this.getArtistIndex(artist))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
createSortedAndFilteredList() {
|
|
||||||
let artistsSorted = this.items
|
|
||||||
if (
|
|
||||||
this.options.hideSingles ||
|
|
||||||
this.options.hideSpotify ||
|
|
||||||
this.options.hideOther
|
|
||||||
) {
|
|
||||||
artistsSorted = artistsSorted.filter((artist) =>
|
|
||||||
this.isArtistVisible(artist)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.options.sort === 'Recently added') {
|
|
||||||
artistsSorted = [...artistsSorted].sort((a, b) =>
|
|
||||||
b.time_added.localeCompare(a.time_added)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.sortedAndFiltered = artistsSorted
|
|
||||||
}
|
|
||||||
|
|
||||||
createGroupedList() {
|
|
||||||
if (!this.options.group) {
|
|
||||||
this.grouped = {}
|
|
||||||
}
|
|
||||||
this.grouped = this.sortedAndFiltered.reduce((r, artist) => {
|
|
||||||
const idx = this.getArtistIndex(artist)
|
|
||||||
r[idx] = [...(r[idx] || []), artist]
|
|
||||||
return r
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
export default class Composers {
|
|
||||||
constructor(
|
|
||||||
items,
|
|
||||||
options = {
|
|
||||||
hideSingles: false,
|
|
||||||
hideSpotify: false,
|
|
||||||
sort: 'Name',
|
|
||||||
group: false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
this.items = items
|
|
||||||
this.options = options
|
|
||||||
this.grouped = {}
|
|
||||||
this.sortedAndFiltered = []
|
|
||||||
this.indexList = []
|
|
||||||
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.createSortedAndFilteredList()
|
|
||||||
this.createGroupedList()
|
|
||||||
this.createIndexList()
|
|
||||||
}
|
|
||||||
|
|
||||||
getComposerIndex(composer) {
|
|
||||||
if (this.options.sort === 'Name') {
|
|
||||||
return composer.name_sort.charAt(0).toUpperCase()
|
|
||||||
}
|
|
||||||
return composer.time_added.substring(0, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
isComposerVisible(composer) {
|
|
||||||
if (
|
|
||||||
this.options.hideSingles &&
|
|
||||||
composer.track_count <= composer.album_count * 2
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (this.options.hideSpotify && composer.data_kind === 'spotify') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
createIndexList() {
|
|
||||||
this.indexList = [
|
|
||||||
...new Set(
|
|
||||||
this.sortedAndFiltered.map((composer) =>
|
|
||||||
this.getComposerIndex(composer)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
createSortedAndFilteredList() {
|
|
||||||
let composersSorted = this.items
|
|
||||||
if (
|
|
||||||
this.options.hideSingles ||
|
|
||||||
this.options.hideSpotify ||
|
|
||||||
this.options.hideOther
|
|
||||||
) {
|
|
||||||
composersSorted = composersSorted.filter((composer) =>
|
|
||||||
this.isComposerVisible(composer)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.options.sort === 'Recently added') {
|
|
||||||
composersSorted = [...composersSorted].sort((a, b) =>
|
|
||||||
b.time_added.localeCompare(a.time_added)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.sortedAndFiltered = composersSorted
|
|
||||||
}
|
|
||||||
|
|
||||||
createGroupedList() {
|
|
||||||
if (!this.options.group) {
|
|
||||||
this.grouped = {}
|
|
||||||
}
|
|
||||||
this.grouped = this.sortedAndFiltered.reduce((r, composer) => {
|
|
||||||
const idx = this.getComposerIndex(composer)
|
|
||||||
r[idx] = [...(r[idx] || []), composer]
|
|
||||||
return r
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
}
|
|
233
web-src/src/lib/GroupByList.js
Normal file
233
web-src/src/lib/GroupByList.js
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
const GROUP_KEY_NONE = 'GROUP_KEY_NONE'
|
||||||
|
|
||||||
|
export function noop() {
|
||||||
|
return {
|
||||||
|
compareFn: null,
|
||||||
|
groupKeyFn: (item) => GROUP_KEY_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep default sorting of item list and build index and group by given field
|
||||||
|
*/
|
||||||
|
export function bySortName(field, defaultValue = '_') {
|
||||||
|
return {
|
||||||
|
// Keep the sort order of the original item list
|
||||||
|
// Assumes that the list is already ordered by name
|
||||||
|
compareFn: null,
|
||||||
|
|
||||||
|
groupKeyFn: (item) => {
|
||||||
|
const fieldValue = item[field] || defaultValue
|
||||||
|
return fieldValue.charAt(0).toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function byName(field, defaultValue = '_') {
|
||||||
|
return {
|
||||||
|
compareFn: (a, b) => {
|
||||||
|
const fieldA = a[field] || defaultValue
|
||||||
|
const fieldB = b[field] || defaultValue
|
||||||
|
|
||||||
|
return fieldA.localeCompare(fieldB)
|
||||||
|
},
|
||||||
|
|
||||||
|
groupKeyFn: (item) => {
|
||||||
|
const fieldValue = item[field] || defaultValue
|
||||||
|
return fieldValue.charAt(0).toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function byYear(field, { direction = 'asc', defaultValue = '0000' }) {
|
||||||
|
return {
|
||||||
|
compareFn: (a, b) => {
|
||||||
|
const fieldA = a[field] || defaultValue
|
||||||
|
const fieldB = b[field] || defaultValue
|
||||||
|
|
||||||
|
const result = fieldA.localeCompare(fieldB)
|
||||||
|
return direction === 'asc' ? result : result * -1
|
||||||
|
},
|
||||||
|
|
||||||
|
groupKeyFn: (item) => {
|
||||||
|
const fieldValue = item[field] || defaultValue
|
||||||
|
return fieldValue.substring(0, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function byDateSinceToday(field, defaultValue = '0000') {
|
||||||
|
return {
|
||||||
|
compareFn: (a, b) => {
|
||||||
|
const fieldA = a[field] || defaultValue
|
||||||
|
const fieldB = b[field] || defaultValue
|
||||||
|
|
||||||
|
return fieldB.localeCompare(fieldA)
|
||||||
|
},
|
||||||
|
|
||||||
|
groupKeyFn: (item) => {
|
||||||
|
const fieldValue = item[field]
|
||||||
|
|
||||||
|
if (!fieldValue) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = new Date().getTime() - new Date(fieldValue).getTime()
|
||||||
|
|
||||||
|
if (diff < 86400000) {
|
||||||
|
// 24h
|
||||||
|
return 'Today'
|
||||||
|
} else if (diff < 604800000) {
|
||||||
|
// 7 days
|
||||||
|
return 'Last week'
|
||||||
|
} else if (diff < 2592000000) {
|
||||||
|
// 30 days
|
||||||
|
return 'Last month'
|
||||||
|
}
|
||||||
|
return fieldValue.substring(0, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupByList {
|
||||||
|
constructor({ items = [], total = 0, offset = 0, limit = -1 } = {}) {
|
||||||
|
this.items = items
|
||||||
|
this.total = total
|
||||||
|
this.offset = offset
|
||||||
|
this.limit = limit
|
||||||
|
|
||||||
|
this.count = items.length
|
||||||
|
this.indexList = []
|
||||||
|
this.group(noop())
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
return this.itemsByGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty() {
|
||||||
|
return !this.items || this.items.length <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
group(options, filterFns = []) {
|
||||||
|
const itemsFiltered = filterFns
|
||||||
|
? this.items.filter((item) => filterFns.every((fn) => fn(item)))
|
||||||
|
: this.items
|
||||||
|
this.count = itemsFiltered.length
|
||||||
|
|
||||||
|
// Sort item list
|
||||||
|
let itemsSorted = options.compareFn
|
||||||
|
? [...itemsFiltered].sort(options.compareFn)
|
||||||
|
: itemsFiltered
|
||||||
|
|
||||||
|
// Create index list
|
||||||
|
this.indexList = [...new Set(itemsSorted.map(options.groupKeyFn))]
|
||||||
|
|
||||||
|
// Group item list
|
||||||
|
this.itemsByGroup = itemsSorted.reduce((r, item) => {
|
||||||
|
const groupKey = options.groupKeyFn(item)
|
||||||
|
r[groupKey] = [...(r[groupKey] || []), item]
|
||||||
|
return r
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
// Use a new index for each iterator. This makes multiple
|
||||||
|
// iterations over the iterable safe for non-trivial cases,
|
||||||
|
// such as use of break or nested looping over the same iterable.
|
||||||
|
let groupIndex = -1
|
||||||
|
let itemIndex = -1
|
||||||
|
|
||||||
|
return {
|
||||||
|
next: () => {
|
||||||
|
/*
|
||||||
|
console.log(
|
||||||
|
'[group-by-list] itemIndex=' +
|
||||||
|
itemIndex +
|
||||||
|
', groupIndex=' +
|
||||||
|
groupIndex
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
if (groupIndex >= this.indexList.length) {
|
||||||
|
// We reached the end of all groups and items
|
||||||
|
//
|
||||||
|
// This should never happen, as the we already
|
||||||
|
// return "done" after we reached the last item
|
||||||
|
// of the last group
|
||||||
|
/*
|
||||||
|
console.log(
|
||||||
|
'[group-by-list] done! (groupIndex >= this.indexList.length)'
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
return { done: true }
|
||||||
|
} else if (groupIndex < 0) {
|
||||||
|
// We start iterating
|
||||||
|
//
|
||||||
|
// Return the first group title as the next item
|
||||||
|
++groupIndex
|
||||||
|
itemIndex = 0
|
||||||
|
|
||||||
|
if (this.indexList[groupIndex] !== GROUP_KEY_NONE) {
|
||||||
|
// Only return the group, if it is not the "noop" default group
|
||||||
|
return {
|
||||||
|
value: {
|
||||||
|
groupKey: this.indexList[groupIndex],
|
||||||
|
itemId: this.indexList[groupIndex],
|
||||||
|
isItem: false,
|
||||||
|
item: {}
|
||||||
|
},
|
||||||
|
done: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentGroupKey = this.indexList[groupIndex]
|
||||||
|
let currentGroupItems = this.itemsByGroup[currentGroupKey]
|
||||||
|
|
||||||
|
if (itemIndex < currentGroupItems.length) {
|
||||||
|
// We are in a group with items left
|
||||||
|
//
|
||||||
|
// Return the current item and increment the item index
|
||||||
|
const currentItem = this.itemsByGroup[currentGroupKey][itemIndex++]
|
||||||
|
return {
|
||||||
|
value: {
|
||||||
|
groupKey: currentGroupKey,
|
||||||
|
itemId: currentItem.id,
|
||||||
|
isItem: true,
|
||||||
|
item: currentItem
|
||||||
|
},
|
||||||
|
done: false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We reached the end of the current groups item list
|
||||||
|
//
|
||||||
|
// Move to the next group and return the group key/title
|
||||||
|
// as the next item
|
||||||
|
++groupIndex
|
||||||
|
itemIndex = 0
|
||||||
|
|
||||||
|
if (groupIndex < this.indexList.length) {
|
||||||
|
currentGroupKey = this.indexList[groupIndex]
|
||||||
|
return {
|
||||||
|
value: {
|
||||||
|
groupKey: currentGroupKey,
|
||||||
|
itemId: currentGroupKey,
|
||||||
|
isItem: false,
|
||||||
|
item: {}
|
||||||
|
},
|
||||||
|
done: false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No group left, we are done iterating
|
||||||
|
/*
|
||||||
|
console.log(
|
||||||
|
'[group-by-list] done! (groupIndex >= this.indexList.length)'
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
return { done: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -140,7 +140,7 @@ section.hero + section.fd-content {
|
|||||||
|
|
||||||
/* Set minimum height to hide "option" section */
|
/* Set minimum height to hide "option" section */
|
||||||
.fd-content-with-option {
|
.fd-content-with-option {
|
||||||
min-height: calc(100vh - 3.25rem - 3.25rem - 5rem);
|
min-height: calc(100vh - 3.25rem - 3.25rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now playing page */
|
/* Now playing page */
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
<template #options>
|
||||||
<index-button-list :index="albums_list.indexList" />
|
<index-button-list :index="albums.indexList" />
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
@ -44,17 +44,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
||||||
<dropdown-menu v-model="sort" :options="sort_options" />
|
<dropdown-menu
|
||||||
|
v-model="selected_groupby_option_name"
|
||||||
|
:options="groupby_option_names"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">Albums</p>
|
<p class="title is-4">Albums</p>
|
||||||
<p class="heading">{{ albums_list.sortedAndFiltered.length }} Albums</p>
|
<p class="heading">{{ albums.count }} Albums</p>
|
||||||
</template>
|
</template>
|
||||||
<template #heading-right />
|
<template #heading-right />
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums :albums="albums_list" />
|
<list-albums :albums="albums" />
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -68,7 +71,7 @@ import ListAlbums from '@/components/ListAlbums.vue'
|
|||||||
import DropdownMenu from '@/components/DropdownMenu.vue'
|
import DropdownMenu from '@/components/DropdownMenu.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import * as types from '@/store/mutation_types'
|
import * as types from '@/store/mutation_types'
|
||||||
import Albums from '@/lib/Albums'
|
import { bySortName, byYear, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -76,16 +79,7 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.albums = response.data
|
vm.albums_list = new GroupByList(response.data)
|
||||||
vm.index_list = [
|
|
||||||
...new Set(
|
|
||||||
vm.albums.items
|
|
||||||
.filter(
|
|
||||||
(album) => !vm.$store.state.hide_singles || album.track_count > 2
|
|
||||||
)
|
|
||||||
.map((album) => album.name_sort.charAt(0).toUpperCase())
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +98,9 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
if (this.albums.items.length > 0) {
|
if (!this.albums_list.isEmpty()) {
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -118,19 +113,53 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
albums: { items: [] },
|
albums_list: new GroupByList(),
|
||||||
sort_options: ['Name', 'Recently added', 'Recently released']
|
|
||||||
|
// List of group by/sort options for itemsGroupByList
|
||||||
|
groupby_options: [
|
||||||
|
{ name: 'Name', options: bySortName('name_sort') },
|
||||||
|
{
|
||||||
|
name: 'Recently added',
|
||||||
|
options: byYear('time_added', {
|
||||||
|
direction: 'desc',
|
||||||
|
defaultValue: '0000'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Recently released',
|
||||||
|
options: byYear('date_released', {
|
||||||
|
direction: 'desc',
|
||||||
|
defaultValue: '0000'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
albums_list() {
|
albums() {
|
||||||
return new Albums(this.albums.items, {
|
const groupBy = this.groupby_options.find(
|
||||||
hideSingles: this.hide_singles,
|
(o) => o.name === this.selected_groupby_option_name
|
||||||
hideSpotify: this.hide_spotify,
|
)
|
||||||
sort: this.sort,
|
this.albums_list.group(groupBy.options, [
|
||||||
group: true
|
(album) => !this.hide_singles || album.track_count <= 2,
|
||||||
})
|
(album) => !this.hide_spotify || album.data_kind !== 'spotify'
|
||||||
|
])
|
||||||
|
|
||||||
|
return this.albums_list
|
||||||
|
},
|
||||||
|
|
||||||
|
groupby_option_names() {
|
||||||
|
return [...this.groupby_options].map((o) => o.name)
|
||||||
|
},
|
||||||
|
|
||||||
|
selected_groupby_option_name: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.albums_sort
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit(types.ALBUMS_SORT, value)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
spotify_enabled() {
|
spotify_enabled() {
|
||||||
@ -153,15 +182,6 @@ export default {
|
|||||||
set(value) {
|
set(value) {
|
||||||
this.$store.commit(types.HIDE_SPOTIFY, value)
|
this.$store.commit(types.HIDE_SPOTIFY, value)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
sort: {
|
|
||||||
get() {
|
|
||||||
return this.$store.state.albums_sort
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$store.commit(types.ALBUMS_SORT, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
||||||
<dropdown-menu v-model="sort" :options="sort_options" />
|
<dropdown-menu
|
||||||
|
v-model="selected_groupby_option_name"
|
||||||
|
:options="groupby_option_names"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -36,7 +39,7 @@
|
|||||||
>{{ artist.track_count }} tracks</a
|
>{{ artist.track_count }} tracks</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<list-albums :albums="albums_list" />
|
<list-albums :albums="albums" :hide_group_title="true" />
|
||||||
<modal-dialog-artist
|
<modal-dialog-artist
|
||||||
:show="show_artist_details_modal"
|
:show="show_artist_details_modal"
|
||||||
:artist="artist"
|
:artist="artist"
|
||||||
@ -53,7 +56,7 @@ import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
|
|||||||
import DropdownMenu from '@/components/DropdownMenu.vue'
|
import DropdownMenu from '@/components/DropdownMenu.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import * as types from '@/store/mutation_types'
|
import * as types from '@/store/mutation_types'
|
||||||
import Albums from '@/lib/Albums'
|
import { bySortName, byYear, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -65,7 +68,7 @@ const dataObject = {
|
|||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.artist = response[0].data
|
vm.artist = response[0].data
|
||||||
vm.albums = response[1].data
|
vm.albums_list = new GroupByList(response[1].data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,22 +97,39 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
artist: {},
|
artist: {},
|
||||||
albums: { items: [] },
|
albums_list: new GroupByList(),
|
||||||
|
|
||||||
|
// List of group by/sort options for itemsGroupByList
|
||||||
|
groupby_options: [
|
||||||
|
{ name: 'Name', options: bySortName('name_sort') },
|
||||||
|
{
|
||||||
|
name: 'Release date',
|
||||||
|
options: byYear('date_released', {
|
||||||
|
direction: 'asc',
|
||||||
|
defaultValue: '0000'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
sort_options: ['Name', 'Release date'],
|
|
||||||
show_artist_details_modal: false
|
show_artist_details_modal: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
albums_list() {
|
albums() {
|
||||||
return new Albums(this.albums.items, {
|
const groupBy = this.groupby_options.find(
|
||||||
sort: this.sort,
|
(o) => o.name === this.selected_groupby_option_name
|
||||||
group: false
|
)
|
||||||
})
|
this.albums_list.group(groupBy.options)
|
||||||
|
|
||||||
|
return this.albums_list
|
||||||
},
|
},
|
||||||
|
|
||||||
sort: {
|
groupby_option_names() {
|
||||||
|
return [...this.groupby_options].map((o) => o.name)
|
||||||
|
},
|
||||||
|
|
||||||
|
selected_groupby_option_name: {
|
||||||
get() {
|
get() {
|
||||||
return this.$store.state.artist_albums_sort
|
return this.$store.state.artist_albums_sort
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
<template #options>
|
||||||
<index-button-list :index="artists_list.indexList" />
|
<index-button-list :index="artists.indexList" />
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
@ -44,19 +44,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
<p class="heading" style="margin-bottom: 24px">Sort by</p>
|
||||||
<dropdown-menu v-model="sort" :options="sort_options" />
|
<dropdown-menu
|
||||||
|
v-model="selected_groupby_option_name"
|
||||||
|
:options="groupby_option_names"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">Artists</p>
|
<p class="title is-4">Artists</p>
|
||||||
<p class="heading">
|
<p class="heading">{{ artists.count }} Artists</p>
|
||||||
{{ artists_list.sortedAndFiltered.length }} Artists
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
<template #heading-right />
|
<template #heading-right />
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-artists :artists="artists_list" />
|
<list-artists :artists="artists" />
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -70,7 +71,7 @@ import ListArtists from '@/components/ListArtists.vue'
|
|||||||
import DropdownMenu from '@/components/DropdownMenu.vue'
|
import DropdownMenu from '@/components/DropdownMenu.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import * as types from '@/store/mutation_types'
|
import * as types from '@/store/mutation_types'
|
||||||
import Artists from '@/lib/Artists'
|
import { bySortName, byYear, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -78,7 +79,7 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.artists = response.data
|
vm.artists_list = new GroupByList(response.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +98,9 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
if (this.artists.items.length > 0) {
|
if (!this.artists_list.isEmpty()) {
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -111,19 +113,54 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
artists: { items: [] },
|
// Original data from API call
|
||||||
sort_options: ['Name', 'Recently added']
|
artists_list: new GroupByList(),
|
||||||
|
|
||||||
|
// List of group by/sort options for itemsGroupByList
|
||||||
|
groupby_options: [
|
||||||
|
{ name: 'Name', options: bySortName('name_sort') },
|
||||||
|
{
|
||||||
|
name: 'Recently added',
|
||||||
|
options: byYear('time_added', {
|
||||||
|
direction: 'desc',
|
||||||
|
defaultValue: '0000'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
artists_list() {
|
// Wraps GroupByList and updates it if filter or sort changes
|
||||||
return new Artists(this.artists.items, {
|
artists() {
|
||||||
hideSingles: this.hide_singles,
|
if (!this.artists_list) {
|
||||||
hideSpotify: this.hide_spotify,
|
return []
|
||||||
sort: this.sort,
|
}
|
||||||
group: true
|
|
||||||
})
|
const groupBy = this.groupby_options.find(
|
||||||
|
(o) => o.name === this.selected_groupby_option_name
|
||||||
|
)
|
||||||
|
this.artists_list.group(groupBy.options, [
|
||||||
|
(artist) =>
|
||||||
|
!this.hide_singles || artist.track_count <= artist.album_count * 2,
|
||||||
|
(artist) => !this.hide_spotify || artist.data_kind !== 'spotify'
|
||||||
|
])
|
||||||
|
|
||||||
|
return this.artists_list
|
||||||
|
},
|
||||||
|
|
||||||
|
// List for the drop down menu
|
||||||
|
groupby_option_names() {
|
||||||
|
return [...this.groupby_options].map((o) => o.name)
|
||||||
|
},
|
||||||
|
|
||||||
|
selected_groupby_option_name: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.artists_sort
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit(types.ARTISTS_SORT, value)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
spotify_enabled() {
|
spotify_enabled() {
|
||||||
@ -146,23 +183,10 @@ export default {
|
|||||||
set(value) {
|
set(value) {
|
||||||
this.$store.commit(types.HIDE_SPOTIFY, value)
|
this.$store.commit(types.HIDE_SPOTIFY, value)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
sort: {
|
|
||||||
get() {
|
|
||||||
return this.$store.state.artists_sort
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$store.commit(types.ARTISTS_SORT, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {}
|
||||||
scrollToTop: function () {
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,16 +4,14 @@
|
|||||||
|
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
<template #options>
|
||||||
<index-button-list :index="albums_list.indexList" />
|
<index-button-list :index="albums.indexList" />
|
||||||
</template>
|
</template>
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">Audiobooks</p>
|
<p class="title is-4">Audiobooks</p>
|
||||||
<p class="heading">
|
<p class="heading">{{ albums.count }} Audiobooks</p>
|
||||||
{{ albums_list.sortedAndFiltered.length }} Audiobooks
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums :albums="albums_list" />
|
<list-albums :albums="albums" />
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -25,7 +23,7 @@ import IndexButtonList from '@/components/IndexButtonList.vue'
|
|||||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||||
import ListAlbums from '@/components/ListAlbums.vue'
|
import ListAlbums from '@/components/ListAlbums.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import Albums from '@/lib/Albums'
|
import { bySortName, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -33,7 +31,8 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.albums = response.data
|
vm.albums = new GroupByList(response.data)
|
||||||
|
vm.albums.group(bySortName('name_sort'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +50,12 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
|
if (!this.albums.isEmpty()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
dataObject.set(vm, response)
|
dataObject.set(vm, response)
|
||||||
@ -61,16 +65,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
albums: { items: [] }
|
albums: new GroupByList()
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
albums_list() {
|
|
||||||
return new Albums(this.albums.items, {
|
|
||||||
sort: 'Name',
|
|
||||||
group: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<p class="heading has-text-centered-mobile">
|
<p class="heading has-text-centered-mobile">
|
||||||
{{ artist.album_count }} albums
|
{{ artist.album_count }} albums
|
||||||
</p>
|
</p>
|
||||||
<list-albums :albums="albums.items" />
|
<list-albums :albums="albums" />
|
||||||
<modal-dialog-artist
|
<modal-dialog-artist
|
||||||
:show="show_artist_details_modal"
|
:show="show_artist_details_modal"
|
||||||
:artist="artist"
|
:artist="artist"
|
||||||
@ -40,6 +40,7 @@ import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
|||||||
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 webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
import { GroupByList } from '../lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -51,7 +52,7 @@ const dataObject = {
|
|||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.artist = response[0].data
|
vm.artist = response[0].data
|
||||||
vm.albums = response[1].data
|
vm.albums = new GroupByList(response[1].data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +65,12 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
|
if (!this.albums.isEmpty()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
dataObject.set(vm, response)
|
dataObject.set(vm, response)
|
||||||
@ -75,7 +81,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
artist: {},
|
artist: {},
|
||||||
albums: {},
|
albums: new GroupByList(),
|
||||||
|
|
||||||
show_artist_details_modal: false
|
show_artist_details_modal: false
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,15 @@
|
|||||||
|
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
<template #options>
|
||||||
<index-button-list :index="artists_list.indexList" />
|
<index-button-list :index="artists.indexList" />
|
||||||
</template>
|
</template>
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">Authors</p>
|
<p class="title is-4">Authors</p>
|
||||||
<p class="heading">
|
<p class="heading">{{ artists.count }} Authors</p>
|
||||||
{{ artists_list.sortedAndFiltered.length }} Authors
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
<template #heading-right />
|
<template #heading-right />
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-artists :artists="artists_list" />
|
<list-artists :artists="artists" />
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -26,7 +24,7 @@ import TabsAudiobooks from '@/components/TabsAudiobooks.vue'
|
|||||||
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 webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import Artists from '@/lib/Artists'
|
import { bySortName, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -34,7 +32,7 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.artists = response.data
|
vm.artists_list = new GroupByList(response.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +50,12 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
|
if (!this.artists_list.isEmpty()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
dataObject.set(vm, response)
|
dataObject.set(vm, response)
|
||||||
@ -62,16 +65,17 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
artists: { items: [] }
|
artists_list: new GroupByList()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
artists_list() {
|
artists() {
|
||||||
return new Artists(this.artists.items, {
|
if (!this.artists_list) {
|
||||||
sort: 'Name',
|
return []
|
||||||
group: true
|
}
|
||||||
})
|
this.artists_list.group(bySortName('name_sort'))
|
||||||
|
return this.artists_list
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<p class="heading">albums</p>
|
<p class="heading">albums</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums :albums="recently_added.items" />
|
<list-albums :albums="recently_added" />
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<nav class="level">
|
<nav class="level">
|
||||||
@ -54,6 +54,7 @@ import TabsMusic from '@/components/TabsMusic.vue'
|
|||||||
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 webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
import { GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -74,7 +75,7 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.recently_added = response[0].data.albums
|
vm.recently_added = new GroupByList(response[0].data.albums)
|
||||||
vm.recently_played = response[1].data.tracks
|
vm.recently_played = response[1].data.tracks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,6 +89,7 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
@ -98,7 +100,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
recently_added: { items: [] },
|
recently_added: [],
|
||||||
recently_played: { items: [] },
|
recently_played: { items: [] },
|
||||||
|
|
||||||
show_track_details_modal: false,
|
show_track_details_modal: false,
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<p class="heading">albums</p>
|
<p class="heading">albums</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums :albums="albums_list" />
|
<list-albums :albums="recently_added" />
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -20,7 +20,7 @@ import TabsMusic from '@/components/TabsMusic.vue'
|
|||||||
import ListAlbums from '@/components/ListAlbums.vue'
|
import ListAlbums from '@/components/ListAlbums.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import Albums from '@/lib/Albums'
|
import { byDateSinceToday, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -34,7 +34,13 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.recently_added = response.data.albums
|
vm.recently_added = new GroupByList(response.data.albums)
|
||||||
|
vm.recently_added.group(
|
||||||
|
byDateSinceToday('time_added', {
|
||||||
|
direction: 'desc',
|
||||||
|
defaultValue: '0000'
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +53,12 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
|
if (!this.recently_added.isEmpty()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
dataObject.set(vm, response)
|
dataObject.set(vm, response)
|
||||||
@ -57,18 +68,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
recently_added: { items: [] }
|
recently_added: new GroupByList()
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
albums_list() {
|
|
||||||
return new Albums(this.recently_added.items, {
|
|
||||||
hideSingles: false,
|
|
||||||
hideSpotify: false,
|
|
||||||
sort: 'Recently added (browse)',
|
|
||||||
group: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
|
||||||
<index-button-list :index="index_list" />
|
|
||||||
</template>
|
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">
|
<p class="title is-4">
|
||||||
{{ name }}
|
{{ name }}
|
||||||
@ -27,28 +24,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<p class="heading has-text-centered-mobile">
|
<p class="heading has-text-centered-mobile">
|
||||||
{{ composer_albums.total }} albums |
|
{{ albums_list.total }} albums |
|
||||||
<a class="has-text-link" @click="open_tracks">tracks</a>
|
<a class="has-text-link" @click="open_tracks">tracks</a>
|
||||||
</p>
|
</p>
|
||||||
<list-item-albums
|
<list-albums :albums="albums_list" :hide_group_title="true" />
|
||||||
v-for="album in composer_albums.items"
|
|
||||||
:key="album.id"
|
|
||||||
:album="album"
|
|
||||||
@click="open_album(album)"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<a @click="open_dialog(album)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-albums>
|
|
||||||
<modal-dialog-album
|
|
||||||
:show="show_details_modal"
|
|
||||||
:album="selected_album"
|
|
||||||
@close="show_details_modal = false"
|
|
||||||
/>
|
|
||||||
<modal-dialog-composer
|
<modal-dialog-composer
|
||||||
:show="show_composer_details_modal"
|
:show="show_composer_details_modal"
|
||||||
:composer="{ name: name }"
|
:composer="{ name: name }"
|
||||||
@ -61,10 +41,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||||
import ListItemAlbums from '@/components/ListItemAlbum.vue'
|
import ListAlbums from '@/components/ListAlbums.vue'
|
||||||
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
|
|
||||||
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
|
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
import { GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -73,7 +53,7 @@ const dataObject = {
|
|||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.name = vm.$route.params.composer
|
vm.name = vm.$route.params.composer
|
||||||
vm.composer_albums = response.data.albums
|
vm.albums_list = new GroupByList(response.data.albums)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +61,7 @@ export default {
|
|||||||
name: 'PageComposer',
|
name: 'PageComposer',
|
||||||
components: {
|
components: {
|
||||||
ContentWithHeading,
|
ContentWithHeading,
|
||||||
ListItemAlbums,
|
ListAlbums,
|
||||||
ModalDialogAlbum,
|
|
||||||
ModalDialogComposer
|
ModalDialogComposer
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -102,29 +81,13 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: '',
|
name: '',
|
||||||
composer_albums: { items: [] },
|
albums_list: new GroupByList(),
|
||||||
show_details_modal: false,
|
|
||||||
selected_album: {},
|
|
||||||
|
|
||||||
show_composer_details_modal: false
|
show_composer_details_modal: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
index_list() {
|
|
||||||
return [
|
|
||||||
...new Set(
|
|
||||||
this.composer_albums.items.map((album) =>
|
|
||||||
album.name_sort.charAt(0).toUpperCase()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
open_tracks: function () {
|
open_tracks: function () {
|
||||||
this.show_details_modal = false
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'ComposerTracks',
|
name: 'ComposerTracks',
|
||||||
params: { composer: this.name }
|
params: { composer: this.name }
|
||||||
@ -136,15 +99,6 @@ export default {
|
|||||||
'composer is "' + this.name + '" and media_kind is music',
|
'composer is "' + this.name + '" and media_kind is music',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
},
|
|
||||||
|
|
||||||
open_album: function (album) {
|
|
||||||
this.$router.push({ path: '/music/albums/' + album.id })
|
|
||||||
},
|
|
||||||
|
|
||||||
open_dialog: function (album) {
|
|
||||||
this.selected_album = album
|
|
||||||
this.show_details_modal = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
|
||||||
<index-button-list :index="index_list" />
|
|
||||||
</template>
|
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">
|
<p class="title is-4">
|
||||||
{{ composer }}
|
{{ composer }}
|
||||||
@ -30,25 +27,7 @@
|
|||||||
<a class="has-text-link" @click="open_albums">albums</a> |
|
<a class="has-text-link" @click="open_albums">albums</a> |
|
||||||
{{ tracks.total }} tracks
|
{{ tracks.total }} tracks
|
||||||
</p>
|
</p>
|
||||||
<list-item-track
|
<list-tracks :tracks="tracks.items" :expression="play_expression" />
|
||||||
v-for="(track, index) in rated_tracks"
|
|
||||||
:key="track.id"
|
|
||||||
:track="track"
|
|
||||||
@click="play_track(index)"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<a @click.prevent.stop="open_dialog(track)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-track>
|
|
||||||
<modal-dialog-track
|
|
||||||
:show="show_details_modal"
|
|
||||||
:track="selected_track"
|
|
||||||
@close="show_details_modal = false"
|
|
||||||
/>
|
|
||||||
<modal-dialog-composer
|
<modal-dialog-composer
|
||||||
:show="show_composer_details_modal"
|
:show="show_composer_details_modal"
|
||||||
:composer="{ name: composer }"
|
:composer="{ name: composer }"
|
||||||
@ -61,8 +40,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||||
import ListItemTrack from '@/components/ListItemTrack.vue'
|
import ListTracks from '@/components/ListTracks.vue'
|
||||||
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
|
|
||||||
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
|
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
|
||||||
@ -81,8 +59,7 @@ export default {
|
|||||||
name: 'PageComposerTracks',
|
name: 'PageComposerTracks',
|
||||||
components: {
|
components: {
|
||||||
ContentWithHeading,
|
ContentWithHeading,
|
||||||
ListItemTrack,
|
ListTracks,
|
||||||
ModalDialogTrack,
|
|
||||||
ModalDialogComposer
|
ModalDialogComposer
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -104,30 +81,13 @@ export default {
|
|||||||
tracks: { items: [] },
|
tracks: { items: [] },
|
||||||
composer: '',
|
composer: '',
|
||||||
|
|
||||||
min_rating: 0,
|
|
||||||
|
|
||||||
show_details_modal: false,
|
|
||||||
selected_track: {},
|
|
||||||
|
|
||||||
show_composer_details_modal: false
|
show_composer_details_modal: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
index_list() {
|
play_expression() {
|
||||||
return [
|
return 'composer is "' + this.composer + '" and media_kind is music'
|
||||||
...new Set(
|
|
||||||
this.tracks.items.map((track) =>
|
|
||||||
track.title_sort.charAt(0).toUpperCase()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
rated_tracks() {
|
|
||||||
return this.tracks.items.filter(
|
|
||||||
(track) => track.rating >= this.min_rating
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -141,30 +101,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
play: function () {
|
play: function () {
|
||||||
webapi.player_play_expression(
|
webapi.player_play_expression(this.play_expression, true)
|
||||||
'composer is "' + this.composer + '" and media_kind is music',
|
|
||||||
true
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
play_track: function (position) {
|
|
||||||
webapi.player_play_expression(
|
|
||||||
'composer is "' + this.composer + '" and media_kind is music',
|
|
||||||
false,
|
|
||||||
position
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
show_rating: function (rating) {
|
|
||||||
if (rating === 0.5) {
|
|
||||||
rating = 0
|
|
||||||
}
|
|
||||||
this.min_rating = Math.ceil(rating) * 20
|
|
||||||
},
|
|
||||||
|
|
||||||
open_dialog: function (track) {
|
|
||||||
this.selected_track = track
|
|
||||||
this.show_details_modal = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,14 @@
|
|||||||
|
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
<template #options>
|
||||||
<index-button-list :index="composers_list.indexList" />
|
<index-button-list :index="composers.indexList" />
|
||||||
</template>
|
</template>
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">
|
<p class="title is-4">Composers</p>
|
||||||
{{ heading }}
|
|
||||||
</p>
|
|
||||||
<p class="heading">{{ composers.total }} composers</p>
|
<p class="heading">{{ composers.total }} composers</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-composers :composers="composers_list" />
|
<list-composers :composers="composers" />
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -25,7 +23,7 @@ import TabsMusic from '@/components/TabsMusic.vue'
|
|||||||
import IndexButtonList from '@/components/IndexButtonList.vue'
|
import IndexButtonList from '@/components/IndexButtonList.vue'
|
||||||
import ListComposers from '@/components/ListComposers.vue'
|
import ListComposers from '@/components/ListComposers.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import Composers from '@/lib/Composers'
|
import { byName, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -33,13 +31,8 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
if (response.data.composers) {
|
vm.composers = new GroupByList(response.data)
|
||||||
vm.composers = response.data.composers
|
vm.composers.group(byName('name_sort'))
|
||||||
vm.heading = vm.$route.params.genre
|
|
||||||
} else {
|
|
||||||
vm.composers = response.data
|
|
||||||
vm.heading = 'Composers'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +45,12 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
|
if (!this.composers.isEmpty()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
dataObject.set(vm, response)
|
dataObject.set(vm, response)
|
||||||
@ -62,46 +60,11 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
composers: { items: [] },
|
composers: new GroupByList()
|
||||||
heading: '',
|
|
||||||
|
|
||||||
show_details_modal: false,
|
|
||||||
selected_composer: {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
methods: {}
|
||||||
index_list() {
|
|
||||||
return [
|
|
||||||
...new Set(
|
|
||||||
this.composers.items.map((composer) =>
|
|
||||||
composer.name.charAt(0).toUpperCase()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
composers_list() {
|
|
||||||
return new Composers(this.composers.items, {
|
|
||||||
sort: 'Name',
|
|
||||||
group: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
open_composer: function (composer) {
|
|
||||||
this.$router.push({
|
|
||||||
name: 'ComposerAlbums',
|
|
||||||
params: { composer: composer.name }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
open_dialog: function (composer) {
|
|
||||||
this.selected_composer = composer
|
|
||||||
this.show_details_modal = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -24,94 +24,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div
|
<list-directories :directories="files.directories" />
|
||||||
v-if="$route.query.directory"
|
|
||||||
class="media"
|
|
||||||
@click="open_parent_directory()"
|
|
||||||
>
|
|
||||||
<figure class="media-left fd-has-action">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="mdi mdi-subdirectory-arrow-left" />
|
|
||||||
</span>
|
|
||||||
</figure>
|
|
||||||
<div class="media-content fd-has-action is-clipped">
|
|
||||||
<h1 class="title is-6">..</h1>
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<list-item-directory
|
<list-playlists :playlists="files.playlists.items" />
|
||||||
v-for="directory in files.directories"
|
|
||||||
:key="directory.path"
|
|
||||||
:directory="directory"
|
|
||||||
@click="open_directory(directory)"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<a @click="open_directory_dialog(directory)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-directory>
|
|
||||||
|
|
||||||
<list-item-playlist
|
<list-tracks
|
||||||
v-for="playlist in files.playlists.items"
|
:tracks="files.tracks.items"
|
||||||
:key="playlist.id"
|
:expression="play_expression"
|
||||||
:playlist="playlist"
|
:show_icon="true"
|
||||||
@click="open_playlist(playlist)"
|
/>
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="mdi mdi-library-music" />
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<a @click="open_playlist_dialog(playlist)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-playlist>
|
|
||||||
|
|
||||||
<list-item-track
|
|
||||||
v-for="(track, index) in files.tracks.items"
|
|
||||||
:key="track.id"
|
|
||||||
:track="track"
|
|
||||||
@click="play_track(index)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="mdi mdi-file-outline" />
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<a @click="open_track_dialog(track)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-track>
|
|
||||||
|
|
||||||
<modal-dialog-directory
|
<modal-dialog-directory
|
||||||
:show="show_directory_details_modal"
|
:show="show_directory_details_modal"
|
||||||
:directory="selected_directory"
|
:directory="selected_directory"
|
||||||
@close="show_directory_details_modal = false"
|
@close="show_directory_details_modal = false"
|
||||||
/>
|
/>
|
||||||
<modal-dialog-playlist
|
|
||||||
:show="show_playlist_details_modal"
|
|
||||||
:playlist="selected_playlist"
|
|
||||||
@close="show_playlist_details_modal = false"
|
|
||||||
/>
|
|
||||||
<modal-dialog-track
|
|
||||||
:show="show_track_details_modal"
|
|
||||||
:track="selected_track"
|
|
||||||
@close="show_track_details_modal = false"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -119,12 +46,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||||
import ListItemDirectory from '@/components/ListItemDirectory.vue'
|
import ListDirectories from '@/components/ListDirectories.vue'
|
||||||
import ListItemPlaylist from '@/components/ListItemPlaylist.vue'
|
import ListPlaylists from '@/components/ListPlaylists.vue'
|
||||||
import ListItemTrack from '@/components/ListItemTrack.vue'
|
import ListTracks from '@/components/ListTracks.vue'
|
||||||
import ModalDialogDirectory from '@/components/ModalDialogDirectory.vue'
|
|
||||||
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue'
|
|
||||||
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
|
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
@ -154,12 +78,9 @@ export default {
|
|||||||
name: 'PageFiles',
|
name: 'PageFiles',
|
||||||
components: {
|
components: {
|
||||||
ContentWithHeading,
|
ContentWithHeading,
|
||||||
ListItemDirectory,
|
ListDirectories,
|
||||||
ListItemPlaylist,
|
ListPlaylists,
|
||||||
ListItemTrack,
|
ListTracks
|
||||||
ModalDialogDirectory,
|
|
||||||
ModalDialogPlaylist,
|
|
||||||
ModalDialogTrack
|
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
@ -181,16 +102,7 @@ export default {
|
|||||||
directories: [],
|
directories: [],
|
||||||
tracks: { items: [] },
|
tracks: { items: [] },
|
||||||
playlists: { items: [] }
|
playlists: { items: [] }
|
||||||
},
|
}
|
||||||
|
|
||||||
show_directory_details_modal: false,
|
|
||||||
selected_directory: {},
|
|
||||||
|
|
||||||
show_playlist_details_modal: false,
|
|
||||||
selected_playlist: {},
|
|
||||||
|
|
||||||
show_track_details_modal: false,
|
|
||||||
selected_track: {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -200,72 +112,18 @@ export default {
|
|||||||
return this.$route.query.directory
|
return this.$route.query.directory
|
||||||
}
|
}
|
||||||
return '/'
|
return '/'
|
||||||
|
},
|
||||||
|
|
||||||
|
play_expression() {
|
||||||
|
return (
|
||||||
|
'path starts with "' + this.current_directory + '" order by path asc'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
open_parent_directory: function () {
|
|
||||||
const parent = this.current_directory.slice(
|
|
||||||
0,
|
|
||||||
this.current_directory.lastIndexOf('/')
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
parent === '' ||
|
|
||||||
this.$store.state.config.directories.includes(this.current_directory)
|
|
||||||
) {
|
|
||||||
this.$router.push({ path: '/files' })
|
|
||||||
} else {
|
|
||||||
this.$router.push({
|
|
||||||
path: '/files',
|
|
||||||
query: {
|
|
||||||
directory: this.current_directory.slice(
|
|
||||||
0,
|
|
||||||
this.current_directory.lastIndexOf('/')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
open_directory: function (directory) {
|
|
||||||
this.$router.push({
|
|
||||||
path: '/files',
|
|
||||||
query: { directory: directory.path }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
open_directory_dialog: function (directory) {
|
|
||||||
this.selected_directory = directory
|
|
||||||
this.show_directory_details_modal = true
|
|
||||||
},
|
|
||||||
|
|
||||||
play: function () {
|
play: function () {
|
||||||
webapi.player_play_expression(
|
webapi.player_play_expression(this.play_expression, false)
|
||||||
'path starts with "' + this.current_directory + '" order by path asc',
|
|
||||||
false
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
play_track: function (position) {
|
|
||||||
webapi.player_play_uri(
|
|
||||||
this.files.tracks.items.map((a) => a.uri).join(','),
|
|
||||||
false,
|
|
||||||
position
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
open_track_dialog: function (track) {
|
|
||||||
this.selected_track = track
|
|
||||||
this.show_track_details_modal = true
|
|
||||||
},
|
|
||||||
|
|
||||||
open_playlist: function (playlist) {
|
|
||||||
this.$router.push({ path: '/playlists/' + playlist.id + '/tracks' })
|
|
||||||
},
|
|
||||||
|
|
||||||
open_playlist_dialog: function (playlist) {
|
|
||||||
this.selected_playlist = playlist
|
|
||||||
this.show_playlist_details_modal = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
<template #options>
|
||||||
<index-button-list :index="index_list" />
|
<index-button-list :index="albums_list.indexList" />
|
||||||
</template>
|
</template>
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">
|
<p class="title is-4">
|
||||||
@ -27,10 +27,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<p class="heading has-text-centered-mobile">
|
<p class="heading has-text-centered-mobile">
|
||||||
{{ genre_albums.total }} albums |
|
{{ albums_list.total }} albums |
|
||||||
<a class="has-text-link" @click="open_tracks">tracks</a>
|
<a class="has-text-link" @click="open_tracks">tracks</a>
|
||||||
</p>
|
</p>
|
||||||
<list-albums :albums="genre_albums.items" />
|
<list-albums :albums="albums_list" />
|
||||||
<modal-dialog-genre
|
<modal-dialog-genre
|
||||||
:show="show_genre_details_modal"
|
:show="show_genre_details_modal"
|
||||||
:genre="{ name: name }"
|
:genre="{ name: name }"
|
||||||
@ -47,6 +47,7 @@ import IndexButtonList from '@/components/IndexButtonList.vue'
|
|||||||
import ListAlbums from '@/components/ListAlbums.vue'
|
import ListAlbums from '@/components/ListAlbums.vue'
|
||||||
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
|
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
import { bySortName, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -55,7 +56,8 @@ const dataObject = {
|
|||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.name = vm.$route.params.genre
|
vm.name = vm.$route.params.genre
|
||||||
vm.genre_albums = response.data.albums
|
vm.albums_list = new GroupByList(response.data.albums)
|
||||||
|
vm.albums_list.group(bySortName('name_sort'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +76,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
|
if (!this.albums_list.isEmpty()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
dataObject.set(vm, response)
|
dataObject.set(vm, response)
|
||||||
@ -84,24 +90,12 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: '',
|
name: '',
|
||||||
genre_albums: { items: [] },
|
albums_list: new GroupByList(),
|
||||||
|
|
||||||
show_genre_details_modal: false
|
show_genre_details_modal: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
index_list() {
|
|
||||||
return [
|
|
||||||
...new Set(
|
|
||||||
this.genre_albums.items.map((album) =>
|
|
||||||
album.name.charAt(0).toUpperCase()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
open_tracks: function () {
|
open_tracks: function () {
|
||||||
this.show_details_modal = false
|
this.show_details_modal = false
|
||||||
@ -113,11 +107,6 @@ export default {
|
|||||||
'genre is "' + this.name + '" and media_kind is music',
|
'genre is "' + this.name + '" and media_kind is music',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
},
|
|
||||||
|
|
||||||
open_dialog: function (album) {
|
|
||||||
this.selected_album = album
|
|
||||||
this.show_details_modal = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,32 +4,14 @@
|
|||||||
|
|
||||||
<content-with-heading>
|
<content-with-heading>
|
||||||
<template #options>
|
<template #options>
|
||||||
<index-button-list :index="index_list" />
|
<index-button-list :index="genres.indexList" />
|
||||||
</template>
|
</template>
|
||||||
<template #heading-left>
|
<template #heading-left>
|
||||||
<p class="title is-4">Genres</p>
|
<p class="title is-4">Genres</p>
|
||||||
<p class="heading">{{ genres.total }} genres</p>
|
<p class="heading">{{ genres.total }} genres</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-item-genre
|
<list-genres :genres="genres" />
|
||||||
v-for="genre in genres.items"
|
|
||||||
:key="genre.name"
|
|
||||||
:genre="genre"
|
|
||||||
@click="open_genre(genre)"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<a @click.prevent.stop="open_dialog(genre)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-genre>
|
|
||||||
<modal-dialog-genre
|
|
||||||
:show="show_details_modal"
|
|
||||||
:genre="selected_genre"
|
|
||||||
@close="show_details_modal = false"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</content-with-heading>
|
</content-with-heading>
|
||||||
</div>
|
</div>
|
||||||
@ -39,9 +21,9 @@
|
|||||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||||
import TabsMusic from '@/components/TabsMusic.vue'
|
import TabsMusic from '@/components/TabsMusic.vue'
|
||||||
import IndexButtonList from '@/components/IndexButtonList.vue'
|
import IndexButtonList from '@/components/IndexButtonList.vue'
|
||||||
import ListItemGenre from '@/components/ListItemGenre.vue'
|
import ListGenres from '@/components/ListGenres.vue'
|
||||||
import ModalDialogGenre from '@/components/ModalDialogGenre.vue'
|
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
import { byName, GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -50,6 +32,8 @@ const dataObject = {
|
|||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.genres = response.data
|
vm.genres = response.data
|
||||||
|
vm.genres = new GroupByList(response.data)
|
||||||
|
vm.genres.group(byName('name_sort'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,8 +43,7 @@ export default {
|
|||||||
ContentWithHeading,
|
ContentWithHeading,
|
||||||
TabsMusic,
|
TabsMusic,
|
||||||
IndexButtonList,
|
IndexButtonList,
|
||||||
ListItemGenre,
|
ListGenres
|
||||||
ModalDialogGenre
|
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
@ -78,33 +61,13 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
genres: { items: [] },
|
genres: new GroupByList()
|
||||||
|
|
||||||
show_details_modal: false,
|
|
||||||
selected_genre: {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {},
|
||||||
index_list() {
|
|
||||||
return [
|
|
||||||
...new Set(
|
|
||||||
this.genres.items.map((genre) => genre.name.charAt(0).toUpperCase())
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {}
|
||||||
open_genre: function (genre) {
|
|
||||||
this.$router.push({ name: 'Genre', params: { genre: genre.name } })
|
|
||||||
},
|
|
||||||
|
|
||||||
open_dialog: function (genre) {
|
|
||||||
this.selected_genre = genre
|
|
||||||
this.show_details_modal = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -27,27 +27,9 @@
|
|||||||
<p class="heading has-text-centered-mobile">
|
<p class="heading has-text-centered-mobile">
|
||||||
{{ album.track_count }} tracks
|
{{ album.track_count }} tracks
|
||||||
</p>
|
</p>
|
||||||
<list-item-track
|
<list-tracks
|
||||||
v-for="track in tracks"
|
:tracks="tracks"
|
||||||
:key="track.id"
|
:show_progress="true"
|
||||||
:track="track"
|
|
||||||
@click="play_track(track)"
|
|
||||||
>
|
|
||||||
<template #progress>
|
|
||||||
<progress-bar :max="track.length_ms" :value="track.seek_ms" />
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<a @click.prevent.stop="open_dialog(track)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-track>
|
|
||||||
<modal-dialog-track
|
|
||||||
:show="show_details_modal"
|
|
||||||
:track="selected_track"
|
|
||||||
@close="show_details_modal = false"
|
|
||||||
@play-count-changed="reload_tracks"
|
@play-count-changed="reload_tracks"
|
||||||
/>
|
/>
|
||||||
<modal-dialog-album
|
<modal-dialog-album
|
||||||
@ -81,11 +63,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||||
import ListItemTrack from '@/components/ListItemTrack.vue'
|
import ListTracks from '@/components/ListTracks.vue'
|
||||||
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
|
|
||||||
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
|
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
|
||||||
import ModalDialog from '@/components/ModalDialog.vue'
|
import ModalDialog from '@/components/ModalDialog.vue'
|
||||||
import ProgressBar from '@/components/ProgressBar.vue'
|
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
@ -106,11 +86,9 @@ export default {
|
|||||||
name: 'PagePodcast',
|
name: 'PagePodcast',
|
||||||
components: {
|
components: {
|
||||||
ContentWithHeading,
|
ContentWithHeading,
|
||||||
ListItemTrack,
|
ListTracks,
|
||||||
ModalDialogTrack,
|
|
||||||
ModalDialogAlbum,
|
ModalDialogAlbum,
|
||||||
ModalDialog,
|
ModalDialog
|
||||||
ProgressBar
|
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
@ -131,9 +109,6 @@ export default {
|
|||||||
album: {},
|
album: {},
|
||||||
tracks: [],
|
tracks: [],
|
||||||
|
|
||||||
show_details_modal: false,
|
|
||||||
selected_track: {},
|
|
||||||
|
|
||||||
show_album_details_modal: false,
|
show_album_details_modal: false,
|
||||||
|
|
||||||
show_remove_podcast_modal: false,
|
show_remove_podcast_modal: false,
|
||||||
@ -152,15 +127,6 @@ export default {
|
|||||||
webapi.player_play_uri(this.album.uri, false)
|
webapi.player_play_uri(this.album.uri, false)
|
||||||
},
|
},
|
||||||
|
|
||||||
play_track: function (track) {
|
|
||||||
webapi.player_play_uri(track.uri, false)
|
|
||||||
},
|
|
||||||
|
|
||||||
open_dialog: function (track) {
|
|
||||||
this.selected_track = track
|
|
||||||
this.show_details_modal = true
|
|
||||||
},
|
|
||||||
|
|
||||||
open_remove_podcast_dialog: function () {
|
open_remove_podcast_dialog: function () {
|
||||||
this.show_album_details_modal = false
|
this.show_album_details_modal = false
|
||||||
webapi.library_track_playlists(this.tracks[0].id).then(({ data }) => {
|
webapi.library_track_playlists(this.tracks[0].id).then(({ data }) => {
|
||||||
|
@ -15,27 +15,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-item-track
|
<list-tracks
|
||||||
v-for="track in new_episodes.items"
|
:tracks="new_episodes.items"
|
||||||
:key="track.id"
|
:show_progress="true"
|
||||||
:track="track"
|
|
||||||
@click="play_track(track)"
|
|
||||||
>
|
|
||||||
<template #progress>
|
|
||||||
<progress-bar :max="track.length_ms" :value="track.seek_ms" />
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<a @click="open_track_dialog(track)">
|
|
||||||
<span class="icon has-text-dark"
|
|
||||||
><i class="mdi mdi-dots-vertical mdi-18px"
|
|
||||||
/></span>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</list-item-track>
|
|
||||||
<modal-dialog-track
|
|
||||||
:show="show_track_details_modal"
|
|
||||||
:track="selected_track"
|
|
||||||
@close="show_track_details_modal = false"
|
|
||||||
@play-count-changed="reload_new_episodes"
|
@play-count-changed="reload_new_episodes"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -64,7 +46,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums
|
<list-albums
|
||||||
:albums="albums.items"
|
:albums="albums"
|
||||||
@play-count-changed="reload_new_episodes()"
|
@play-count-changed="reload_new_episodes()"
|
||||||
@podcast-deleted="reload_podcasts()"
|
@podcast-deleted="reload_podcasts()"
|
||||||
/>
|
/>
|
||||||
@ -80,13 +62,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
import ContentWithHeading from '@/templates/ContentWithHeading.vue'
|
||||||
import ListItemTrack from '@/components/ListItemTrack.vue'
|
import ListTracks from '@/components/ListTracks.vue'
|
||||||
import ListAlbums from '@/components/ListAlbums.vue'
|
import ListAlbums from '@/components/ListAlbums.vue'
|
||||||
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
|
|
||||||
import ModalDialogAddRss from '@/components/ModalDialogAddRss.vue'
|
import ModalDialogAddRss from '@/components/ModalDialogAddRss.vue'
|
||||||
import ProgressBar from '@/components/ProgressBar.vue'
|
|
||||||
import * as types from '@/store/mutation_types'
|
import * as types from '@/store/mutation_types'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
|
import { GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
const dataObject = {
|
const dataObject = {
|
||||||
load: function (to) {
|
load: function (to) {
|
||||||
@ -97,7 +78,7 @@ const dataObject = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set: function (vm, response) {
|
set: function (vm, response) {
|
||||||
vm.albums = response[0].data
|
vm.albums = new GroupByList(response[0].data)
|
||||||
vm.new_episodes = response[1].data.tracks
|
vm.new_episodes = response[1].data.tracks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,11 +87,9 @@ export default {
|
|||||||
name: 'PagePodcasts',
|
name: 'PagePodcasts',
|
||||||
components: {
|
components: {
|
||||||
ContentWithHeading,
|
ContentWithHeading,
|
||||||
ListItemTrack,
|
ListTracks,
|
||||||
ListAlbums,
|
ListAlbums,
|
||||||
ModalDialogTrack,
|
ModalDialogAddRss
|
||||||
ModalDialogAddRss,
|
|
||||||
ProgressBar
|
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
@ -118,6 +97,7 @@ export default {
|
|||||||
next((vm) => dataObject.set(vm, response))
|
next((vm) => dataObject.set(vm, response))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
const vm = this
|
const vm = this
|
||||||
dataObject.load(to).then((response) => {
|
dataObject.load(to).then((response) => {
|
||||||
@ -128,13 +108,10 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
albums: { items: [] },
|
albums: [],
|
||||||
new_episodes: { items: [] },
|
new_episodes: { items: [] },
|
||||||
|
|
||||||
show_url_modal: false,
|
show_url_modal: false
|
||||||
|
|
||||||
show_track_details_modal: false,
|
|
||||||
selected_track: {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -145,15 +122,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
play_track: function (track) {
|
|
||||||
webapi.player_play_uri(track.uri, false)
|
|
||||||
},
|
|
||||||
|
|
||||||
open_track_dialog: function (track) {
|
|
||||||
this.selected_track = track
|
|
||||||
this.show_track_details_modal = true
|
|
||||||
},
|
|
||||||
|
|
||||||
mark_all_played: function () {
|
mark_all_played: function () {
|
||||||
this.new_episodes.items.forEach((ep) => {
|
this.new_episodes.items.forEach((ep) => {
|
||||||
webapi.library_track_update(ep.id, { play_count: 'increment' })
|
webapi.library_track_update(ep.id, { play_count: 'increment' })
|
||||||
@ -173,7 +141,7 @@ export default {
|
|||||||
|
|
||||||
reload_podcasts: function () {
|
reload_podcasts: function () {
|
||||||
webapi.library_albums('podcast').then(({ data }) => {
|
webapi.library_albums('podcast').then(({ data }) => {
|
||||||
this.albums = data
|
this.albums = new GroupByList(data)
|
||||||
this.reload_new_episodes()
|
this.reload_new_episodes()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
<p class="title is-4">Artists</p>
|
<p class="title is-4">Artists</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-artists :artists="artists.items" />
|
<list-artists :artists="artists" :hide_group_title="true" />
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<nav v-if="show_all_artists_button" class="level">
|
<nav v-if="show_all_artists_button" class="level">
|
||||||
@ -105,7 +105,7 @@
|
|||||||
<p class="title is-4">Albums</p>
|
<p class="title is-4">Albums</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums :albums="albums.items" />
|
<list-albums :albums="albums" :hide_group_title="true" />
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<nav v-if="show_all_albums_button" class="level">
|
<nav v-if="show_all_albums_button" class="level">
|
||||||
@ -183,7 +183,7 @@
|
|||||||
<p class="title is-4">Podcasts</p>
|
<p class="title is-4">Podcasts</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums :albums="podcasts.items" />
|
<list-albums :albums="podcasts" />
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<nav v-if="show_all_podcasts_button" class="level">
|
<nav v-if="show_all_podcasts_button" class="level">
|
||||||
@ -209,7 +209,7 @@
|
|||||||
<p class="title is-4">Audiobooks</p>
|
<p class="title is-4">Audiobooks</p>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<list-albums :albums="audiobooks.items" />
|
<list-albums :albums="audiobooks" />
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<nav v-if="show_all_audiobooks_button" class="level">
|
<nav v-if="show_all_audiobooks_button" class="level">
|
||||||
@ -242,6 +242,7 @@ import ListComposers from '@/components/ListComposers.vue'
|
|||||||
import ListPlaylists from '@/components/ListPlaylists.vue'
|
import ListPlaylists from '@/components/ListPlaylists.vue'
|
||||||
import webapi from '@/webapi'
|
import webapi from '@/webapi'
|
||||||
import * as types from '@/store/mutation_types'
|
import * as types from '@/store/mutation_types'
|
||||||
|
import { GroupByList } from '@/lib/GroupByList'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PageSearch',
|
name: 'PageSearch',
|
||||||
@ -261,12 +262,12 @@ export default {
|
|||||||
search_query: '',
|
search_query: '',
|
||||||
|
|
||||||
tracks: { items: [], total: 0 },
|
tracks: { items: [], total: 0 },
|
||||||
artists: { items: [], total: 0 },
|
artists: new GroupByList(),
|
||||||
albums: { items: [], total: 0 },
|
albums: new GroupByList(),
|
||||||
composers: { items: [], total: 0 },
|
composers: { items: [], total: 0 },
|
||||||
playlists: { items: [], total: 0 },
|
playlists: { items: [], total: 0 },
|
||||||
audiobooks: { items: [], total: 0 },
|
audiobooks: new GroupByList(),
|
||||||
podcasts: { items: [], total: 0 }
|
podcasts: new GroupByList()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -393,8 +394,8 @@ export default {
|
|||||||
|
|
||||||
webapi.search(searchParams).then(({ data }) => {
|
webapi.search(searchParams).then(({ data }) => {
|
||||||
this.tracks = data.tracks ? data.tracks : { items: [], total: 0 }
|
this.tracks = data.tracks ? data.tracks : { items: [], total: 0 }
|
||||||
this.artists = data.artists ? data.artists : { items: [], total: 0 }
|
this.artists = new GroupByList(data.artists)
|
||||||
this.albums = data.albums ? data.albums : { items: [], total: 0 }
|
this.albums = new GroupByList(data.albums)
|
||||||
this.composers = data.composers
|
this.composers = data.composers
|
||||||
? data.composers
|
? data.composers
|
||||||
: { items: [], total: 0 }
|
: { items: [], total: 0 }
|
||||||
@ -431,7 +432,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
webapi.search(searchParams).then(({ data }) => {
|
webapi.search(searchParams).then(({ data }) => {
|
||||||
this.audiobooks = data.albums ? data.albums : { items: [], total: 0 }
|
this.audiobooks = new GroupByList(data.albums)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -462,7 +463,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
webapi.search(searchParams).then(({ data }) => {
|
webapi.search(searchParams).then(({ data }) => {
|
||||||
this.podcasts = data.albums ? data.albums : { items: [], total: 0 }
|
this.podcasts = new GroupByList(data.albums)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ export const router = createRouter({
|
|||||||
if (to.meta.has_tabs) {
|
if (to.meta.has_tabs) {
|
||||||
resolve({ el: '#top', top: 140 })
|
resolve({ el: '#top', top: 140 })
|
||||||
} else {
|
} else {
|
||||||
resolve({ el: '#top', top: 100 })
|
resolve({ el: '#top', top: 110 })
|
||||||
}
|
}
|
||||||
}, wait_ms)
|
}, wait_ms)
|
||||||
})
|
})
|
||||||
|
@ -90,7 +90,7 @@ export default {
|
|||||||
if (this.$route.meta.has_tabs) {
|
if (this.$route.meta.has_tabs) {
|
||||||
this.$scrollTo('#top', { offset: -140 })
|
this.$scrollTo('#top', { offset: -140 })
|
||||||
} else {
|
} else {
|
||||||
this.$scrollTo('#top', { offset: -100 })
|
this.$scrollTo('#top', { offset: -110 })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user