diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue index b06cdcd75b126c3d5b7f68abce5923caf0c18a2a..21c62cf34a5ea8063e4e076f9f58f1b5ec31af03 100644 --- a/front/src/components/audio/artist/Card.vue +++ b/front/src/components/audio/artist/Card.vue @@ -1,61 +1,38 @@ <template> - <div class="ui card"> - <div class="content"> - <div class="header"> - <router-link class="discrete link" :to="{name: 'library.artists.detail', params: {id: artist.id }}"> - {{ artist.name }} - </router-link> - </div> - <div class="description"> - <table class="ui compact very basic fixed single line unstackable table"> - <tbody> - <tr v-for="album in albums"> - <td> - <img class="ui mini image" v-if="album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](album.cover.small_square_crop)"> - <img class="ui mini image" v-else src="../../../assets/audio/default-cover.png"> - </td> - <td colspan="4"> - <router-link :title="album.title" class="discrete link" :to="{name: 'library.albums.detail', params: {id: album.id }}"> - <strong>{{ album.title }}</strong> - </router-link><br /> - {{ album.tracks_count }} tracks - </td> - <td> - <play-button class="right floated basic icon" :is-playable="album.is_playable" :discrete="true" :album="album"></play-button> - </td> - </tr> - </tbody> - </table> - <div class="center aligned segment" v-if="artist.albums.length > initialAlbums"> - <em v-if="!showAllAlbums" @click="showAllAlbums = true" class="expand"> - <translate translate-context="Content/Artist/Card.Link" :translate-params="{count: artist.albums.length - initialAlbums}" :translate-n="artist.albums.length - initialAlbums" translate-plural="Show %{ count } more albums">Show 1 more album</translate> - </em> - <em v-else @click="showAllAlbums = false" class="expand"> - <translate translate-context="Content/*/Card.Link/Verb">Collapse</translate> - </em> - </div> - </div> + <div class="flat inline card"> + <div :class="['ui', 'image', 'with-overlay', {'default-cover': !cover.original}]" v-lazy:background-image="imageUrl"> + <play-button class="play-overlay" :icon-only="true" :is-playable="artist.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :artist="artist"></play-button> </div> - <div class="extra content"> - <span> - <i class="sound icon"></i> - <translate translate-context="Content/Artist/Card" :translate-params="{count: artist.albums.length}" :translate-n="artist.albums.length" translate-plural="%{ count } albums">1 album</translate> - </span> - <play-button :is-playable="isPlayable" class="mini basic orange right floated" :artist="artist"> - <translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate> - </play-button> + <div class="content"> + <router-link :title="artist.name" :to="{name: 'library.artists.detail', params: {id: artist.id}}"> + {{ artist.name|truncate(30) }} + </router-link> + <div> + <i class="small sound icon"></i> + <translate translate-context="Content/Artist/Card" :translate-params="{count: artist.albums.length}" :translate-n="artist.albums.length" translate-plural="%{ count } albums">1 album</translate> </div> + <tags-list label-classes="tiny" :truncate-size="20" :limit="2" :show-more="false" :tags="artist.tags"></tags-list> + + <play-button + class="play-button basic icon" + :dropdown-only="true" + :is-playable="artist.is_playable" + :dropdown-icon-classes="['ellipsis', 'vertical', 'large', 'grey']" + :artist="artist"></play-button> </div> + </div> </template> <script> import backend from '@/audio/backend' import PlayButton from '@/components/audio/PlayButton' +import TagsList from "@/components/tags/List" export default { props: ['artist'], components: { - PlayButton + PlayButton, + TagsList }, data () { return { @@ -65,16 +42,23 @@ export default { } }, computed: { - albums () { - if (this.showAllAlbums) { - return this.artist.albums + + imageUrl () { + let url = '../../../assets/audio/default-cover.png' + let cover = this.cover + if (cover.original) { + url = this.$store.getters['instance/absoluteUrl'](cover.medium_square_crop) + } else { + return null } - return this.artist.albums.slice(0, this.initialAlbums) + return url }, - isPlayable () { - return this.artist.albums.filter((a) => { - return a.is_playable - }).length > 0 + cover () { + return this.artist.albums.map((a) => { + return a.cover + }).filter((c) => { + return !!c + })[0] || {} } } } @@ -82,4 +66,27 @@ export default { <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> +.default-cover { + background-image: url("../../../assets/audio/default-cover.png") !important; +} + +.play-button { + position: absolute; + right: 0; + bottom: 40%; +} + +.with-overlay { + background-size: cover !important; + background-position: center !important; + height: 8em; + width: 8em; + display: flex !important; + justify-content: center !important; + align-items: center !important; +} +.flat.card .with-overlay.image { + border-radius: 50% !important; + margin: 0 auto; +} </style> diff --git a/front/src/components/audio/artist/Widget.vue b/front/src/components/audio/artist/Widget.vue index 1b88feae76121b868b103139a6eb3dbb9ddf047c..603b96fbc8dd9f7855bb206760f3f123b9357116 100644 --- a/front/src/components/audio/artist/Widget.vue +++ b/front/src/components/audio/artist/Widget.vue @@ -1,6 +1,6 @@ <template> <div class="wrapper"> - <h3 class="ui header"> + <h3 v-if="header" class="ui header"> <slot name="title"></slot> <span class="ui tiny circular label">{{ count }}</span> </h3> @@ -12,28 +12,7 @@ <div v-if="isLoading" class="ui inverted active dimmer"> <div class="ui loader"></div> </div> - <div class="flat inline card" v-for="object in objects" :key="object.id"> - <div :class="['ui', 'image', 'with-overlay', {'default-cover': !getCover(object).original}]" v-lazy:background-image="getImageUrl(object)"> - <play-button class="play-overlay" :icon-only="true" :is-playable="object.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :artist="object"></play-button> - </div> - <div class="content"> - <router-link :title="object.name" :to="{name: 'library.artists.detail', params: {id: object.id}}"> - {{ object.name|truncate(30) }} - </router-link> - <div> - <i class="small sound icon"></i> - <translate translate-context="Content/Artist/Card" :translate-params="{count: object.albums.length}" :translate-n="object.albums.length" translate-plural="%{ count } albums">1 album</translate> - </div> - <tags-list label-classes="tiny" :truncate-size="20" :limit="2" :show-more="false" :tags="object.tags"></tags-list> - - <play-button - class="play-button basic icon" - :dropdown-only="true" - :is-playable="object.is_playable" - :dropdown-icon-classes="['ellipsis', 'vertical', 'large', 'grey']" - :artist="object"></play-button> - </div> - </div> + <artist-card :artist="artist" v-for="artist in objects" :key="artist.id"></artist-card> </div> <div v-if="!isLoading && objects.length === 0">No results matching your query.</div> </div> @@ -42,17 +21,16 @@ <script> import _ from '@/lodash' import axios from 'axios' -import PlayButton from '@/components/audio/PlayButton' -import TagsList from "@/components/tags/List" +import ArtistCard from "@/components/audio/artist/Card" export default { props: { filters: {type: Object, required: true}, controls: {type: Boolean, default: true}, + header: {type: Boolean, default: true}, }, components: { - PlayButton, - TagsList + ArtistCard, }, data () { return { @@ -96,23 +74,6 @@ export default { this.offset = Math.max(this.offset - this.limit, 0) } }, - getImageUrl (object) { - let url = '../../../assets/audio/default-cover.png' - let cover = this.getCover(object) - if (cover.original) { - url = this.$store.getters['instance/absoluteUrl'](cover.medium_square_crop) - } else { - return null - } - return url - }, - getCover (object) { - return object.albums.map((a) => { - return a.cover - }).filter((c) => { - return !!c - })[0] || {} - } }, watch: { offset () { @@ -127,21 +88,12 @@ export default { <style scoped lang="scss"> @import "../../../style/vendor/media"; -.default-cover { - background-image: url("../../../assets/audio/default-cover.png") !important; -} - .wrapper { width: 100%; } .ui.cards { justify-content: flex-start; } -.play-button { - position: absolute; - right: 0; - bottom: 0; -} .ui.three.cards .card { width: 100%; @@ -151,19 +103,6 @@ export default { width: 25em; } } -.with-overlay { - background-size: cover !important; - background-position: center !important; - height: 8em; - width: 8em; - display: flex !important; - justify-content: center !important; - align-items: center !important; -} -.flat.card .with-overlay.image { - border-radius: 50% !important; - margin: 0 auto; -} </style> <style> .ui.cards .ui.button { diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue index f16a6740f8954c7381cf53ef656b18d37e8952c2..c1a13aed9975207d6271a272f57c27ce1688fa54 100644 --- a/front/src/components/library/Artists.vue +++ b/front/src/components/library/Artists.vue @@ -35,27 +35,18 @@ <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label> <select class="ui dropdown" v-model="paginateBy"> <option :value="parseInt(12)">12</option> - <option :value="parseInt(25)">25</option> + <option :value="parseInt(30)">30</option> <option :value="parseInt(50)">50</option> </select> </div> </div> </div> <div class="ui hidden divider"></div> - <div - v-if="result" - v-masonry - transition-duration="0" - item-selector=".card" - percent-position="true" - stagger="0"> - <div v-if="result.results.length > 0" class="ui cards"> - <artist-card - v-masonry-tile - v-for="artist in result.results" - :key="artist.id" - :artist="artist"></artist-card> + <div v-if="result && result.results.length > 0" class="ui three cards"> + <div v-if="isLoading" class="ui inverted active dimmer"> + <div class="ui loader"></div> </div> + <artist-card :artist="artist" v-for="artist in result.results" :key="artist.id"></artist-card> </div> <div class="ui center aligned basic segment"> <pagination @@ -108,7 +99,7 @@ export default { page: parseInt(this.defaultPage), query: this.defaultQuery, tags: this.defaultTags.filter((t) => { return t.length > 0 }) || [], - paginateBy: parseInt(this.defaultPaginateBy || 12), + paginateBy: parseInt(this.defaultPaginateBy || 30), orderingDirection: defaultOrdering.direction || "+", ordering: defaultOrdering.field, orderingOptions: [["creation_date", "creation_date"], ["name", "name"]] @@ -205,5 +196,22 @@ export default { </script> <!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped> +<style scoped lang="scss"> +@import "../../style/vendor/media"; + +.wrapper { + width: 100%; +} +.ui.cards { + justify-content: flex-start; +} + +.ui.three.cards .card { + width: 100%; +} +@include media(">tablet") { + .ui.three.cards .card { + width: 25em; + } +} </style> diff --git a/front/src/style/themes/_dark.scss b/front/src/style/themes/_dark.scss index f612914ca87ad3e14876d8026ec95757ffb2e380..fbf3ca99dc69ead2dc3c29444ff6af1a2b1e8f0d 100644 --- a/front/src/style/themes/_dark.scss +++ b/front/src/style/themes/_dark.scss @@ -8,40 +8,56 @@ $discrete-text-color: rgba(223, 235, 240, 0.904); $border-color: rgb(63, 88, 102); $light-shadow-color: rgba(223, 235, 240, 0.15); $shadow-color: rgba(63, 102, 97, 0.95); -$box-shadow: 0px 1px 3px 0px rgba(63, 88, 102, 0.95), 0px 0px 0px 1px rgba(63, 88, 102, 0.98); +$box-shadow: 0px 1px 3px 0px rgba(63, 88, 102, 0.95), + 0px 0px 0px 1px rgba(63, 88, 102, 0.98); $link-color: rgb(255, 144, 0); .theme-dark { background-color: $background-color; .ui.labeled.input { - input, .label { + input, + .label { background-color: $input-background-color; &::placeholder { color: $light-background-color; - } - } } .ui.statistics .statistic { - > .label, > .value { + > .label, + > .value { color: $text-color; } } - .ui.link.list.list .active.item, .ui.link.list.list .active.item a:not(.ui) { + .ui.link.list.list .active.item, + .ui.link.list.list .active.item a:not(.ui) { color: inherit; } - .ui.form textarea, .ui.form select, .ui.selection.dropdown, .ui.dropdown.selected, .ui.dropdown .menu .selected.item, .ui.form input:not([type]), .ui.form input[type="date"], .ui.form input[type="datetime-local"], .ui.form input[type="email"], .ui.form input[type="number"], .ui.form input[type="password"], .ui.form input[type="search"], .ui.form input[type="tel"], .ui.form input[type="time"], .ui.form input[type="text"], .ui.form input[type="file"], .ui.form input[type="url"] { + .ui.form textarea, + .ui.form select, + .ui.selection.dropdown, + .ui.dropdown.selected, + .ui.dropdown .menu .selected.item, + .ui.form input:not([type]), + .ui.form input[type="date"], + .ui.form input[type="datetime-local"], + .ui.form input[type="email"], + .ui.form input[type="number"], + .ui.form input[type="password"], + .ui.form input[type="search"], + .ui.form input[type="tel"], + .ui.form input[type="time"], + .ui.form input[type="text"], + .ui.form input[type="file"], + .ui.form input[type="url"] { background-color: $input-background-color; &::placeholder { color: $light-background-color; - } } .ui.dropdown .menu .item:hover { background-color: $light-background-color; color: $text-color; - } .main.pusher > .ui.secondary.menu { background-color: $background-color; @@ -54,7 +70,9 @@ $link-color: rgb(255, 144, 0); } } .ui.modal { - > .header, > .content, > .actions { + > .header, + > .content, + > .actions { background-color: $background-color; } > .header { @@ -65,29 +83,36 @@ $link-color: rgb(255, 144, 0); border-top: 1px solid $border-color; } } - main, .main, footer, .modal { - + main, + .main, + footer, + .modal { .ui.menu { background-color: $light-background-color; .item { - color: $text-color; } } - .ui.secondary.menu .dropdown.item:hover, .ui.secondary.menu .link.item:hover, .ui.secondary.menu a.item:hover { + .ui.secondary.menu .dropdown.item:hover, + .ui.secondary.menu .link.item:hover, + .ui.secondary.menu a.item:hover { background: $background-color; color: $text-color; } - .header, .ui.form .field > label, .sub.header { + .header, + .ui.form .field > label, + .sub.header { color: $text-color; } .ui.attached.header { background-color: transparent; } - .ui.toggle.checkbox input:checked ~ .box, .ui.toggle.checkbox input:checked ~ label { + .ui.toggle.checkbox input:checked ~ .box, + .ui.toggle.checkbox input:checked ~ label { color: $text-color !important; } - .ui.toggle.checkbox .box::before, .ui.toggle.checkbox label::before { + .ui.toggle.checkbox .box::before, + .ui.toggle.checkbox label::before { background-color: $light-background-color; } a:not(.ui):not(.discrete) { @@ -96,8 +121,12 @@ $link-color: rgb(255, 144, 0); .ui.segment:not(.basic) { background-color: $light-background-color; } - .ui.list, .ui.dropdown { - .item, div.item, a.item, .button.item { + .ui.list, + .ui.dropdown { + .item, + div.item, + a.item, + .button.item { background-color: $background-color; color: $discrete-text-color; } @@ -113,10 +142,13 @@ $link-color: rgb(255, 144, 0); color: $discrete-text-color; } } - label, .toggle label { + label, + .toggle label { color: $text-color !important; } - &, .main.pusher, .ui.vertical.segment { + &, + .main.pusher, + .ui.vertical.segment { color: $text-color; background-color: $background-color; } @@ -125,21 +157,28 @@ $link-color: rgb(255, 144, 0); color: $discrete-text-color; } - .ui.table thead th, .ui.table { + .ui.table thead th, + .ui.table { color: $text-color; } .ui.divider:not(.vertical):not(.horizontal) { border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; } - .ui.cards > .card, .ui.card { + .ui.cards > .card, + .ui.card { color: $text-color; background-color: $background-color; - box-shadow: $box-shadow; - .content, .header, .description { + &:not(.flat) { + box-shadow: $box-shadow; + } + .content, + .header, + .description { color: $text-color; } - .extra, .meta { + .extra, + .meta { color: $discrete-text-color; } } @@ -150,31 +189,40 @@ $link-color: rgb(255, 144, 0); } // buttons - [class='ui button ui button'], [class='ui button'], [class='ui icon button'], [class='ui fluid button'], [class='ui cancel button'] { + [class="ui button ui button"], + [class="ui button"], + [class="ui icon button"], + [class="ui fluid button"], + [class="ui cancel button"] { background-color: $light-background-color; color: $text-color; &:hover { background-color: $button-hover-color; - } } - .ui.buttons > .ui.button:not(.basic):not(.inverted), .ui.buttons:not(.basic):not(.inverted) > .button { + .ui.buttons > .ui.button:not(.basic):not(.inverted), + .ui.buttons:not(.basic):not(.inverted) > .button { box-shadow: 0px 0px 0px 1px $light-shadow-color inset; - } - .ui.basic.buttons:not(:hover):not(.green):not(.orange):not(.yellow):not(.red) .button, .ui.basic.button { + .ui.basic.buttons:not(:hover):not(.green):not(.orange):not(.yellow):not(.red) + .button, + .ui.basic.button { box-shadow: 0px 0px 0px 1px $text-color inset; &:not(:hover):not(.green):not(.orange):not(.yellow):not(.red) { color: $text-color !important; } } - .ui.basic.buttons .button, .ui.basic.button { + .ui.basic.buttons .button, + .ui.basic.button { &:hover { color: $text-color !important; } - } - .ui.basic.buttons:not(.green):not(.orange):not(.yellow):not(.red) .button:hover, .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):hover, .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):active, .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):focus { + .ui.basic.buttons:not(.green):not(.orange):not(.yellow):not(.red) + .button:hover, + .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):hover, + .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):active, + .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):focus { color: $background-color !important; } // loading /dimmers @@ -185,7 +233,8 @@ $link-color: rgb(255, 144, 0); background-color: $loading-background-color; } // table - .ui.basic.table tbody tr, .ui.table tr td { + .ui.basic.table tbody tr, + .ui.table tr td { border-bottom: 1px solid $border-color; } .ui.table thead th { @@ -193,13 +242,15 @@ $link-color: rgb(255, 144, 0); } .ui.table { &:not(.basic) { - &, thead th { + &, + thead th { background-color: $light-background-color; } } } } - .ui.link.list.list a.item:hover, .ui.link.list.list .item a:not(.ui):not(.button):hover { + .ui.link.list.list a.item:hover, + .ui.link.list.list .item a:not(.ui):not(.button):hover { color: $link-color; } [data-tooltip]::after { @@ -208,13 +259,13 @@ $link-color: rgb(255, 144, 0); } .ui.progress > .label { color: $text-color; - } i.grey.icon { color: $text-color !important; } input { - &::selection, &::-moz-selection { + &::selection, + &::-moz-selection { background: $background-color; color: $text-color; }