mirror of
https://github.com/koel/koel
synced 2024-11-10 14:44:13 +00:00
Refactor Edit songs form
This commit is contained in:
parent
c3b0cbe361
commit
30237d27ad
1 changed files with 291 additions and 287 deletions
|
@ -76,335 +76,339 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { every, filter } from 'lodash'
|
||||
import { every, filter } from 'lodash'
|
||||
|
||||
import { br2nl, forceReloadWindow } from '../../utils'
|
||||
import { songInfo } from '../../services/info'
|
||||
import { artistStore, albumStore, songStore } from '../../stores'
|
||||
import { br2nl, forceReloadWindow } from '../../utils'
|
||||
import { songInfo } from '../../services/info'
|
||||
import { artistStore, albumStore, songStore } from '../../stores'
|
||||
|
||||
import soundBar from '../shared/sound-bar.vue'
|
||||
import typeahead from '../shared/typeahead.vue'
|
||||
import soundBar from '../shared/sound-bar.vue'
|
||||
import typeahead from '../shared/typeahead.vue'
|
||||
|
||||
const COMPILATION_STATES = {
|
||||
NONE: 0, // No songs belong to a compilation album
|
||||
ALL: 1, // All songs belong to compilation album(s)
|
||||
SOME: 2 // Some of the songs belong to compilation album(s)
|
||||
}
|
||||
const COMPILATION_STATES = {
|
||||
NONE: 0, // No songs belong to a compilation album
|
||||
ALL: 1, // All songs belong to compilation album(s)
|
||||
SOME: 2 // Some of the songs belong to compilation album(s)
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { soundBar, typeahead },
|
||||
export default {
|
||||
components: { soundBar, typeahead },
|
||||
|
||||
data () {
|
||||
return {
|
||||
shown: false,
|
||||
songs: [],
|
||||
currentView: '',
|
||||
loading: false,
|
||||
needsReload: false,
|
||||
data () {
|
||||
return {
|
||||
shown: false,
|
||||
songs: [],
|
||||
currentView: '',
|
||||
loading: false,
|
||||
needsReload: false,
|
||||
|
||||
artistState: artistStore.state,
|
||||
artistTypeaheadOptions: {
|
||||
displayKey: 'name',
|
||||
filterKey: 'name'
|
||||
},
|
||||
artistState: artistStore.state,
|
||||
artistTypeaheadOptions: {
|
||||
displayKey: 'name',
|
||||
filterKey: 'name'
|
||||
},
|
||||
|
||||
albumState: albumStore.state,
|
||||
albumTypeaheadOptions: {
|
||||
displayKey: 'name',
|
||||
filterKey: 'name'
|
||||
},
|
||||
albumState: albumStore.state,
|
||||
albumTypeaheadOptions: {
|
||||
displayKey: 'name',
|
||||
filterKey: 'name'
|
||||
},
|
||||
|
||||
/**
|
||||
* In order not to mess up the original songs, we manually assign and manipulate
|
||||
* their attributes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
formData: {
|
||||
title: '',
|
||||
albumName: '',
|
||||
artistName: '',
|
||||
lyrics: '',
|
||||
track: '',
|
||||
compilationState: null
|
||||
}
|
||||
/**
|
||||
* In order not to mess up the original songs, we manually assign and manipulate
|
||||
* their attributes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
formData: {
|
||||
title: '',
|
||||
albumName: '',
|
||||
artistName: '',
|
||||
lyrics: '',
|
||||
track: '',
|
||||
compilationState: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Determine if we're editing but one song.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
editSingle () {
|
||||
return this.songs.length === 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if all songs we're editing are by the same artist.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
bySameArtist () {
|
||||
return every(this.songs, song => {
|
||||
song.artist.id === this.songs[0].artist.id
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if all songs we're editing are from the same album.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
inSameAlbum () {
|
||||
return every(this.songs, song => {
|
||||
song.album.id === this.songs[0].album.id
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* URL of the cover to display.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
coverUrl () {
|
||||
return this.inSameAlbum ? this.songs[0].album.cover : '/public/img/covers/unknown-album.png'
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the compilation state of the songs.
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
compilationState () {
|
||||
const contributedSongs = filter(this.songs, song => song.contributing_artist_id)
|
||||
|
||||
if (!contributedSongs.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.NONE
|
||||
} else if (contributedSongs.length === this.songs.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.ALL
|
||||
} else {
|
||||
this.formData.compilationState = COMPILATION_STATES.SOME
|
||||
}
|
||||
|
||||
return this.formData.compilationState
|
||||
},
|
||||
|
||||
/**
|
||||
* The song title to be displayed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedTitle () {
|
||||
return this.editSingle ? this.formData.title : `${this.songs.length} songs selected`
|
||||
},
|
||||
|
||||
/**
|
||||
* The album name to be displayed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedAlbum () {
|
||||
if (this.editSingle) {
|
||||
return this.formData.albumName
|
||||
} else {
|
||||
return this.formData.albumName ? this.formData.albumName : 'Mixed Albums'
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Determine if we're editing but one song.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
editSingle () {
|
||||
return this.songs.length === 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if all songs we're editing are by the same artist.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
bySameArtist () {
|
||||
return every(this.songs, song => song.artist.id === this.songs[0].artist.id)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if all songs we're editing are from the same album.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
inSameAlbum () {
|
||||
return every(this.songs, song => song.album.id === this.songs[0].album.id)
|
||||
},
|
||||
|
||||
/**
|
||||
* URL of the cover to display.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
coverUrl () {
|
||||
return this.inSameAlbum ? this.songs[0].album.cover : '/public/img/covers/unknown-album.png'
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the compilation state of the songs.
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
compilationState () {
|
||||
const contributedSongs = filter(this.songs, song => song.contributing_artist_id)
|
||||
|
||||
if (!contributedSongs.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.NONE
|
||||
} else if (contributedSongs.length === this.songs.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.ALL
|
||||
} else {
|
||||
this.formData.compilationState = COMPILATION_STATES.SOME
|
||||
}
|
||||
|
||||
return this.formData.compilationState
|
||||
},
|
||||
|
||||
/**
|
||||
* The song title to be displayed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedTitle () {
|
||||
return this.editSingle ? this.formData.title : `${this.songs.length} songs selected`
|
||||
},
|
||||
|
||||
/**
|
||||
* The album name to be displayed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedAlbum () {
|
||||
if (this.editSingle) {
|
||||
return this.formData.albumName
|
||||
} else {
|
||||
return this.formData.albumName ? this.formData.albumName : 'Mixed Albums'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The artist name to be displayed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedArtist () {
|
||||
if (this.editSingle) {
|
||||
return this.formData.artistName
|
||||
} else {
|
||||
return this.formData.artistName ? this.formData.artistName : 'Mixed Artists'
|
||||
}
|
||||
/**
|
||||
* The artist name to be displayed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedArtist () {
|
||||
if (this.editSingle) {
|
||||
return this.formData.artistName
|
||||
} else {
|
||||
return this.formData.artistName ? this.formData.artistName : 'Mixed Artists'
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open (songs) {
|
||||
this.shown = true
|
||||
this.songs = songs
|
||||
this.currentView = 'details'
|
||||
this.needsReload = false
|
||||
methods: {
|
||||
open (songs) {
|
||||
this.shown = true
|
||||
this.songs = songs
|
||||
this.currentView = 'details'
|
||||
this.needsReload = false
|
||||
|
||||
if (this.editSingle) {
|
||||
this.formData.title = this.songs[0].title
|
||||
this.formData.albumName = this.songs[0].album.name
|
||||
this.formData.artistName = this.songs[0].artist.name
|
||||
if (this.editSingle) {
|
||||
this.formData.title = this.songs[0].title
|
||||
this.formData.albumName = this.songs[0].album.name
|
||||
this.formData.artistName = this.songs[0].artist.name
|
||||
|
||||
// If we're editing only one song and the song's info (including lyrics)
|
||||
// hasn't been loaded, load it now.
|
||||
if (!this.songs[0].infoRetrieved) {
|
||||
this.loading = true
|
||||
// If we're editing only one song and the song's info (including lyrics)
|
||||
// hasn't been loaded, load it now.
|
||||
if (!this.songs[0].infoRetrieved) {
|
||||
this.loading = true
|
||||
|
||||
songInfo.fetch(this.songs[0]).then(r => {
|
||||
this.loading = false
|
||||
this.formData.lyrics = br2nl(this.songs[0].lyrics)
|
||||
this.formData.track = this.songs[0].track
|
||||
this.initCompilationStateCheckbox()
|
||||
})
|
||||
} else {
|
||||
songInfo.fetch(this.songs[0]).then(r => {
|
||||
this.loading = false
|
||||
this.formData.lyrics = br2nl(this.songs[0].lyrics)
|
||||
this.formData.track = this.songs[0].track
|
||||
this.initCompilationStateCheckbox()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.formData.albumName = this.inSameAlbum ? this.songs[0].album.name : ''
|
||||
this.formData.artistName = this.bySameArtist ? this.songs[0].artist.name : ''
|
||||
this.loading = false
|
||||
this.formData.lyrics = br2nl(this.songs[0].lyrics)
|
||||
this.formData.track = this.songs[0].track
|
||||
this.initCompilationStateCheckbox()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the compilation state's checkbox of the editing songs' album(s).
|
||||
*/
|
||||
initCompilationStateCheckbox () {
|
||||
// This must be wrapped in a $nextTick callback, because the form is dynamically
|
||||
// attached into DOM in conjunction with `this.loading` data binding.
|
||||
this.$nextTick(() => {
|
||||
const chk = this.$refs.compilationStateChk
|
||||
|
||||
switch (this.compilationState) {
|
||||
case COMPILATION_STATES.ALL:
|
||||
chk.checked = true
|
||||
chk.indeterminate = false
|
||||
break
|
||||
case COMPILATION_STATES.NONE:
|
||||
chk.checked = false
|
||||
chk.indeterminate = false
|
||||
break
|
||||
default:
|
||||
chk.checked = false
|
||||
chk.indeterminate = true
|
||||
break
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Manually set the compilation state.
|
||||
* We can't use v-model here due to the tri-state nature of the property.
|
||||
* Also, following iTunes style, we don't support circular switching of the states -
|
||||
* once the user clicks the checkbox, there's no going back to indeterminate state.
|
||||
*/
|
||||
changeCompilationState (e) {
|
||||
this.formData.compilationState = e.target.checked ? COMPILATION_STATES.ALL : COMPILATION_STATES.NONE
|
||||
this.needsReload = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the modal.
|
||||
*/
|
||||
close () {
|
||||
this.shown = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit the form.
|
||||
*/
|
||||
submit () {
|
||||
this.loading = true
|
||||
|
||||
songStore.update(this.songs, this.formData).then(r => {
|
||||
this.loading = false
|
||||
this.close()
|
||||
this.needsReload && forceReloadWindow()
|
||||
}).catch(r => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
this.formData.albumName = this.inSameAlbum ? this.songs[0].album.name : ''
|
||||
this.formData.artistName = this.bySameArtist ? this.songs[0].artist.name : ''
|
||||
this.loading = false
|
||||
this.initCompilationStateCheckbox()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the compilation state's checkbox of the editing songs' album(s).
|
||||
*/
|
||||
initCompilationStateCheckbox () {
|
||||
// This must be wrapped in a $nextTick callback, because the form is dynamically
|
||||
// attached into DOM in conjunction with `this.loading` data binding.
|
||||
this.$nextTick(() => {
|
||||
const chk = this.$refs.compilationStateChk
|
||||
|
||||
switch (this.compilationState) {
|
||||
case COMPILATION_STATES.ALL:
|
||||
chk.checked = true
|
||||
chk.indeterminate = false
|
||||
break
|
||||
case COMPILATION_STATES.NONE:
|
||||
chk.checked = false
|
||||
chk.indeterminate = false
|
||||
break
|
||||
default:
|
||||
chk.checked = false
|
||||
chk.indeterminate = true
|
||||
break
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Manually set the compilation state.
|
||||
* We can't use v-model here due to the tri-state nature of the property.
|
||||
* Also, following iTunes style, we don't support circular switching of the states -
|
||||
* once the user clicks the checkbox, there's no going back to indeterminate state.
|
||||
*/
|
||||
changeCompilationState (e) {
|
||||
this.formData.compilationState = e.target.checked ? COMPILATION_STATES.ALL : COMPILATION_STATES.NONE
|
||||
this.needsReload = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the modal.
|
||||
*/
|
||||
close () {
|
||||
this.shown = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit the form.
|
||||
*/
|
||||
submit () {
|
||||
this.loading = true
|
||||
|
||||
songStore.update(this.songs, this.formData).then(r => {
|
||||
this.loading = false
|
||||
this.close()
|
||||
this.needsReload && forceReloadWindow()
|
||||
}).catch(r => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
@import "../../../sass/partials/_vars.scss";
|
||||
@import "../../../sass/partials/_mixins.scss";
|
||||
@import "../../../sass/partials/_vars.scss";
|
||||
@import "../../../sass/partials/_mixins.scss";
|
||||
|
||||
#editSongsOverlay {
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
#editSongsOverlay {
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, .7);
|
||||
overflow: auto;
|
||||
|
||||
@include vertical-center();
|
||||
|
||||
$borderRadius: 5px;
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, .7);
|
||||
overflow: auto;
|
||||
max-width: 460px;
|
||||
background: #fff;
|
||||
border-radius: $borderRadius;
|
||||
color: #333;
|
||||
|
||||
@include vertical-center();
|
||||
input[type="checkbox"] {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
$borderRadius: 5px;
|
||||
.form-row:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
> header, > div, > footer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="number"], textarea {
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
max-width: 460px;
|
||||
background: #fff;
|
||||
border-radius: $borderRadius;
|
||||
color: #333;
|
||||
max-width: 100%;
|
||||
|
||||
input[type="checkbox"] {
|
||||
border: 1px solid #ccc;
|
||||
&:focus {
|
||||
border-color: $colorOrange;
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 192px;
|
||||
}
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
background: #eee;
|
||||
border-radius: $borderRadius $borderRadius 0 0;
|
||||
|
||||
img {
|
||||
flex: 0 0 96px;
|
||||
}
|
||||
|
||||
.form-row:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.meta {
|
||||
flex: 1;
|
||||
padding-left: 8px;
|
||||
|
||||
> header, > div, > footer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="number"], textarea {
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
&:focus {
|
||||
border-color: $colorOrange;
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 192px;
|
||||
}
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
background: #eee;
|
||||
border-radius: $borderRadius $borderRadius 0 0;
|
||||
|
||||
img {
|
||||
flex: 0 0 96px;
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
line-height: 2.2rem;
|
||||
margin-bottom: .3rem;
|
||||
}
|
||||
|
||||
.meta {
|
||||
flex: 1;
|
||||
padding-left: 8px;
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
line-height: 2.2rem;
|
||||
margin-bottom: .3rem;
|
||||
}
|
||||
|
||||
.mixed {
|
||||
opacity: .5;
|
||||
}
|
||||
.mixed {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue