Merge pull request #1441 from chme/web/next

[web] Update web interface
This commit is contained in:
Christian Meffert 2022-04-16 07:11:04 +02:00 committed by GitHub
commit b3a661cae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 738 additions and 645 deletions

View File

@ -25,7 +25,6 @@ htdocsassetsdir = $(datadir)/owntone/htdocs/assets
dist_htdocsassets_DATA = \ dist_htdocsassets_DATA = \
assets/index.css \ assets/index.css \
assets/index.js \ assets/index.js \
assets/vendor.js \
assets/materialdesignicons-webfont.svg \ assets/materialdesignicons-webfont.svg \
assets/materialdesignicons-webfont.ttf \ assets/materialdesignicons-webfont.ttf \
assets/materialdesignicons-webfont.woff2 \ assets/materialdesignicons-webfont.woff2 \

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OwnTone</title> <title>OwnTone</title>
<script type="module" crossorigin src="/assets/index.js"></script> <script type="module" crossorigin src="/assets/index.js"></script>
<link rel="modulepreload" href="/assets/vendor.js">
<link rel="stylesheet" href="/assets/index.css"> <link rel="stylesheet" href="/assets/index.css">
</head> </head>
<body> <body>

File diff suppressed because it is too large Load Diff

View File

@ -12,17 +12,16 @@
"dependencies": { "dependencies": {
"@aacassandra/vue3-progressbar": "^1.0.3", "@aacassandra/vue3-progressbar": "^1.0.3",
"@ts-pro/vue-eternal-loading": "^1.2.0", "@ts-pro/vue-eternal-loading": "^1.2.0",
"@vueform/slider": "^2.0.9", "@vueform/slider": "github:chme/slider#faff83ed8a77f2cdbcb7252505ef734301efd139",
"axios": "^0.26.1", "axios": "^0.26.1",
"bulma": "^0.9.3", "bulma": "^0.9.3",
"bulma-switch": "^2.0.4", "bulma-switch": "^2.0.4",
"luxon": "^2.3.1",
"mdi": "^2.2.43", "mdi": "^2.2.43",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"reconnectingwebsocket": "^1.0.0", "reconnectingwebsocket": "^1.0.0",
"spotify-web-api-js": "^1.5.2", "spotify-web-api-js": "^1.5.2",
"string-to-color": "^2.2.2", "string-to-color": "^2.2.2",
"vue": "^3.2.31", "vue": "^3.2.33",
"vue-router": "^4.0.14", "vue-router": "^4.0.14",
"vue-scrollto": "^2.20.0", "vue-scrollto": "^2.20.0",
"vue3-click-away": "^1.2.4", "vue3-click-away": "^1.2.4",
@ -31,12 +30,12 @@
"vuex": "^4.0.2" "vuex": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^2.2.4", "@vitejs/plugin-vue": "^2.3.1",
"eslint": "^8.11.0", "eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-vue": "^8.5.0", "eslint-plugin-vue": "^8.6.0",
"prettier": "2.6.0", "prettier": "2.6.2",
"sass": "^1.49.9", "sass": "^1.50.0",
"vite": "^2.8.6" "vite": "^2.9.5"
} }
} }

View File

