[web] Replace switch control

This commit is contained in:
Alain Nussbaumer 2024-09-09 20:55:41 +02:00
parent e12ab3dd08
commit e0a2ab159e
16 changed files with 237 additions and 519 deletions

View File

@ -0,0 +1,70 @@
<template>
<label class="toggle">
<input
:checked="modelValue"
type="checkbox"
class="toggle-checkbox is-rounded mr-2"
@change="$emit('update:modelValue', !modelValue)"
/>
<div class="toggle-switch"></div>
<slot name="label" class="toggle-label" />
</label>
</template>
<script>
export default {
name: 'ControlSwitch',
props: {
modelValue: Boolean
},
emits: ['update:modelValue']
}
</script>
<style scoped>
.toggle {
cursor: pointer;
display: inline-block;
}
.toggle-switch {
display: inline-block;
background: var(--bulma-grey-light);
border-radius: 1rem;
width: 2.75rem;
height: 1.5rem;
position: relative;
vertical-align: middle;
transition: background 0.25s;
margin-right: 0.5rem;
}
.toggle-switch:before,
.toggle-switch:after {
content: '';
}
.toggle-switch:before {
display: block;
background: var(--bulma-white);
border-radius: 50%;
width: 1rem;
height: 1rem;
position: absolute;
top: 0.25rem;
left: 0.25rem;
transition: left 0.25s;
}
.toggle:hover .toggle-switch:before {
background: var(--bulma-white);
}
.toggle-checkbox:checked + .toggle-switch {
background: var(--bulma-primary);
}
.toggle-checkbox:checked + .toggle-switch:before {
left: 1.5rem;
}
.toggle-checkbox {
position: absolute;
visibility: hidden;
}
</style>

View File

@ -9,7 +9,7 @@
:url="item.artwork_url" :url="item.artwork_url"
:artist="item.artist" :artist="item.artist"
:album="item.name" :album="item.name"
class="fd-has-shadow fd-cover fd-cover-normal-image mb-5" class="fd-has-shadow fd-cover fd-cover-normal-image"
/> />
<p class="title is-4"> <p class="title is-4">
<a class="has-text-link" @click="open" v-text="item.name" /> <a class="has-text-link" @click="open" v-text="item.name" />
@ -184,5 +184,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -86,5 +86,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -87,13 +87,12 @@
</p> </p>
<p> <p>
<span class="heading" v-text="$t('dialog.track.type')" /> <span class="heading" v-text="$t('dialog.track.type')" />
<span class="title is-6"> <span
<span class="title is-6"
v-text=" v-text="
`${$t(`media.kind.${item.media_kind}`)} - ${$t(`data.kind.${item.data_kind}`)}` `${$t(`media.kind.${item.media_kind}`)} - ${$t(`data.kind.${item.data_kind}`)}`
" "
/> />
</span>
</p> </p>
<p v-if="item.samplerate"> <p v-if="item.samplerate">
<span class="heading" v-text="$t('dialog.track.quality')" /> <span class="heading" v-text="$t('dialog.track.quality')" />
@ -296,5 +295,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -2,18 +2,18 @@
<modal-dialog <modal-dialog
:show="show" :show="show"
:title="$t('dialog.update.title')" :title="$t('dialog.update.title')"
:ok_action="library.updating ? '' : $t('dialog.update.rescan')" :ok_action="libraryStore.updating ? '' : $t('dialog.update.rescan')"
:close_action="$t('dialog.update.cancel')" :close_action="$t('dialog.update.cancel')"
@ok="update_library" @ok="update_library"
@close="close()" @close="close()"
> >
<template #modal-content> <template #modal-content>
<div v-if="!library.updating"> <div v-if="!libraryStore.updating">
<p class="mb-3" v-text="$t('dialog.update.info')" /> <p class="mb-3" v-text="$t('dialog.update.info')" />
<div v-if="spotify_enabled || rss.tracks > 0" class="field"> <div v-if="spotify_enabled || rss.tracks > 0" class="field">
<div class="control"> <div class="control">
<div class="select is-small"> <div class="select is-small">
<select v-model="update_dialog_scan_kind"> <select v-model="libraryStore.update_dialog_scan_kind">
<option value="" v-text="$t('dialog.update.all')" /> <option value="" v-text="$t('dialog.update.all')" />
<option value="files" v-text="$t('dialog.update.local')" /> <option value="files" v-text="$t('dialog.update.local')" />
<option <option
@ -31,13 +31,11 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<input <control-switch v-model="rescan_metadata">
id="rescan" <template #label>
v-model="rescan_metadata" <span v-text="$t('dialog.update.rescan-metadata')" />
type="checkbox" </template>
class="switch is-rounded is-small" </control-switch>
/>
<label for="rescan" v-text="$t('dialog.update.rescan-metadata')" />
</div> </div>
</div> </div>
<div v-else> <div v-else>
@ -48,6 +46,7 @@
</template> </template>
<script> <script>
import ControlSwitch from '@/components/ControlSwitch.vue'
import ModalDialog from '@/components/ModalDialog.vue' import ModalDialog from '@/components/ModalDialog.vue'
import { useLibraryStore } from '@/stores/library' import { useLibraryStore } from '@/stores/library'
import { useServicesStore } from '@/stores/services' import { useServicesStore } from '@/stores/services'
@ -55,7 +54,7 @@ import webapi from '@/webapi'
export default { export default {
name: 'ModalDialogUpdate', name: 'ModalDialogUpdate',
components: { ModalDialog }, components: { ControlSwitch, ModalDialog },
props: { show: Boolean }, props: { show: Boolean },
emits: ['close'], emits: ['close'],
@ -73,42 +72,26 @@ export default {
}, },
computed: { computed: {
library() {
return this.libraryStore.$state
},
rss() { rss() {
return this.libraryStore.rss return this.libraryStore.rss
}, },
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
},
update_dialog_scan_kind: {
get() {
return this.library.update_dialog_scan_kind
},
set(value) {
this.library.update_dialog_scan_kind = value
}
} }
}, },
methods: { methods: {
close() { close() {
this.update_dialog_scan_kind = '' this.libraryStore.update_dialog_scan_kind = ''
this.$emit('close') this.$emit('close')
}, },
update_library() { update_library() {
if (this.rescan_metadata) { if (this.rescan_metadata) {
webapi.library_rescan(this.update_dialog_scan_kind) webapi.library_rescan(this.libraryStore.update_dialog_scan_kind)
} else { } else {
webapi.library_update(this.update_dialog_scan_kind) webapi.library_update(this.libraryStore.update_dialog_scan_kind)
} }
} }
} }
} }
</script> </script>
<style></style>

