Skip to content

Commit

Permalink
feat: ✨ Added extra UI for playlists that are read-only.
Browse files Browse the repository at this point in the history
Closes #90
  • Loading branch information
EricLambrecht committed Oct 27, 2019
1 parent e32443f commit 000906a
Show file tree
Hide file tree
Showing 15 changed files with 204 additions and 57 deletions.
26 changes: 26 additions & 0 deletions src/components/_base/Badge.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<template>
<span>
<slot />
</span>
</template>

<script>
export default {
name: 'Badge',
}
</script>

<style lang="scss" scoped>
span {
padding: 0 5px;
display: inline-flex;
align-items: center;
justify-content: center;
height: 16px;
font-size: 9px;
font-weight: bold;
border-radius: 8px;
background: var(--spotify-green);
color: white;
}
</style>
59 changes: 58 additions & 1 deletion src/components/_base/Button.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<template>
<div v-if="skeleton" class="skeleton">
<span v-if="hasIcon" class="icon">
<slot name="icon" />
</span>
<slot />
</div>
<button
v-else
v-bind="$attrs"
:class="{
button: true,
Expand Down Expand Up @@ -50,6 +57,10 @@ export default {
type: Boolean,
default: false,
},
skeleton: {
type: Boolean,
default: false,
},
},
computed: {
hasIcon() {
Expand All @@ -61,7 +72,8 @@ export default {

<style lang="scss" scoped>
button,
.button {
.button,
.skeleton {
display: inline-flex;
align-items: center;
justify-content: center;
Expand Down Expand Up @@ -127,4 +139,49 @@ button,
.label {
}
$color-bg: #ddd;
$color-highlight: lighten($color-bg, 3%);
.skeleton {
position: relative;
border-radius: 5px;
background: $color-bg;
color: $color-bg;
overflow: hidden;
cursor: default;
.icon {
margin-right: 10px;
display: inline-flex;
align-items: center;
.fa-icon {
color: $color-bg !important;
}
}
&:hover {
background: $color-bg;
}
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, $color-bg, $color-highlight, $color-bg);
animation: shine 0.8s ease-out infinite;
}
}
@keyframes shine {
0% {
transform: translate3d(-100%, 0, 0);
}
100% {
transform: translate3d(100%, 0, 0);
}
}
</style>
2 changes: 2 additions & 0 deletions src/components/_base/_setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Vue from 'vue'
* Apart from that, this is also the approach that big libraries like element-ui take.
*/

import Badge from './Badge'
import Button from './Button'
import ButtonGroup from './ButtonGroup'
import CheckboxButton from './CheckboxButton'
Expand All @@ -31,6 +32,7 @@ import SquareImage from './SquareImage'
import Text from './Text'
import TextInput from './TextInput'

Vue.component('b-badge', Badge)
Vue.component('b-button', Button)
Vue.component('b-button-group', ButtonGroup)
Vue.component('b-checkbox-button', CheckboxButton)
Expand Down
11 changes: 10 additions & 1 deletion src/components/core/MainWindowHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<b-text v-if="playlistExists">
{{ playlistLengthMs | formatTime('h [hr.,] mm [min.]') }}
</b-text>
<b-badge v-if="playlistExists && playlistIsReadOnly" class="badge">
read-only
</b-badge>
</div>
</div>
</div>
Expand All @@ -42,6 +45,7 @@ export default {
'playlistTrackCount',
'playlistExists',
'playlistLengthMs',
'playlistIsReadOnly',
]),
},
}
Expand Down Expand Up @@ -79,16 +83,21 @@ export default {
margin: 11px 0 0 1px;
display: flex;
flex-direction: row;
align-items: center;
font-size: 15px;
color: #888;
:not(:first-child) {
:not(:first-child):not(.badge) {
&::before {
content: '';
margin: 0 5px;
}
}
.badge {
margin-left: 10px;
}
}
@media screen and (max-width: 768px) {
Expand Down
35 changes: 31 additions & 4 deletions src/components/editor/EditorOperationPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@
<b-row>
<b-column>
<b-button-group class="buttons">
<b-button tertiary @click="onClickShuffle">
<b-button
v-if="!playlistExists || !playlistIsReadOnly"
:skeleton="!playlistExists"
tertiary
@click="onClickShuffle"
>
<v-icon slot="icon" name="random" label="shuffle" />
Shuffle
</b-button>
<sort-configuration />
<b-button
v-if="!playlistExists || !playlistIsReadOnly"
:skeleton="!playlistExists"
tertiary
@click="openSortModal"
>
<v-icon slot="icon" name="sort" label="sort" />
Sort
</b-button>
<b-button
tertiary
:pressed="showStatistics"
:skeleton="!playlistExists"
@click="onClickStatistics"
>
<v-icon slot="icon" name="chart-line" label="playlist-stats" />
Expand All @@ -25,14 +39,15 @@
</b-column>
</b-row>
</b-grid>
<sort-configuration :show="showSortModal" @close="closeSortModal" />
</div>
</template>

<script>
import 'vue-awesome/icons/chart-line'
import 'vue-awesome/icons/random'
import 'vue-awesome/icons/sort'
import { mapActions, mapState } from 'vuex'
import { mapActions, mapState, mapGetters } from 'vuex'
import TimeOfDaySwitch from './TimeOfDaySwitch'
import TempoSwitch from './TempoSwitch'
Expand All @@ -42,16 +57,28 @@ import SortConfiguration from './SortConfiguration'
export default {
name: 'EditorOperationPanel',
components: {
SortConfiguration,
TimeOfDaySwitch,
TempoSwitch,
SortConfiguration,
},
data() {
return {
showSortModal: false,
}
},
computed: {
...mapState('playlistStatistics', {
showStatistics: state => state.show,
}),
...mapGetters('editor', ['playlistIsReadOnly', 'playlistExists']),
},
methods: {
openSortModal() {
this.showSortModal = true
},
closeSortModal() {
this.showSortModal = false
},
async onClickShuffle() {
const confirmed = await this.askForConfirmation({
headline: 'Shuffle',
Expand Down
79 changes: 36 additions & 43 deletions src/components/editor/SortConfiguration.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
<template>
<div class="container">
<b-button tertiary class="toggle" @click="openModal">
<v-icon slot="icon" name="sort" label="sort" />
Sort
</b-button>
<b-modal headline="Sort settings" :show="showModal">
<b-radio-button-group
name="sortMode"
label="Sort Mode"
class="sort-mode"
:options="sortModeOptions"
:value="sortMode"
@change="onSortModeChange"
/>
<SortByTrackPropertyOptions
v-show="sortMode === 'trackProperty'"
@change="onOptionsChange"
/>
<SortByAudioFeatureOptions
v-show="sortMode === 'audioFeature'"
@change="onOptionsChange"
/>
<div slot="footer">
<b-button-group>
<b-button tertiary @click="closeModal">
Close
</b-button>
<b-button primary @click="sort">
Sort
</b-button>
</b-button-group>
</div>
</b-modal>
</div>
<b-modal headline="Sort settings" :show="show">
<b-radio-button-group
name="sortMode"
label="Sort Mode"
class="sort-mode"
:options="sortModeOptions"
:value="sortMode"
@change="onSortModeChange"
/>
<SortByTrackPropertyOptions
v-show="sortMode === 'trackProperty'"
@change="onOptionsChange"
/>
<SortByAudioFeatureOptions
v-show="sortMode === 'audioFeature'"
@change="onOptionsChange"
/>
<div slot="footer">
<b-button-group>
<b-button tertiary @click="onCloseClick">
Close
</b-button>
<b-button primary @click="sort">
Sort
</b-button>
</b-button-group>
</div>
</b-modal>
</template>

<script>
Expand All @@ -47,6 +41,12 @@ import SortByAudioFeatureOptions from './SortByAudioFeatureOptions'
export default {
name: 'SortConfiguration',
components: { SortByAudioFeatureOptions, SortByTrackPropertyOptions },
props: {
show: {
type: Boolean,
default: false,
},
},
data() {
return {
showModal: false,
Expand All @@ -66,11 +66,8 @@ export default {
}
},
methods: {
openModal() {
this.showModal = true
},
closeModal() {
this.showModal = false
onCloseClick() {
this.$emit('close')
},
onOptionsChange(options) {
this.options = options
Expand Down Expand Up @@ -101,10 +98,6 @@ export default {
</script>

<style lang="scss" scoped>
.container {
display: inline-block;
}
.sort-mode {
margin-bottom: 12px;
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/init/AppInitializer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export default {
try {
this.status = 'Authenticating...'
await this.$store.dispatch('user/requestToken')
this.status = 'Fetching user...'
await this.$store.dispatch('user/fetchMe')
this.status = 'Fetching playlists...'
await this.$store.dispatch('user/getPlaylists')
await this.$router.replace({ name: 'home' })
Expand Down
8 changes: 3 additions & 5 deletions src/playlist-modifications/SortByAudioFeature.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import get from 'lodash/get'
import Rearranger from './Rearranger'
import AbstractSort from './AbstractSort'

export default class Sort extends AbstractSort {
export default class SortByAudioFeature extends AbstractSort {
static sort(playlist, order, options) {
const { sortBy, audioFeatures } = options

if (typeof sortBy === 'undefined') {
throw Error(
'"options.sortBy must be defined in order to sort by audio feature.'
'"options.sortBy" must be defined in order to sort by audio feature.'
)
}
if (typeof audioFeatures === 'undefined') {
throw Error(
'"options.audioFeatures must be provided in order to sort by audio feature.'
'"options.audioFeatures" must be provided in order to sort by audio feature.'
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/playlist-modifications/SortByTrackProperty.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import get from 'lodash/get'
import AbstractSort from './AbstractSort'

export default class Sort extends AbstractSort {
export default class SortByTrackProperty extends AbstractSort {
static sort(playlist, order, options) {
const tracks = [...playlist.tracks.items.map(item => item.track)]
const { sortBy } = options
Expand Down
Loading

0 comments on commit 000906a

Please sign in to comment.