@ -33,7 +33,6 @@ import ModalDialogUpdate from '@/components/ModalDialogUpdate.vue'
import webapi from '@/webapi' import webapi from '@/webapi'
import * as types from '@/store/mutation_types' import * as types from '@/store/mutation_types'
import ReconnectingWebSocket from 'reconnectingwebsocket' import ReconnectingWebSocket from 'reconnectingwebsocket'
import moment from 'moment'
export default { export default {
name: 'App', name: 'App',
@ -90,7 +89,6 @@ export default {
}, },
created: function () { created: function () {
moment.locale(navigator.language)
this.connect() this.connect()
// Start the progress bar on app start // Start the progress bar on app start

View File

@ -9,7 +9,7 @@
</div> </div>
<div v-else-if="album.isItem" class="media" @click="open_album(album.item)"> <div v-else-if="album.isItem" class="media" @click="open_album(album.item)">
<div v-if="is_visible_artwork" class="media-left fd-has-action"> <div v-if="is_visible_artwork" class="media-left fd-has-action">
<p class="image is-64x64 fd-has-shadow fd-has-action"> <div class="image is-64x64 fd-has-shadow fd-has-action">
<figure> <figure>
<img <img
v-lazy="{ v-lazy="{
@ -20,7 +20,7 @@
:artist="album.item.artist" :artist="album.item.artist"
/> />
</figure> </figure>
</p> </div>
</div> </div>
<div class="media-content fd-has-action is-clipped"> <div class="media-content fd-has-action is-clipped">
<div style="margin-top: 0.7rem"> <div style="margin-top: 0.7rem">
@ -34,7 +34,7 @@
v-if="album.item.date_released && album.item.media_kind === 'music'" v-if="album.item.date_released && album.item.media_kind === 'music'"
class="subtitle is-7 has-text-grey has-text-weight-normal" class="subtitle is-7 has-text-grey has-text-weight-normal"
> >
{{ $filters.time(album.item.date_released, 'L') }} {{ $filters.date(album.item.date_released) }}
</h2> </h2>
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@
<p v-if="album.date_released"> <p v-if="album.date_released">
<span class="heading">Release date</span> <span class="heading">Release date</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.time(album.date_released, 'L') $filters.date(album.date_released)
}}</span> }}</span>
</p> </p>
<p v-else-if="album.year > 0"> <p v-else-if="album.year > 0">
@ -49,7 +49,7 @@
<p> <p>
<span class="heading">Length</span> <span class="heading">Length</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.duration(album.length_ms) $filters.durationInHours(album.length_ms)
}}</span> }}</span>
</p> </p>
<p> <p>
@ -61,7 +61,7 @@
<p> <p>
<span class="heading">Added at</span> <span class="heading">Added at</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.time(album.time_added, 'L LT') $filters.datetime(album.time_added)
}}</span> }}</span>
</p> </p>
</div> </div>

View File

@ -27,7 +27,7 @@
<p> <p>
<span class="heading">Added at</span> <span class="heading">Added at</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.time(artist.time_added, 'L LT') $filters.datetime(artist.time_added)
}}</span> }}</span>
</p> </p>
</div> </div>

View File

@ -23,6 +23,12 @@
composer.track_count composer.track_count
}}</a> }}</a>
</p> </p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(composer.length_ms)
}}</span>
</p>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">

View File

@ -11,6 +11,22 @@
genre.name genre.name
}}</a> }}</a>
</p> </p>
<div class="content is-small">
<p>
<span class="heading">Albums</span>
<span class="title is-6">{{ genre.album_count }}</span>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ genre.track_count }}</span>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(genre.length_ms)
}}</span>
</p>
</div>
</div> </div>
<footer class="card-footer"> <footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add"> <a class="card-footer-item has-text-dark" @click="queue_add">

View File

@ -56,7 +56,7 @@
<p> <p>
<span class="heading">Length</span> <span class="heading">Length</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.duration(item.length_ms) $filters.durationInHours(item.length_ms)
}}</span> }}</span>
</p> </p>
<p> <p>

View File

@ -48,7 +48,7 @@
<p v-if="track.date_released"> <p v-if="track.date_released">
<span class="heading">Release date</span> <span class="heading">Release date</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.time(track.date_released, 'L') $filters.date(track.date_released)
}}</span> }}</span>
</p> </p>
<p v-else-if="track.year > 0"> <p v-else-if="track.year > 0">
@ -70,7 +70,7 @@
<p> <p>
<span class="heading">Length</span> <span class="heading">Length</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.duration(track.length_ms) $filters.durationInHours(track.length_ms)
}}</span> }}</span>
</p> </p>
<p> <p>
@ -107,7 +107,7 @@
<p> <p>
<span class="heading">Added at</span> <span class="heading">Added at</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.time(track.time_added, 'L LT') $filters.datetime(track.time_added)
}}</span> }}</span>
</p> </p>
<p> <p>

View File

@ -11,7 +11,7 @@
<b>{{ album.artists[0].name }}</b> <b>{{ album.artists[0].name }}</b>
</h2> </h2>
<h2 class="subtitle is-7 has-text-grey has-text-weight-normal"> <h2 class="subtitle is-7 has-text-grey has-text-weight-normal">
({{ album.album_type }}, {{ $filters.time(album.release_date, 'L') }}) ({{ album.album_type }}, {{ $filters.date(album.release_date) }})
</h2> </h2>
</div> </div>
<div class="media-right"> <div class="media-right">