View File

@ -1,15 +1,14 @@
<template> <template>
<div class="field"> <div class="field">
<input <control-switch
:id="setting.name" :id="setting.name"
v-model="setting.value" v-model="setting.value"
type="checkbox" @update:model-value="update_setting"
class="switch is-rounded mr-2" >
@change="update_setting" <template #label>
/> <slot name="label" />
<label class="pt-0" :for="setting.name"> </template>
<slot name="label" /> </control-switch>
</label>
<i <i
class="is-size-7" class="is-size-7"
:class="{ 'has-text-info': is_success, 'has-text-danger': is_error }" :class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
@ -22,11 +21,13 @@
</template> </template>
<script> <script>
import ControlSwitch from '@/components/ControlSwitch.vue'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'SettingsCheckbox', name: 'SettingsCheckbox',
components: { ControlSwitch },
props: { props: {
category: { required: true, type: String }, category: { required: true, type: String },
name: { required: true, type: String } name: { required: true, type: String }
@ -104,5 +105,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -1,176 +1,7 @@
@charset "utf-8"; @charset "utf-8";
@import 'bulma/bulma.sass'; @import 'bulma/bulma';
@import 'bulma-switch'; @import 'bulma/sass/utilities/mixins';
@media (prefers-color-scheme: dark) {
body,
html,
.content table th,
td,
.fd-tabs-section {
background-color: $black-ter !important;
color: $grey-light;
}
.tabs ul {
border-bottom-color: $grey-dark;
}
.tabs a:hover {
border-bottom-color: $grey-lighter;
color: $grey-lighter !important;
}
a:hover,
a.has-text-dark:hover,
a.has-text-dark:focus {
color: $grey-lighter !important;
}
.media + .media {
border-top-color: $grey-dark !important;
}
.tabs a {
border-bottom-color: $grey-dark;
}
.tabs a,
.hero.is-light .title,
.title,
.subtitle,
.navbar.is-light .navbar-brand > .navbar-item,
.navbar-item,
.navbar.is-white .navbar-brand a.navbar-item,
.navbar.is-dark .navbar-brand .navbar-item,
.navbar.is-light .navbar-burger {
color: $grey-light;
}
.navbar-item.has-dropdown-up .navbar-dropdown {
border-bottom-color: $grey-dark;
}
.navbar-dropdown {
background-color: $grey-darker;
border-top-color: $grey-dark;
}
a.tag:hover,
a.tag.is-delete:hover,
a.dropdown-item:hover,
a.dropdown-item:focus,
a.navbar-item:hover,
a.navbar-item:focus,
a.navbar-item:active,
.button:hover,
.button.is-white:focus,
.button.is-white:hover,
.button.is-dark:hover,
.button.is-light:hover,
hr,
.navbar-burger:hover,
.navbar.is-white .navbar-brand > a.navbar-item:hover,
.navbar.is-light .navbar-brand > a.navbar-item:focus,
.navbar.is-light .navbar-brand > a.navbar-item:hover,
.navbar.is-dark .navbar-brand > a.navbar-item:focus,
.navbar.is-dark .navbar-brand > a.navbar-item:hover,
.navbar-dropdown a.navbar-item:hover,
.navbar-dropdown a.navbar-item:focus,
.modal-content .input,
.modal-content select,
.tabs.is-toggle a:hover {
background-color: $grey-dark;
color: $grey-lighter;
}
.card-footer .has-text-dark,
.media .has-text-dark {
color: $grey-light !important;
}
.navbar-menu,
.navbar-brand,
.notification,
.card {
background-color: $grey-darker;
color: $grey-light;
}
a.tag,
.button,
.button:active,
.button:focus,
.dropdown-content,
.dropdown-item,
.input,
.input .switch {
background-color: $grey-darker;
border-width: 0;
color: $grey-lighter;
}
.input::placeholder,
.control.has-icons-left .icon {
color: $grey;
}
.label,
.tabs a:hover,
.control.has-icons-left .input:focus ~ .icon {
color: $grey-lighter;
}
.tabs.is-toggle a:hover,
.navbar-item .buttons .button,
.modal-content select,
.modal-content select:hover {
border-color: $grey-dark;
border-width: 1px;
}
.tabs.is-toggle a {
background-color: $grey-darker;
border-color: $grey-darker;
}
.button.is-light,
.button.is-dark,
.button.is-white,
.button[disabled],
.button[disabled]:hover {
background-color: $grey-darker;
color: $grey-light;
}
.has-text-grey-light,
a.has-text-grey-light:hover {
color: $grey !important;
}
.table,
code {
background: transparent;
}
.card-footer {
border-top-color: $grey-dark;
}
.card-footer-item:not(:last-child) {
border-right-color: $grey-dark;
}
.hero.is-light.is-bold {
background-image: linear-gradient(
141deg,
$black-ter 0%,
$grey-darker 71%,
$grey-dark
);
}
}
/* Lyrics animation */
@keyframes pop-color {
0% {
color: $black;
}
100% {
color: $success;
}
}
.media.with-progress h2:last-of-type {
margin-bottom: 6px;
}
.media.with-progress {
margin-top: 6px;
}
a.navbar-item {
padding: 0 1rem;
}
.fd-is-not-allowed { .fd-is-not-allowed {
cursor: not-allowed; cursor: not-allowed;
@ -180,13 +11,6 @@ a.navbar-item {
cursor: move; cursor: move;
} }
.fd-is-square .button {
height: 27px;
min-width: 27px;
padding-left: 0.25rem;
padding-right: 0.25rem;
}
.fd-is-text-clipped { .fd-is-text-clipped {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -196,8 +20,7 @@ a.navbar-item {
.fd-tabs-section { .fd-tabs-section {
padding-bottom: 0; padding-bottom: 0;
padding-top: 0; padding-top: 0;
background: $white; background: var(--bulma-body-background-color);
top: $navbar-height;
z-index: 20; z-index: 20;
position: fixed; position: fixed;
width: 100%; width: 100%;
@ -205,16 +28,12 @@ a.navbar-item {
.fd-has-shadow img { .fd-has-shadow img {
box-shadow: box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 0.25rem 0.5rem 0 var(--bulma-background-active),
0 6px 20px 0 rgba(0, 0, 0, 0.19); 0 0.375rem 1.25rem 0 var(--bulma-background-active);
}
.fd-page-with-tabs {
margin-top: $navbar-height !important;
} }
.is-full-height { .is-full-height {
min-height: calc(100vh - calc(2 * $navbar-height)); min-height: calc(100vh - calc(2 * var(--bulma-navbar-height)));
} }
.is-disabled { .is-disabled {
@ -233,23 +52,23 @@ a.navbar-item {
width: 4rem; width: 4rem;
height: 4rem; height: 4rem;
img { img {
border-radius: $radius-small; border-radius: var(--bulma-radius-small);
max-width: 4rem; max-width: 4rem;
max-height: 4rem; max-height: 4rem;
} }
} }
&-medium-image { &-medium-image {
@include from($tablet) { @include tablet {
justify-content: right; justify-content: right;
} }
img { img {
border-radius: $radius; border-radius: var(--bulma-radius);
max-height: calc(150px - 1.5rem); max-height: calc(150px - 1.5rem);
} }
} }
&-normal-image { &-normal-image {
img { img {
border-radius: $radius-large; border-radius: var(--bulma-radius-large);
width: 100%; width: 100%;
} }
} }
@ -262,7 +81,7 @@ a.navbar-item {
} }
} }
img { img {
border-radius: $radius-large; border-radius: var(--bulma-radius-large);
max-height: calc(100vh - 26rem); max-height: calc(100vh - 26rem);
} }
&.is-masked { &.is-masked {
@ -271,14 +90,6 @@ a.navbar-item {
} }
} }
.sortable-chosen .media-right {
visibility: hidden;
}
.sortable-ghost h1,
.sortable-ghost h2 {
color: $danger;
}
.media:first-of-type { .media:first-of-type {
padding-top: 1rem; padding-top: 1rem;
} }
@ -299,35 +110,20 @@ a.navbar-item {
opacity: 1; opacity: 1;
} }
/* Add a little bit of spacing between title and subtitle */
.title:not(.is-spaced) + .subtitle + .subtitle {
margin-top: -1.3rem !important;
}
/* Only scroll content if modal contains a card component */ /* Only scroll content if modal contains a card component */
.modal-content .card-content { .modal-content .card-content {
max-height: calc(100vh - 200px); max-height: calc(100vh - 200px);
overflow: auto; overflow: auto;
} }
.fd-width-auto {
min-width: auto;
}
/* Show scrollbar for navbar menu in desktop mode if content exceeds the screen size */ /* Show scrollbar for navbar menu in desktop mode if content exceeds the screen size */
@include desktop { @include desktop {
.navbar-dropdown { .navbar-dropdown {
max-height: calc(100vh - calc(2 * $navbar-height) - 2rem); max-height: calc(100vh - calc(2 * var(--bulma-navbar-height)) - 2rem);
overflow: auto; overflow: auto;
} }
} }
/* Limit the size of the bottom navbar menu to not be displayed behind the Safari browser menu on iOS */
.fd-bottom-navbar .navbar-menu {
max-height: calc(100vh - calc(2 * $navbar-height) - 1rem);
overflow: scroll;
}
.buttons { .buttons {
@include mobile { @include mobile {
&.fd-is-centered-mobile { &.fd-is-centered-mobile {
@ -342,12 +138,20 @@ a.navbar-item {
} }
} }
.heading {
display: block;
font-size: 0.75rem;
letter-spacing: 1px;
margin-bottom: 0px !important;
text-transform: uppercase;
}
.column { .column {
&.fd-has-cover { &.fd-has-cover {
@include mobile { @include mobile {
margin: auto; margin: auto;
} }
@include from($tablet) { @include tablet {
margin-right: 0; margin-right: 0;
} }
} }
@ -364,6 +168,17 @@ a.navbar-item {
padding: 1.5rem !important; padding: 1.5rem !important;
} }
.dropdown-menu {
@include mobile {
width: 100vw;
}
}
.dropdown-content {
max-height: calc(100vh - calc(2 * var(--bulma-navbar-height)));
overflow: auto;
}
/* Slider */ /* Slider */
@mixin thumb { @mixin thumb {
-webkit-appearance: none; -webkit-appearance: none;
@ -371,43 +186,51 @@ a.navbar-item {
height: var(--th); height: var(--th);
box-sizing: border-box; box-sizing: border-box;
border-radius: 50%; border-radius: 50%;
background: $light; background: var(--bulma-light);
border: 1px solid $grey-light; border: 1px solid var(--bulma-grey-light);
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background: $grey-light; background: var(--bulma-grey-light);
border: 1px solid $grey-dark; border: 1px solid var(--bulma-grey-dark);
} }
} }
@mixin thumb-inactive { @mixin thumb-inactive {
box-sizing: border-box; box-sizing: border-box;
background-color: $light; background-color: var(--bulma-light);
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background-color: $grey-dark; background-color: var(--bulma-grey-dark);
border: 1px solid $grey-darker; border: 1px solid var(--bulma-grey-darker);
} }
} }
@mixin track { @mixin track {
height: calc(var(--sh)); height: calc(var(--sh));
border-radius: calc(var(--sh) / 2); border-radius: calc(var(--sh) / 2);
background: linear-gradient(90deg, $dark var(--sx), $grey-light var(--sx)); background: linear-gradient(
90deg,
var(--bulma-dark) var(--sx),
var(--bulma-grey-light) var(--sx)
);
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
$grey-light var(--sx), var(--bulma-grey-light) var(--sx),
$grey-dark var(--sx) var(--bulma-grey-dark) var(--sx)
); );
} }
} }
@mixin track-inactive { @mixin track-inactive {
background: linear-gradient(90deg, $grey-light var(--sx), $light var(--sx)); background: linear-gradient(
90deg,
var(--bulma-grey-light) var(--sx),
var(--bulma-light) var(--sx)
);
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
$grey-dark var(--sx), var(--bulma-grey-dark) var(--sx),
$black-ter var(--sx) var(--bulma-black-ter) var(--sx)
); );
} }
} }

