mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Remove jQuery
This commit is contained in:
parent
7ea6700929
commit
9dc0ddebb5
33 changed files with 718 additions and 704 deletions
|
@ -34,7 +34,7 @@ elixir(function (mix) {
|
|||
.styles([
|
||||
'resources/assets/css/**/*.css',
|
||||
'node_modules/font-awesome/css/font-awesome.min.css',
|
||||
'node_modules/rangeslider.js/dist/rangeslider.css',
|
||||
'node_modules/nouislider/distribute/nouislider.min.css',
|
||||
], 'public/css/vendors.css', './');
|
||||
|
||||
mix.version(['css/vendors.css', 'css/app.css', 'js/vendors.js', 'js/main.js']);
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"alertify.js": "^1.0.12",
|
||||
"axios": "^0.15.3",
|
||||
"blueimp-md5": "^2.3.0",
|
||||
"font-awesome": "^4.5.0",
|
||||
"ismobilejs": "^0.4.0",
|
||||
"jquery": "^3.1.1",
|
||||
"local-storage": "^1.4.2",
|
||||
"lodash": "^4.6.1",
|
||||
"nouislider": "^9.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"plyr": "1.5.x",
|
||||
"rangeslider.js": "^2.2.1",
|
||||
"raven-js": "^3.9.1",
|
||||
"select": "^1.0.6",
|
||||
"slugify": "^1.0.2",
|
||||
|
@ -60,6 +60,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"postinstall": "cross-env NODE_ENV=production && gulp --production",
|
||||
"build": "cross-env NODE_ENV=production && gulp --production",
|
||||
"test": "eslint resources/assets/js --ext=js,vue && mocha --compilers js:babel-register --require resources/assets/js/tests/helper.js resources/assets/js/tests/**/*Test.js",
|
||||
"e2e": "gulp e2e",
|
||||
"dev": "cross-env NODE_ENV=development && gulp watch"
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import $ from 'jquery'
|
||||
|
||||
import siteHeader from './components/site-header/index.vue'
|
||||
import siteFooter from './components/site-footer/index.vue'
|
||||
|
@ -33,7 +32,7 @@ import overlay from './components/shared/overlay.vue'
|
|||
import loginForm from './components/auth/login-form.vue'
|
||||
import editSongsForm from './components/modals/edit-songs-form.vue'
|
||||
|
||||
import { event, showOverlay, hideOverlay, forceReloadWindow } from './utils'
|
||||
import { event, showOverlay, hideOverlay, forceReloadWindow, $ } from './utils'
|
||||
import { sharedStore, userStore, preferenceStore as preferences } from './stores'
|
||||
import { playback, ls } from './services'
|
||||
import { focusDirective, clickawayDirective } from './directives'
|
||||
|
@ -57,14 +56,18 @@ export default {
|
|||
}
|
||||
|
||||
// Create the element to be the ghost drag image.
|
||||
$('<div id="dragGhost"></div>').appendTo('body')
|
||||
const dragGhost = document.createElement('div')
|
||||
dragGhost.id = 'dragGhost'
|
||||
document.body.appendChild(dragGhost)
|
||||
|
||||
// And the textarea to copy stuff
|
||||
$('<textarea id="copyArea"></textarea>').appendTo('body')
|
||||
const copyArea = document.createElement('textarea')
|
||||
copyArea.id = 'copyArea'
|
||||
document.body.appendChild(copyArea)
|
||||
|
||||
// Add an ugly mac/non-mac class for OS-targeting styles.
|
||||
// I'm crying inside.
|
||||
$('html').addClass(navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : 'non-mac')
|
||||
$.addClass(document.documentElement, navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : 'non-mac')
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -104,12 +107,14 @@ export default {
|
|||
* @param {Object} e The keydown event
|
||||
*/
|
||||
togglePlayback (e) {
|
||||
if ($(e.target).is('input,textarea,button,select')) {
|
||||
if ($.is(e.target, 'input,textarea,button,select')) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ah... Good ol' jQuery. Whatever play/pause control is there, we blindly click it.
|
||||
$('#mainFooter .play:visible, #mainFooter .pause:visible').click()
|
||||
// Whatever play/pause control is there, we blindly click it.
|
||||
const play = document.querySelector('#mainFooter .play')
|
||||
play ? play.click() : document.querySelector('#mainFooter .pause').click()
|
||||
|
||||
e.preventDefault()
|
||||
},
|
||||
|
||||
|
@ -119,7 +124,7 @@ export default {
|
|||
* @param {Object} e The keydown event
|
||||
*/
|
||||
playPrev (e) {
|
||||
if ($(e.target).is('input,textarea')) {
|
||||
if ($.is(e.target, 'input,textarea')) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -133,7 +138,7 @@ export default {
|
|||
* @param {Object} e The keydown event
|
||||
*/
|
||||
playNext (e) {
|
||||
if ($(e.target).is('input,textarea')) {
|
||||
if ($.is(e.target, 'input,textarea')) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -147,11 +152,13 @@ export default {
|
|||
* @param {Object} e The keydown event
|
||||
*/
|
||||
search (e) {
|
||||
if ($(e.target).is('input,textarea') || e.metaKey || e.ctrlKey) {
|
||||
if ($.is(e.target, 'input,textarea') || e.metaKey || e.ctrlKey) {
|
||||
return true
|
||||
}
|
||||
|
||||
$('#searchForm input[type="search"]').focus().select()
|
||||
const selectBox = document.querySelector('#searchForm input[type="search"]')
|
||||
selectBox.focus()
|
||||
selectBox.select()
|
||||
e.preventDefault()
|
||||
},
|
||||
|
||||
|
|
|
@ -36,9 +36,8 @@
|
|||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../../../utils'
|
||||
import { event, $ } from '../../../utils'
|
||||
import { sharedStore, songStore, preferenceStore as preferences } from '../../../stores'
|
||||
import { songInfo } from '../../../services'
|
||||
|
||||
|
@ -68,9 +67,9 @@ export default {
|
|||
*/
|
||||
'state.showExtraPanel' (newVal) {
|
||||
if (newVal && !isMobile.any) {
|
||||
$('html').addClass('with-extra-panel')
|
||||
$.addClass(document.documentElement, 'with-extra-panel')
|
||||
} else {
|
||||
$('html').removeClass('with-extra-panel')
|
||||
$.removeClass(document.documentElement, 'with-extra-panel')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -78,7 +77,7 @@ export default {
|
|||
mounted () {
|
||||
// On ready, add 'with-extra-panel' class.
|
||||
if (!isMobile.any) {
|
||||
$('html').addClass('with-extra-panel')
|
||||
$.addClass(document.documentElement, 'with-extra-panel')
|
||||
}
|
||||
|
||||
if (isMobile.phone) {
|
||||
|
|
|
@ -47,7 +47,7 @@ export default {
|
|||
*/
|
||||
loadMore () {
|
||||
this.loading = true
|
||||
youtubeService.searchVideosRelatedToSong(this.song, () => {
|
||||
youtubeService.searchVideosRelatedToSong(this.song).then(() => {
|
||||
this.videos = this.song.youtube.items
|
||||
this.loading = false
|
||||
})
|
||||
|
|
|
@ -104,10 +104,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
|
||||
import { userStore, preferenceStore, sharedStore } from '../../../stores'
|
||||
import { forceReloadWindow } from '../../../utils'
|
||||
import { forceReloadWindow, $ } from '../../../utils'
|
||||
import { http, ls } from '../../../services'
|
||||
|
||||
export default {
|
||||
|
@ -129,11 +127,13 @@ export default {
|
|||
update () {
|
||||
// A little validation put in a small place.
|
||||
if ((this.pwd || this.confirmPwd) && this.pwd !== this.confirmPwd) {
|
||||
$('#inputProfilePassword, #inputProfileConfirmPassword').addClass('error')
|
||||
document.querySelectorAll('#inputProfilePassword, #inputProfileConfirmPassword')
|
||||
.forEach(el => $.addClass(el, 'error'))
|
||||
return
|
||||
}
|
||||
|
||||
$('#inputProfilePassword, #inputProfileConfirmPassword').removeClass('error')
|
||||
document.querySelectorAll('#inputProfilePassword, #inputProfileConfirmPassword')
|
||||
.forEach(el => $.removeClass(el, 'error'))
|
||||
|
||||
userStore.updateProfile(this.pwd).then(() => {
|
||||
this.pwd = ''
|
||||
|
|
|
@ -56,9 +56,8 @@
|
|||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../../../utils'
|
||||
import { event, $ } from '../../../utils'
|
||||
import { sharedStore, userStore, songStore, queueStore } from '../../../stores'
|
||||
import playlists from './playlists.vue'
|
||||
|
||||
|
@ -87,7 +86,7 @@ export default {
|
|||
* @param {Object} e The dragleave event.
|
||||
*/
|
||||
removeDroppableState (e) {
|
||||
$(e.target).removeClass('droppable')
|
||||
$.removeClass(e.target, 'droppable')
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -96,7 +95,7 @@ export default {
|
|||
* @param {Object} e The dragover event.
|
||||
*/
|
||||
allowDrop (e) {
|
||||
$(e.target).addClass('droppable')
|
||||
$.addClass(e.target, 'droppable')
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
return false
|
||||
|
|
|
@ -20,9 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../../../utils'
|
||||
import { event, $ } from '../../../utils'
|
||||
import { songStore, playlistStore, favoriteStore } from '../../../stores'
|
||||
|
||||
export default {
|
||||
|
@ -97,7 +95,7 @@ export default {
|
|||
* @param {Object} e The dragleave event.
|
||||
*/
|
||||
removeDroppableState (e) {
|
||||
$(e.target).removeClass('droppable')
|
||||
$.removeClass(e.target, 'droppable')
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -107,7 +105,7 @@ export default {
|
|||
* @param {Object} e The dragover event.
|
||||
*/
|
||||
allowDrop (e) {
|
||||
$(e.target).addClass('droppable')
|
||||
$.addClass(e.target, 'droppable')
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
return false
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
|
||||
<script>
|
||||
import { map } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { pluralize } from '../../utils'
|
||||
import { queueStore, artistStore, sharedStore } from '../../stores'
|
||||
|
@ -97,8 +96,9 @@ export default {
|
|||
e.dataTransfer.effectAllowed = 'move'
|
||||
|
||||
// Set a fancy drop image using our ghost element.
|
||||
const $ghost = $('#dragGhost').text(`All ${pluralize(songIds.length, 'song')} in ${this.album.name}`)
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0)
|
||||
const ghost = document.getElementById('dragGhost')
|
||||
ghost.innerText = `All ${pluralize(songIds.length, 'song')} in ${this.album.name}`
|
||||
e.dataTransfer.setDragImage(ghost, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
|
||||
<script>
|
||||
import { map } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { pluralize } from '../../utils'
|
||||
import { artistStore, queueStore, sharedStore } from '../../stores'
|
||||
|
@ -86,8 +85,9 @@ export default {
|
|||
e.dataTransfer.effectAllowed = 'move'
|
||||
|
||||
// Set a fancy drop image using our ghost element.
|
||||
const $ghost = $('#dragGhost').text(`All ${pluralize(songIds.length, 'song')} by ${this.artist.name}`)
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0)
|
||||
const ghost = document.getElementById('dragGhost')
|
||||
ghost.innerText = `All ${pluralize(songIds.length, 'song')} by ${this.artist.name}`
|
||||
e.dataTransfer.setDragImage(ghost, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,9 +52,8 @@
|
|||
<script>
|
||||
import { find, invokeMap, filter, map } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { filterBy, orderBy, limitBy, event, pluralize } from '../../utils'
|
||||
import { filterBy, orderBy, limitBy, event, pluralize, $ } from '../../utils'
|
||||
import { playlistStore, queueStore, songStore, favoriteStore } from '../../stores'
|
||||
import { playback } from '../../services'
|
||||
import router from '../../router'
|
||||
|
@ -310,10 +309,10 @@ export default {
|
|||
selectRowsBetweenIndexes (indexes) {
|
||||
indexes.sort((a, b) => a - b)
|
||||
|
||||
const rows = $(this.$refs.wrapper).find('tbody tr')
|
||||
const rows = this.$refs.wrapper.querySelectorAll('tbody tr')
|
||||
|
||||
for (let i = indexes[0]; i <= indexes[1]; ++i) {
|
||||
this.getComponentBySongId($(rows[i - 1]).data('song-id')).select()
|
||||
this.getComponentBySongId(rows[i - 1].getAttribute('data-song-id')).select()
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -347,8 +346,9 @@ export default {
|
|||
e.dataTransfer.effectAllowed = 'move'
|
||||
|
||||
// Set a fancy drop image using our ghost element.
|
||||
const $ghost = $('#dragGhost').text(`${pluralize(songIds.length, 'song')}`)
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0)
|
||||
const ghost = document.getElementById('dragGhost')
|
||||
ghost.innerText = `${pluralize(songIds.length, 'song')}`
|
||||
e.dataTransfer.setDragImage(ghost, 0, 0)
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -363,7 +363,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
$(e.target).parents('tr').addClass('droppable')
|
||||
$.addClass(e.target.parentNode, 'droppable')
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
return false
|
||||
|
@ -401,7 +401,7 @@ export default {
|
|||
* @param {Object} e
|
||||
*/
|
||||
removeDroppableState (e) {
|
||||
return $(e.target).parents('tr').removeClass('droppable')
|
||||
$.removeClass(e.target.parentNode, 'droppable')
|
||||
},
|
||||
|
||||
openContextMenu (songId, e) {
|
||||
|
@ -433,16 +433,15 @@ export default {
|
|||
|
||||
// Scroll the item into view if it's lost into oblivion.
|
||||
if (this.type === 'queue') {
|
||||
const $wrapper = $(this.$refs.wrapper)
|
||||
const $row = $wrapper.find(`.song-item[data-song-id="${song.id}"]`)
|
||||
const row = this.$refs.wrapper.querySelector(`.song-item[data-song-id="${song.id}"]`)
|
||||
|
||||
if (!$row.length) {
|
||||
if (!row) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($wrapper[0].getBoundingClientRect().top + $wrapper[0].getBoundingClientRect().height <
|
||||
$row[0].getBoundingClientRect().top) {
|
||||
$wrapper.scrollTop($wrapper.scrollTop() + $row.position().top)
|
||||
const wrapperRec = this.$refs.wrapper.getBoundingClientRect()
|
||||
if (wrapperRec.top + wrapperRec.height < row.getBoundingClientRect().top) {
|
||||
this.$refs.wrapper.scrollTop = this.$refs.wrapper.scrollTop + row.offsetTop
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
|
||||
import songMenuMethods from '../../mixins/song-menu-methods'
|
||||
|
||||
import { event, isClipboardSupported, copyText } from '../../utils'
|
||||
|
@ -79,15 +77,11 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
// Make sure the menu isn't off-screen
|
||||
if (this.$el.getBoundingClientRect().bottom > window.innerHeight) {
|
||||
$(this.$el).css({
|
||||
top: 'auto',
|
||||
bottom: 0
|
||||
})
|
||||
this.$el.style.top = 'auto'
|
||||
this.$el.style.bottom = 0
|
||||
} else {
|
||||
$(this.$el).css({
|
||||
top: this.top,
|
||||
bottom: 'auto'
|
||||
})
|
||||
this.$el.style.top = this.top
|
||||
this.$el.style.bottom = 'auto'
|
||||
}
|
||||
|
||||
this.$refs.menu.focus()
|
||||
|
@ -160,25 +154,26 @@ export default {
|
|||
* they don't appear off-screen.
|
||||
*/
|
||||
mounted () {
|
||||
$(this.$el).find('.has-sub').hover(e => {
|
||||
const $submenu = $(e.target).find('.submenu:first')
|
||||
if (!$submenu.length) {
|
||||
this.$el.querySelectorAll('.has-sub').forEach(item => {
|
||||
const submenu = item.querySelector('.submenu')
|
||||
if (!submenu) {
|
||||
return
|
||||
}
|
||||
|
||||
$submenu.show()
|
||||
item.addEventListener('mouseenter', e => {
|
||||
submenu.style.display = 'block'
|
||||
|
||||
// Make sure the submenu isn't off-screen
|
||||
if ($submenu[0].getBoundingClientRect().bottom > window.innerHeight) {
|
||||
$submenu.css({
|
||||
top: 'auto',
|
||||
bottom: 0
|
||||
})
|
||||
}
|
||||
}, e => {
|
||||
$(e.target).find('.submenu:first').hide().css({
|
||||
top: 0,
|
||||
bottom: 'auto'
|
||||
// Make sure the submenu isn't off-screen
|
||||
if (submenu.getBoundingClientRect().bottom > window.innerHeight) {
|
||||
submenu.style.top = 'auto'
|
||||
submenu.style.bottom = 0
|
||||
}
|
||||
})
|
||||
|
||||
item.addEventListener('mouseleave', e => {
|
||||
submenu.style.top = 0
|
||||
submenu.style.bottom = 'auto'
|
||||
submenu.style.display = 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -21,8 +21,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
import { filterBy } from '../../utils'
|
||||
import { filterBy, $ } from '../../utils'
|
||||
|
||||
export default {
|
||||
props: ['options', 'value', 'items'],
|
||||
|
@ -46,10 +45,16 @@ export default {
|
|||
* Navigate down the result list.
|
||||
*/
|
||||
down (e) {
|
||||
const selected = $(this.$el).find('.result li.selected')
|
||||
const selected = this.$el.querySelector('.result li.selected')
|
||||
|
||||
if (!selected.length || !selected.removeClass('selected').next('li').addClass('selected').length) {
|
||||
$(this.$el).find('.result li:first').addClass('selected')
|
||||
if (!selected || !selected.nextElementSibling) {
|
||||
// No item selected, or we're at the end of the list.
|
||||
// Select the first item now.
|
||||
$.addClass(this.$el.querySelector('.result li:first-child'), 'selected')
|
||||
selected && $.removeClass(selected, 'selected')
|
||||
} else {
|
||||
$.removeClass(selected, 'selected')
|
||||
$.addClass(selected.nextElementSibling, 'selected')
|
||||
}
|
||||
|
||||
this.scrollSelectedIntoView(false)
|
||||
|
@ -60,10 +65,14 @@ export default {
|
|||
* Navigate up the result list.
|
||||
*/
|
||||
up (e) {
|
||||
const selected = $(this.$el).find('.result li.selected')
|
||||
const selected = this.$el.querySelector('.result li.selected')
|
||||
|
||||
if (!selected.length || !selected.removeClass('selected').prev('li').addClass('selected').length) {
|
||||
$(this.$el).find('.result li:last').addClass('selected')
|
||||
if (!selected || !selected.previousElementSibling) {
|
||||
$.addClass(this.$el.querySelector('.result li:last-child'), 'selected')
|
||||
selected && $.removeClass(selected, 'selected')
|
||||
} else {
|
||||
$.removeClass(selected, 'selected')
|
||||
$.addClass(selected.previousElementSibling, 'selected')
|
||||
}
|
||||
|
||||
this.scrollSelectedIntoView(true)
|
||||
|
@ -108,16 +117,16 @@ export default {
|
|||
},
|
||||
|
||||
resultClick (e) {
|
||||
$(this.$el).find('.result li.selected').removeClass('selected')
|
||||
$(e.target).addClass('selected')
|
||||
const selected = this.$el.querySelector('.result li.selected')
|
||||
$.removeClass(selected, 'selected')
|
||||
$.addClass(e.target, 'selected')
|
||||
|
||||
this.apply()
|
||||
this.showingResult = false
|
||||
},
|
||||
|
||||
apply () {
|
||||
this.mutatedValue = $(this.$el).find('.result li.selected').text().trim() || this.mutatedValue
|
||||
// In Vue 2.0, we can use v-model on custom components like this.
|
||||
this.mutatedValue = this.$el.querySelector('.result li.selected').innerText.trim() || this.mutatedValue
|
||||
this.$emit('input', this.mutatedValue)
|
||||
},
|
||||
|
||||
|
@ -127,7 +136,7 @@ export default {
|
|||
* @param {boolean} alignTop Whether the item should be aligned to top of its container.
|
||||
*/
|
||||
scrollSelectedIntoView (alignTop) {
|
||||
const elem = $(this.$el).find('.result li.selected')[0]
|
||||
const elem = this.$el.querySelector('.result li.selected')
|
||||
if (!elem) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,13 +9,7 @@
|
|||
</div>
|
||||
<div class="bands">
|
||||
<span class="band preamp">
|
||||
<input
|
||||
type="range"
|
||||
min="-20"
|
||||
max="20"
|
||||
step="0.01"
|
||||
data-orientation="vertical"
|
||||
v-model="preampGainValue">
|
||||
<span class="slider"></span>
|
||||
<label>Preamp</label>
|
||||
</span>
|
||||
|
||||
|
@ -26,13 +20,7 @@
|
|||
</span>
|
||||
|
||||
<span class="band amp" v-for="band in bands">
|
||||
<input
|
||||
type="range"
|
||||
min="-20"
|
||||
max="20"
|
||||
step="0.01"
|
||||
data-orientation="vertical"
|
||||
:value="band.filter.gain.value">
|
||||
<span class="slider"></span>
|
||||
<label>{{ band.label }}</label>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -41,17 +29,14 @@
|
|||
|
||||
<script>
|
||||
import { map, cloneDeep } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import rangeslider from 'rangeslider.js'
|
||||
import nouislider from 'nouislider'
|
||||
|
||||
import { isAudioContextSupported, event } from '../../utils'
|
||||
import { isAudioContextSupported, event, $ } from '../../utils'
|
||||
import { equalizerStore, preferenceStore as preferences } from '../../stores'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
idx: 0,
|
||||
bands: [],
|
||||
preampGainValue: 0,
|
||||
selectedPresetIndex: -1
|
||||
|
@ -90,8 +75,7 @@ export default {
|
|||
methods: {
|
||||
/**
|
||||
* Init the equalizer.
|
||||
*
|
||||
* @param {Element} player The audio player's DOM.
|
||||
* @param {Element} player The audio player's node.
|
||||
*/
|
||||
init (player) {
|
||||
const settings = equalizerStore.get()
|
||||
|
@ -113,8 +97,8 @@ export default {
|
|||
let prevFilter = null
|
||||
|
||||
// Create 10 bands with the frequencies similar to those of Winamp and connect them together.
|
||||
const freqs = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]
|
||||
freqs.forEach((f, i) => {
|
||||
const frequencies = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]
|
||||
frequencies.forEach((frequency, i) => {
|
||||
const filter = context.createBiquadFilter()
|
||||
|
||||
if (i === 0) {
|
||||
|
@ -127,60 +111,60 @@ export default {
|
|||
|
||||
filter.gain.value = settings.gains[i] ? settings.gains[i] : 0
|
||||
filter.Q.value = 1
|
||||
filter.frequency.value = f
|
||||
filter.frequency.value = frequency
|
||||
|
||||
prevFilter ? prevFilter.connect(filter) : this.preampGainNode.connect(filter)
|
||||
prevFilter = filter
|
||||
|
||||
this.bands.push({
|
||||
filter,
|
||||
label: (f + '').replace('000', 'K')
|
||||
label: (frequency + '').replace('000', 'K')
|
||||
})
|
||||
})
|
||||
|
||||
prevFilter.connect(context.destination)
|
||||
|
||||
this.$nextTick(this.createRangeSliders)
|
||||
this.$nextTick(this.createSliders)
|
||||
|
||||
// Now we set this value to trigger the audio processing.
|
||||
this.selectedPresetIndex = preferences.selectedPreset
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the UI slider for both the preamp and the normal bands using rangeslider.js.
|
||||
* Create the UI sliders for both the preamp and the normal bands.
|
||||
*/
|
||||
createRangeSliders () {
|
||||
$('#equalizer input[type="range"]').each((i, el) => {
|
||||
$(el).rangeslider({
|
||||
/**
|
||||
* Force the polyfill and its styles on all browsers.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
polyfill: false,
|
||||
createSliders () {
|
||||
const config = equalizerStore.get()
|
||||
document.querySelectorAll('#equalizer .slider').forEach((el, i) => {
|
||||
nouislider.create(el, {
|
||||
connect: [false, true],
|
||||
// the first element is the preamp. The rest are gains.
|
||||
start: i === 0 ? config.preamp : config.gains[i - 1],
|
||||
range: { min: -20, max: 20 },
|
||||
orientation: 'vertical',
|
||||
direction: 'rtl'
|
||||
})
|
||||
|
||||
/**
|
||||
* Change the gain/preamp value when the user drags the sliders.
|
||||
*
|
||||
* @param {Float} position
|
||||
* @param {Float} value
|
||||
*/
|
||||
onSlide: (position, value) => {
|
||||
if ($(el).parents('.band').is('.preamp')) {
|
||||
this.changePreampGain(value)
|
||||
} else {
|
||||
this.changeFilterGain(this.bands[i - 1].filter, value)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the settings and set the preset index to -1 (which is None) on slideEnd.
|
||||
*/
|
||||
onSlideEnd: () => {
|
||||
this.selectedPresetIndex = -1
|
||||
this.save()
|
||||
/**
|
||||
* Update the audio effect upon sliding / tapping.
|
||||
*/
|
||||
el.noUiSlider.on('slide', (values, handle) => {
|
||||
const value = values[handle]
|
||||
if (el.parentNode.matches('.preamp')) {
|
||||
this.changePreampGain(value)
|
||||
} else {
|
||||
this.changeFilterGain(this.bands[i - 1].filter, value)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Save the equalizer values after the change is done.
|
||||
*/
|
||||
el.noUiSlider.on('change', () => {
|
||||
// User has customized the equalizer. No preset should be selected.
|
||||
this.selectedPresetIndex = -1
|
||||
this.save()
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -208,21 +192,19 @@ export default {
|
|||
* Load a preset when the user select it from the dropdown.
|
||||
*/
|
||||
loadPreset (preset) {
|
||||
$('#equalizer input[type=range]').each((i, input) => {
|
||||
document.querySelectorAll('#equalizer .slider').forEach((el, i) => {
|
||||
// We treat our preamp slider differently.
|
||||
if ($(input).parents('.band').is('.preamp')) {
|
||||
if ($.is(el.parentNode, '.preamp')) {
|
||||
this.changePreampGain(preset.preamp)
|
||||
// Update the slider values into GUI.
|
||||
el.noUiSlider.set(preset.preamp)
|
||||
} else {
|
||||
this.changeFilterGain(this.bands[i - 1].filter, preset.gains[i - 1])
|
||||
input.value = preset.gains[i - 1]
|
||||
// Update the slider values into GUI.
|
||||
el.noUiSlider.set(preset.gains[i - 1])
|
||||
}
|
||||
})
|
||||
|
||||
this.$nextTick(() => {
|
||||
// Update the slider values into GUI.
|
||||
$('#equalizer input[type="range"]').rangeslider('update', true)
|
||||
})
|
||||
|
||||
this.save()
|
||||
},
|
||||
|
||||
|
@ -236,9 +218,7 @@ export default {
|
|||
|
||||
mounted () {
|
||||
event.on('equalizer:init', player => {
|
||||
if (isAudioContextSupported()) {
|
||||
this.init(player)
|
||||
}
|
||||
isAudioContextSupported() && this.init(player)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -322,6 +302,10 @@ export default {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.slider {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.indicators {
|
||||
height: 100px;
|
||||
width: 20px;
|
||||
|
@ -347,49 +331,56 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The range slider styles
|
||||
*/
|
||||
.rangeslider {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
&--vertical {
|
||||
min-height: 100px;
|
||||
width: 16px;
|
||||
|
||||
&::before {
|
||||
.noUi {
|
||||
&-connect {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
width: 2px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rangeslider__fill {
|
||||
width: 2px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
background: #333;
|
||||
top: 0;
|
||||
left: 7px;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rangeslider__handle {
|
||||
left: 0;
|
||||
&-target {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
width: 16px;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: 0;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
top: 0;
|
||||
left: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
&-handle {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
|
||||
&::before, &::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-vertical {
|
||||
.noUi-handle {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../utils'
|
||||
import { event, $ } from '../utils'
|
||||
import toTopButton from '../components/shared/to-top-button.vue'
|
||||
|
||||
/**
|
||||
|
@ -41,7 +39,7 @@ export default {
|
|||
* Scroll to top of the wrapper.
|
||||
*/
|
||||
scrollToTop () {
|
||||
$(this.$refs.wrapper).animate({ scrollTop: 0 }, 500)
|
||||
$.scrollTo(this.$refs.wrapper, 0, 500)
|
||||
this.showBackToTop = false
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import $ from 'jquery'
|
||||
|
||||
import { $ } from '../utils'
|
||||
import { queueStore, playlistStore, favoriteStore } from '../stores'
|
||||
|
||||
/**
|
||||
|
@ -24,7 +23,9 @@ export default {
|
|||
* Close all submenus.
|
||||
*/
|
||||
close () {
|
||||
$(this.$el).find('.submenu').hide()
|
||||
this.$el.querySelectorAll('.submenu').forEach(el => {
|
||||
el.style.display = 'none'
|
||||
})
|
||||
this.shown = false
|
||||
},
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import $ from 'jquery'
|
||||
import { map } from 'lodash'
|
||||
import { map, reduce } from 'lodash'
|
||||
|
||||
import { playlistStore, favoriteStore } from '../stores'
|
||||
import { ls } from '.'
|
||||
import { $ } from '../utils'
|
||||
|
||||
export const download = {
|
||||
/**
|
||||
|
@ -12,10 +12,8 @@ export const download = {
|
|||
*/
|
||||
fromSongs (songs) {
|
||||
songs = [].concat(songs)
|
||||
const ids = map(songs, 'id')
|
||||
const params = $.param({ songs: ids })
|
||||
|
||||
return this.trigger(`songs?${params}`)
|
||||
const query = reduce(songs, (q, song) => `songs[]=${song.id}&${segment}`, '')
|
||||
return this.trigger(`songs?${query}`)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -73,8 +71,9 @@ export const download = {
|
|||
*/
|
||||
trigger (uri) {
|
||||
const sep = uri.indexOf('?') === -1 ? '?' : '&'
|
||||
const frameId = `downloader${Date.now()}`
|
||||
$(`<iframe id="${frameId}" style="display:none"></iframe`).appendTo('body')
|
||||
document.getElementById(frameId).src = `/api/download/${uri}${sep}jwt-token=${ls.get('jwt-token')}`
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.style.display = 'none'
|
||||
iframe.setAttribute('src', `/api/download/${uri}${sep}jwt-token=${ls.get('jwt-token')}`)
|
||||
document.body.appendChild(iframe)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import $ from 'jquery'
|
||||
import axios from 'axios'
|
||||
import NProgress from 'nprogress'
|
||||
|
||||
import { event } from '../utils'
|
||||
|
@ -9,15 +9,11 @@ import { ls } from '../services'
|
|||
*/
|
||||
export const http = {
|
||||
request (method, url, data, successCb = null, errorCb = null) {
|
||||
return $.ajax({
|
||||
axios.request({
|
||||
url,
|
||||
data,
|
||||
dataType: 'json',
|
||||
url: `/api/${url}`,
|
||||
method: method.toUpperCase(),
|
||||
headers: {
|
||||
Authorization: `Bearer ${ls.get('jwt-token')}`
|
||||
}
|
||||
}).done(successCb).fail(errorCb)
|
||||
method: method.toLowerCase()
|
||||
}).then(successCb).catch(errorCb)
|
||||
},
|
||||
|
||||
get (url, successCb = null, errorCb = null) {
|
||||
|
@ -40,25 +36,37 @@ export const http = {
|
|||
* Init the service.
|
||||
*/
|
||||
init () {
|
||||
$(document).ajaxComplete((e, r, settings) => {
|
||||
axios.defaults.baseURL = '/api'
|
||||
|
||||
// Intercept the request to make sure the token is injected into the header.
|
||||
axios.interceptors.request.use(config => {
|
||||
config.headers.Authorization = `Bearer ${ls.get('jwt-token')}`
|
||||
return config
|
||||
})
|
||||
|
||||
// Intercept the response and…
|
||||
axios.interceptors.response.use(response => {
|
||||
NProgress.done()
|
||||
|
||||
if (r.status === 400 || r.status === 401) {
|
||||
if (!(settings.method === 'POST' && /\/api\/me\/?$/.test(settings.url))) {
|
||||
// This is not a failed login. Log out then.
|
||||
event.emit('logout')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const token = r.getResponseHeader('Authorization')
|
||||
// …get the token from the header or response data if exists, and save it.
|
||||
const token = response.headers['Authorization'] || response.data['token']
|
||||
if (token) {
|
||||
ls.set('jwt-token', token)
|
||||
}
|
||||
|
||||
if (r.responseJSON && r.responseJSON.token && r.responseJSON.token.length > 10) {
|
||||
ls.set('jwt-token', r.responseJSON.token)
|
||||
return response
|
||||
}, error => {
|
||||
NProgress.done()
|
||||
// Also, if we receive a Bad Request / Unauthorized error
|
||||
if (error.response.status === 400 || error.response.status === 401) {
|
||||
// and we're not trying to login
|
||||
if (!(error.config.method === 'post' && /\/api\/me\/?$/.test(error.config.url))) {
|
||||
// the token must have expired. Log out.
|
||||
event.emit('logout')
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,10 @@ export const albumInfo = {
|
|||
return
|
||||
}
|
||||
|
||||
http.get(`album/${album.id}/info`, data => {
|
||||
data && this.merge(album, data)
|
||||
http.get(`album/${album.id}/info`, response => {
|
||||
response.data && this.merge(album, response.data)
|
||||
resolve(album)
|
||||
}, r => reject(r))
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@ export const artistInfo = {
|
|||
return
|
||||
}
|
||||
|
||||
http.get(`artist/${artist.id}/info`, data => {
|
||||
data && this.merge(artist, data)
|
||||
http.get(`artist/${artist.id}/info`, response => {
|
||||
response.data && this.merge(artist, response.data)
|
||||
resolve(artist)
|
||||
}, r => reject(r))
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -14,14 +14,14 @@ export const songInfo = {
|
|||
return
|
||||
}
|
||||
|
||||
http.get(`${song.id}/info`, data => {
|
||||
song.lyrics = data.lyrics
|
||||
data.artist_info && artistInfo.merge(song.artist, data.artist_info)
|
||||
data.album_info && albumInfo.merge(song.album, data.album_info)
|
||||
song.youtube = data.youtube
|
||||
http.get(`${song.id}/info`, response => {
|
||||
song.lyrics = response.data.lyrics
|
||||
response.data.artist_info && artistInfo.merge(song.artist, response.data.artist_info)
|
||||
response.data.album_info && albumInfo.merge(song.album, response.data.album_info)
|
||||
song.youtube = response.data.youtube
|
||||
song.infoRetrieved = true
|
||||
resolve(song)
|
||||
}, r => reject(r))
|
||||
}, error => reject(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { shuffle, orderBy } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import plyr from 'plyr'
|
||||
import Vue from 'vue'
|
||||
|
||||
|
@ -10,7 +9,7 @@ import router from '../router'
|
|||
|
||||
export const playback = {
|
||||
player: null,
|
||||
$volumeInput: null,
|
||||
volumeInput: null,
|
||||
repeatModes: ['NO_REPEAT', 'REPEAT_ALL', 'REPEAT_ONE'],
|
||||
initialized: false,
|
||||
|
||||
|
@ -27,9 +26,8 @@ export const playback = {
|
|||
controls: []
|
||||
})[0]
|
||||
|
||||
this.audio = $('audio')
|
||||
|
||||
this.$volumeInput = $('#volumeRange')
|
||||
this.audio = document.querySelector('audio')
|
||||
this.volumeInput = document.getElementById('volumeRange')
|
||||
|
||||
/**
|
||||
* Listen to 'error' event on the audio player and play the next song if any.
|
||||
|
@ -69,8 +67,8 @@ export const playback = {
|
|||
return
|
||||
}
|
||||
|
||||
const $preloader = $('<audio>')
|
||||
$preloader.attr('src', songStore.getSourceUrl(nextSong))
|
||||
const preloader = document.createElement('audio')
|
||||
preloader.setAttribute('src', songStore.getSourceUrl(nextSong))
|
||||
|
||||
nextSong.preloaded = true
|
||||
})
|
||||
|
@ -80,8 +78,8 @@ export const playback = {
|
|||
* When user drags the volume control, this event will be triggered, and we
|
||||
* update the volume on the plyr object.
|
||||
*/
|
||||
this.$volumeInput.on('input', e => {
|
||||
this.setVolume($(e.target).val())
|
||||
this.volumeInput.addEventListener('input', e => {
|
||||
this.setVolume(e.target.value)
|
||||
})
|
||||
|
||||
// On init, set the volume to the value found in the local storage.
|
||||
|
@ -124,8 +122,8 @@ export const playback = {
|
|||
// the audio media object and cause our equalizer to malfunction.
|
||||
this.player.media.src = songStore.getSourceUrl(song)
|
||||
|
||||
$('title').text(`${song.title} ♫ ${config.appTitle}`)
|
||||
$('.plyr audio').attr('title', `${song.artist.name} - ${song.title}`)
|
||||
document.title = `${song.title} ♫ ${config.appTitle}`
|
||||
document.querySelector('.plyr audio').setAttribute('title', `${song.artist.name} - ${song.title}`)
|
||||
|
||||
// We'll just "restart" playing the song, which will handle notification, scrobbling etc.
|
||||
this.restart()
|
||||
|
@ -273,7 +271,7 @@ export const playback = {
|
|||
preferences.volume = volume
|
||||
}
|
||||
|
||||
this.$volumeInput.val(volume)
|
||||
this.volumeInput.value = volume
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -299,7 +297,7 @@ export const playback = {
|
|||
* Completely stop playback.
|
||||
*/
|
||||
stop () {
|
||||
$('title').text(config.appTitle)
|
||||
document.title = config.appTitle
|
||||
this.player.pause()
|
||||
this.player.seek(0)
|
||||
|
||||
|
|
|
@ -7,18 +7,19 @@ export const youtube = {
|
|||
* Search for YouTube videos related to a song.
|
||||
*
|
||||
* @param {Object} song
|
||||
* @param {Function} cb
|
||||
*/
|
||||
searchVideosRelatedToSong (song, cb = null) {
|
||||
searchVideosRelatedToSong (song) {
|
||||
if (!song.youtube) {
|
||||
song.youtube = {}
|
||||
}
|
||||
|
||||
const pageToken = song.youtube.nextPageToken || ''
|
||||
http.get(`youtube/search/song/${song.id}?pageToken=${pageToken}`).then(data => {
|
||||
song.youtube.nextPageToken = data.nextPageToken
|
||||
song.youtube.items.push(...data.items)
|
||||
cb && cb()
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(`youtube/search/song/${song.id}?pageToken=${pageToken}`, response => {
|
||||
song.youtube.nextPageToken = response.data.nextPageToken
|
||||
song.youtube.items.push(...response.data.items)
|
||||
resolve()
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -44,10 +44,10 @@ export const favoriteStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/like', { song: song.id }, data => {
|
||||
http.post('interaction/like', { song: song.id }, response => {
|
||||
// We don't really need to notify just for one song.
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -92,10 +92,10 @@ export const favoriteStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/batch/like', { songs: map(songs, 'id') }, data => {
|
||||
http.post('interaction/batch/like', { songs: map(songs, 'id') }, response => {
|
||||
alerts.success(`Added ${pluralize(songs.length, 'song')} into Favorites.`)
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -113,10 +113,10 @@ export const favoriteStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/batch/unlike', { songs: map(songs, 'id') }, data => {
|
||||
http.post('interaction/batch/unlike', { songs: map(songs, 'id') }, response => {
|
||||
alerts.success(`Removed ${pluralize(songs.length, 'song')} from Favorites.`)
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,13 +101,14 @@ export const playlistStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('playlist', { name, songs }, playlist => {
|
||||
http.post('playlist', { name, songs }, response => {
|
||||
const playlist = response.data
|
||||
playlist.songs = songs
|
||||
this.objectifySongs(playlist)
|
||||
this.add(playlist)
|
||||
alerts.success(`Created playlist "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
}, r => reject(r))
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -120,11 +121,11 @@ export const playlistStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete(`playlist/${playlist.id}`, {}, data => {
|
||||
http.delete(`playlist/${playlist.id}`, {}, response => {
|
||||
this.remove(playlist)
|
||||
alerts.success(`Deleted playlist "${playlist.name}".`)
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -146,13 +147,10 @@ export const playlistStore = {
|
|||
|
||||
NProgress.start()
|
||||
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') },
|
||||
data => {
|
||||
alerts.success(`Added ${pluralize(songs.length, 'song')} into "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
},
|
||||
r => reject(r)
|
||||
)
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') }, () => {
|
||||
alerts.success(`Added ${pluralize(songs.length, 'song')} into "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -168,13 +166,10 @@ export const playlistStore = {
|
|||
playlist.songs = difference(playlist.songs, songs)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') },
|
||||
data => {
|
||||
alerts.success(`Removed ${pluralize(songs.length, 'song')} from "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
},
|
||||
r => reject(r)
|
||||
)
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') }, () => {
|
||||
alerts.success(`Removed ${pluralize(songs.length, 'song')} from "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -187,7 +182,7 @@ export const playlistStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put(`playlist/${playlist.id}`, { name: playlist.name }, data => resolve(playlist), r => reject(r))
|
||||
http.put(`playlist/${playlist.id}`, { name: playlist.name }, () => resolve(playlist), error => reject(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ export const settingStore = {
|
|||
|
||||
update () {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('settings', this.all, data => {
|
||||
http.post('settings', this.all, response => {
|
||||
alerts.success('Settings saved.')
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,11 +30,10 @@ export const sharedStore = {
|
|||
this.reset()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get('data', data => {
|
||||
http.get('data', response => {
|
||||
assign(this.state, response.data)
|
||||
// Don't allow downloading on mobile devices
|
||||
data.allowDownload = data.allowDownload && !isMobile.any
|
||||
|
||||
assign(this.state, data)
|
||||
this.state.allowDownload = this.state.allowDownload && !isMobile.any
|
||||
|
||||
// Always disable YouTube integration on mobile.
|
||||
this.state.useYouTube = this.state.useYouTube && !isMobile.phone
|
||||
|
@ -55,8 +54,8 @@ export const sharedStore = {
|
|||
// Keep a copy of the media path. We'll need this to properly warn the user later.
|
||||
this.state.originalMediaPath = this.state.settings.media_path
|
||||
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(this.state)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -180,14 +180,14 @@ export const songStore = {
|
|||
return new Promise((resolve, reject) => {
|
||||
const oldCount = song.playCount
|
||||
|
||||
http.post('interaction/play', { song: song.id }, data => {
|
||||
http.post('interaction/play', { song: song.id }, response => {
|
||||
// Use the data from the server to make sure we don't miss a play from another device.
|
||||
song.playCount = data.play_count
|
||||
song.playCount = response.data.play_count
|
||||
song.album.playCount += song.playCount - oldCount
|
||||
song.artist.playCount += song.playCount - oldCount
|
||||
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -215,7 +215,9 @@ export const songStore = {
|
|||
*/
|
||||
scrobble (song) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post(`${song.id}/scrobble/${song.playStartTime}`, {}, data => resolve(data), r => reject(r))
|
||||
http.post(`${song.id}/scrobble/${song.playStartTime}`, {}, response => {
|
||||
resolve(data.response)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -230,11 +232,12 @@ export const songStore = {
|
|||
http.put('songs', {
|
||||
data,
|
||||
songs: map(songs, 'id')
|
||||
}, songs => {
|
||||
}, response => {
|
||||
const songs = response.data
|
||||
each(songs, song => this.syncUpdatedSong(song))
|
||||
alerts.success(`Updated ${pluralize(songs.length, 'song')}.`)
|
||||
resolve(songs)
|
||||
}, r => reject(r))
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -105,7 +105,9 @@ export const userStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('me', { email, password }, data => resolve(data), r => reject(r))
|
||||
http.post('me', { email, password }, response => {
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -114,7 +116,9 @@ export const userStore = {
|
|||
*/
|
||||
logout () {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete('me', {}, data => resolve(data), r => reject(r))
|
||||
http.delete('me', {}, response => {
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -136,7 +140,7 @@ export const userStore = {
|
|||
alerts.success('Profile updated.')
|
||||
resolve(this.current)
|
||||
},
|
||||
r => reject(r))
|
||||
error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -151,12 +155,13 @@ export const userStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('user', { name, email, password }, user => {
|
||||
http.post('user', { name, email, password }, response => {
|
||||
const user = response.data
|
||||
this.setAvatar(user)
|
||||
this.all.unshift(user)
|
||||
alerts.success(`New user "${name}" created.`)
|
||||
resolve(user)
|
||||
}, r => reject(r))
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -179,7 +184,7 @@ export const userStore = {
|
|||
user.password = ''
|
||||
alerts.success('User profile updated.')
|
||||
resolve(user)
|
||||
}, r => reject(r))
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -192,7 +197,7 @@ export const userStore = {
|
|||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete(`user/${user.id}`, {}, data => {
|
||||
http.delete(`user/${user.id}`, {}, () => {
|
||||
this.all = without(this.all, user)
|
||||
alerts.success(`User "${user.name}" deleted.`)
|
||||
|
||||
|
@ -218,8 +223,8 @@ export const userStore = {
|
|||
/**
|
||||
* Brian May enters the stage.
|
||||
*/
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
resolve(response.data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
49
resources/assets/js/utils/$.js
Normal file
49
resources/assets/js/utils/$.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
export const $ = {
|
||||
is (el, selector) {
|
||||
return (el.matches ||
|
||||
el.matchesSelector ||
|
||||
el.msMatchesSelector ||
|
||||
el.mozMatchesSelector ||
|
||||
el.webkitMatchesSelector ||
|
||||
el.oMatchesSelector).call(el, selector)
|
||||
},
|
||||
|
||||
addClass (el, className) {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.add(className)
|
||||
} else {
|
||||
el.className += ` ${className}`
|
||||
}
|
||||
},
|
||||
|
||||
removeClass (el, className) {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.remove(className)
|
||||
} else {
|
||||
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ')
|
||||
}
|
||||
},
|
||||
|
||||
scrollTo (el, to, duration) {
|
||||
if (duration <= 0 || !el) {
|
||||
return
|
||||
}
|
||||
const difference = to - el.scrollTop
|
||||
const perTick = difference / duration * 10
|
||||
window.setTimeout(() => {
|
||||
el.scrollTop = el.scrollTop + perTick
|
||||
if (el.scrollTop === to) {
|
||||
return
|
||||
}
|
||||
this.scrollTo(el, to, duration - 10);
|
||||
}, 10)
|
||||
}
|
||||
}
|
|
@ -3,3 +3,4 @@ export * from './filters'
|
|||
export * from './formatters'
|
||||
export * from './supports'
|
||||
export * from './common'
|
||||
export * from './$'
|
||||
|
|
|
@ -279,6 +279,7 @@ class SongTest extends TestCase
|
|||
'compilationState' => 1,
|
||||
],
|
||||
])
|
||||
|
||||
->put('/api/songs', [
|
||||
'songs' => [$song->id],
|
||||
'data' => [
|
||||
|
|
Loading…
Reference in a new issue