mirror of
https://github.com/owntone/owntone-server.git
synced 2025-11-07 04:42:58 -05:00
[web] Migration to Vue 3 and Vite
This commit is contained in:
@@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<figure>
|
||||
<img v-lazyload
|
||||
:data-src="artwork_url_with_size"
|
||||
:data-err="dataURI"
|
||||
:key="artwork_url_with_size"
|
||||
<img v-lazy="{ src: artwork_url_with_size, lifecycle: lazy_lifecycle }"
|
||||
@click="$emit('click')">
|
||||
</figure>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import webapi from '@/webapi'
|
||||
import SVGRenderer from '@/lib/SVGRenderer'
|
||||
import stringToColor from 'string-to-color'
|
||||
import { renderSVG } from '@/lib/SVGRenderer'
|
||||
|
||||
export default {
|
||||
name: 'CoverArtwork',
|
||||
@@ -19,12 +15,16 @@ export default {
|
||||
|
||||
data () {
|
||||
return {
|
||||
svg: new SVGRenderer(),
|
||||
width: 600,
|
||||
height: 600,
|
||||
font_family: 'sans-serif',
|
||||
font_size: 200,
|
||||
font_weight: 600
|
||||
font_weight: 600,
|
||||
lazy_lifecycle: {
|
||||
error: (el) => {
|
||||
el.src = this.dataURI()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -48,47 +48,18 @@ export default {
|
||||
return this.artist.substring(0, 2)
|
||||
}
|
||||
return ''
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
background_color () {
|
||||
return stringToColor(this.alt_text)
|
||||
},
|
||||
|
||||
is_background_light () {
|
||||
// Based on https://stackoverflow.com/a/44615197
|
||||
const hex = this.background_color.replace(/#/, '')
|
||||
const r = parseInt(hex.substr(0, 2), 16)
|
||||
const g = parseInt(hex.substr(2, 2), 16)
|
||||
const b = parseInt(hex.substr(4, 2), 16)
|
||||
|
||||
const luma = [
|
||||
0.299 * r,
|
||||
0.587 * g,
|
||||
0.114 * b
|
||||
].reduce((a, b) => a + b) / 255
|
||||
|
||||
return luma > 0.5
|
||||
},
|
||||
|
||||
text_color () {
|
||||
return this.is_background_light ? '#000000' : '#ffffff'
|
||||
},
|
||||
|
||||
rendererParams () {
|
||||
return {
|
||||
methods: {
|
||||
dataURI: function () {
|
||||
return renderSVG(this.caption, this.alt_text, {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
textColor: this.text_color,
|
||||
backgroundColor: this.background_color,
|
||||
caption: this.caption,
|
||||
fontFamily: this.font_family,
|
||||
fontSize: this.font_size,
|
||||
fontWeight: this.font_weight
|
||||
}
|
||||
},
|
||||
|
||||
dataURI () {
|
||||
return this.svg.render(this.rendererParams)
|
||||
font_family: this.font_family,
|
||||
font_size: this.font_size,
|
||||
font_weight: this.font_weight
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="dropdown" :class="{ 'is-active': is_active }" v-click-outside="onClickOutside">
|
||||
<div class="dropdown" :class="{ 'is-active': is_active }" v-click-away="onClickOutside">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu" @click="is_active = !is_active">
|
||||
<span>{{ value }}</span>
|
||||
<span>{{ modelValue }}</span>
|
||||
<span class="icon is-small">
|
||||
<i class="mdi mdi-chevron-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="dropdown-content">
|
||||
<a class="dropdown-item"
|
||||
v-for="option in options" :key="option"
|
||||
:class="{'is-active': value === option}"
|
||||
:class="{'is-active': modelValue === option}"
|
||||
@click="select(option)">
|
||||
{{ option }}
|
||||
</a>
|
||||
@@ -25,7 +25,8 @@
|
||||
export default {
|
||||
name: 'DropdownMenu',
|
||||
|
||||
props: ['value', 'options'],
|
||||
props: ['modelValue', 'options'],
|
||||
emits: ['update:modelValue'],
|
||||
|
||||
data () {
|
||||
return {
|
||||
@@ -40,7 +41,7 @@ export default {
|
||||
|
||||
select (option) {
|
||||
this.is_active = false
|
||||
this.$emit('input', option)
|
||||
this.$emit('update:modelValue', option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
|
||||
methods: {
|
||||
nav: function (id) {
|
||||
this.$router.push({ path: this.$router.currentRoute.path + '#index_' + id })
|
||||
this.$router.push({ hash: '#index_' + id })
|
||||
},
|
||||
|
||||
scroll_to_top: function () {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
<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>
|
||||
<list-item-album v-for="album in albums.grouped[idx]"
|
||||
|
||||
<div class="media" v-for="album in albums.grouped[idx]"
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)">
|
||||
<template slot="artwork" v-if="is_visible_artwork">
|
||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
||||
<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
|
||||
:artwork_url="album.artwork_url"
|
||||
:artist="album.artist"
|
||||
@@ -16,13 +18,28 @@
|
||||
:maxwidth="64"
|
||||
:maxheight="64" />
|
||||
</p>
|
||||
</template>
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(album)">
|
||||
</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>
|
||||
</a>
|
||||
</template>
|
||||
</list-item-album>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
@@ -30,7 +47,7 @@
|
||||
:key="album.id"
|
||||
:album="album"
|
||||
@click="open_album(album)">
|
||||
<template slot="artwork" v-if="is_visible_artwork">
|
||||
<template v-slot:artwork v-if="is_visible_artwork">
|
||||
<p class="image is-64x64 fd-has-shadow fd-has-action">
|
||||
<cover-artwork
|
||||
:artwork_url="album.artwork_url"
|
||||
@@ -40,8 +57,8 @@
|
||||
:maxheight="64" />
|
||||
</p>
|
||||
</template>
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(album)">
|
||||
<template v-slot:actions>
|
||||
<a @click.prevent.stop="open_dialog(album)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
</a>
|
||||
</template>
|
||||
@@ -60,7 +77,7 @@
|
||||
delete_action="Remove"
|
||||
@close="show_remove_podcast_modal = false"
|
||||
@delete="remove_podcast">
|
||||
<template slot="modal-content">
|
||||
<template v-slot: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>
|
||||
</template>
|
||||
@@ -69,10 +86,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItemAlbum from '@/components/ListItemAlbum'
|
||||
import ModalDialogAlbum from '@/components/ModalDialogAlbum'
|
||||
import ModalDialog from '@/components/ModalDialog'
|
||||
import CoverArtwork from '@/components/CoverArtwork'
|
||||
import ListItemAlbum from '@/components/ListItemAlbum.vue'
|
||||
import ModalDialogAlbum from '@/components/ModalDialogAlbum.vue'
|
||||
import ModalDialog from '@/components/ModalDialog.vue'
|
||||
import CoverArtwork from '@/components/CoverArtwork.vue'
|
||||
import webapi from '@/webapi'
|
||||
import Albums from '@/lib/Albums'
|
||||
|
||||
@@ -105,7 +122,10 @@ export default {
|
||||
if (Array.isArray(this.albums)) {
|
||||
return this.albums
|
||||
}
|
||||
return this.albums.sortedAndFiltered
|
||||
if (this.albums) {
|
||||
return this.albums.sortedAndFiltered
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
is_grouped: function () {
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
:key="artist.id"
|
||||
:artist="artist"
|
||||
@click="open_artist(artist)">
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(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>
|
||||
@@ -20,8 +20,8 @@
|
||||
:key="artist.id"
|
||||
:artist="artist"
|
||||
@click="open_artist(artist)">
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(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>
|
||||
@@ -32,8 +32,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItemArtist from '@/components/ListItemArtist'
|
||||
import ModalDialogArtist from '@/components/ModalDialogArtist'
|
||||
import ListItemArtist from '@/components/ListItemArtist.vue'
|
||||
import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
|
||||
import Artists from '@/lib/Artists'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
:key="composer.id"
|
||||
:composer="composer"
|
||||
@click="open_composer(composer)">
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(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>
|
||||
@@ -20,8 +20,8 @@
|
||||
:key="composer.id"
|
||||
:composer="composer"
|
||||
@click="open_composer(composer)">
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(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>
|
||||
@@ -32,8 +32,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItemComposer from '@/components/ListItemComposer'
|
||||
import ModalDialogComposer from '@/components/ModalDialogComposer'
|
||||
import ListItemComposer from '@/components/ListItemComposer.vue'
|
||||
import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
|
||||
import Composers from '@/lib/Composers'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
<template functional>
|
||||
<div class="media" :id="'index_' + props.album.name_sort.charAt(0).toUpperCase()">
|
||||
<template>
|
||||
<div class="media" :id="'index_' + album.name_sort.charAt(0).toUpperCase()">
|
||||
<div class="media-left fd-has-action"
|
||||
v-if="$slots['artwork']"
|
||||
@click="listeners.click">
|
||||
v-if="$slots['artwork']">
|
||||
<slot name="artwork"></slot>
|
||||
</div>
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<div class="media-content fd-has-action is-clipped">
|
||||
<div style="margin-top:0.7rem;">
|
||||
<h1 class="title is-6">{{ props.album.name }}</h1>
|
||||
<h2 class="subtitle is-7 has-text-grey"><b>{{ props.album.artist }}</b></h2>
|
||||
<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="props.album.date_released && props.album.media_kind === 'music'">
|
||||
{{ props.album.date_released | time('L') }}
|
||||
v-if="album.date_released && album.media_kind === 'music'">
|
||||
{{ $filters.time(album.date_released, 'L') }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template functional>
|
||||
<template>
|
||||
<div class="media">
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<h1 class="title is-6">{{ props.artist.name }}</h1>
|
||||
<div class="media-content fd-has-action is-clipped">
|
||||
<h1 class="title is-6">{{ artist.name }}</h1>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<slot name="actions"></slot>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template functional>
|
||||
<div class="media" :id="'index_' + props.composer.name.charAt(0).toUpperCase()">
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<h1 class="title is-6">{{ props.composer.name }}</h1>
|
||||
<template>
|
||||
<div class="media" :id="'index_' + composer.name.charAt(0).toUpperCase()">
|
||||
<div class="media-content fd-has-action is-clipped">
|
||||
<h1 class="title is-6">{{ composer.name }}</h1>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<slot name="actions"></slot>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template functional>
|
||||
<template>
|
||||
<div class="media">
|
||||
<figure class="media-left fd-has-action" @click="listeners.click">
|
||||
<figure class="media-left fd-has-action">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-folder"></i>
|
||||
</span>
|
||||
</figure>
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<h1 class="title is-6">{{ props.directory.path.substring(props.directory.path.lastIndexOf('/') + 1) }}</h1>
|
||||
<h2 class="subtitle is-7 has-text-grey-light">{{ props.directory.path }}</h2>
|
||||
<div class="media-content fd-has-action is-clipped">
|
||||
<h1 class="title is-6">{{ directory.path.substring(directory.path.lastIndexOf('/') + 1) }}</h1>
|
||||
<h2 class="subtitle is-7 has-text-grey-light">{{ directory.path }}</h2>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<slot name="actions"></slot>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template functional>
|
||||
<div class="media" :id="'index_' + props.genre.name.charAt(0).toUpperCase()">
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<h1 class="title is-6">{{ props.genre.name }}</h1>
|
||||
<template>
|
||||
<div class="media" :id="'index_' + genre.name.charAt(0).toUpperCase()">
|
||||
<div class="media-content fd-has-action is-clipped">
|
||||
<h1 class="title is-6">{{ genre.name }}</h1>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<slot name="actions"></slot>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template functional>
|
||||
<template>
|
||||
<div class="media">
|
||||
<figure class="media-left fd-has-action" v-if="slots().icon" @click="listeners.click">
|
||||
<figure class="media-left fd-has-action" v-if="$slots.icon">
|
||||
<slot name="icon"></slot>
|
||||
</figure>
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<h1 class="title is-6">{{ props.playlist.name }}</h1>
|
||||
<div class="media-content fd-has-action is-clipped">
|
||||
<h1 class="title is-6">{{ playlist.name }}</h1>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<slot name="actions"></slot>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template functional>
|
||||
<div class="media" :id="'index_' + props.track.title_sort.charAt(0).toUpperCase()" :class="{ 'with-progress': slots().progress }">
|
||||
<figure class="media-left fd-has-action" v-if="slots().icon" @click="listeners.click">
|
||||
<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>
|
||||
</figure>
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<h1 class="title is-6" :class="{ 'has-text-grey': props.track.media_kind === 'podcast' && props.track.play_count > 0 }">{{ props.track.title }}</h1>
|
||||
<h2 class="subtitle is-7 has-text-grey"><b>{{ props.track.artist }}</b></h2>
|
||||
<h2 class="subtitle is-7 has-text-grey">{{ props.track.album }}</h2>
|
||||
<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>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<list-item-playlist v-for="playlist in playlists" :key="playlist.id" :playlist="playlist" @click="open_playlist(playlist)">
|
||||
<template slot="icon">
|
||||
<template v-slot: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>
|
||||
</span>
|
||||
</template>
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(playlist)">
|
||||
<template v-slot:actions>
|
||||
<a @click.prevent.stop="open_dialog(playlist)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
</a>
|
||||
</template>
|
||||
@@ -17,8 +17,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItemPlaylist from '@/components/ListItemPlaylist'
|
||||
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist'
|
||||
import ListItemPlaylist from '@/components/ListItemPlaylist.vue'
|
||||
import ModalDialogPlaylist from '@/components/ModalDialogPlaylist.vue'
|
||||
|
||||
export default {
|
||||
name: 'ListPlaylists',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<list-item-track v-for="(track, index) in tracks" :key="track.id" :track="track" @click="play_track(index, track)">
|
||||
<template slot="actions">
|
||||
<a @click="open_dialog(track)">
|
||||
<template v-slot:actions>
|
||||
<a @click.prevent.stop="open_dialog(track)">
|
||||
<span class="icon has-text-dark"><i class="mdi mdi-dots-vertical mdi-18px"></i></span>
|
||||
</a>
|
||||
</template>
|
||||
@@ -12,8 +12,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItemTrack from '@/components/ListItemTrack'
|
||||
import ModalDialogTrack from '@/components/ModalDialogTrack'
|
||||
import ListItemTrack from '@/components/ListItemTrack.vue'
|
||||
import ModalDialogTrack from '@/components/ModalDialogTrack.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</p>
|
||||
<p v-if="album.date_released">
|
||||
<span class="heading">Release date</span>
|
||||
<span class="title is-6">{{ album.date_released | time('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,7 +37,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Length</span>
|
||||
<span class="title is-6">{{ album.length_ms | duration }}</span>
|
||||
<span class="title is-6">{{ $filters.duration(album.length_ms) }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Type</span>
|
||||
@@ -45,7 +45,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Added at</span>
|
||||
<span class="title is-6">{{ album.time_added | time('L LT') }}</span>
|
||||
<span class="title is-6">{{ $filters.time(album.time_added, 'L LT') }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CoverArtwork from '@/components/CoverArtwork'
|
||||
import CoverArtwork from '@/components/CoverArtwork.vue'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Added at</span>
|
||||
<span class="title is-6">{{ artist.time_added | time('L LT') }}</span>
|
||||
<span class="title is-6">{{ $filters.time(artist.time_added, 'L LT') }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Length</span>
|
||||
<span class="title is-6">{{ item.length_ms | duration }}</span>
|
||||
<span class="title is-6">{{ $filters.duration(item.length_ms) }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Path</span>
|
||||
@@ -56,7 +56,7 @@
|
||||
<span class="title is-6">
|
||||
{{ item.type }}
|
||||
<span v-if="item.samplerate"> | {{ item.samplerate }} Hz</span>
|
||||
<span v-if="item.channels"> | {{ item.channels | channels }}</span>
|
||||
<span v-if="item.channels"> | {{ $filters.channels(item.channels) }}</span>
|
||||
<span v-if="item.bitrate"> | {{ item.bitrate }} Kb/s</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</p>
|
||||
<p v-if="track.date_released">
|
||||
<span class="heading">Release date</span>
|
||||
<span class="title is-6">{{ track.date_released | time('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>
|
||||
@@ -47,7 +47,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Length</span>
|
||||
<span class="title is-6">{{ track.length_ms | duration }}</span>
|
||||
<span class="title is-6">{{ $filters.duration(track.length_ms) }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Path</span>
|
||||
@@ -62,13 +62,13 @@
|
||||
<span class="title is-6">
|
||||
{{ track.type }}
|
||||
<span v-if="track.samplerate"> | {{ track.samplerate }} Hz</span>
|
||||
<span v-if="track.channels"> | {{ track.channels | channels }}</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">{{ track.time_added | time('L LT') }}</span>
|
||||
<span class="title is-6">{{ $filters.time(track.time_added, 'L LT') }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Rating</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
close_action="Close"
|
||||
@ok="update_library"
|
||||
@close="close()">
|
||||
<template slot="modal-content">
|
||||
<template v-slot: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">
|
||||
@@ -36,7 +36,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ModalDialog from '@/components/ModalDialog'
|
||||
import ModalDialog from '@/components/ModalDialog.vue'
|
||||
import * as types from '@/store/mutation_types'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
|
||||
@@ -53,14 +53,21 @@
|
||||
<div class="level-item fd-expanded">
|
||||
<div class="fd-expanded">
|
||||
<p class="heading">Volume</p>
|
||||
<range-slider
|
||||
<Slider v-model="player.volume"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:tooltips="false"
|
||||
@change="set_volume"
|
||||
:classes="{ target: 'slider'}" />
|
||||
<!--range-slider
|
||||
class="slider fd-has-action"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
:value="player.volume"
|
||||
@change="set_volume">
|
||||
</range-slider>
|
||||
</range-slider-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,7 +89,15 @@
|
||||
<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>
|
||||
<range-slider
|
||||
<Slider v-model="stream_volume"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:tooltips="false"
|
||||
:disabled="!playing"
|
||||
@change="set_stream_volume"
|
||||
:classes="{ target: 'slider'}" />
|
||||
<!--range-slider
|
||||
class="slider fd-has-action"
|
||||
min="0"
|
||||
max="100"
|
||||
@@ -90,7 +105,7 @@
|
||||
:disabled="!playing"
|
||||
:value="stream_volume"
|
||||
@change="set_stream_volume">
|
||||
</range-slider>
|
||||
</range-slider-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,14 +157,21 @@
|
||||
<div class="level-item fd-expanded">
|
||||
<div class="fd-expanded">
|
||||
<p class="heading">Volume</p>
|
||||
<range-slider
|
||||
<Slider v-model="player.volume"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:tooltips="false"
|
||||
@change="set_volume"
|
||||
:classes="{ target: 'slider'}" />
|
||||
<!--range-slider
|
||||
class="slider fd-has-action"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
:value="player.volume"
|
||||
@change="set_volume">
|
||||
</range-slider>
|
||||
</range-slider-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,7 +197,15 @@
|
||||
<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>
|
||||
<range-slider
|
||||
<Slider v-model="stream_volume"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:tooltips="false"
|
||||
:disabled="!playing"
|
||||
@change="set_stream_volume"
|
||||
:classes="{ target: 'slider'}" />
|
||||
<!-- range-slider
|
||||
class="slider fd-has-action"
|
||||
min="0"
|
||||
max="100"
|
||||
@@ -183,7 +213,7 @@
|
||||
:disabled="!playing"
|
||||
:value="stream_volume"
|
||||
@change="set_stream_volume">
|
||||
</range-slider>
|
||||
</range-slider-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,17 +227,18 @@
|
||||
<script>
|
||||
import webapi from '@/webapi'
|
||||
import _audio from '@/audio'
|
||||
import NavbarItemLink from './NavbarItemLink'
|
||||
import NavbarItemOutput from './NavbarItemOutput'
|
||||
import PlayerButtonPlayPause from '@/components/PlayerButtonPlayPause'
|
||||
import PlayerButtonNext from '@/components/PlayerButtonNext'
|
||||
import PlayerButtonPrevious from '@/components/PlayerButtonPrevious'
|
||||
import PlayerButtonShuffle from '@/components/PlayerButtonShuffle'
|
||||
import PlayerButtonConsume from '@/components/PlayerButtonConsume'
|
||||
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat'
|
||||
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack'
|
||||
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward'
|
||||
import RangeSlider from 'vue-range-slider'
|
||||
import NavbarItemLink from './NavbarItemLink.vue'
|
||||
import NavbarItemOutput from './NavbarItemOutput.vue'
|
||||
import PlayerButtonPlayPause from '@/components/PlayerButtonPlayPause.vue'
|
||||
import PlayerButtonNext from '@/components/PlayerButtonNext.vue'
|
||||
import PlayerButtonPrevious from '@/components/PlayerButtonPrevious.vue'
|
||||
import PlayerButtonShuffle from '@/components/PlayerButtonShuffle.vue'
|
||||
import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue'
|
||||
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue'
|
||||
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue'
|
||||
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue'
|
||||
//import RangeSlider from 'vue-range-slider'
|
||||
import Slider from '@vueform/slider'
|
||||
import * as types from '@/store/mutation_types'
|
||||
|
||||
export default {
|
||||
@@ -215,7 +246,8 @@ export default {
|
||||
components: {
|
||||
NavbarItemLink,
|
||||
NavbarItemOutput,
|
||||
RangeSlider,
|
||||
//RangeSlider,
|
||||
Slider,
|
||||
PlayerButtonPlayPause,
|
||||
PlayerButtonNext,
|
||||
PlayerButtonPrevious,
|
||||
|
||||
@@ -14,7 +14,15 @@
|
||||
<div class="level-item fd-expanded">
|
||||
<div class="fd-expanded">
|
||||
<p class="heading" :class="{ 'has-text-grey-light': !output.selected }">{{ output.name }}</p>
|
||||
<range-slider
|
||||
<Slider v-model="volume"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:tooltips="false"
|
||||
:disabled="!output.selected"
|
||||
@change="set_volume"
|
||||
:classes="{ target: 'slider'}" />
|
||||
<!--range-slider
|
||||
class="slider fd-has-action"
|
||||
min="0"
|
||||
max="100"
|
||||
@@ -22,7 +30,7 @@
|
||||
:disabled="!output.selected"
|
||||
:value="volume"
|
||||
@change="set_volume" >
|
||||
</range-slider>
|
||||
</range-slider-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,12 +39,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RangeSlider from 'vue-range-slider'
|
||||
//import RangeSlider from 'vue-range-slider'
|
||||
import Slider from '@vueform/slider'
|
||||
import webapi from '@/webapi'
|
||||
|
||||
export default {
|
||||
name: 'NavbarItemOutput',
|
||||
components: { RangeSlider },
|
||||
components: {
|
||||
// RangeSlider
|
||||
Slider
|
||||
},
|
||||
|
||||
props: ['output'],
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavbarItemLink from './NavbarItemLink'
|
||||
import NavbarItemLink from './NavbarItemLink.vue'
|
||||
import * as types from '@/store/mutation_types'
|
||||
|
||||
export default {
|
||||
|
||||
22
web-src/src/components/ProgressBar.vue
Normal file
22
web-src/src/components/ProgressBar.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div v-if="width > 0" class="progress-bar mt-2" :style="{ width: width_percent }" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ProgressBar',
|
||||
props: ['max', 'value'],
|
||||
|
||||
computed: {
|
||||
width () {
|
||||
if (this.value > 0 && this.max > 0) {
|
||||
return parseInt(this.value * 100 / this.max)
|
||||
}
|
||||
return 0
|
||||
},
|
||||
width_percent () {
|
||||
return this.width + '%'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -81,6 +81,7 @@ export default {
|
||||
this.timerId = -1
|
||||
|
||||
const newValue = this.$refs.settings_checkbox.checked
|
||||
console.log(this.$refs.settings_checkbox)
|
||||
if (newValue === this.value) {
|
||||
this.statusUpdate = ''
|
||||
return
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<template functional>
|
||||
<template>
|
||||
<div class="media">
|
||||
<div class="media-left fd-has-action"
|
||||
v-if="$slots['artwork']"
|
||||
@click="listeners.click">
|
||||
v-if="$slots['artwork']">
|
||||
<slot name="artwork"></slot>
|
||||
</div>
|
||||
<div class="media-content fd-has-action is-clipped" @click="listeners.click">
|
||||
<h1 class="title is-6">{{ props.album.name }}</h1>
|
||||
<h2 class="subtitle is-7 has-text-grey"><b>{{ props.album.artists[0].name }}</b></h2>
|
||||
<h2 class="subtitle is-7 has-text-grey has-text-weight-normal">({{ props.album.album_type }}, {{ props.album.release_date | time('L') }})</h2>
|
||||
<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>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<slot name="actions"></slot>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Release date</span>
|
||||
<span class="title is-6">{{ album.release_date | time('L') }}</span>
|
||||
<span class="title is-6">{{ $filters.time(album.release_date, 'L') }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Type</span>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Release date</span>
|
||||
<span class="title is-6">{{ album.release_date | time('L') }}</span>
|
||||
<span class="title is-6">{{ $filters.time(album.release_date, 'L') }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Track / Disc</span>
|
||||
@@ -31,7 +31,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Length</span>
|
||||
<span class="title is-6">{{ track.duration_ms | duration }}</span>
|
||||
<span class="title is-6">{{ $filters.duration(track.duration_ms) }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="heading">Path</span>
|
||||
|
||||
@@ -5,17 +5,21 @@
|
||||
<div class="column is-four-fifths">
|
||||
<div class="tabs is-centered is-small">
|
||||
<ul>
|
||||
<router-link tag="li" to="/audiobooks/artists" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-artist"></i></span>
|
||||
<span class="">Authors</span>
|
||||
</a>
|
||||
<router-link to="/audiobooks/artists" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Authors</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/audiobooks/albums" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-album"></i></span>
|
||||
<span class="">Audiobooks</span>
|
||||
</a>
|
||||
<router-link to="/audiobooks/albums" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Audiobooks</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,41 +5,53 @@
|
||||
<div class="column is-four-fifths">
|
||||
<div class="tabs is-centered is-small">
|
||||
<ul>
|
||||
<router-link tag="li" to="/music/browse" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-web"></i></span>
|
||||
<span class="">Browse</span>
|
||||
</a>
|
||||
<router-link to="/music/browse" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Browse</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/music/artists" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-artist"></i></span>
|
||||
<span class="">Artists</span>
|
||||
</a>
|
||||
<router-link to="/music/artists" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Artists</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/music/albums" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-album"></i></span>
|
||||
<span class="">Albums</span>
|
||||
</a>
|
||||
<router-link to="/music/albums" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Albums</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/music/genres" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-speaker"></i></span>
|
||||
<span class="">Genres</span>
|
||||
</a>
|
||||
<router-link to="/music/genres" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Genres</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/music/composers" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-book-open-page-variant"></i></span>
|
||||
<span class="">Composers</span>
|
||||
</a>
|
||||
<router-link to="/music/composers" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Composers</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/music/spotify" v-if="spotify_enabled" active-class="is-active">
|
||||
<a>
|
||||
<span class="icon is-small"><i class="mdi mdi-spotify"></i></span>
|
||||
<span class="">Spotify</span>
|
||||
</a>
|
||||
<router-link to="/music/spotify" v-if="spotify_enabled" custom v-slot="{ navigate, isActive }">
|
||||
<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="">Spotify</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,25 +5,33 @@
|
||||
<div class="column is-four-fifths">
|
||||
<div class="tabs is-centered is-small">
|
||||
<ul>
|
||||
<router-link tag="li" to="/settings/webinterface" active-class="is-active">
|
||||
<a>
|
||||
<span class="">Webinterface</span>
|
||||
</a>
|
||||
<router-link to="/settings/webinterface" custom v-slot="{ navigate, isActive }">
|
||||
<li :class="{'is-active': isActive}">
|
||||
<a @click="navigate" @keypress.enter="navigate">
|
||||
<span class="">Webinterface</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/settings/remotes-outputs" active-class="is-active">
|
||||
<a>
|
||||
<span class="">Remotes & Outputs</span>
|
||||
</a>
|
||||
<router-link to="/settings/remotes-outputs" custom v-slot="{ navigate, isActive }">
|
||||
<li :class="{'is-active': isActive}">
|
||||
<a @click="navigate" @keypress.enter="navigate">
|
||||
<span class="">Remotes & Outputs</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/settings/artwork" active-class="is-active">
|
||||
<a>
|
||||
<span class="">Artwork</span>
|
||||
</a>
|
||||
<router-link to="/settings/artwork" custom v-slot="{ navigate, isActive }">
|
||||
<li :class="{'is-active': isActive}">
|
||||
<a @click="navigate" @keypress.enter="navigate">
|
||||
<span class="">Artwork</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
<router-link tag="li" to="/settings/online-services" active-class="is-active">
|
||||
<a>
|
||||
<span class="">Online Services</span>
|
||||
</a>
|
||||
<router-link to="/settings/online-services" custom v-slot="{ navigate, isActive }">
|
||||
<li :class="{'is-active': isActive}">
|
||||
<a @click="navigate" @keypress.enter="navigate">
|
||||
<span class="">Online Services</span>
|
||||
</a>
|
||||
</li>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user