View File

@ -1,47 +1,33 @@
<template> <template>
<div class="fd-page-with-tabs"> <div>
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :indices="albums.indices" /> <index-button-list :indices="albums.indices" />
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.albums.filter')" /> <p class="heading" v-text="$t('page.albums.filter')" />
<div class="field"> <div class="field">
<div class="control"> <control-switch v-model="uiStore.hide_singles">
<input <template #label>
id="switchHideSingles" <span v-text="$t('page.albums.hide-singles')" />
v-model="hide_singles" </template>
type="checkbox" </control-switch>
class="switch is-rounded"
/>
<label
for="switchHideSingles"
v-text="$t('page.albums.hide-singles')"
/>
</div>
<p class="help" v-text="$t('page.albums.hide-singles-help')" /> <p class="help" v-text="$t('page.albums.hide-singles-help')" />
</div> </div>
<div v-if="spotify_enabled" class="field"> <div v-if="spotify_enabled" class="field">
<div class="control"> <control-switch v-model="uiStore.hide_spotify">
<input <template #label>
id="switchHideSpotify" <span v-text="$t('page.albums.hide-spotify')" />
v-model="hide_spotify" </template>
type="checkbox" </control-switch>
class="switch is-rounded"
/>
<label
for="switchHideSpotify"
v-text="$t('page.albums.hide-spotify')"
/>
</div>
<p class="help" v-text="$t('page.albums.hide-spotify-help')" /> <p class="help" v-text="$t('page.albums.hide-spotify-help')" />
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.albums.sort.title')" /> <p class="heading" v-text="$t('page.albums.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_id" v-model:value="uiStore.albums_sort"
:options="groupings" :options="groupings"
/> />
</div> </div>
@ -54,7 +40,6 @@
v-text="$t('page.albums.count', { count: albums.count })" v-text="$t('page.albums.count', { count: albums.count })"
/> />
</template> </template>
<template #heading-right />
<template #content> <template #content>
<list-albums :items="albums" /> <list-albums :items="albums" />
</template> </template>
@ -65,6 +50,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
@ -88,6 +74,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlDropdown, ControlDropdown,
ControlSwitch,
IndexButtonList, IndexButtonList,
ListAlbums, ListAlbums,
TabsMusic TabsMusic
@ -157,43 +144,17 @@ export default {
computed: { computed: {
albums() { albums() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.selected_grouping_id (grouping) => grouping.id === this.uiStore.albums_sort
) )
options.filters = [ options.filters = [
(album) => !this.hide_singles || album.track_count > 2, (album) => !this.uiStore.hide_singles || album.track_count > 2,
(album) => !this.hide_spotify || album.data_kind !== 'spotify' (album) => !this.uiStore.hide_spotify || album.data_kind !== 'spotify'
] ]
return this.albums_list.group(options) return this.albums_list.group(options)
}, },
hide_singles: {
get() {
return this.uiStore.hide_singles
},
set(value) {
this.uiStore.hide_singles = value
}
},
hide_spotify: {
get() {
return this.uiStore.hide_spotify
},
set(value) {
this.uiStore.hide_spotify = value
}
},
selected_grouping_id: {
get() {
return this.uiStore.albums_sort
},
set(value) {
this.uiStore.albums_sort = value
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }
} }
} }
</script> </script>
<style></style>

