[web] Format web sources with prettier and run fix linting errors

This commit is contained in:
chme
2022-02-19 06:39:14 +01:00
parent d7f1c13585
commit c78f861f45
116 changed files with 5274 additions and 2887 deletions

View File

@@ -1,7 +1,9 @@
<template>
<figure>
<img v-lazy="{ src: artwork_url_with_size, lifecycle: lazy_lifecycle }"
@click="$emit('click')">
<img
v-lazy="{ src: artwork_url_with_size, lifecycle: lazy_lifecycle }"
@click="$emit('click')"
/>
</figure>
</template>
@@ -13,7 +15,7 @@ export default {
name: 'CoverArtwork',
props: ['artist', 'album', 'artwork_url', 'maxwidth', 'maxheight'],
data () {
data() {
return {
width: 600,
height: 600,
@@ -31,16 +33,20 @@ export default {
computed: {
artwork_url_with_size: function () {
if (this.maxwidth > 0 && this.maxheight > 0) {
return webapi.artwork_url_append_size_params(this.artwork_url, this.maxwidth, this.maxheight)
return webapi.artwork_url_append_size_params(
this.artwork_url,
this.maxwidth,
this.maxheight
)
}
return webapi.artwork_url_append_size_params(this.artwork_url)
},
alt_text () {
alt_text() {
return this.artist + ' - ' + this.album
},
caption () {
caption() {
if (this.album) {
return this.album.substring(0, 2)
}

View File

@@ -1,19 +1,31 @@
<template>
<div class="dropdown" :class="{ 'is-active': is_active }" v-click-away="onClickOutside">
<div
v-click-away="onClickOutside"
class="dropdown"
:class="{ 'is-active': is_active }"
>
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu" @click="is_active = !is_active">
<button
class="button"
aria-haspopup="true"
aria-controls="dropdown-menu"
@click="is_active = !is_active"
>
<span>{{ modelValue }}</span>
<span class="icon is-small">
<i class="mdi mdi-chevron-down" aria-hidden="true"></i>
<i class="mdi mdi-chevron-down" aria-hidden="true" />
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div id="dropdown-menu" class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item"
v-for="option in options" :key="option"
:class="{'is-active': modelValue === option}"
@click="select(option)">
<a
v-for="option in options"
:key="option"
class="dropdown-item"
:class="{ 'is-active': modelValue === option }"
@click="select(option)"
>
{{ option }}
</a>
</div>
@@ -28,18 +40,18 @@ export default {
props: ['modelValue', 'options'],
emits: ['update:modelValue'],
data () {
data() {
return {
is_active: false
}
},
methods: {
onClickOutside (event) {
onClickOutside(event) {
this.is_active = false
},
select (option) {
select(option) {
this.is_active = false
this.$emit('update:modelValue', option)
}
@@ -47,5 +59,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,7 +1,13 @@
<template>
<section>
<nav class="buttons is-centered fd-is-square" style="margin-bottom: 16px;">
<a v-for="char in filtered_index" :key="char" class="button is-small" @click="nav(char)">{{ char }}</a>
<nav class="buttons is-centered fd-is-square" style="margin-bottom: 16px">
<a
v-for="char in filtered_index"
:key="char"
class="button is-small"
@click="nav(char)"
>{{ char }}</a
>
</nav>
</section>
</template>
@@ -13,9 +19,9 @@ export default {
props: ['index'],
computed: {
filtered_index () {
filtered_index() {
const specialChars = '!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
return this.index.filter(c => !specialChars.includes(c))
return this.index.filter((c) => !specialChars.includes(c))
}
},
@@ -31,5 +37,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -2,84 +2,105 @@
<div>
<div v-if="is_grouped">
<div v-for="idx in albums.indexList" :key="idx" class="mb-6">
<span class="tag is-info is-light is-small has-text-weight-bold" :id="'index_' + idx">{{ idx }}</span>
<span
:id="'index_' + idx"
class="tag is-info is-light is-small has-text-weight-bold"
>{{ idx }}</span
>
<div class="media" v-for="album in albums.grouped[idx]"
:key="album.id"
:album="album"
@click="open_album(album)">
<div class="media-left fd-has-action"
v-if="is_visible_artwork">
<p class="image is-64x64 fd-has-shadow fd-has-action">
<cover-artwork
<div
v-for="album in albums.grouped[idx]"
:key="album.id"
class="media"
:album="album"
@click="open_album(album)"
>
<div v-if="is_visible_artwork" class="media-left fd-has-action">
<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" />
:maxheight="64"
/>
</p>
</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 class="subtitle is-7 has-text-grey has-text-weight-normal"
v-if="album.date_released && album.media_kind === 'music'">
{{ $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"></i></span>
</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">
<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>
<div v-else>
<list-item-album v-for="album in albums_list"
:key="album.id"
:album="album"
@click="open_album(album)">
<template v-slot:artwork v-if="is_visible_artwork">
<list-item-album
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
<cover-artwork
:artwork_url="album.artwork_url"
:artist="album.artist"
:album="album.name"
:maxwidth="64"
:maxheight="64" />
:maxheight="64"
/>
</p>
</template>
<template v-slot:actions>
<template #actions>
<a @click.prevent.stop="open_dialog(album)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
<span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px"
/></span>
</a>
</template>
</list-item-album>
</div>
<modal-dialog-album
:show="show_details_modal"
:album="selected_album"
:media_kind="media_kind"
@remove-podcast="open_remove_podcast_dialog()"
@play-count-changed="play_count_changed()"
@close="show_details_modal = false" />
:show="show_details_modal"
:album="selected_album"
:media_kind="media_kind"
@remove-podcast="open_remove_podcast_dialog()"
@play-count-changed="play_count_changed()"
@close="show_details_modal = false"
/>
<modal-dialog
:show="show_remove_podcast_modal"
title="Remove podcast"
delete_action="Remove"
@close="show_remove_podcast_modal = false"
@delete="remove_podcast">
<template v-slot:modal-content>
:show="show_remove_podcast_modal"
title="Remove podcast"
delete_action="Remove"
@close="show_remove_podcast_modal = false"
@delete="remove_podcast"
>
<template #modal-content>
<p>Permanently remove this podcast from your library?</p>
<p class="is-size-7">(This will also remove the RSS playlist <b>{{ rss_playlist_to_remove.name }}</b>.)</p>
<p class="is-size-7">
(This will also remove the RSS playlist
<b>{{ rss_playlist_to_remove.name }}</b
>.)
</p>
</template>
</modal-dialog>
</div>
@@ -99,7 +120,7 @@ export default {
props: ['albums', 'media_kind'],
data () {
data() {
return {
show_details_modal: false,
selected_album: {},
@@ -110,8 +131,11 @@ export default {
},
computed: {
is_visible_artwork () {
return this.$store.getters.settings_option('webinterface', 'show_cover_artwork_in_album_lists').value
is_visible_artwork() {
return this.$store.getters.settings_option(
'webinterface',
'show_cover_artwork_in_album_lists'
).value
},
media_kind_resolved: function () {
@@ -129,7 +153,7 @@ export default {
},
is_grouped: function () {
return (this.albums instanceof Albums && this.albums.options.group)
return this.albums instanceof Albums && this.albums.options.group
}
},
@@ -151,19 +175,24 @@ export default {
},
open_remove_podcast_dialog: function () {
webapi.library_album_tracks(this.selected_album.id, { limit: 1 }).then(({ data }) => {
webapi.library_track_playlists(data.items[0].id).then(({ data }) => {
const rssPlaylists = data.items.filter(pl => pl.type === 'rss')
if (rssPlaylists.length !== 1) {
this.$store.dispatch('add_notification', { text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.', type: 'danger' })
return
}
webapi
.library_album_tracks(this.selected_album.id, { limit: 1 })
.then(({ data }) => {
webapi.library_track_playlists(data.items[0].id).then(({ data }) => {
const rssPlaylists = data.items.filter((pl) => pl.type === 'rss')
if (rssPlaylists.length !== 1) {
this.$store.dispatch('add_notification', {
text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.',
type: 'danger'
})
return
}
this.rss_playlist_to_remove = rssPlaylists[0]
this.show_remove_podcast_modal = true
this.show_details_modal = false
this.rss_playlist_to_remove = rssPlaylists[0]
this.show_remove_podcast_modal = true
this.show_details_modal = false
})
})
})
},
play_count_changed: function () {
@@ -172,13 +201,14 @@ export default {
remove_podcast: function () {
this.show_remove_podcast_modal = false
webapi.library_playlist_delete(this.rss_playlist_to_remove.id).then(() => {
this.$emit('podcast-deleted')
})
webapi
.library_playlist_delete(this.rss_playlist_to_remove.id)
.then(() => {
this.$emit('podcast-deleted')
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -2,32 +2,49 @@
<div>
<div v-if="is_grouped">
<div v-for="idx in artists.indexList" :key="idx" class="mb-6">
<span class="tag is-info is-light is-small has-text-weight-bold" :id="'index_' + idx">{{ idx }}</span>
<list-item-artist v-for="artist in artists.grouped[idx]"
:key="artist.id"
:artist="artist"
@click="open_artist(artist)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(artist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
<span
:id="'index_' + idx"
class="tag is-info is-light is-small has-text-weight-bold"
>{{ idx }}</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 v-else>
<list-item-artist v-for="artist in artists_list"
:key="artist.id"
:artist="artist"
@click="open_artist(artist)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(artist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
<list-item-artist
v-for="artist in artists_list"
: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>
<modal-dialog-artist :show="show_details_modal" :artist="selected_artist" :media_kind="media_kind" @close="show_details_modal = false" />
<modal-dialog-artist
:show="show_details_modal"
:artist="selected_artist"
:media_kind="media_kind"
@close="show_details_modal = false"
/>
</div>
</template>
@@ -42,7 +59,7 @@ export default {
props: ['artists', 'media_kind'],
data () {
data() {
return {
show_details_modal: false,
selected_artist: {}
@@ -62,7 +79,7 @@ export default {
},
is_grouped: function () {
return (this.artists instanceof Artists && this.artists.options.group)
return this.artists instanceof Artists && this.artists.options.group
}
},
@@ -86,5 +103,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -2,32 +2,49 @@
<div>
<div v-if="is_grouped">
<div v-for="idx in composers.indexList" :key="idx" class="mb-6">
<span class="tag is-info is-light is-small has-text-weight-bold" :id="'index_' + idx">{{ idx }}</span>
<list-item-composer v-for="composer in composers.grouped[idx]"
:key="composer.id"
:composer="composer"
@click="open_composer(composer)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(composer)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
<span
:id="'index_' + idx"
class="tag is-info is-light is-small has-text-weight-bold"
>{{ idx }}</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 v-else>
<list-item-composer v-for="composer in composers_list"
:key="composer.id"
:composer="composer"
@click="open_composer(composer)">
<template v-slot:actions>
<a @click.prevent.stop="open_dialog(composer)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
</a>
</template>
<list-item-composer
v-for="composer in composers_list"
: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>
<modal-dialog-composer :show="show_details_modal" :composer="selected_composer" :media_kind="media_kind" @close="show_details_modal = false" />
<modal-dialog-composer
:show="show_details_modal"
:composer="selected_composer"
:media_kind="media_kind"
@close="show_details_modal = false"
/>
</div>
</template>
@@ -42,7 +59,7 @@ export default {
props: ['composers', 'media_kind'],
data () {
data() {
return {
show_details_modal: false,
selected_composer: {}
@@ -51,7 +68,9 @@ export default {
computed: {
media_kind_resolved: function () {
return this.media_kind ? this.media_kind : this.selected_composer.media_kind
return this.media_kind
? this.media_kind
: this.selected_composer.media_kind
},
composers_list: function () {
@@ -62,14 +81,17 @@ export default {
},
is_grouped: function () {
return (this.composers instanceof Composers && this.composers.options.group)
return this.composers instanceof Composers && this.composers.options.group
}
},
methods: {
open_composer: function (composer) {
this.selected_composer = composer
this.$router.push({ name: 'ComposerTracks', params: { composer: composer.name } })
this.$router.push({
name: 'ComposerTracks',
params: { composer: composer.name }
})
},
open_dialog: function (composer) {
@@ -80,5 +102,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,21 +1,26 @@
<template>
<div class="media" :id="'index_' + album.name_sort.charAt(0).toUpperCase()">
<div class="media-left fd-has-action"
v-if="$slots['artwork']">
<slot name="artwork"></slot>
<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 class="subtitle is-7 has-text-grey has-text-weight-normal"
v-if="album.date_released && album.media_kind === 'music'">
<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"></slot>
<div class="media-right" style="padding-top: 0.7rem">
<slot name="actions" />
</div>
</div>
</template>
@@ -27,5 +32,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,10 +1,12 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">{{ artist.name }}</h1>
<h1 class="title is-6">
{{ artist.name }}
</h1>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -16,5 +18,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,10 +1,12 @@
<template>
<div class="media" :id="'index_' + composer.name.charAt(0).toUpperCase()">
<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>
<h1 class="title is-6">
{{ composer.name }}
</h1>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -16,5 +18,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -2,15 +2,19 @@
<div class="media">
<figure class="media-left fd-has-action">
<span class="icon">
<i class="mdi mdi-folder"></i>
<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>
<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"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -22,5 +26,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,10 +1,12 @@
<template>
<div class="media" :id="'index_' + genre.name.charAt(0).toUpperCase()">
<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>
<h1 class="title is-6">
{{ genre.name }}
</h1>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -16,5 +18,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,13 +1,15 @@
<template>
<div class="media">
<figure class="media-left fd-has-action" v-if="$slots.icon">
<slot name="icon"></slot>
<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>
<h1 class="title is-6">
{{ playlist.name }}
</h1>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -19,5 +21,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,16 +1,44 @@
<template>
<div class="media" v-if="is_next || !show_only_next_items">
<div class="media-left" v-if="edit_mode">
<span class="icon has-text-grey fd-is-movable handle"><i class="mdi mdi-drag-horizontal mdi-18px"></i></span>
<div v-if="is_next || !show_only_next_items" class="media">
<div v-if="edit_mode" class="media-left">
<span class="icon has-text-grey fd-is-movable handle"
><i class="mdi mdi-drag-horizontal mdi-18px"
/></span>
</div>
<div class="media-content fd-has-action is-clipped" v-on:click="play">
<h1 class="title is-6" :class="{ 'has-text-primary': item.id === state.item_id, 'has-text-grey-light': !is_next }">{{ item.title }}</h1>
<h2 class="subtitle is-7" :class="{ 'has-text-primary': item.id === state.item_id, 'has-text-grey-light': !is_next, 'has-text-grey': is_next && item.id !== state.item_id }"><b>{{ item.artist }}</b></h2>
<h2 class="subtitle is-7" :class="{ 'has-text-primary': item.id === state.item_id, 'has-text-grey-light': !is_next, 'has-text-grey': is_next && item.id !== state.item_id }">{{ item.album }}</h2>
<div class="media-content fd-has-action is-clipped" @click="play">
<h1
class="title is-6"
:class="{
'has-text-primary': item.id === state.item_id,
'has-text-grey-light': !is_next
}"
>
{{ item.title }}
</h1>
<h2
class="subtitle is-7"
:class="{
'has-text-primary': item.id === state.item_id,
'has-text-grey-light': !is_next,
'has-text-grey': is_next && item.id !== state.item_id
}"
>
<b>{{ item.artist }}</b>
</h2>
<h2
class="subtitle is-7"
:class="{
'has-text-primary': item.id === state.item_id,
'has-text-grey-light': !is_next,
'has-text-grey': is_next && item.id !== state.item_id
}"
>
{{ item.album }}
</h2>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -20,14 +48,20 @@ import webapi from '@/webapi'
export default {
name: 'ListItemQueueItem',
props: ['item', 'position', 'current_position', 'show_only_next_items', 'edit_mode'],
props: [
'item',
'position',
'current_position',
'show_only_next_items',
'edit_mode'
],
computed: {
state () {
state() {
return this.$store.state.player
},
is_next () {
is_next() {
return this.current_position < 0 || this.position >= this.current_position
}
},
@@ -40,5 +74,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,16 +1,32 @@
<template>
<div class="media" :id="'index_' + track.title_sort.charAt(0).toUpperCase()" :class="{ 'with-progress': $slots.progress }">
<figure class="media-left fd-has-action" v-if="$slots.icon">
<slot name="icon"></slot>
<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"></slot>
<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"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -22,5 +38,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,18 +1,36 @@
<template>
<div>
<list-item-playlist v-for="playlist in playlists" :key="playlist.id" :playlist="playlist" @click="open_playlist(playlist)">
<template v-slot:icon>
<list-item-playlist
v-for="playlist in playlists"
:key="playlist.id"
:playlist="playlist"
@click="open_playlist(playlist)"
>
<template #icon>
<span class="icon">
<i class="mdi" :class="{ 'mdi-library-music': playlist.type !== 'folder', 'mdi-rss': playlist.type === 'rss', 'mdi-folder': playlist.type === 'folder' }"></i>
<i
class="mdi"
:class="{
'mdi-library-music': playlist.type !== 'folder',
'mdi-rss': playlist.type === 'rss',
'mdi-folder': playlist.type === 'folder'
}"
/>
</span>
</template>
<template v-slot:actions>
<template #actions>
<a @click.prevent.stop="open_dialog(playlist)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
<span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px"
/></span>
</a>
</template>
</list-item-playlist>
<modal-dialog-playlist :show="show_details_modal" :playlist="selected_playlist" @close="show_details_modal = false" />
<modal-dialog-playlist
:show="show_details_modal"
:playlist="selected_playlist"
@close="show_details_modal = false"
/>
</div>
</template>
@@ -26,7 +44,7 @@ export default {
props: ['playlists'],
data () {
data() {
return {
show_details_modal: false,
selected_playlist: {}
@@ -50,5 +68,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,13 +1,24 @@
<template>
<div>
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" @click="play_track(index, track)">
<template v-slot:actions>
<list-item-track
v-for="(track, index) in tracks"
:key="track.id"
:track="track"
@click="play_track(index, track)"
>
<template #actions>
<a @click.prevent.stop="open_dialog(track)">
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
<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-track
:show="show_details_modal"
:track="selected_track"
@close="show_details_modal = false"
/>
</div>
</template>
@@ -22,7 +33,7 @@ export default {
props: ['tracks', 'uris', 'expression'],
data () {
data() {
return {
show_details_modal: false,
selected_track: {}
@@ -48,5 +59,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,30 +1,47 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4" v-if="title">
<p v-if="title" class="title is-4">
{{ title }}
</p>
<slot name="modal-content"></slot>
<slot name="modal-content" />
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="$emit('close')">
<span class="icon"><i class="mdi mdi-cancel"></i></span> <span class="is-size-7">{{ close_action ? close_action : 'Cancel' }}</span>
<span class="icon"><i class="mdi mdi-cancel" /></span>
<span class="is-size-7">{{
close_action ? close_action : 'Cancel'
}}</span>
</a>
<a v-if="delete_action" class="card-footer-item has-background-danger has-text-white has-text-weight-bold" @click="$emit('delete')">
<span class="icon"><i class="mdi mdi-delete"></i></span> <span class="is-size-7">{{ delete_action }}</span>
<a
v-if="delete_action"
class="card-footer-item has-background-danger has-text-white has-text-weight-bold"
@click="$emit('delete')"
>
<span class="icon"><i class="mdi mdi-delete" /></span>
<span class="is-size-7">{{ delete_action }}</span>
</a>
<a v-if="ok_action" class="card-footer-item has-background-info has-text-white has-text-weight-bold" @click="$emit('ok')">
<span class="icon"><i class="mdi mdi-check"></i></span> <span class="is-size-7">{{ ok_action }}</span>
<a
v-if="ok_action"
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="$emit('ok')"
>
<span class="icon"><i class="mdi mdi-check" /></span>
<span class="is-size-7">{{ ok_action }}</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -37,5 +54,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
@@ -10,32 +10,54 @@
<form @submit.prevent="add_stream">
<div class="field">
<p class="control is-expanded has-icons-left">
<input class="input is-shadowless" type="text" placeholder="http://url-to-rss" v-model="url" :disabled="loading" ref="url_field">
<input
ref="url_field"
v-model="url"
class="input is-shadowless"
type="text"
placeholder="http://url-to-rss"
:disabled="loading"
/>
<span class="icon is-left">
<i class="mdi mdi-rss"></i>
<i class="mdi mdi-rss" />
</span>
</p>
<p class="help">Adding a podcast includes creating an RSS playlist, that will allow OwnTone to manage the podcast subscription.
<p class="help">
Adding a podcast includes creating an RSS playlist, that
will allow OwnTone to manage the podcast subscription.
</p>
</div>
</form>
</div>
<footer class="card-footer" v-if="loading">
<footer v-if="loading" class="card-footer">
<a class="card-footer-item button is-loading">
<span class="icon"><i class="mdi mdi-web"></i></span> <span class="is-size-7">Processing ...</span>
<span class="icon"><i class="mdi mdi-web" /></span>
<span class="is-size-7">Processing ...</span>
</a>
</footer>
<footer class="card-footer" v-else>
<a class="card-footer-item has-text-danger" @click="$emit('close')">
<span class="icon"><i class="mdi mdi-cancel"></i></span> <span class="is-size-7">Cancel</span>
<footer v-else class="card-footer">
<a
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><i class="mdi mdi-cancel" /></span>
<span class="is-size-7">Cancel</span>
</a>
<a class="card-footer-item has-background-info has-text-white has-text-weight-bold" @click="add_stream">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="add_stream"
>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -48,28 +70,15 @@ export default {
name: 'ModalDialogAddRss',
props: ['show'],
data () {
data() {
return {
url: '',
loading: false
}
},
methods: {
add_stream: function () {
this.loading = true
webapi.library_add(this.url).then(() => {
this.$emit('close')
this.$emit('podcast-added')
this.url = ''
}).catch(() => {
this.loading = false
})
}
},
watch: {
'show' () {
show() {
if (this.show) {
this.loading = false
@@ -79,9 +88,24 @@ export default {
}, 10)
}
}
},
methods: {
add_stream: function () {
this.loading = true
webapi
.library_add(this.url)
.then(() => {
this.$emit('close')
this.$emit('podcast-added')
this.url = ''
})
.catch(() => {
this.loading = false
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,44 +1,63 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
Add stream URL
</p>
<form v-on:submit.prevent="play" class="fd-has-margin-bottom">
<p class="title is-4">Add stream URL</p>
<form class="fd-has-margin-bottom" @submit.prevent="play">
<div class="field">
<p class="control is-expanded has-icons-left">
<input class="input is-shadowless" type="text" placeholder="http://url-to-stream" v-model="url" :disabled="loading" ref="url_field">
<input
ref="url_field"
v-model="url"
class="input is-shadowless"
type="text"
placeholder="http://url-to-stream"
:disabled="loading"
/>
<span class="icon is-left">
<i class="mdi mdi-web"></i>
<i class="mdi mdi-web" />
</span>
</p>
</div>
</form>
</div>
<footer class="card-footer" v-if="loading">
<footer v-if="loading" class="card-footer">
<a class="card-footer-item has-text-dark">
<span class="icon"><i class="mdi mdi-web"></i></span> <span class="is-size-7">Loading ...</span>
<span class="icon"><i class="mdi mdi-web" /></span>
<span class="is-size-7">Loading ...</span>
</a>
</footer>
<footer class="card-footer" v-else>
<a class="card-footer-item has-text-danger" @click="$emit('close')">
<span class="icon"><i class="mdi mdi-cancel"></i></span> <span class="is-size-7">Cancel</span>
<footer v-else class="card-footer">
<a
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><i class="mdi mdi-cancel" /></span>
<span class="is-size-7">Cancel</span>
</a>
<a class="card-footer-item has-text-dark" @click="add_stream">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-background-info has-text-white has-text-weight-bold" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="play"
>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -51,37 +70,15 @@ export default {
name: 'ModalDialogAddUrlStream',
props: ['show'],
data () {
data() {
return {
url: '',
loading: false
}
},
methods: {
add_stream: function () {
this.loading = true
webapi.queue_add(this.url).then(() => {
this.$emit('close')
this.url = ''
}).catch(() => {
this.loading = false
})
},
play: function () {
this.loading = true
webapi.player_play_uri(this.url, false).then(() => {
this.$emit('close')
this.url = ''
}).catch(() => {
this.loading = false
})
}
},
watch: {
'show' () {
show() {
if (this.show) {
this.loading = false
@@ -91,9 +88,36 @@ export default {
}, 10)
}
}
},
methods: {
add_stream: function () {
this.loading = true
webapi
.queue_add(this.url)
.then(() => {
this.$emit('close')
this.url = ''
})
.catch(() => {
this.loading = false
})
},
play: function () {
this.loading = true
webapi
.player_play_uri(this.url, false)
.then(() => {
this.$emit('close')
this.url = ''
})
.catch(() => {
this.loading = false
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
@@ -10,22 +10,33 @@
:artwork_url="album.artwork_url"
:artist="album.artist"
:album="album.name"
class="image is-square fd-has-margin-bottom fd-has-shadow" />
class="image is-square fd-has-margin-bottom fd-has-shadow"
/>
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{ album.name }}</a>
<a class="has-text-link" @click="open_album">{{
album.name
}}</a>
</p>
<div class="buttons" v-if="media_kind_resolved === 'podcast'">
<a class="button is-small" @click="mark_played">Mark as played</a>
<a class="button is-small" @click="$emit('remove-podcast')">Remove podcast</a>
<div v-if="media_kind_resolved === 'podcast'" class="buttons">
<a class="button is-small" @click="mark_played"
>Mark as played</a
>
<a class="button is-small" @click="$emit('remove-podcast')"
>Remove podcast</a
>
</div>
<div class="content is-small">
<p v-if="album.artist">
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artist }}</a>
<a class="title is-6 has-text-link" @click="open_artist">{{
album.artist
}}</a>
</p>
<p v-if="album.date_released">
<span class="heading">Release date</span>
<span class="title is-6">{{ $filters.time(album.date_released, 'L') }}</span>
<span class="title is-6">{{
$filters.time(album.date_released, 'L')
}}</span>
</p>
<p v-else-if="album.year > 0">
<span class="heading">Year</span>
@@ -37,32 +48,45 @@
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ $filters.duration(album.length_ms) }}</span>
<span class="title is-6">{{
$filters.duration(album.length_ms)
}}</span>
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ album.media_kind }} - {{ album.data_kind }}</span>
<span class="title is-6"
>{{ album.media_kind }} - {{ album.data_kind }}</span
>
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{ $filters.time(album.time_added, 'L LT') }}</span>
<span class="title is-6">{{
$filters.time(album.time_added, 'L LT')
}}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -77,7 +101,7 @@ export default {
components: { CoverArtwork },
props: ['show', 'album', 'media_kind', 'new_tracks'],
data () {
data() {
return {
artwork_visible: false
}
@@ -123,17 +147,21 @@ export default {
if (this.media_kind_resolved === 'podcast') {
// No artist page for podcasts
} else if (this.media_kind_resolved === 'audiobook') {
this.$router.push({ path: '/audiobooks/artists/' + this.album.artist_id })
this.$router.push({
path: '/audiobooks/artists/' + this.album.artist_id
})
} else {
this.$router.push({ path: '/music/artists/' + this.album.artist_id })
}
},
mark_played: function () {
webapi.library_album_track_update(this.album.id, { play_count: 'played' }).then(({ data }) => {
this.$emit('play-count-changed')
this.$emit('close')
})
webapi
.library_album_track_update(this.album.id, { play_count: 'played' })
.then(({ data }) => {
this.$emit('play-count-changed')
this.$emit('close')
})
},
artwork_loaded: function () {
@@ -147,5 +175,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,13 +1,15 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ artist.name }}</a>
<a class="has-text-link" @click="open_artist">{{
artist.name
}}</a>
</p>
<div class="content is-small">
<p>
@@ -24,24 +26,33 @@
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{ $filters.time(artist.time_added, 'L LT') }}</span>
<span class="title is-6">{{
$filters.time(artist.time_added, 'L LT')
}}</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -78,5 +89,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,37 +1,50 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_albums">{{ composer.name }}</a>
<a class="has-text-link" @click="open_albums">{{
composer.name
}}</a>
</p>
<p>
<span class="heading">Albums</span>
<a class="has-text-link is-6" @click="open_albums">{{ composer.album_count }}</a>
<a class="has-text-link is-6" @click="open_albums">{{
composer.album_count
}}</a>
</p>
<p>
<span class="heading">Tracks</span>
<a class="has-text-link is-6" @click="open_tracks">{{ composer.track_count }}</a>
<a class="has-text-link is-6" @click="open_tracks">{{
composer.track_count
}}</a>
</p>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -47,31 +60,43 @@ export default {
methods: {
play: function () {
this.$emit('close')
webapi.player_play_expression('composer is "' + this.composer.name + '" and media_kind is music', false)
webapi.player_play_expression(
'composer is "' + this.composer.name + '" and media_kind is music',
false
)
},
queue_add: function () {
this.$emit('close')
webapi.queue_expression_add('composer is "' + this.composer.name + '" and media_kind is music')
webapi.queue_expression_add(
'composer is "' + this.composer.name + '" and media_kind is music'
)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_expression_add_next('composer is "' + this.composer.name + '" and media_kind is music')
webapi.queue_expression_add_next(
'composer is "' + this.composer.name + '" and media_kind is music'
)
},
open_albums: function () {
this.$emit('close')
this.$router.push({ name: 'ComposerAlbums', params: { composer: this.composer.name } })
this.$router.push({
name: 'ComposerAlbums',
params: { composer: this.composer.name }
})
},
open_tracks: function () {
this.show_details_modal = false
this.$router.push({ name: 'ComposerTracks', params: { composer: this.composer.name } })
this.$router.push({
name: 'ComposerTracks',
params: { composer: this.composer.name }
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
@@ -12,18 +12,25 @@
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -39,21 +46,27 @@ export default {
methods: {
play: function () {
this.$emit('close')
webapi.player_play_expression('path starts with "' + this.directory.path + '" order by path asc', false)
webapi.player_play_expression(
'path starts with "' + this.directory.path + '" order by path asc',
false
)
},
queue_add: function () {
this.$emit('close')
webapi.queue_expression_add('path starts with "' + this.directory.path + '" order by path asc')
webapi.queue_expression_add(
'path starts with "' + this.directory.path + '" order by path asc'
)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_expression_add_next('path starts with "' + this.directory.path + '" order by path asc')
webapi.queue_expression_add_next(
'path starts with "' + this.directory.path + '" order by path asc'
)
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,29 +1,38 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_genre">{{ genre.name }}</a>
<a class="has-text-link" @click="open_genre">{{
genre.name
}}</a>
</p>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -39,17 +48,24 @@ export default {
methods: {
play: function () {
this.$emit('close')
webapi.player_play_expression('genre is "' + this.genre.name + '" and media_kind is music', false)
webapi.player_play_expression(
'genre is "' + this.genre.name + '" and media_kind is music',
false
)
},
queue_add: function () {
this.$emit('close')
webapi.queue_expression_add('genre is "' + this.genre.name + '" and media_kind is music')
webapi.queue_expression_add(
'genre is "' + this.genre.name + '" and media_kind is music'
)
},
queue_add_next: function () {
this.$emit('close')
webapi.queue_expression_add_next('genre is "' + this.genre.name + '" and media_kind is music')
webapi.queue_expression_add_next(
'genre is "' + this.genre.name + '" and media_kind is music'
)
},
open_genre: function () {
@@ -60,5 +76,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,13 +1,15 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ playlist.name }}</a>
<a class="has-text-link" @click="open_playlist">{{
playlist.name
}}</a>
</p>
<div class="content is-small">
<p>
@@ -20,20 +22,27 @@
</p>
</div>
</div>
<footer class="card-footer" v-if="!playlist.folder">
<footer v-if="!playlist.folder" class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -70,5 +79,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,41 +1,59 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
Save queue to playlist
</p>
<form v-on:submit.prevent="save" class="fd-has-margin-bottom">
<p class="title is-4">Save queue to playlist</p>
<form class="fd-has-margin-bottom" @submit.prevent="save">
<div class="field">
<p class="control is-expanded has-icons-left">
<input class="input is-shadowless" type="text" placeholder="Playlist name" v-model="playlist_name" :disabled="loading" ref="playlist_name_field">
<input
ref="playlist_name_field"
v-model="playlist_name"
class="input is-shadowless"
type="text"
placeholder="Playlist name"
:disabled="loading"
/>
<span class="icon is-left">
<i class="mdi mdi-file-music"></i>
<i class="mdi mdi-file-music" />
</span>
</p>
</div>
</form>
</div>
<footer class="card-footer" v-if="loading">
<footer v-if="loading" class="card-footer">
<a class="card-footer-item has-text-dark">
<span class="icon"><i class="mdi mdi-web"></i></span> <span class="is-size-7">Saving ...</span>
<span class="icon"><i class="mdi mdi-web" /></span>
<span class="is-size-7">Saving ...</span>
</a>
</footer>
<footer class="card-footer" v-else>
<a class="card-footer-item has-text-danger" @click="$emit('close')">
<span class="icon"><i class="mdi mdi-cancel"></i></span> <span class="is-size-7">Cancel</span>
<footer v-else class="card-footer">
<a
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><i class="mdi mdi-cancel" /></span>
<span class="is-size-7">Cancel</span>
</a>
<a class="card-footer-item has-background-info has-text-white has-text-weight-bold" @click="save">
<span class="icon"><i class="mdi mdi-content-save"></i></span> <span class="is-size-7">Save</span>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="save"
>
<span class="icon"><i class="mdi mdi-content-save" /></span>
<span class="is-size-7">Save</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -48,13 +66,26 @@ export default {
name: 'ModalDialogPlaylistSave',
props: ['show'],
data () {
data() {
return {
playlist_name: '',
loading: false
}
},
watch: {
show() {
if (this.show) {
this.loading = false
// We need to delay setting the focus to the input field until the field is part of the dom and visible
setTimeout(() => {
this.$refs.playlist_name_field.focus()
}, 10)
}
}
},
methods: {
save: function () {
if (this.playlist_name.length < 1) {
@@ -62,29 +93,18 @@ export default {
}
this.loading = true
webapi.queue_save_playlist(this.playlist_name).then(() => {
this.$emit('close')
this.playlist_name = ''
}).catch(() => {
this.loading = false
})
}
},
watch: {
'show' () {
if (this.show) {
this.loading = false
// We need to delay setting the focus to the input field until the field is part of the dom and visible
setTimeout(() => {
this.$refs.playlist_name_field.focus()
}, 10)
}
webapi
.queue_save_playlist(this.playlist_name)
.then(() => {
this.$emit('close')
this.playlist_name = ''
})
.catch(() => {
this.loading = false
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
@@ -15,12 +15,22 @@
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a v-if="item.album_id" class="title is-6 has-text-link" @click="open_album">{{ item.album }}</a>
<a
v-if="item.album_id"
class="title is-6 has-text-link"
@click="open_album"
>{{ item.album }}</a
>
<span v-else class="title is-6">{{ item.album }}</span>
</p>
<p v-if="item.album_artist">
<span class="heading">Album artist</span>
<a v-if="item.album_artist_id" class="title is-6 has-text-link" @click="open_album_artist">{{ item.album_artist }}</a>
<a
v-if="item.album_artist_id"
class="title is-6 has-text-link"
@click="open_album_artist"
>{{ item.album_artist }}</a
>
<span v-else class="title is-6">{{ item.album_artist }}</span>
</p>
<p v-if="item.composer">
@@ -33,15 +43,21 @@
</p>
<p v-if="item.genre">
<span class="heading">Genre</span>
<a class="title is-6 has-text-link" @click="open_genre">{{ item.genre }}</a>
<a class="title is-6 has-text-link" @click="open_genre">{{
item.genre
}}</a>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ item.track_number }} / {{ item.disc_number }}</span>
<span class="title is-6"
>{{ item.track_number }} / {{ item.disc_number }}</span
>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ $filters.duration(item.length_ms) }}</span>
<span class="title is-6">{{
$filters.duration(item.length_ms)
}}</span>
</p>
<p>
<span class="heading">Path</span>
@@ -49,14 +65,26 @@
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ item.media_kind }} - {{ item.data_kind }} <span class="has-text-weight-normal" v-if="item.data_kind === 'spotify'">(<a @click="open_spotify_artist">artist</a>, <a @click="open_spotify_album">album</a>)</span></span>
<span class="title is-6"
>{{ item.media_kind }} - {{ item.data_kind }}
<span
v-if="item.data_kind === 'spotify'"
class="has-text-weight-normal"
>(<a @click="open_spotify_artist">artist</a>,
<a @click="open_spotify_album">album</a>)</span
></span
>
</p>
<p>
<span class="heading">Quality</span>
<span class="title is-6">
{{ item.type }}
<span v-if="item.samplerate"> | {{ item.samplerate }} Hz</span>
<span v-if="item.channels"> | {{ $filters.channels(item.channels) }}</span>
<span v-if="item.samplerate">
| {{ item.samplerate }} Hz</span
>
<span v-if="item.channels">
| {{ $filters.channels(item.channels) }}</span
>
<span v-if="item.bitrate"> | {{ item.bitrate }} Kb/s</span>
</span>
</p>
@@ -64,15 +92,21 @@
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="remove">
<span class="icon"><i class="mdi mdi-delete"></i></span> <span class="is-size-7">Remove</span>
<span class="icon"><i class="mdi mdi-delete" /></span>
<span class="is-size-7">Remove</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -86,12 +120,28 @@ export default {
name: 'ModalDialogQueueItem',
props: ['show', 'item'],
data () {
data() {
return {
spotify_track: {}
}
},
watch: {
item() {
if (this.item && this.item.data_kind === 'spotify') {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
spotifyApi
.getTrack(this.item.path.slice(this.item.path.lastIndexOf(':') + 1))
.then((response) => {
this.spotify_track = response
})
} else {
this.spotify_track = {}
}
}
},
methods: {
remove: function () {
this.$emit('close')
@@ -123,30 +173,19 @@ export default {
open_spotify_artist: function () {
this.$emit('close')
this.$router.push({ path: '/music/spotify/artists/' + this.spotify_track.artists[0].id })
this.$router.push({
path: '/music/spotify/artists/' + this.spotify_track.artists[0].id
})
},
open_spotify_album: function () {
this.$emit('close')
this.$router.push({ path: '/music/spotify/albums/' + this.spotify_track.album.id })
}
},
watch: {
'item' () {
if (this.item && this.item.data_kind === 'spotify') {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
spotifyApi.getTrack(this.item.path.slice(this.item.path.lastIndexOf(':') + 1)).then((response) => {
this.spotify_track = response
})
} else {
this.spotify_track = {}
}
this.$router.push({
path: '/music/spotify/albums/' + this.spotify_track.album.id
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,36 +1,52 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
Remote pairing request
</p>
<form v-on:submit.prevent="kickoff_pairing">
<p class="title is-4">Remote pairing request</p>
<form @submit.prevent="kickoff_pairing">
<label class="label">
{{ pairing.remote }}
</label>
<div class="field">
<div class="control">
<input class="input" type="text" placeholder="Enter pairing code" v-model="pairing_req.pin" ref="pin_field">
<input
ref="pin_field"
v-model="pairing_req.pin"
class="input"
type="text"
placeholder="Enter pairing code"
/>
</div>
</div>
</form>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-danger" @click="$emit('close')">
<span class="icon"><i class="mdi mdi-cancel"></i></span> <span class="is-size-7">Cancel</span>
<a
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><i class="mdi mdi-cancel" /></span>
<span class="is-size-7">Cancel</span>
</a>
<a class="card-footer-item has-background-info has-text-white has-text-weight-bold" @click="kickoff_pairing">
<span class="icon"><i class="mdi mdi-cellphone-iphone"></i></span> <span class="is-size-7">Pair Remote</span>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="kickoff_pairing"
>
<span class="icon"><i class="mdi mdi-cellphone-iphone" /></span>
<span class="is-size-7">Pair Remote</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -43,28 +59,20 @@ export default {
name: 'ModalDialogRemotePairing',
props: ['show'],
data () {
data() {
return {
pairing_req: { pin: '' }
}
},
computed: {
pairing () {
pairing() {
return this.$store.state.pairing
}
},
methods: {
kickoff_pairing () {
webapi.pairing_kickoff(this.pairing_req).then(() => {
this.pairing_req.pin = ''
})
}
},
watch: {
'show' () {
show() {
if (this.show) {
this.loading = false
@@ -74,9 +82,16 @@ export default {
}, 10)
}
}
},
methods: {
kickoff_pairing() {
webapi.pairing_kickoff(this.pairing_req).then(() => {
this.pairing_req.pin = ''
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
@@ -12,18 +12,34 @@
<p class="subtitle">
{{ track.artist }}
</p>
<div class="buttons" v-if="track.media_kind === 'podcast'">
<a class="button is-small" v-if="track.play_count > 0" @click="mark_new">Mark as new</a>
<a class="button is-small" v-if="track.play_count === 0" @click="mark_played">Mark as played</a>
<div v-if="track.media_kind === 'podcast'" class="buttons">
<a
v-if="track.play_count > 0"
class="button is-small"
@click="mark_new"
>Mark as new</a
>
<a
v-if="track.play_count === 0"
class="button is-small"
@click="mark_played"
>Mark as played</a
>
</div>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{ track.album }}</a>
<a class="title is-6 has-text-link" @click="open_album">{{
track.album
}}</a>
</p>
<p v-if="track.album_artist && track.media_kind !== 'audiobook'">
<p
v-if="track.album_artist && track.media_kind !== 'audiobook'"
>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ track.album_artist }}</a>
<a class="title is-6 has-text-link" @click="open_artist">{{
track.album_artist
}}</a>
</p>
<p v-if="track.composer">
<span class="heading">Composer</span>
@@ -31,7 +47,9 @@
</p>
<p v-if="track.date_released">
<span class="heading">Release date</span>
<span class="title is-6">{{ $filters.time(track.date_released, 'L') }}</span>
<span class="title is-6">{{
$filters.time(track.date_released, 'L')
}}</span>
</p>
<p v-else-if="track.year > 0">
<span class="heading">Year</span>
@@ -39,15 +57,21 @@
</p>
<p v-if="track.genre">
<span class="heading">Genre</span>
<a class="title is-6 has-text-link" @click="open_genre">{{ track.genre }}</a>
<a class="title is-6 has-text-link" @click="open_genre">{{
track.genre
}}</a>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ track.track_number }} / {{ track.disc_number }}</span>
<span class="title is-6"
>{{ track.track_number }} / {{ track.disc_number }}</span
>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ $filters.duration(track.length_ms) }}</span>
<span class="title is-6">{{
$filters.duration(track.length_ms)
}}</span>
</p>
<p>
<span class="heading">Path</span>
@@ -55,24 +79,42 @@
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ track.media_kind }} - {{ track.data_kind }} <span class="has-text-weight-normal" v-if="track.data_kind === 'spotify'">(<a @click="open_spotify_artist">artist</a>, <a @click="open_spotify_album">album</a>)</span></span>
<span class="title is-6"
>{{ track.media_kind }} - {{ track.data_kind }}
<span
v-if="track.data_kind === 'spotify'"
class="has-text-weight-normal"
>(<a @click="open_spotify_artist">artist</a>,
<a @click="open_spotify_album">album</a>)</span
></span
>
</p>
<p>
<span class="heading">Quality</span>
<span class="title is-6">
{{ track.type }}
<span v-if="track.samplerate"> | {{ track.samplerate }} Hz</span>
<span v-if="track.channels"> | {{ $filters.channels(track.channels) }}</span>
<span v-if="track.bitrate"> | {{ track.bitrate }} Kb/s</span>
<span v-if="track.samplerate">
| {{ track.samplerate }} Hz</span
>
<span v-if="track.channels">
| {{ $filters.channels(track.channels) }}</span
>
<span v-if="track.bitrate">
| {{ track.bitrate }} Kb/s</span
>
</span>
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{ $filters.time(track.time_added, 'L LT') }}</span>
<span class="title is-6">{{
$filters.time(track.time_added, 'L LT')
}}</span>
</p>
<p>
<span class="heading">Rating</span>
<span class="title is-6">{{ Math.floor(track.rating / 10) }} / 10</span>
<span class="title is-6"
>{{ Math.floor(track.rating / 10) }} / 10</span
>
</p>
<p v-if="track.comment">
<span class="heading">Comment</span>
@@ -82,18 +124,25 @@
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play_track">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -108,12 +157,28 @@ export default {
props: ['show', 'track'],
data () {
data() {
return {
spotify_track: {}
}
},
watch: {
track() {
if (this.track && this.track.data_kind === 'spotify') {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
spotifyApi
.getTrack(this.track.path.slice(this.track.path.lastIndexOf(':') + 1))
.then((response) => {
this.spotify_track = response
})
} else {
this.spotify_track = {}
}
}
},
methods: {
play_track: function () {
this.$emit('close')
@@ -143,7 +208,9 @@ export default {
open_artist: function () {
this.$emit('close')
this.$router.push({ path: '/music/artists/' + this.track.album_artist_id })
this.$router.push({
path: '/music/artists/' + this.track.album_artist_id
})
},
open_genre: function () {
@@ -152,44 +219,37 @@ export default {
open_spotify_artist: function () {
this.$emit('close')
this.$router.push({ path: '/music/spotify/artists/' + this.spotify_track.artists[0].id })
this.$router.push({
path: '/music/spotify/artists/' + this.spotify_track.artists[0].id
})
},
open_spotify_album: function () {
this.$emit('close')
this.$router.push({ path: '/music/spotify/albums/' + this.spotify_track.album.id })
this.$router.push({
path: '/music/spotify/albums/' + this.spotify_track.album.id
})
},
mark_new: function () {
webapi.library_track_update(this.track.id, { play_count: 'reset' }).then(() => {
this.$emit('play-count-changed')
this.$emit('close')
})
webapi
.library_track_update(this.track.id, { play_count: 'reset' })
.then(() => {
this.$emit('play-count-changed')
this.$emit('close')
})
},
mark_played: function () {
webapi.library_track_update(this.track.id, { play_count: 'increment' }).then(() => {
this.$emit('play-count-changed')
this.$emit('close')
})
}
},
watch: {
'track' () {
if (this.track && this.track.data_kind === 'spotify') {
const spotifyApi = new SpotifyWebApi()
spotifyApi.setAccessToken(this.$store.state.spotify.webapi_token)
spotifyApi.getTrack(this.track.path.slice(this.track.path.lastIndexOf(':') + 1)).then((response) => {
this.spotify_track = response
webapi
.library_track_update(this.track.id, { play_count: 'increment' })
.then(() => {
this.$emit('play-count-changed')
this.$emit('close')
})
} else {
this.spotify_track = {}
}
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,29 +1,34 @@
<template>
<modal-dialog
:show="show"
title="Update library"
:ok_action="library.updating ? '' : 'Rescan'"
close_action="Close"
@ok="update_library"
@close="close()">
<template v-slot:modal-content>
:show="show"
title="Update library"
:ok_action="library.updating ? '' : 'Rescan'"
close_action="Close"
@ok="update_library"
@close="close()"
>
<template #modal-content>
<div v-if="!library.updating">
<p class="mb-3">Scan for new, deleted and modified files</p>
<div class="field" v-if="spotify_enabled || rss.tracks > 0">
<div v-if="spotify_enabled || rss.tracks > 0" class="field">
<div class="control">
<div class="select is-small">
<select v-model="update_dialog_scan_kind">
<option value="">Update everything</option>
<option value="files">Only update local library</option>
<option value="spotify" v-if="spotify_enabled">Only update Spotify</option>
<option value="rss" v-if="rss.tracks > 0">Only update RSS feeds</option>
<option v-if="spotify_enabled" value="spotify">
Only update Spotify
</option>
<option v-if="rss.tracks > 0" value="rss">
Only update RSS feeds
</option>
</select>
</div>
</div>
</div>
<div class="field">
<label class="checkbox is-size-7 is-small">
<input type="checkbox" v-model="rescan_metadata">
<input v-model="rescan_metadata" type="checkbox" />
Rescan metadata for unmodified files
</label>
</div>
@@ -45,37 +50,37 @@ export default {
components: { ModalDialog },
props: ['show'],
data () {
data() {
return {
rescan_metadata: false
}
},
computed: {
library () {
library() {
return this.$store.state.library
},
rss () {
rss() {
return this.$store.state.rss_count
},
spotify_enabled () {
spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid
},
update_dialog_scan_kind: {
get () {
get() {
return this.$store.state.update_dialog_scan_kind
},
set (value) {
set(value) {
this.$store.commit(types.UPDATE_DIALOG_SCAN_KIND, value)
}
}
},
methods: {
update_library () {
update_library() {
if (this.rescan_metadata) {
webapi.library_rescan(this.update_dialog_scan_kind)
} else {
@@ -83,7 +88,7 @@ export default {
}
},
close () {
close() {
this.update_dialog_scan_kind = ''
this.$emit('close')
}
@@ -91,5 +96,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,65 +1,140 @@
<template>
<nav class="fd-bottom-navbar navbar is-white is-fixed-bottom" :style="zindex" :class="{ 'is-transparent': is_now_playing_page, 'is-dark': !is_now_playing_page }" role="navigation" aria-label="player controls">
<nav
class="fd-bottom-navbar navbar is-white is-fixed-bottom"
:style="zindex"
:class="{
'is-transparent': is_now_playing_page,
'is-dark': !is_now_playing_page
}"
role="navigation"
aria-label="player controls"
>
<div class="navbar-brand fd-expanded">
<!-- Link to queue -->
<navbar-item-link to="/" exact>
<span class="icon"><i class="mdi mdi-24px mdi-playlist-play"></i></span>
<span class="icon"><i class="mdi mdi-24px mdi-playlist-play" /></span>
</navbar-item-link>
<!-- Now playing artist/title (not visible on "now playing" page) -->
<router-link to="/now-playing" v-if="!is_now_playing_page" class="navbar-item is-expanded is-clipped" active-class="is-active" exact>
<router-link
v-if="!is_now_playing_page"
to="/now-playing"
class="navbar-item is-expanded is-clipped"
active-class="is-active"
exact
>
<div class="is-clipped">
<p class="is-size-7 fd-is-text-clipped">
<strong>{{ now_playing.title }}</strong><br>
{{ now_playing.artist }}<span v-if="now_playing.data_kind === 'url'"> - {{ now_playing.album }}</span>
<strong>{{ now_playing.title }}</strong
><br />
{{ now_playing.artist
}}<span v-if="now_playing.data_kind === 'url'">
- {{ now_playing.album }}</span
>
</p>
</div>
</router-link>
<!-- Skip previous (not visible on "now playing" page) -->
<player-button-previous v-if="is_now_playing_page" class="navbar-item fd-margin-left-auto" icon_style="mdi-24px"></player-button-previous>
<player-button-seek-back v-if="is_now_playing_page" seek_ms="10000" class="navbar-item" icon_style="mdi-24px"></player-button-seek-back>
<player-button-previous
v-if="is_now_playing_page"
class="navbar-item fd-margin-left-auto"
icon_style="mdi-24px"
/>
<player-button-seek-back
v-if="is_now_playing_page"
seek_ms="10000"
class="navbar-item"
icon_style="mdi-24px"
/>
<!-- Play/pause -->
<player-button-play-pause class="navbar-item" icon_style="mdi-36px" show_disabled_message></player-button-play-pause>
<player-button-seek-forward v-if="is_now_playing_page" seek_ms="30000" class="navbar-item" icon_style="mdi-24px"></player-button-seek-forward>
<player-button-play-pause
class="navbar-item"
icon_style="mdi-36px"
show_disabled_message
/>
<player-button-seek-forward
v-if="is_now_playing_page"
seek_ms="30000"
class="navbar-item"
icon_style="mdi-24px"
/>
<!-- Skip next (not visible on "now playing" page) -->
<player-button-next v-if="is_now_playing_page" class="navbar-item" icon_style="mdi-24px"></player-button-next>
<player-button-next
v-if="is_now_playing_page"
class="navbar-item"
icon_style="mdi-24px"
/>
<!-- Player menu button (only visible on mobile and tablet) -->
<a class="navbar-item fd-margin-left-auto is-hidden-desktop" @click="show_player_menu = !show_player_menu">
<span class="icon"><i class="mdi mdi-18px" :class="{ 'mdi-chevron-up': !show_player_menu, 'mdi-chevron-down': show_player_menu }"></i></span>
<a
class="navbar-item fd-margin-left-auto is-hidden-desktop"
@click="show_player_menu = !show_player_menu"
>
<span class="icon"
><i
class="mdi mdi-18px"
:class="{
'mdi-chevron-up': !show_player_menu,
'mdi-chevron-down': show_player_menu
}"
/></span>
</a>
<!-- Player menu dropup menu (only visible on desktop) -->
<div class="navbar-item has-dropdown has-dropdown-up fd-margin-left-auto is-hidden-touch"
:class="{ 'is-active': show_player_menu }">
<a class="navbar-link is-arrowless"
@click="show_player_menu = !show_player_menu">
<span class="icon"><i class="mdi mdi-18px"
:class="{ 'mdi-chevron-up': !show_player_menu, 'mdi-chevron-down': show_player_menu }"></i></span>
<div
class="navbar-item has-dropdown has-dropdown-up fd-margin-left-auto is-hidden-touch"
:class="{ 'is-active': show_player_menu }"
>
<a
class="navbar-link is-arrowless"
@click="show_player_menu = !show_player_menu"
>
<span class="icon"
><i
class="mdi mdi-18px"
:class="{
'mdi-chevron-up': !show_player_menu,
'mdi-chevron-down': show_player_menu
}"
/></span>
</a>
<div class="navbar-dropdown is-right is-boxed" style="margin-right: 6px; margin-bottom: 6px; border-radius: 6px;">
<div
class="navbar-dropdown is-right is-boxed"
style="margin-right: 6px; margin-bottom: 6px; border-radius: 6px"
>
<div class="navbar-item">
<!-- Outputs: master volume -->
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0;">
<a class="button is-white is-small" @click="toggle_mute_volume">
<span class="icon"><i class="mdi mdi-18px" :class="{ 'mdi-volume-off': player.volume <= 0, 'mdi-volume-high': player.volume > 0 }"></i></span>
<div class="level-item" style="flex-grow: 0">
<a
class="button is-white is-small"
@click="toggle_mute_volume"
>
<span class="icon"
><i
class="mdi mdi-18px"
:class="{
'mdi-volume-off': player.volume <= 0,
'mdi-volume-high': player.volume > 0
}"
/></span>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
<p class="heading">Volume</p>
<Slider v-model="player.volume"
<Slider
v-model="player.volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:classes="{ target: 'slider' }"
@change="set_volume"
:classes="{ target: 'slider'}" />
/>
<!--range-slider
class="slider fd-has-action"
min="0"
@@ -75,28 +150,53 @@
</div>
<!-- Outputs: master volume -->
<hr class="fd-navbar-divider">
<navbar-item-output v-for="output in outputs" :key="output.id" :output="output"></navbar-item-output>
<hr class="fd-navbar-divider" />
<navbar-item-output
v-for="output in outputs"
:key="output.id"
:output="output"
/>
<!-- Outputs: stream volume -->
<hr class="fd-navbar-divider">
<hr class="fd-navbar-divider" />
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0;">
<a class="button is-white is-small" :class="{ 'is-loading': loading }"><span class="icon fd-has-action" :class="{ 'has-text-grey-light': !playing && !loading, 'is-loading': loading }" @click="togglePlay"><i class="mdi mdi-18px mdi-radio-tower"></i></span></a>
<div class="level-item" style="flex-grow: 0">
<a
class="button is-white is-small"
:class="{ 'is-loading': loading }"
><span
class="icon fd-has-action"
:class="{
'has-text-grey-light': !playing && !loading,
'is-loading': loading
}"
@click="togglePlay"
><i class="mdi mdi-18px mdi-radio-tower" /></span
></a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
<p class="heading" :class="{ 'has-text-grey-light': !playing }">HTTP stream <a href="stream.mp3"><span class="is-lowercase">(stream.mp3)</span></a></p>
<Slider v-model="stream_volume"
<p
class="heading"
:class="{ 'has-text-grey-light': !playing }"
>
HTTP stream
<a href="stream.mp3"
><span class="is-lowercase">(stream.mp3)</span></a
>
</p>
<Slider
v-model="stream_volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:disabled="!playing"
:classes="{ target: 'slider' }"
@change="set_stream_volume"
:classes="{ target: 'slider'}" />
/>
<!--range-slider
class="slider fd-has-action"
min="0"
@@ -113,14 +213,14 @@
</div>
<!-- Playback controls -->
<hr class="fd-navbar-divider">
<hr class="fd-navbar-divider" />
<div class="navbar-item">
<div class="level is-mobile fd-expanded">
<div class="level-item">
<div class="buttons has-addons">
<player-button-repeat class="button"></player-button-repeat>
<player-button-shuffle class="button"></player-button-shuffle>
<player-button-consume class="button"></player-button-consume>
<player-button-repeat class="button" />
<player-button-shuffle class="button" />
<player-button-consume class="button" />
</div>
</div>
</div>
@@ -130,40 +230,51 @@
</div>
<!-- Player menu (only visible on mobile and tablet) -->
<div class="navbar-menu is-hidden-desktop" :class="{ 'is-active': show_player_menu }">
<div class="navbar-start">
</div>
<div
class="navbar-menu is-hidden-desktop"
:class="{ 'is-active': show_player_menu }"
>
<div class="navbar-start" />
<div class="navbar-end">
<!-- Repeat/shuffle/consume -->
<div class="navbar-item">
<div class="buttons is-centered">
<player-button-repeat class="button" icon_style="mdi-18px"></player-button-repeat>
<player-button-shuffle class="button" icon_style="mdi-18px"></player-button-shuffle>
<player-button-consume class="button" icon_style="mdi-18px"></player-button-consume>
<player-button-repeat class="button" icon_style="mdi-18px" />
<player-button-shuffle class="button" icon_style="mdi-18px" />
<player-button-consume class="button" icon_style="mdi-18px" />
</div>
</div>
<hr class="fd-navbar-divider">
<hr class="fd-navbar-divider" />
<!-- Outputs: master volume -->
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0;">
<div class="level-item" style="flex-grow: 0">
<a class="button is-white is-small" @click="toggle_mute_volume">
<span class="icon"><i class="mdi mdi-18px" :class="{ 'mdi-volume-off': player.volume <= 0, 'mdi-volume-high': player.volume > 0 }"></i></span>
<span class="icon"
><i
class="mdi mdi-18px"
:class="{
'mdi-volume-off': player.volume <= 0,
'mdi-volume-high': player.volume > 0
}"
/></span>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
<p class="heading">Volume</p>
<Slider v-model="player.volume"
<Slider
v-model="player.volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:classes="{ target: 'slider' }"
@change="set_volume"
:classes="{ target: 'slider'}" />
/>
<!--range-slider
class="slider fd-has-action"
min="0"
@@ -179,32 +290,54 @@
</div>
<!-- Outputs: speaker volumes -->
<navbar-item-output v-for="output in outputs" :key="output.id" :output="output"></navbar-item-output>
<navbar-item-output
v-for="output in outputs"
:key="output.id"
:output="output"
/>
<!-- Outputs: stream volume -->
<hr class="fd-navbar-divider">
<hr class="fd-navbar-divider" />
<div class="navbar-item fd-has-margin-bottom">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0;">
<a class="button is-white is-small" :class="{ 'is-loading': loading }">
<span class="icon fd-has-action"
:class="{ 'has-text-grey-light': !playing && !loading, 'is-loading': loading }"
@click="togglePlay"><i class="mdi mdi-18px mdi-radio-tower"></i>
<div class="level-item" style="flex-grow: 0">
<a
class="button is-white is-small"
:class="{ 'is-loading': loading }"
>
<span
class="icon fd-has-action"
:class="{
'has-text-grey-light': !playing && !loading,
'is-loading': loading
}"
@click="togglePlay"
><i class="mdi mdi-18px mdi-radio-tower" />
</span>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
<p class="heading" :class="{ 'has-text-grey-light': !playing }">HTTP stream <a href="stream.mp3"><span class="is-lowercase">(stream.mp3)</span></a></p>
<Slider v-model="stream_volume"
<p
class="heading"
:class="{ 'has-text-grey-light': !playing }"
>
HTTP stream
<a href="stream.mp3"
><span class="is-lowercase">(stream.mp3)</span></a
>
</p>
<Slider
v-model="stream_volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:disabled="!playing"
:classes="{ target: 'slider' }"
@change="set_stream_volume"
:classes="{ target: 'slider'}" />
/>
<!-- range-slider
class="slider fd-has-action"
min="0"
@@ -258,7 +391,7 @@ export default {
PlayerButtonSeekBack
},
data () {
data() {
return {
old_volume: 0,
@@ -273,49 +406,67 @@ export default {
computed: {
show_player_menu: {
get () {
get() {
return this.$store.state.show_player_menu
},
set (value) {
set(value) {
this.$store.commit(types.SHOW_PLAYER_MENU, value)
}
},
show_burger_menu () {
show_burger_menu() {
return this.$store.state.show_burger_menu
},
zindex () {
zindex() {
if (this.show_burger_menu) {
return 'z-index: 20'
}
return ''
},
state () {
state() {
return this.$store.state.player
},
now_playing () {
now_playing() {
return this.$store.getters.now_playing
},
is_now_playing_page () {
is_now_playing_page() {
return this.$route.path === '/now-playing'
},
outputs () {
outputs() {
return this.$store.state.outputs
},
player () {
player() {
return this.$store.state.player
},
config () {
config() {
return this.$store.state.config
}
},
watch: {
'$store.state.player.volume'() {
if (this.player.volume > 0) {
this.old_volume = this.player.volume
}
}
},
// on app mounted
mounted() {
this.setupAudio()
},
// on app destroyed
unmounted() {
this.closeAudio()
},
methods: {
on_click_outside_outputs () {
on_click_outside_outputs() {
this.show_outputs_menu = false
},
@@ -334,21 +485,24 @@ export default {
setupAudio: function () {
const a = _audio.setupAudio()
a.addEventListener('waiting', e => {
a.addEventListener('waiting', (e) => {
this.playing = false
this.loading = true
})
a.addEventListener('playing', e => {
a.addEventListener('playing', (e) => {
this.playing = true
this.loading = false
})
a.addEventListener('ended', e => {
a.addEventListener('ended', (e) => {
this.playing = false
this.loading = false
})
a.addEventListener('error', e => {
a.addEventListener('error', (e) => {
this.closeAudio()
this.$store.dispatch('add_notification', { text: 'HTTP stream error: failed to load stream or stopped loading due to network problem', type: 'danger' })
this.$store.dispatch('add_notification', {
text: 'HTTP stream error: failed to load stream or stopped loading due to network problem',
type: 'danger'
})
this.playing = false
this.loading = false
})
@@ -385,27 +539,8 @@ export default {
this.stream_volume = newVolume
_audio.setVolume(this.stream_volume / 100)
}
},
watch: {
'$store.state.player.volume' () {
if (this.player.volume > 0) {
this.old_volume = this.player.volume
}
}
},
// on app mounted
mounted () {
this.setupAudio()
},
// on app destroyed
destroyed () {
this.closeAudio()
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,6 +1,11 @@
<template>
<a class="navbar-item" :class="{ 'is-active': is_active }" @click.stop.prevent="open_link()" :href="full_path()">
<slot></slot>
<a
class="navbar-item"
:class="{ 'is-active': is_active }"
:href="full_path()"
@click.stop.prevent="open_link()"
>
<slot />
</a>
</template>
@@ -15,7 +20,7 @@ export default {
},
computed: {
is_active () {
is_active() {
if (this.exact) {
return this.$route.path === this.to
}
@@ -23,19 +28,19 @@ export default {
},
show_player_menu: {
get () {
get() {
return this.$store.state.show_player_menu
},
set (value) {
set(value) {
this.$store.commit(types.SHOW_PLAYER_MENU, value)
}
},
show_burger_menu: {
get () {
get() {
return this.$store.state.show_burger_menu
},
set (value) {
set(value) {
this.$store.commit(types.SHOW_BURGER_MENU, value)
}
}

View File

@@ -2,26 +2,39 @@
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0;">
<div class="level-item" style="flex-grow: 0">
<a class="button is-white is-small">
<span class="icon fd-has-action"
:class="{ 'has-text-grey-light': !output.selected }"
v-on:click="set_enabled">
<i class="mdi mdi-18px" :class="type_class" :title="output.type"></i>
<span
class="icon fd-has-action"
:class="{ 'has-text-grey-light': !output.selected }"
@click="set_enabled"
>
<i
class="mdi mdi-18px"
:class="type_class"
:title="output.type"
/>
</span>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
<p class="heading" :class="{ 'has-text-grey-light': !output.selected }">{{ output.name }}</p>
<Slider v-model="volume"
<p
class="heading"
:class="{ 'has-text-grey-light': !output.selected }"
>
{{ output.name }}
</p>
<Slider
v-model="volume"
:min="0"
:max="100"
:step="1"
:tooltips="false"
:disabled="!output.selected"
:classes="{ target: 'slider' }"
@change="set_volume"
:classes="{ target: 'slider'}" />
/>
<!--range-slider
class="slider fd-has-action"
min="0"
@@ -45,15 +58,15 @@ import webapi from '@/webapi'
export default {
name: 'NavbarItemOutput',
components: {
// RangeSlider
components: {
// RangeSlider
Slider
},
},
props: ['output'],
computed: {
type_class () {
type_class() {
if (this.output.type.startsWith('AirPlay')) {
return 'mdi-airplay'
} else if (this.output.type === 'Chromecast') {
@@ -65,7 +78,7 @@ export default {
}
},
volume () {
volume() {
return this.output.selected ? this.output.volume : 0
}
},
@@ -89,5 +102,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,80 +1,134 @@
<template>
<nav class="fd-top-navbar navbar is-light is-fixed-top" :style="zindex" role="navigation" aria-label="main navigation">
<nav
class="fd-top-navbar navbar is-light is-fixed-top"
:style="zindex"
role="navigation"
aria-label="main navigation"
>
<div class="navbar-brand">
<navbar-item-link to="/playlists" v-if="is_visible_playlists">
<span class="icon"><i class="mdi mdi-library-music"></i></span>
<navbar-item-link v-if="is_visible_playlists" to="/playlists">
<span class="icon"><i class="mdi mdi-library-music" /></span>
</navbar-item-link>
<navbar-item-link to="/music" v-if="is_visible_music">
<span class="icon"><i class="mdi mdi-music"></i></span>
<navbar-item-link v-if="is_visible_music" to="/music">
<span class="icon"><i class="mdi mdi-music" /></span>
</navbar-item-link>
<navbar-item-link to="/podcasts" v-if="is_visible_podcasts">
<span class="icon"><i class="mdi mdi-microphone"></i></span>
<navbar-item-link v-if="is_visible_podcasts" to="/podcasts">
<span class="icon"><i class="mdi mdi-microphone" /></span>
</navbar-item-link>
<navbar-item-link to="/audiobooks" v-if="is_visible_audiobooks">
<span class="icon"><i class="mdi mdi-book-open-variant"></i></span>
<navbar-item-link v-if="is_visible_audiobooks" to="/audiobooks">
<span class="icon"><i class="mdi mdi-book-open-variant" /></span>
</navbar-item-link>
<navbar-item-link to="/radio" v-if="is_visible_radio">
<span class="icon"><i class="mdi mdi-radio"></i></span>
<navbar-item-link v-if="is_visible_radio" to="/radio">
<span class="icon"><i class="mdi mdi-radio" /></span>
</navbar-item-link>
<navbar-item-link to="/files" v-if="is_visible_files">
<span class="icon"><i class="mdi mdi-folder-open"></i></span>
<navbar-item-link v-if="is_visible_files" to="/files">
<span class="icon"><i class="mdi mdi-folder-open" /></span>
</navbar-item-link>
<navbar-item-link to="/search" v-if="is_visible_search">
<span class="icon"><i class="mdi mdi-magnify"></i></span>
<navbar-item-link v-if="is_visible_search" to="/search">
<span class="icon"><i class="mdi mdi-magnify" /></span>
</navbar-item-link>
<div class="navbar-burger" @click="show_burger_menu = !show_burger_menu" :class="{ 'is-active': show_burger_menu }">
<span></span>
<span></span>
<span></span>
<div
class="navbar-burger"
:class="{ 'is-active': show_burger_menu }"
@click="show_burger_menu = !show_burger_menu"
>
<span />
<span />
<span />
</div>
</div>
<div class="navbar-menu" :class="{ 'is-active': show_burger_menu }">
<div class="navbar-start">
</div>
<div class="navbar-start" />
<div class="navbar-end">
<!-- Burger menu entries -->
<div class="navbar-item has-dropdown is-hoverable"
:class="{ 'is-active': show_settings_menu }"
@click="on_click_outside_settings">
<div
class="navbar-item has-dropdown is-hoverable"
:class="{ 'is-active': show_settings_menu }"
@click="on_click_outside_settings"
>
<a class="navbar-link is-arrowless">
<span class="icon is-hidden-touch"><i class="mdi mdi-24px mdi-menu"></i></span>
<span class="icon is-hidden-touch"
><i class="mdi mdi-24px mdi-menu"
/></span>
<span class="is-hidden-desktop has-text-weight-bold">OwnTone</span>
</a>
<div class="navbar-dropdown is-right">
<navbar-item-link to="/playlists">
<span class="icon"><i class="mdi mdi-library-music" /></span>
<b>Playlists</b>
</navbar-item-link>
<navbar-item-link to="/music" exact>
<span class="icon"><i class="mdi mdi-music" /></span>
<b>Music</b>
</navbar-item-link>
<navbar-item-link to="/music/artists">
<span class="fd-navbar-item-level2">Artists</span>
</navbar-item-link>
<navbar-item-link to="/music/albums">
<span class="fd-navbar-item-level2">Albums</span>
</navbar-item-link>
<navbar-item-link to="/music/genres">
<span class="fd-navbar-item-level2">Genres</span>
</navbar-item-link>
<navbar-item-link v-if="spotify_enabled" to="/music/spotify">
<span class="fd-navbar-item-level2">Spotify</span>
</navbar-item-link>
<navbar-item-link to="/podcasts">
<span class="icon"><i class="mdi mdi-microphone" /></span>
<b>Podcasts</b>
</navbar-item-link>
<navbar-item-link to="/audiobooks">
<span class="icon"><i class="mdi mdi-book-open-variant" /></span>
<b>Audiobooks</b>
</navbar-item-link>
<navbar-item-link to="/radio">
<span class="icon"><i class="mdi mdi-radio" /></span>
<b>Radio</b>
</navbar-item-link>
<navbar-item-link to="/files">
<span class="icon"><i class="mdi mdi-folder-open" /></span>
<b>Files</b>
</navbar-item-link>
<navbar-item-link to="/search">
<span class="icon"><i class="mdi mdi-magnify" /></span>
<b>Search</b>
</navbar-item-link>
<hr class="fd-navbar-divider" />
<navbar-item-link to="/playlists"><span class="icon"><i class="mdi mdi-library-music"></i></span> <b>Playlists</b></navbar-item-link>
<navbar-item-link to="/music" exact><span class="icon"><i class="mdi mdi-music"></i></span> <b>Music</b></navbar-item-link>
<navbar-item-link to="/music/artists"><span class="fd-navbar-item-level2">Artists</span></navbar-item-link>
<navbar-item-link to="/music/albums"><span class="fd-navbar-item-level2">Albums</span></navbar-item-link>
<navbar-item-link to="/music/genres"><span class="fd-navbar-item-level2">Genres</span></navbar-item-link>
<navbar-item-link to="/music/spotify" v-if="spotify_enabled"><span class="fd-navbar-item-level2">Spotify</span></navbar-item-link>
<navbar-item-link to="/podcasts"><span class="icon"><i class="mdi mdi-microphone"></i></span> <b>Podcasts</b></navbar-item-link>
<navbar-item-link to="/audiobooks"><span class="icon"><i class="mdi mdi-book-open-variant"></i></span> <b>Audiobooks</b></navbar-item-link>
<navbar-item-link to="/radio"><span class="icon"><i class="mdi mdi-radio"></i></span> <b>Radio</b></navbar-item-link>
<navbar-item-link to="/files"><span class="icon"><i class="mdi mdi-folder-open"></i></span> <b>Files</b></navbar-item-link>
<navbar-item-link to="/search"><span class="icon"><i class="mdi mdi-magnify"></i></span> <b>Search</b></navbar-item-link>
<hr class="fd-navbar-divider">
<navbar-item-link to="/settings/webinterface">Settings</navbar-item-link>
<a class="navbar-item" @click.stop.prevent="show_update_dialog = true; show_settings_menu = false; show_burger_menu = false">
<navbar-item-link to="/settings/webinterface">
Settings
</navbar-item-link>
<a
class="navbar-item"
@click.stop.prevent="
show_update_dialog = true
show_settings_menu = false
show_burger_menu = false
"
>
Update Library
</a>
<navbar-item-link to="/about">About</navbar-item-link>
<navbar-item-link to="/about"> About </navbar-item-link>
<div class="navbar-item is-hidden-desktop" style="margin-bottom: 2.5rem;"></div>
<div
class="navbar-item is-hidden-desktop"
style="margin-bottom: 2.5rem"
/>
</div>
</div>
</div>
</div>
<div class="is-overlay" v-show="show_settings_menu"
style="z-index:10; width: 100vw; height:100vh;"
@click="show_settings_menu = false"></div>
<div
v-show="show_settings_menu"
class="is-overlay"
style="z-index: 10; width: 100vw; height: 100vh"
@click="show_settings_menu = false"
/>
</nav>
</template>
@@ -86,82 +140,103 @@ export default {
name: 'NavbarTop',
components: { NavbarItemLink },
data () {
data() {
return {
show_settings_menu: false
}
},
computed: {
is_visible_playlists () {
return this.$store.getters.settings_option('webinterface', 'show_menu_item_playlists').value
is_visible_playlists() {
return this.$store.getters.settings_option(
'webinterface',
'show_menu_item_playlists'
).value
},
is_visible_music () {
return this.$store.getters.settings_option('webinterface', 'show_menu_item_music').value
is_visible_music() {
return this.$store.getters.settings_option(
'webinterface',
'show_menu_item_music'
).value
},
is_visible_podcasts () {
return this.$store.getters.settings_option('webinterface', 'show_menu_item_podcasts').value
is_visible_podcasts() {
return this.$store.getters.settings_option(
'webinterface',
'show_menu_item_podcasts'
).value
},
is_visible_audiobooks () {
return this.$store.getters.settings_option('webinterface', 'show_menu_item_audiobooks').value
is_visible_audiobooks() {
return this.$store.getters.settings_option(
'webinterface',
'show_menu_item_audiobooks'
).value
},
is_visible_radio () {
return this.$store.getters.settings_option('webinterface', 'show_menu_item_radio').value
is_visible_radio() {
return this.$store.getters.settings_option(
'webinterface',
'show_menu_item_radio'
).value
},
is_visible_files () {
return this.$store.getters.settings_option('webinterface', 'show_menu_item_files').value
is_visible_files() {
return this.$store.getters.settings_option(
'webinterface',
'show_menu_item_files'
).value
},
is_visible_search () {
return this.$store.getters.settings_option('webinterface', 'show_menu_item_search').value
is_visible_search() {
return this.$store.getters.settings_option(
'webinterface',
'show_menu_item_search'
).value
},
player () {
player() {
return this.$store.state.player
},
config () {
config() {
return this.$store.state.config
},
library () {
library() {
return this.$store.state.library
},
audiobooks () {
audiobooks() {
return this.$store.state.audiobooks_count
},
podcasts () {
podcasts() {
return this.$store.state.podcasts_count
},
spotify_enabled () {
spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid
},
show_burger_menu: {
get () {
get() {
return this.$store.state.show_burger_menu
},
set (value) {
set(value) {
this.$store.commit(types.SHOW_BURGER_MENU, value)
}
},
show_player_menu () {
show_player_menu() {
return this.$store.state.show_player_menu
},
show_update_dialog: {
get () {
get() {
return this.$store.state.show_update_dialog
},
set (value) {
set(value) {
this.$store.commit(types.SHOW_UPDATE_DIALOG, value)
}
},
zindex () {
zindex() {
if (this.show_player_menu) {
return 'z-index: 20'
}
@@ -169,19 +244,18 @@ export default {
}
},
methods: {
on_click_outside_settings () {
this.show_settings_menu = !this.show_settings_menu
watch: {
$route(to, from) {
this.show_settings_menu = false
}
},
watch: {
$route (to, from) {
this.show_settings_menu = false
methods: {
on_click_outside_settings() {
this.show_settings_menu = !this.show_settings_menu
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,9 +1,17 @@
<template>
<section class="fd-notifications" v-if="notifications.length > 0">
<section v-if="notifications.length > 0" class="fd-notifications">
<div class="columns is-centered">
<div class="column is-half">
<div class="notification has-shadow " v-for="notification in notifications" :key="notification.id" :class="['notification', notification.type ? `is-${notification.type}` : '']">
<button class="delete" v-on:click="remove(notification)"></button>
<div
v-for="notification in notifications"
:key="notification.id"
class="notification has-shadow"
:class="[
'notification',
notification.type ? `is-${notification.type}` : ''
]"
>
<button class="delete" @click="remove(notification)" />
{{ notification.text }}
</div>
</div>
@@ -16,14 +24,14 @@ import * as types from '@/store/mutation_types'
export default {
name: 'Notifications',
components: { },
components: {},
data () {
data() {
return { showNav: false }
},
computed: {
notifications () {
notifications() {
return this.$store.state.notifications.list
}
},

View File

@@ -1,6 +1,6 @@
<template>
<a @click="toggle_consume_mode" :class="{ 'is-warning': is_consume }">
<span class="icon"><i class="mdi mdi-fire" :class="icon_style"></i></span>
<a :class="{ 'is-warning': is_consume }" @click="toggle_consume_mode">
<span class="icon"><i class="mdi mdi-fire" :class="icon_style" /></span>
</a>
</template>
@@ -15,7 +15,7 @@ export default {
},
computed: {
is_consume () {
is_consume() {
return this.$store.state.player.consume
}
},
@@ -28,5 +28,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,6 +1,8 @@
<template>
<a @click="play_next" :disabled="disabled">
<span class="icon"><i class="mdi mdi-skip-forward" :class="icon_style"></i></span>
<a :disabled="disabled" @click="play_next">
<span class="icon"
><i class="mdi mdi-skip-forward" :class="icon_style"
/></span>
</a>
</template>
@@ -15,7 +17,7 @@ export default {
},
computed: {
disabled () {
disabled() {
return !this.$store.state.queue || this.$store.state.queue.count <= 0
}
},
@@ -32,5 +34,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,6 +1,17 @@
<template>
<a @click="toggle_play_pause" :disabled="disabled">
<span class="icon"><i class="mdi" :class="[icon_style, { 'mdi-play': !is_playing, 'mdi-pause': is_playing && is_pause_allowed, 'mdi-stop': is_playing && !is_pause_allowed }]"></i></span>
<a :disabled="disabled" @click="toggle_play_pause">
<span class="icon"
><i
class="mdi"
:class="[
icon_style,
{
'mdi-play': !is_playing,
'mdi-pause': is_playing && is_pause_allowed,
'mdi-stop': is_playing && !is_pause_allowed
}
]"
/></span>
</a>
</template>
@@ -16,16 +27,18 @@ export default {
},
computed: {
is_playing () {
is_playing() {
return this.$store.state.player.state === 'play'
},
is_pause_allowed () {
return (this.$store.getters.now_playing &&
this.$store.getters.now_playing.data_kind !== 'pipe')
is_pause_allowed() {
return (
this.$store.getters.now_playing &&
this.$store.getters.now_playing.data_kind !== 'pipe'
)
},
disabled () {
disabled() {
return !this.$store.state.queue || this.$store.state.queue.count <= 0
}
},
@@ -34,7 +47,12 @@ export default {
toggle_play_pause: function () {
if (this.disabled) {
if (this.show_disabled_message) {
this.$store.dispatch('add_notification', { text: 'Queue is empty', type: 'info', topic: 'connection', timeout: 2000 })
this.$store.dispatch('add_notification', {
text: 'Queue is empty',
type: 'info',
topic: 'connection',
timeout: 2000
})
}
return
}
@@ -51,5 +69,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,6 +1,8 @@
<template>
<a @click="play_previous" :disabled="disabled">
<span class="icon"><i class="mdi mdi-skip-backward" :class="icon_style"></i></span>
<a :disabled="disabled" @click="play_previous">
<span class="icon"
><i class="mdi mdi-skip-backward" :class="icon_style"
/></span>
</a>
</template>
@@ -15,7 +17,7 @@ export default {
},
computed: {
disabled () {
disabled() {
return !this.$store.state.queue || this.$store.state.queue.count <= 0
}
},
@@ -32,5 +34,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,6 +1,17 @@
<template>
<a @click="toggle_repeat_mode" :class="{ 'is-warning': !is_repeat_off }">
<span class="icon"><i class="mdi" :class="[icon_style, { 'mdi-repeat': is_repeat_all, 'mdi-repeat-once': is_repeat_single, 'mdi-repeat-off': is_repeat_off }]"></i></span>
<a :class="{ 'is-warning': !is_repeat_off }" @click="toggle_repeat_mode">
<span class="icon"
><i
class="mdi"
:class="[
icon_style,
{
'mdi-repeat': is_repeat_all,
'mdi-repeat-once': is_repeat_single,
'mdi-repeat-off': is_repeat_off
}
]"
/></span>
</a>
</template>
@@ -15,13 +26,13 @@ export default {
},
computed: {
is_repeat_all () {
is_repeat_all() {
return this.$store.state.player.repeat === 'all'
},
is_repeat_single () {
is_repeat_single() {
return this.$store.state.player.repeat === 'single'
},
is_repeat_off () {
is_repeat_off() {
return !this.is_repeat_all && !this.is_repeat_single
}
},
@@ -40,5 +51,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,6 +1,6 @@
<template>
<a @click="seek" :disabled="disabled" v-if="visible">
<span class="icon"><i class="mdi mdi-rewind" :class="icon_style"></i></span>
<a v-if="visible" :disabled="disabled" @click="seek">
<span class="icon"><i class="mdi mdi-rewind" :class="icon_style" /></span>
</a>
</template>
@@ -12,17 +12,21 @@ export default {
props: ['seek_ms', 'icon_style'],
computed: {
now_playing () {
now_playing() {
return this.$store.getters.now_playing
},
is_stopped () {
is_stopped() {
return this.$store.state.player.state === 'stop'
},
disabled () {
return !this.$store.state.queue || this.$store.state.queue.count <= 0 || this.is_stopped ||
this.now_playing.data_kind === 'pipe'
disabled() {
return (
!this.$store.state.queue ||
this.$store.state.queue.count <= 0 ||
this.is_stopped ||
this.now_playing.data_kind === 'pipe'
)
},
visible () {
visible() {
return ['podcast', 'audiobook'].includes(this.now_playing.media_kind)
}
},

View File

@@ -1,6 +1,8 @@
<template>
<a @click="seek" :disabled="disabled" v-if="visible">
<span class="icon"><i class="mdi mdi-fast-forward" :class="icon_style"></i></span>
<a v-if="visible" :disabled="disabled" @click="seek">
<span class="icon"
><i class="mdi mdi-fast-forward" :class="icon_style"
/></span>
</a>
</template>
@@ -12,17 +14,21 @@ export default {
props: ['seek_ms', 'icon_style'],
computed: {
now_playing () {
now_playing() {
return this.$store.getters.now_playing
},
is_stopped () {
is_stopped() {
return this.$store.state.player.state === 'stop'
},
disabled () {
return !this.$store.state.queue || this.$store.state.queue.count <= 0 || this.is_stopped ||
this.now_playing.data_kind === 'pipe'
disabled() {
return (
!this.$store.state.queue ||
this.$store.state.queue.count <= 0 ||
this.is_stopped ||
this.now_playing.data_kind === 'pipe'
)
},
visible () {
visible() {
return ['podcast', 'audiobook'].includes(this.now_playing.media_kind)
}
},

View File

@@ -1,6 +1,13 @@
<template>
<a @click="toggle_shuffle_mode" :class="{ 'is-warning': is_shuffle }">
<span class="icon"><i class="mdi" :class="[icon_style, { 'mdi-shuffle': is_shuffle, 'mdi-shuffle-disabled': !is_shuffle }]"></i></span>
<a :class="{ 'is-warning': is_shuffle }" @click="toggle_shuffle_mode">
<span class="icon"
><i
class="mdi"
:class="[
icon_style,
{ 'mdi-shuffle': is_shuffle, 'mdi-shuffle-disabled': !is_shuffle }
]"
/></span>
</a>
</template>
@@ -15,7 +22,7 @@ export default {
},
computed: {
is_shuffle () {
is_shuffle() {
return this.$store.state.player.shuffle
}
},
@@ -28,5 +35,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,5 +1,9 @@
<template>
<div v-if="width > 0" class="progress-bar mt-2" :style="{ width: width_percent }" />
<div
v-if="width > 0"
class="progress-bar mt-2"
:style="{ width: width_percent }"
/>
</template>
<script>
@@ -8,13 +12,13 @@ export default {
props: ['max', 'value'],
computed: {
width () {
width() {
if (this.value > 0 && this.max > 0) {
return parseInt(this.value * 100 / this.max)
return parseInt((this.value * 100) / this.max)
}
return 0
},
width_percent () {
width_percent() {
return this.width + '%'
}
}

View File

@@ -1,19 +1,25 @@
<template>
<div class="field">
<label class="checkbox">
<input type="checkbox"
:checked="value"
@change="set_update_timer"
ref="settings_checkbox">
<slot name="label"></slot>
<i class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"> {{ info }}</i>
<input
ref="settings_checkbox"
type="checkbox"
:checked="value"
@change="set_update_timer"
/>
<slot name="label" />
<i
class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"
>
{{ info }}</i
>
</label>
<p class="help" v-if="$slots['info']">
<slot name="info"></slot>
<p v-if="$slots['info']" class="help">
<slot name="info" />
</p>
</div>
</template>
@@ -27,7 +33,7 @@ export default {
props: ['category_name', 'option_name'],
data () {
data() {
return {
timerDelay: 2000,
timerId: -1,
@@ -38,22 +44,26 @@ export default {
},
computed: {
category () {
return this.$store.state.settings.categories.find(elem => elem.name === this.category_name)
category() {
return this.$store.state.settings.categories.find(
(elem) => elem.name === this.category_name
)
},
option () {
option() {
if (!this.category) {
return {}
}
return this.category.options.find(elem => elem.name === this.option_name)
return this.category.options.find(
(elem) => elem.name === this.option_name
)
},
value () {
value() {
return this.option.value
},
info () {
info() {
if (this.statusUpdate === 'success') {
return '(setting saved)'
} else if (this.statusUpdate === 'error') {
@@ -64,7 +74,7 @@ export default {
},
methods: {
set_update_timer () {
set_update_timer() {
if (this.timerId > 0) {
window.clearTimeout(this.timerId)
this.timerId = -1
@@ -77,7 +87,7 @@ export default {
}
},
update_setting () {
update_setting() {
this.timerId = -1
const newValue = this.$refs.settings_checkbox.checked
@@ -92,15 +102,19 @@ export default {
name: this.option_name,
value: newValue
}
webapi.settings_update(this.category.name, option).then(() => {
this.$store.commit(types.UPDATE_SETTINGS_OPTION, option)
this.statusUpdate = 'success'
}).catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_checkbox.checked = this.value
}).finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)
})
webapi
.settings_update(this.category.name, option)
.then(() => {
this.$store.commit(types.UPDATE_SETTINGS_OPTION, option)
this.statusUpdate = 'success'
})
.catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_checkbox.checked = this.value
})
.finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)
})
},
clear_status: function () {
@@ -110,5 +124,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -2,25 +2,31 @@
<fieldset :disabled="disabled">
<div class="field">
<label class="label has-text-weight-normal">
<slot name="label"></slot>
<i class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"> {{ info }}</i>
<slot name="label" />
<i
class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"
>
{{ info }}</i
>
</label>
<div class="control">
<input class="input"
type="number"
min="0"
style="width: 10em;"
:placeholder="placeholder"
:value="value"
@input="set_update_timer"
ref="settings_number">
<input
ref="settings_number"
class="input"
type="number"
min="0"
style="width: 10em"
:placeholder="placeholder"
:value="value"
@input="set_update_timer"
/>
</div>
<p class="help" v-if="$slots['info']">
<slot name="info"></slot>
<p v-if="$slots['info']" class="help">
<slot name="info" />
</p>
</div>
</fieldset>
@@ -35,7 +41,7 @@ export default {
props: ['category_name', 'option_name', 'placeholder', 'disabled'],
data () {
data() {
return {
timerDelay: 2000,
timerId: -1,
@@ -45,22 +51,26 @@ export default {
},
computed: {
category () {
return this.$store.state.settings.categories.find(elem => elem.name === this.category_name)
category() {
return this.$store.state.settings.categories.find(
(elem) => elem.name === this.category_name
)
},
option () {
option() {
if (!this.category) {
return {}
}
return this.category.options.find(elem => elem.name === this.option_name)
return this.category.options.find(
(elem) => elem.name === this.option_name
)
},
value () {
value() {
return this.option.value
},
info () {
info() {
if (this.statusUpdate === 'success') {
return '(setting saved)'
} else if (this.statusUpdate === 'error') {
@@ -71,7 +81,7 @@ export default {
},
methods: {
set_update_timer () {
set_update_timer() {
if (this.timerId > 0) {
window.clearTimeout(this.timerId)
this.timerId = -1
@@ -84,7 +94,7 @@ export default {
}
},
update_setting () {
update_setting() {
this.timerId = -1
const newValue = this.$refs.settings_number.value
@@ -98,15 +108,19 @@ export default {
name: this.option_name,
value: parseInt(newValue, 10)
}
webapi.settings_update(this.category.name, option).then(() => {
this.$store.commit(types.UPDATE_SETTINGS_OPTION, option)
this.statusUpdate = 'success'
}).catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_number.value = this.value
}).finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)
})
webapi
.settings_update(this.category.name, option)
.then(() => {
this.$store.commit(types.UPDATE_SETTINGS_OPTION, option)
this.statusUpdate = 'success'
})
.catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_number.value = this.value
})
.finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)
})
},
clear_status: function () {
@@ -116,5 +130,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -2,21 +2,29 @@
<fieldset :disabled="disabled">
<div class="field">
<label class="label has-text-weight-normal">
<slot name="label"></slot>
<i class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"> {{ info }}</i>
<slot name="label" />
<i
class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"
>
{{ info }}</i
>
</label>
<div class="control">
<input class="input" type="text" :placeholder="placeholder"
:value="value"
@input="set_update_timer"
ref="settings_text">
<input
ref="settings_text"
class="input"
type="text"
:placeholder="placeholder"
:value="value"
@input="set_update_timer"
/>
</div>
<p class="help" v-if="$slots['info']">
<slot name="info"></slot>
<p v-if="$slots['info']" class="help">
<slot name="info" />
</p>
</div>
</fieldset>
@@ -31,7 +39,7 @@ export default {
props: ['category_name', 'option_name', 'placeholder', 'disabled'],
data () {
data() {
return {
timerDelay: 2000,
timerId: -1,
@@ -42,22 +50,26 @@ export default {
},
computed: {
category () {
return this.$store.state.settings.categories.find(elem => elem.name === this.category_name)
category() {
return this.$store.state.settings.categories.find(
(elem) => elem.name === this.category_name
)
},
option () {
option() {
if (!this.category) {
return {}
}
return this.category.options.find(elem => elem.name === this.option_name)
return this.category.options.find(
(elem) => elem.name === this.option_name
)
},
value () {
value() {
return this.option.value
},
info () {
info() {
if (this.statusUpdate === 'success') {
return '(setting saved)'
} else if (this.statusUpdate === 'error') {
@@ -68,7 +80,7 @@ export default {
},
methods: {
set_update_timer () {
set_update_timer() {
if (this.timerId > 0) {
window.clearTimeout(this.timerId)
this.timerId = -1
@@ -81,7 +93,7 @@ export default {
}
},
update_setting () {
update_setting() {
this.timerId = -1
const newValue = this.$refs.settings_text.value
@@ -95,15 +107,19 @@ export default {
name: this.option_name,
value: newValue
}
webapi.settings_update(this.category.name, option).then(() => {
this.$store.commit(types.UPDATE_SETTINGS_OPTION, option)
this.statusUpdate = 'success'
}).catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_text.value = this.value
}).finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)
})
webapi
.settings_update(this.category.name, option)
.then(() => {
this.$store.commit(types.UPDATE_SETTINGS_OPTION, option)
this.statusUpdate = 'success'
})
.catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_text.value = this.value
})
.finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)
})
},
clear_status: function () {
@@ -113,5 +129,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,16 +1,21 @@
<template>
<div class="media">
<div class="media-left fd-has-action"
v-if="$slots['artwork']">
<slot name="artwork"></slot>
<div v-if="$slots['artwork']" class="media-left fd-has-action">
<slot name="artwork" />
</div>
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">{{ album.name }}</h1>
<h2 class="subtitle is-7 has-text-grey"><b>{{ album.artists[0].name }}</b></h2>
<h2 class="subtitle is-7 has-text-grey has-text-weight-normal">({{ album.album_type }}, {{ $filters.time(album.release_date, 'L') }})</h2>
<h1 class="title is-6">
{{ album.name }}
</h1>
<h2 class="subtitle is-7 has-text-grey">
<b>{{ album.artists[0].name }}</b>
</h2>
<h2 class="subtitle is-7 has-text-grey has-text-weight-normal">
({{ album.album_type }}, {{ $filters.time(album.release_date, 'L') }})
</h2>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -22,5 +27,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,10 +1,12 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="open_artist">
<h1 class="title is-6">{{ artist.name }}</h1>
<div class="media-content fd-has-action is-clipped" @click="open_artist">
<h1 class="title is-6">
{{ artist.name }}
</h1>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -22,5 +24,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,11 +1,15 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="open_playlist">
<h1 class="title is-6">{{ playlist.name }}</h1>
<h2 class="subtitle is-7">{{ playlist.owner.display_name }}</h2>
<div class="media-content fd-has-action is-clipped" @click="open_playlist">
<h1 class="title is-6">
{{ playlist.name }}
</h1>
<h2 class="subtitle is-7">
{{ playlist.owner.display_name }}
</h2>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -17,11 +21,12 @@ export default {
methods: {
open_playlist: function () {
this.$router.push({ path: '/music/spotify/playlists/' + this.playlist.id })
this.$router.push({
path: '/music/spotify/playlists/' + this.playlist.id
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,14 +1,30 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" v-on:click="play">
<h1 class="title is-6" :class="{ 'has-text-grey-light': track.is_playable === false }">{{ track.name }}</h1>
<h2 class="subtitle is-7" :class="{ 'has-text-grey': track.is_playable, 'has-text-grey-light': track.is_playable === false }"><b>{{ track.artists[0].name }}</b></h2>
<h2 class="subtitle is-7" v-if="track.is_playable === false">
(Track is not playable<span v-if="track.restrictions && track.restrictions.reason">, restriction reason: {{ track.restrictions.reason }}</span>)
<div class="media-content fd-has-action is-clipped" @click="play">
<h1
class="title is-6"
:class="{ 'has-text-grey-light': track.is_playable === false }"
>
{{ track.name }}
</h1>
<h2
class="subtitle is-7"
:class="{
'has-text-grey': track.is_playable,
'has-text-grey-light': track.is_playable === false
}"
>
<b>{{ track.artists[0].name }}</b>
</h2>
<h2 v-if="track.is_playable === false" class="subtitle is-7">
(Track is not playable<span
v-if="track.restrictions && track.restrictions.reason"
>, restriction reason: {{ track.restrictions.reason }}</span
>)
</h2>
</div>
<div class="media-right">
<slot name="actions"></slot>
<slot name="actions" />
</div>
</div>
</template>
@@ -29,5 +45,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,25 +1,39 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<figure class="image is-square fd-has-margin-bottom" v-show="artwork_visible">
<img :src="artwork_url" @load="artwork_loaded" @error="artwork_error" class="fd-has-shadow">
<figure
v-show="artwork_visible"
class="image is-square fd-has-margin-bottom"
>
<img
:src="artwork_url"
class="fd-has-shadow"
@load="artwork_loaded"
@error="artwork_error"
/>
</figure>
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{ album.name }}</a>
<a class="has-text-link" @click="open_album">{{
album.name
}}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artists[0].name }}</a>
<a class="title is-6 has-text-link" @click="open_artist">{{
album.artists[0].name
}}</a>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{ $filters.time(album.release_date, 'L') }}</span>
<span class="title is-6">{{
$filters.time(album.release_date, 'L')
}}</span>
</p>
<p>
<span class="heading">Type</span>
@@ -29,18 +43,25 @@
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -53,7 +74,7 @@ export default {
name: 'SpotifyModalDialogAlbum',
props: ['show', 'album'],
data () {
data() {
return {
artwork_visible: false
}
@@ -89,7 +110,9 @@ export default {
},
open_artist: function () {
this.$router.push({ path: '/music/spotify/artists/' + this.album.artists[0].id })
this.$router.push({
path: '/music/spotify/artists/' + this.album.artists[0].id
})
},
artwork_loaded: function () {
@@ -103,5 +126,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,18 +1,23 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{ artist.name }}</a>
<a class="has-text-link" @click="open_artist">{{
artist.name
}}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Popularity / Followers</span>
<span class="title is-6">{{ artist.popularity }} / {{ artist.followers.total }}</span>
<span class="title is-6"
>{{ artist.popularity }} /
{{ artist.followers.total }}</span
>
</p>
<p>
<span class="heading">Genres</span>
@@ -22,18 +27,25 @@
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -69,5 +81,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,18 +1,22 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{ playlist.name }}</a>
<a class="has-text-link" @click="open_playlist">{{
playlist.name
}}</a>
</p>
<div class="content is-small">
<p>
<span class="heading">Owner</span>
<span class="title is-6">{{ playlist.owner.display_name }}</span>
<span class="title is-6">{{
playlist.owner.display_name
}}</span>
</p>
<p>
<span class="heading">Tracks</span>
@@ -26,18 +30,25 @@
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -67,11 +78,12 @@ export default {
},
open_playlist: function () {
this.$router.push({ path: '/music/spotify/playlists/' + this.playlist.id })
this.$router.push({
path: '/music/spotify/playlists/' + this.playlist.id
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<transition name="fade">
<div class="modal is-active" v-if="show">
<div class="modal-background" @click="$emit('close')"></div>
<div v-if="show" class="modal is-active">
<div class="modal-background" @click="$emit('close')" />
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
@@ -15,23 +15,33 @@
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{ album.name }}</a>
<a class="title is-6 has-text-link" @click="open_album">{{
album.name
}}</a>
</p>
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{ album.artists[0].name }}</a>
<a class="title is-6 has-text-link" @click="open_artist">{{
album.artists[0].name
}}</a>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{ $filters.time(album.release_date, 'L') }}</span>
<span class="title is-6">{{
$filters.time(album.release_date, 'L')
}}</span>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6">{{ track.track_number }} / {{ track.disc_number }}</span>
<span class="title is-6"
>{{ track.track_number }} / {{ track.disc_number }}</span
>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{ $filters.duration(track.duration_ms) }}</span>
<span class="title is-6">{{
$filters.duration(track.duration_ms)
}}</span>
</p>
<p>
<span class="heading">Path</span>
@@ -41,18 +51,25 @@
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"><i class="mdi mdi-playlist-plus"></i></span> <span class="is-size-7">Add</span>
<span class="icon"><i class="mdi mdi-playlist-plus" /></span>
<span class="is-size-7">Add</span>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"><i class="mdi mdi-playlist-play"></i></span> <span class="is-size-7">Add Next</span>
<span class="icon"><i class="mdi mdi-playlist-play" /></span>
<span class="is-size-7">Add Next</span>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><i class="mdi mdi-play"></i></span> <span class="is-size-7">Play</span>
<span class="icon"><i class="mdi mdi-play" /></span>
<span class="is-size-7">Play</span>
</a>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
<button
class="modal-close is-large"
aria-label="close"
@click="$emit('close')"
/>
</div>
</transition>
</div>
@@ -86,11 +103,12 @@ export default {
},
open_artist: function () {
this.$router.push({ path: '/music/spotify/artists/' + this.album.artists[0].id })
this.$router.push({
path: '/music/spotify/artists/' + this.album.artists[0].id
})
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -5,18 +5,30 @@
<div class="column is-four-fifths">
<div class="tabs is-centered is-small">
<ul>
<router-link to="/audiobooks/artists" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/audiobooks/artists"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-artist"></i></span>
<span class="icon is-small"
><i class="mdi mdi-artist"
/></span>
<span class="">Authors</span>
</a>
</li>
</router-link>
<router-link to="/audiobooks/albums" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/audiobooks/albums"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-album"></i></span>
<span class="icon is-small"
><i class="mdi mdi-album"
/></span>
<span class="">Audiobooks</span>
</a>
</li>
@@ -35,5 +47,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -5,50 +5,85 @@
<div class="column is-four-fifths">
<div class="tabs is-centered is-small">
<ul>
<router-link to="/music/browse" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/music/browse"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-web"></i></span>
<span class="icon is-small"><i class="mdi mdi-web" /></span>
<span class="">Browse</span>
</a>
</li>
</router-link>
<router-link to="/music/artists" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/music/artists"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-artist"></i></span>
<span class="icon is-small"
><i class="mdi mdi-artist"
/></span>
<span class="">Artists</span>
</a>
</li>
</router-link>
<router-link to="/music/albums" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/music/albums"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-album"></i></span>
<span class="icon is-small"
><i class="mdi mdi-album"
/></span>
<span class="">Albums</span>
</a>
</li>
</router-link>
<router-link to="/music/genres" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/music/genres"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-speaker"></i></span>
<span class="icon is-small"
><i class="mdi mdi-speaker"
/></span>
<span class="">Genres</span>
</a>
</li>
</router-link>
<router-link to="/music/composers" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/music/composers"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-book-open-page-variant"></i></span>
<span class="icon is-small"
><i class="mdi mdi-book-open-page-variant"
/></span>
<span class="">Composers</span>
</a>
</li>
</router-link>
<router-link to="/music/spotify" v-if="spotify_enabled" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-if="spotify_enabled"
v-slot="{ navigate, isActive }"
to="/music/spotify"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"><i class="mdi mdi-spotify"></i></span>
<span class="icon is-small"
><i class="mdi mdi-spotify"
/></span>
<span class="">Spotify</span>
</a>
</li>
@@ -66,12 +101,11 @@ export default {
name: 'TabsMusic',
computed: {
spotify_enabled () {
spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid
}
}
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,5 +1,5 @@
<template>
<section class="section fd-remove-padding-bottom" v-if="spotify_enabled">
<section v-if="spotify_enabled" class="section fd-remove-padding-bottom">
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths">
@@ -7,13 +7,17 @@
<ul>
<li :class="{ 'is-active': $route.path === '/search/library' }">
<a @click="search_library">
<span class="icon is-small"><i class="mdi mdi-library-books"></i></span>
<span class="icon is-small"
><i class="mdi mdi-library-books"
/></span>
<span class="">Library</span>
</a>
</li>
<li :class="{ 'is-active': $route.path === '/search/spotify' }">
<a @click="search_spotify">
<span class="icon is-small"><i class="mdi mdi-spotify"></i></span>
<span class="icon is-small"
><i class="mdi mdi-spotify"
/></span>
<span class="">Spotify</span>
</a>
</li>
@@ -32,7 +36,7 @@ export default {
props: ['query'],
computed: {
spotify_enabled () {
spotify_enabled() {
return this.$store.state.spotify.webapi_token_valid
},
@@ -68,5 +72,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

@@ -5,29 +5,45 @@
<div class="column is-four-fifths">
<div class="tabs is-centered is-small">
<ul>
<router-link to="/settings/webinterface" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/settings/webinterface"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Webinterface</span>
</a>
</li>
</router-link>
<router-link to="/settings/remotes-outputs" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/settings/remotes-outputs"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Remotes &amp; Outputs</span>
</a>
</li>
</router-link>
<router-link to="/settings/artwork" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/settings/artwork"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Artwork</span>
</a>
</li>
</router-link>
<router-link to="/settings/online-services" custom v-slot="{ navigate, isActive }">
<li :class="{'is-active': isActive}">
<router-link
v-slot="{ navigate, isActive }"
to="/settings/online-services"
custom
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Online Services</span>
</a>
@@ -45,10 +61,8 @@
export default {
name: 'TabsSettings',
computed: {
}
computed: {}
}
</script>
<style>
</style>
<style></style>