View File

@ -32,7 +32,7 @@
<p> <p>
<span class="heading">Release date</span> <span class="heading">Release date</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.time(album.release_date, 'L') $filters.date(album.release_date)
}}</span> }}</span>
</p> </p>
<p> <p>

View File

@ -28,7 +28,7 @@
<p> <p>
<span class="heading">Release date</span> <span class="heading">Release date</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.time(album.release_date, 'L') $filters.date(album.release_date)
}}</span> }}</span>
</p> </p>
<p> <p>
@ -40,7 +40,7 @@
<p> <p>
<span class="heading">Length</span> <span class="heading">Length</span>
<span class="title is-6">{{ <span class="title is-6">{{
$filters.duration(track.duration_ms) $filters.durationInHours(track.duration_ms)
}}</span> }}</span>
</p> </p>
<p> <p>

View File

@ -1,25 +1,47 @@
import moment from 'moment' import { DateTime, Duration } from 'luxon'
import momentDurationFormatSetup from 'moment-duration-format'
momentDurationFormatSetup(moment)
export const filters = { export const filters = {
duration: function (value, format) { durationInHours: function (value_ms) {
if (format) { const seconds = Math.floor(value_ms / 1000)
return moment.duration(value).format(format) if (seconds > 3600) {
return Duration.fromObject({ seconds: seconds })
.shiftTo('hours', 'minutes', 'seconds')
.toFormat('hh:mm:ss')
} }
return moment.duration(value).format('hh:*mm:ss') return Duration.fromObject({ seconds: seconds })
.shiftTo('minutes', 'seconds')
.toFormat('mm:ss')
}, },
time: function (value, format) { durationInDays: function (value_ms) {
if (format) { const minutes = Math.floor(value_ms / 60000)
return moment(value).format(format) if (minutes > 1440) {
// 60 * 24
return Duration.fromObject({ minutes: minutes })
.shiftTo('days', 'hours', 'minutes')
.toHuman()
} else if (minutes > 60) {
return Duration.fromObject({ minutes: minutes })
.shiftTo('hours', 'minutes')
.toHuman()
} }
return moment(value).format() return Duration.fromObject({ minutes: minutes })
.shiftTo('minutes')
.toHuman()
}, },
timeFromNow: function (value, withoutSuffix) { date: function (value) {
return moment(value).fromNow(withoutSuffix) return DateTime.fromISO(value).toLocaleString(DateTime.DATE_FULL)
},
datetime: function (value) {
return DateTime.fromISO(value).toLocaleString(DateTime.DATETIME_MED)
},
timeFromNow: function (value) {
var diff = DateTime.now().diff(DateTime.fromISO(value))
return this.durationInDays(diff.as('milliseconds'))
}, },
number: function (value) { number: function (value) {

View File

@ -132,10 +132,12 @@ section.hero + section.fd-content {
.fd-page { .fd-page {
margin-top: 3.25rem; margin-top: 3.25rem;
margin-bottom: 3.25rem;
} }
.fd-page-with-tabs { .fd-page-with-tabs {
margin-top: 6.25rem !important; margin-top: 6.25rem !important;
margin-bottom: 3.25rem;
} }
/* Set minimum height to hide "option" section */ /* Set minimum height to hide "option" section */

View File

@ -61,20 +61,15 @@
<tr> <tr>
<th>Total playtime</th> <th>Total playtime</th>
<td class="has-text-right"> <td class="has-text-right">
{{ {{ $filters.durationInDays(library.db_playtime * 1000) }}
$filters.duration(
library.db_playtime * 1000,
'y [years], d [days], h [hours], m [minutes]'
)
}}
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Library updated</th> <th>Library updated</th>
<td class="has-text-right"> <td class="has-text-right">
{{ $filters.timeFromNow(library.updated_at) }} {{ $filters.timeFromNow(library.updated_at) }} ago
<span class="has-text-grey" <span class="has-text-grey"
>({{ $filters.time(library.updated_at, 'lll') }})</span >({{ $filters.datetime(library.updated_at) }})</span
> >
</td> </td>
</tr> </tr>
@ -83,7 +78,7 @@
<td class="has-text-right"> <td class="has-text-right">
{{ $filters.timeFromNow(library.started_at, true) }} {{ $filters.timeFromNow(library.started_at, true) }}
<span class="has-text-grey" <span class="has-text-grey"
>({{ $filters.time(library.started_at, 'll') }})</span >({{ $filters.datetime(library.started_at) }})</span
> >
</td> </td>
</tr> </tr>

View File

@ -3,7 +3,7 @@
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4">
{{ name }} {{ composer.name }}
</p> </p>
</template> </template>
<template #heading-right> <template #heading-right>
@ -24,14 +24,16 @@
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
{{ albums_list.total }} albums | {{ composer.album_count }} albums |
<a class="has-text-link" @click="open_tracks">tracks</a> <a class="has-text-link" @click="open_tracks"
>{{ composer.track_count }} tracks</a
>
</p> </p>
<list-albums :albums="albums_list" :hide_group_title="true" /> <list-albums :albums="albums_list" :hide_group_title="true" />
<modal-dialog-composer <modal-dialog-composer
:show="show_composer_details_modal" :show="show_composer_details_modal"
:composer="{ name: name }" :composer="composer"
@close="show_composer_details_modal = false" @close="show_composer_details_modal = false"
/> />
</template> </template>
@ -48,12 +50,15 @@ import { GroupByList } from '@/lib/GroupByList'
const dataObject = { const dataObject = {
load: function (to) { load: function (to) {
return webapi.library_composer(to.params.composer) return Promise.all([
webapi.library_composer(to.params.composer),
webapi.library_composer_albums(to.params.composer)
])
}, },
set: function (vm, response) { set: function (vm, response) {
vm.name = vm.$route.params.composer vm.composer = response[0].data
vm.albums_list = new GroupByList(response.data.albums) vm.albums_list = new GroupByList(response[1].data.albums)
} }
} }
@ -80,7 +85,7 @@ export default {
data() { data() {
return { return {
name: '', composer: {},
albums_list: new GroupByList(), albums_list: new GroupByList(),
show_composer_details_modal: false show_composer_details_modal: false
} }
@ -90,13 +95,13 @@ export default {
open_tracks: function () { open_tracks: function () {
this.$router.push({ this.$router.push({
name: 'ComposerTracks', name: 'ComposerTracks',
params: { composer: this.name } params: { composer: this.composer.name }
}) })
}, },
play: function () { play: function () {
webapi.player_play_expression( webapi.player_play_expression(
'composer is "' + this.name + '" and media_kind is music', 'composer is "' + this.composer.name + '" and media_kind is music',
true true
) )
} }

View File

@ -3,7 +3,7 @@
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4">
{{ composer }} {{ composer.name }}
</p> </p>
</template> </template>
<template #heading-right> <template #heading-right>
@ -24,13 +24,15 @@
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_albums">albums</a> | <a class="has-text-link" @click="open_albums"
{{ tracks.total }} tracks >{{ composer.album_count }} albums</a
>
| {{ composer.track_count }} tracks
</p> </p>
<list-tracks :tracks="tracks.items" :expression="play_expression" /> <list-tracks :tracks="tracks.items" :expression="play_expression" />
<modal-dialog-composer <modal-dialog-composer
:show="show_composer_details_modal" :show="show_composer_details_modal"
:composer="{ name: composer }" :composer="composer"
@close="show_composer_details_modal = false" @close="show_composer_details_modal = false"
/> />
</template> </template>
@ -46,12 +48,15 @@ import webapi from '@/webapi'
const dataObject = { const dataObject = {
load: function (to) { load: function (to) {
return webapi.library_composer_tracks(to.params.composer) return Promise.all([
webapi.library_composer(to.params.composer),
webapi.library_composer_tracks(to.params.composer)
])
}, },
set: function (vm, response) { set: function (vm, response) {
vm.composer = vm.$route.params.composer vm.composer = response[0].data
vm.tracks = response.data.tracks vm.tracks = response[1].data.tracks
} }
} }
@ -79,7 +84,7 @@ export default {
data() { data() {
return { return {
tracks: { items: [] }, tracks: { items: [] },
composer: '', composer: {},
show_composer_details_modal: false show_composer_details_modal: false
} }
@ -87,7 +92,7 @@ export default {
computed: { computed: {
play_expression() { play_expression() {
return 'composer is "' + this.composer + '" and media_kind is music' return 'composer is "' + this.composer.name + '" and media_kind is music'
} }
}, },
@ -96,7 +101,7 @@ export default {
this.show_details_modal = false this.show_details_modal = false
this.$router.push({ this.$router.push({
name: 'ComposerAlbums', name: 'ComposerAlbums',
params: { composer: this.composer } params: { composer: this.composer.name }
}) })
}, },

View File

@ -27,7 +27,7 @@ import { byName, GroupByList } from '@/lib/GroupByList'
const dataObject = { const dataObject = {
load: function (to) { load: function (to) {
return webapi.library_composers() return webapi.library_composers('music')
}, },
set: function (vm, response) { set: function (vm, response) {

View File

@ -6,7 +6,7 @@
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4">
{{ name }} {{ genre.name }}
</p> </p>
</template> </template>
<template #heading-right> <template #heading-right>
@ -27,13 +27,15 @@
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
{{ albums_list.total }} albums | {{ genre.album_count }} albums |
<a class="has-text-link" @click="open_tracks">tracks</a> <a class="has-text-link" @click="open_tracks"
>{{ genre.track_count }} tracks</a
>
</p> </p>
<list-albums :albums="albums_list" /> <list-albums :albums="albums_list" />
<modal-dialog-genre <modal-dialog-genre
:show="show_genre_details_modal" :show="show_genre_details_modal"
:genre="{ name: name }" :genre="genre"
@close="show_genre_details_modal = false" @close="show_genre_details_modal = false"
/> />
</template> </template>
@ -51,12 +53,15 @@ import { bySortName, GroupByList } from '@/lib/GroupByList'
const dataObject = { const dataObject = {
load: function (to) { load: function (to) {
return webapi.library_genre(to.params.genre) return Promise.all([
webapi.library_genre(to.params.genre),
webapi.library_genre_albums(to.params.genre)
])
}, },
set: function (vm, response) { set: function (vm, response) {
vm.name = vm.$route.params.genre vm.genre = response[0].data
vm.albums_list = new GroupByList(response.data.albums) vm.albums_list = new GroupByList(response[1].data.albums)
vm.albums_list.group(bySortName('name_sort')) vm.albums_list.group(bySortName('name_sort'))
} }
} }
@ -89,7 +94,7 @@ export default {
data() { data() {
return { return {
name: '', genre: {},
albums_list: new GroupByList(), albums_list: new GroupByList(),
show_genre_details_modal: false show_genre_details_modal: false
@ -99,12 +104,15 @@ export default {
methods: { methods: {
open_tracks: function () { open_tracks: function () {
this.show_details_modal = false this.show_details_modal = false
this.$router.push({ name: 'GenreTracks', params: { genre: this.name } }) this.$router.push({
name: 'GenreTracks',
params: { genre: this.genre.name }
})
}, },
play: function () { play: function () {
webapi.player_play_expression( webapi.player_play_expression(
'genre is "' + this.name + '" and media_kind is music', 'genre is "' + this.genre.name + '" and media_kind is music',
true true
) )
} }

View File

@ -6,7 +6,7 @@
</template> </template>
<template #heading-left> <template #heading-left>
<p class="title is-4"> <p class="title is-4">
{{ genre }} {{ genre.name }}
</p> </p>
</template> </template>
<template #heading-right> <template #heading-right>
@ -27,13 +27,15 @@
</template> </template>
<template #content> <template #content>
<p class="heading has-text-centered-mobile"> <p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_genre">albums</a> | <a class="has-text-link" @click="open_genre"
{{ tracks.total }} tracks >{{ genre.album_count }} albums</a
>
| {{ genre.track_count }} tracks
</p> </p>
<list-tracks :tracks="tracks.items" :expression="expression" /> <list-tracks :tracks="tracks.items" :expression="expression" />
<modal-dialog-genre <modal-dialog-genre
:show="show_genre_details_modal" :show="show_genre_details_modal"
:genre="{ name: genre }" :genre="genre"
@close="show_genre_details_modal = false" @close="show_genre_details_modal = false"
/> />
</template> </template>
@ -50,12 +52,15 @@ import webapi from '@/webapi'
const dataObject = { const dataObject = {
load: function (to) { load: function (to) {
return webapi.library_genre_tracks(to.params.genre) return Promise.all([
webapi.library_genre(to.params.genre),
webapi.library_genre_tracks(to.params.genre)
])
}, },
set: function (vm, response) { set: function (vm, response) {
vm.genre = vm.$route.params.genre vm.genre = response[0].data
vm.tracks = response.data.tracks vm.tracks = response[1].data.tracks
} }
} }
@ -102,14 +107,14 @@ export default {
}, },
expression() { expression() {
return 'genre is "' + this.genre + '" and media_kind is music' return 'genre is "' + this.genre.name + '" and media_kind is music'
} }
}, },
methods: { methods: {
open_genre: function () { open_genre: function () {
this.show_details_modal = false this.show_details_modal = false
this.$router.push({ name: 'Genre', params: { genre: this.genre } }) this.$router.push({ name: 'Genre', params: { genre: this.genre.name } })
}, },
play: function () { play: function () {

View File

@ -27,7 +27,7 @@ import { byName, GroupByList } from '@/lib/GroupByList'
const dataObject = { const dataObject = {
load: function (to) { load: function (to) {
return webapi.library_genres() return webapi.library_genres('music')
}, },
set: function (vm, response) { set: function (vm, response) {

View File

@ -14,6 +14,7 @@
<div class="container has-text-centered"> <div class="container has-text-centered">
<p class="control has-text-centered fd-progress-now-playing"> <p class="control has-text-centered fd-progress-now-playing">
<Slider <Slider
ref="slider"
v-model="item_progress_ms" v-model="item_progress_ms"
:min="0" :min="0"
:max="state.item_length_ms" :max="state.item_length_ms"
@ -22,6 +23,8 @@
:disabled="state.state === 'stop'" :disabled="state.state === 'stop'"
:classes="{ target: 'seek-slider' }" :classes="{ target: 'seek-slider' }"
@change="seek" @change="seek"
@start="start_dragging"
@end="end_dragging"
/> />
<!--range-slider <!--range-slider
class="seek-slider fd-has-action" class="seek-slider fd-has-action"
@ -35,8 +38,8 @@
</p> </p>
<p class="content"> <p class="content">
<span <span
>{{ $filters.duration(item_progress_ms) }} / >{{ $filters.durationInHours(item_progress_ms) }} /
{{ $filters.duration(now_playing.length_ms) }}</span {{ $filters.durationInHours(now_playing.length_ms) }}</span
> >
</p> </p>
</div> </div>
@ -101,6 +104,7 @@ export default {
return { return {
item_progress_ms: 0, item_progress_ms: 0,
interval_id: 0, interval_id: 0,
is_dragged: false,
show_details_modal: false, show_details_modal: false,
selected_item: {} selected_item: {}
@ -157,6 +161,10 @@ export default {
} }
}, },
mounted: function () {
console.log(this.$refs.slider)
},
created() { created() {
this.item_progress_ms = this.state.item_progress_ms this.item_progress_ms = this.state.item_progress_ms
webapi.player_status().then(({ data }) => { webapi.player_status().then(({ data }) => {
@ -176,7 +184,19 @@ export default {
methods: { methods: {
tick: function () { tick: function () {
this.item_progress_ms += 1000 if (!this.is_dragged) {
this.item_progress_ms += 1000
}
},
start_dragging: function () {
console.log('@start')
this.is_dragged = true
},
end_dragging: function () {
console.log('@end')
this.is_dragged = false
}, },
seek: function (newPosition) { seek: function (newPosition) {

View File

@ -50,7 +50,7 @@
:context_uri="album.uri" :context_uri="album.uri"
> >
<template #actions> <template #actions>
<a @click="open_track_dialog(track)"> <a @click.prevent.stop="open_track_dialog(track)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>

View File

@ -26,7 +26,7 @@
</p> </p>
</template> </template>
<template #actions> <template #actions>
<a @click="open_album_dialog(album)"> <a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>
@ -65,7 +65,7 @@
:playlist="playlist" :playlist="playlist"
> >
<template #actions> <template #actions>
<a @click="open_playlist_dialog(playlist)"> <a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>

View File

@ -13,7 +13,7 @@
:playlist="playlist" :playlist="playlist"
> >
<template #actions> <template #actions>
<a @click="open_playlist_dialog(playlist)"> <a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>

View File

@ -25,7 +25,7 @@
</p> </p>
</template> </template>
<template #actions> <template #actions>
<a @click="open_album_dialog(album)"> <a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>

View File

@ -34,7 +34,7 @@
:context_uri="playlist.uri" :context_uri="playlist.uri"
> >
<template #actions> <template #actions>
<a @click="open_track_dialog(item.track)"> <a @click.prevent.stop="open_track_dialog(item.track)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>

View File

@ -53,7 +53,7 @@
:context_uri="track.uri" :context_uri="track.uri"
> >
<template #actions> <template #actions>
<a @click="open_track_dialog(track)"> <a @click.prevent.stop="open_track_dialog(track)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>
@ -103,7 +103,7 @@
:artist="artist" :artist="artist"
> >
<template #actions> <template #actions>
<a @click="open_artist_dialog(artist)"> <a @click.prevent.stop="open_artist_dialog(artist)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>
@ -164,7 +164,7 @@
</p> </p>
</template> </template>
<template #actions> <template #actions>
<a @click="open_album_dialog(album)"> <a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>
@ -213,7 +213,7 @@
:playlist="playlist" :playlist="playlist"
> >
<template #actions> <template #actions>
<a @click="open_playlist_dialog(playlist)"> <a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark" <span class="icon has-text-dark"
><i class="mdi mdi-dots-vertical mdi-18px" ><i class="mdi mdi-dots-vertical mdi-18px"
/></span> /></span>

View File

@ -292,15 +292,20 @@ export default {
}) })
}, },
library_genres() { library_genres(media_kind = undefined) {
return axios.get('./api/library/genres') return axios.get('./api/library/genres', {
params: { media_kind: media_kind }
})
}, },
library_genre(genre) { library_genre(genre) {
return axios.get(`./api/library/genres/${encodeURIComponent(genre)}`)
},
library_genre_albums(genre) {
const genreParams = { const genreParams = {
type: 'albums', type: 'albums',
media_kind: 'music', expression: `genre is "${genre}" and media_kind is music`
expression: 'genre is "' + genre + '"'
} }
return axios.get('./api/search', { return axios.get('./api/search', {
params: genreParams params: genreParams
@ -310,8 +315,7 @@ export default {
library_genre_tracks(genre) { library_genre_tracks(genre) {
const genreParams = { const genreParams = {
type: 'tracks', type: 'tracks',
media_kind: 'music', expression: `genre is "${genre}" and media_kind is music`
expression: 'genre is "' + genre + '"'
} }
return axios.get('./api/search', { return axios.get('./api/search', {
params: genreParams params: genreParams
@ -329,15 +333,20 @@ export default {
}) })
}, },
library_composers() { library_composers(media_kind = undefined) {
return axios.get('./api/library/composers') return axios.get('./api/library/composers', {
params: { media_kind: media_kind }
})
}, },
library_composer(composer) { library_composer(composer) {
return axios.get(`./api/library/composers/${encodeURIComponent(composer)}`)
},
library_composer_albums(composer) {
const params = { const params = {
type: 'albums', type: 'albums',
media_kind: 'music', expression: `composer is "${composer}" and media_kind is music`
expression: 'composer is "' + composer + '"'
} }
return axios.get('./api/search', { return axios.get('./api/search', {
params: params params: params
@ -347,8 +356,7 @@ export default {
library_composer_tracks(composer) { library_composer_tracks(composer) {
const params = { const params = {
type: 'tracks', type: 'tracks',
media_kind: 'music', expression: `composer is "${composer}" and media_kind is music`
expression: 'composer is "' + composer + '"'
} }
return axios.get('./api/search', { return axios.get('./api/search', {
params: params params: params