View File

@ -4,27 +4,20 @@
<template #options> <template #options>
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.filter')" /> <p class="heading" v-text="$t('page.artist.filter')" />
<div v-if="spotify_enabled" class="field"> <div v-if="spotify_enabled" class="field">
<div class="control"> <control-switch v-model="uiStore.hide_spotify">
<input <template #label>
id="switchHideSpotify" <span v-text="$t('page.artist.hide-spotify')" />
v-model="hide_spotify" </template>
type="checkbox" </control-switch>
class="switch is-rounded"
/>
<label
for="switchHideSpotify"
v-text="$t('page.artist.hide-spotify')"
/>
</div>
<p class="help" v-text="$t('page.artist.hide-spotify-help')" /> <p class="help" v-text="$t('page.artist.hide-spotify-help')" />
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" /> <p class="heading" v-text="$t('page.artist.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_id" v-model:value="uiStore.artist_albums_sort"
:options="groupings" :options="groupings"
/> />
</div> </div>
@ -73,6 +66,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import ListAlbums from '@/components/ListAlbums.vue' import ListAlbums from '@/components/ListAlbums.vue'
import ModalDialogArtist from '@/components/ModalDialogArtist.vue' import ModalDialogArtist from '@/components/ModalDialogArtist.vue'
@ -99,6 +93,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlDropdown, ControlDropdown,
ControlSwitch,
ListAlbums, ListAlbums,
ModalDialogArtist ModalDialogArtist
}, },
@ -136,29 +131,13 @@ export default {
computed: { computed: {
albums() { albums() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.selected_grouping_id (grouping) => grouping.id === this.uiStore.artist_albums_sort
) )
options.filters = [ options.filters = [
(album) => !this.hide_spotify || album.data_kind !== 'spotify' (album) => !this.uiStore.hide_spotify || album.data_kind !== 'spotify'
] ]
return this.albums_list.group(options) return this.albums_list.group(options)
}, },
hide_spotify: {
get() {
return this.uiStore.hide_spotify
},
set(value) {
this.uiStore.hide_spotify = value
}
},
selected_grouping_id: {
get() {
return this.uiStore.artist_albums_sort
},
set(value) {
this.uiStore.artist_albums_sort = value
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
@ -187,5 +166,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -5,27 +5,20 @@
<index-button-list :indices="tracks.indices" /> <index-button-list :indices="tracks.indices" />
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.filter')" /> <p class="heading" v-text="$t('page.artist.filter')" />
<div v-if="spotify_enabled" class="field"> <div v-if="spotify_enabled" class="field">
<div class="control"> <control-switch v-model="uiStore.hide_spotify">
<input <template #label>
id="switchHideSpotify" <span v-text="$t('page.artist.hide-spotify')" />
v-model="hide_spotify" </template>
type="checkbox" </control-switch>
class="switch is-rounded"
/>
<label
for="switchHideSpotify"
v-text="$t('page.artist.hide-spotify')"
/>
</div>
<p class="help" v-text="$t('page.artist.hide-spotify-help')" /> <p class="help" v-text="$t('page.artist.hide-spotify-help')" />
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" /> <p class="heading" v-text="$t('page.artist.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_id" v-model:value="uiStore.artist_tracks_sort"
:options="groupings" :options="groupings"
/> />
</div> </div>
@ -74,6 +67,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListTracks from '@/components/ListTracks.vue' import ListTracks from '@/components/ListTracks.vue'
@ -101,6 +95,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlDropdown, ControlDropdown,
ControlSwitch,
IndexButtonList, IndexButtonList,
ListTracks, ListTracks,
ModalDialogArtist ModalDialogArtist
@ -147,22 +142,6 @@ export default {
.map((track) => track.item.album_id) .map((track) => track.item.album_id)
).size ).size
}, },
hide_spotify: {
get() {
return this.uiStore.hide_spotify
},
set(value) {
this.uiStore.hide_spotify = value
}
},
selected_grouping_id: {
get() {
return this.uiStore.artist_tracks_sort
},
set(value) {
this.uiStore.artist_tracks_sort = value
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
@ -171,10 +150,10 @@ export default {
}, },
tracks() { tracks() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.selected_grouping_id (grouping) => grouping.id === this.uiStore.artist_tracks_sort
) )
options.filters = [ options.filters = [
(track) => !this.hide_spotify || track.data_kind !== 'spotify' (track) => !this.uiStore.hide_spotify || track.data_kind !== 'spotify'
] ]
return this.tracks_list.group(options) return this.tracks_list.group(options)
} }
@ -197,5 +176,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -1,47 +1,33 @@
<template> <template>
<div class="fd-page-with-tabs"> <div>
<tabs-music /> <tabs-music />
<content-with-heading> <content-with-heading>
<template #options> <template #options>
<index-button-list :indices="artists.indices" /> <index-button-list :indices="artists.indices" />
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artists.filter')" /> <p class="heading" v-text="$t('page.artists.filter')" />
<div class="field"> <div class="field">
<div class="control"> <control-switch v-model="uiStore.hide_singles">
<input <template #label>
id="switchHideSingles" <span v-text="$t('page.artists.hide-singles')" />
v-model="hide_singles" </template>
type="checkbox" </control-switch>
class="switch is-rounded"
/>
<label
for="switchHideSingles"
v-text="$t('page.artists.hide-singles')"
/>
</div>
<p class="help" v-text="$t('page.artists.hide-singles-help')" /> <p class="help" v-text="$t('page.artists.hide-singles-help')" />
</div> </div>
<div v-if="spotify_enabled" class="field"> <div v-if="spotify_enabled" class="field">
<div class="control"> <control-switch v-model="uiStore.hide_spotify">
<input <template #label>
id="switchHideSpotify" <span v-text="$t('page.artists.hide-spotify')" />
v-model="hide_spotify" </template>
type="checkbox" </control-switch>
class="switch is-rounded"
/>
<label
for="switchHideSpotify"
v-text="$t('page.artists.hide-spotify')"
/>
</div>
<p class="help" v-text="$t('page.artists.hide-spotify-help')" /> <p class="help" v-text="$t('page.artists.hide-spotify-help')" />
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artists.sort.title')" /> <p class="heading" v-text="$t('page.artists.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_id" v-model:value="uiStore.artists_sort"
:options="groupings" :options="groupings"
/> />
</div> </div>
@ -54,7 +40,6 @@
v-text="$t('page.artists.count', { count: artists.count })" v-text="$t('page.artists.count', { count: artists.count })"
/> />
</template> </template>
<template #heading-right />
<template #content> <template #content>
<list-artists :items="artists" /> <list-artists :items="artists" />
</template> </template>
@ -65,6 +50,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlDropdown from '@/components/ControlDropdown.vue' import ControlDropdown from '@/components/ControlDropdown.vue'
import ControlSwitch from '@/components/ControlSwitch.vue'
import { GroupedList } from '@/lib/GroupedList' import { GroupedList } from '@/lib/GroupedList'
import IndexButtonList from '@/components/IndexButtonList.vue' import IndexButtonList from '@/components/IndexButtonList.vue'
import ListArtists from '@/components/ListArtists.vue' import ListArtists from '@/components/ListArtists.vue'
@ -88,6 +74,7 @@ export default {
components: { components: {
ContentWithHeading, ContentWithHeading,
ControlDropdown, ControlDropdown,
ControlSwitch,
IndexButtonList, IndexButtonList,
ListArtists, ListArtists,
TabsMusic TabsMusic
@ -125,47 +112,21 @@ export default {
}, },
computed: { computed: {
// Wraps GroupedList and updates it if filter or sort changes
artists() { artists() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.selected_grouping_id (grouping) => grouping.id === this.uiStore.artists_sort
) )
options.filters = [ options.filters = [
(artist) => (artist) =>
!this.hide_singles || artist.track_count > artist.album_count * 2, !this.uiStore.hide_singles ||
(artist) => !this.hide_spotify || artist.data_kind !== 'spotify' artist.track_count > artist.album_count * 2,
(artist) => !this.uiStore.hide_spotify || artist.data_kind !== 'spotify'
] ]
return this.artists_list.group(options) return this.artists_list.group(options)
}, },
hide_singles: {
get() {
return this.uiStore.hide_singles
},
set(value) {
this.uiStore.hide_singles = value
}
},
hide_spotify: {
get() {
return this.uiStore.hide_spotify
},
set(value) {
this.uiStore.hide_spotify = value
}
},
selected_grouping_id: {
get() {
return this.uiStore.artists_sort
},
set(value) {
this.uiStore.artists_sort = value
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
} }
} }
} }
</script> </script>
<style></style>

View File

@ -87,5 +87,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -104,5 +104,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -5,9 +5,9 @@
<index-button-list :indices="tracks.indices" /> <index-button-list :indices="tracks.indices" />
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.artist.sort.title')" /> <p class="heading" v-text="$t('page.artist.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_id" v-model:value="uiStore.composer_tracks_sort"
:options="groupings" :options="groupings"
/> />
</div> </div>
@ -130,17 +130,9 @@ export default {
expression() { expression() {
return `composer is "${this.composer.name}" and media_kind is music` return `composer is "${this.composer.name}" and media_kind is music`
}, },
selected_grouping_id: {
get() {
return this.uiStore.composer_tracks_sort
},
set(value) {
this.uiStore.composer_tracks_sort = value
}
},
tracks() { tracks() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.selected_grouping_id (grouping) => grouping.id === this.uiStore.composer_tracks_sort
) )
return this.tracks_list.group(options) return this.tracks_list.group(options)
} }
@ -160,5 +152,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -5,9 +5,9 @@
<index-button-list :indices="tracks.indices" /> <index-button-list :indices="tracks.indices" />
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<p class="heading mb-5" v-text="$t('page.genre.sort.title')" /> <p class="heading" v-text="$t('page.genre.sort.title')" />
<control-dropdown <control-dropdown
v-model:value="selected_grouping_id" v-model:value="uiStore.genre_tracks_sort"
:options="groupings" :options="groupings"
/> />
</div> </div>
@ -126,17 +126,9 @@ export default {
expression() { expression() {
return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}` return `genre is "${this.genre.name}" and media_kind is ${this.media_kind}`
}, },
selected_grouping_id: {
get() {
return this.uiStore.genre_tracks_sort
},
set(value) {
this.uiStore.genre_tracks_sort = value
}
},
tracks() { tracks() {
const { options } = this.groupings.find( const { options } = this.groupings.find(
(grouping) => grouping.id === this.selected_grouping_id (grouping) => grouping.id === this.uiStore.genre_tracks_sort
) )
return this.tracks_list.group(options) return this.tracks_list.group(options)
} }
@ -157,5 +149,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="fd-page-with-tabs"> <div>
<tabs-settings /> <tabs-settings />
<content-with-heading> <content-with-heading>
<template #heading-left> <template #heading-left>
@ -51,16 +51,15 @@
/> />
<div v-for="output in outputs" :key="output.id"> <div v-for="output in outputs" :key="output.id">
<div class="field"> <div class="field">
<div class="control"> <control-switch
<input :id="output.id"
:id="output.id" v-model="output.selected"
v-model="output.selected" @update:model-value="toggleOutput(output.id)"
type="checkbox" >
class="switch is-rounded mr-2" <template #label>
@change="output_toggle(output.id)" <span v-text="output.name" />
/> </template>
<label :for="output.id" class="checkbox" v-text="output.name" /> </control-switch>
</div>
</div> </div>
<form <form
v-if="output.needs_auth_key" v-if="output.needs_auth_key"
@ -94,6 +93,7 @@
<script> <script>
import ContentWithHeading from '@/templates/ContentWithHeading.vue' import ContentWithHeading from '@/templates/ContentWithHeading.vue'
import ControlSwitch from '@/components/ControlSwitch.vue'
import TabsSettings from '@/components/TabsSettings.vue' import TabsSettings from '@/components/TabsSettings.vue'
import { useOutputsStore } from '@/stores/outputs' import { useOutputsStore } from '@/stores/outputs'
import { useRemotesStore } from '@/stores/remotes' import { useRemotesStore } from '@/stores/remotes'
@ -101,7 +101,7 @@ import webapi from '@/webapi'
export default { export default {
name: 'PageSettingsRemotesOutputs', name: 'PageSettingsRemotesOutputs',
components: { ContentWithHeading, TabsSettings }, components: { ContentWithHeading, ControlSwitch, TabsSettings },
setup() { setup() {
return { outputsStore: useOutputsStore(), remotesStore: useRemotesStore() } return { outputsStore: useOutputsStore(), remotesStore: useRemotesStore() }
@ -127,14 +127,12 @@ export default {
kickoff_pairing() { kickoff_pairing() {
webapi.pairing_kickoff(this.pairing_req) webapi.pairing_kickoff(this.pairing_req)
}, },
kickoff_verification(outputId) { kickoff_verification(identifier) {
webapi.output_update(outputId, this.verification_req) webapi.output_update(identifier, this.verification_req)
}, },
output_toggle(outputId) { toggleOutput(identifier) {
webapi.output_toggle(outputId) webapi.output_toggle(identifier)
} }
} }
} }
</script> </script>
<style></style>