Rewrite player component to script setup
This commit is contained in:
parent
8c11b6d0ea
commit
cec34d49fa
|
@ -52,6 +52,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dompurify": "^2.3.3",
|
"@types/dompurify": "^2.3.3",
|
||||||
|
"@types/howler": "^2.2.7",
|
||||||
"@types/jest": "28.1.3",
|
"@types/jest": "28.1.3",
|
||||||
"@types/jquery": "3.5.14",
|
"@types/jquery": "3.5.14",
|
||||||
"@types/lodash-es": "4.17.6",
|
"@types/lodash-es": "4.17.6",
|
||||||
|
|
|
@ -12,22 +12,15 @@ import ReportModal from '~/components/moderation/ReportModal.vue'
|
||||||
import { useIntervalFn, useToggle, useWindowSize } from '@vueuse/core'
|
import { useIntervalFn, useToggle, useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
import { computed, nextTick, onMounted, ref, watchEffect } from 'vue'
|
import { computed, nextTick, onMounted, ref, watchEffect } from 'vue'
|
||||||
import {
|
import { Track } from '~/types'
|
||||||
ListenWSEvent,
|
|
||||||
PendingReviewEditsWSEvent,
|
|
||||||
PendingReviewReportsWSEvent,
|
|
||||||
PendingReviewRequestsWSEvent,
|
|
||||||
Track
|
|
||||||
} from '~/types'
|
|
||||||
import useWebSocketHandler from '~/composables/useWebSocketHandler'
|
|
||||||
import { CLIENT_RADIOS } from '~/utils/clientRadios'
|
|
||||||
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
|
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
|
||||||
|
import useQueue from '~/composables/useQueue'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
// Tracks
|
// Tracks
|
||||||
const currentTrack = computed(() => store.getters['queue/currentTrack'])
|
const { currentTrack } = useQueue()
|
||||||
const getTrackInformationText = (track: Track | undefined) => {
|
const getTrackInformationText = (track: Track | undefined) => {
|
||||||
if (!track) {
|
if (!track) {
|
||||||
return null
|
return null
|
||||||
|
@ -60,49 +53,6 @@ onMounted(async () => {
|
||||||
document.getElementById('fake-content')?.classList.add('loaded')
|
document.getElementById('fake-content')?.classList.add('loaded')
|
||||||
})
|
})
|
||||||
|
|
||||||
// WebSocket handlers
|
|
||||||
useWebSocketHandler('inbox.item_added', () => {
|
|
||||||
store.commit('ui/incrementNotifications', { type: 'inbox', count: 1 })
|
|
||||||
})
|
|
||||||
|
|
||||||
useWebSocketHandler('mutation.created', (event) => {
|
|
||||||
store.commit('ui/incrementNotifications', {
|
|
||||||
type: 'pendingReviewEdits',
|
|
||||||
value: (event as PendingReviewEditsWSEvent).pending_review_count
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
useWebSocketHandler('mutation.updated', (event) => {
|
|
||||||
store.commit('ui/incrementNotifications', {
|
|
||||||
type: 'pendingReviewEdits',
|
|
||||||
value: (event as PendingReviewEditsWSEvent).pending_review_count
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
useWebSocketHandler('report.created', (event) => {
|
|
||||||
store.commit('ui/incrementNotifications', {
|
|
||||||
type: 'pendingReviewReports',
|
|
||||||
value: (event as PendingReviewReportsWSEvent).unresolved_count
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
useWebSocketHandler('user_request.created', (event) => {
|
|
||||||
store.commit('ui/incrementNotifications', {
|
|
||||||
type: 'pendingReviewRequests',
|
|
||||||
value: (event as PendingReviewRequestsWSEvent).pending_count
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
useWebSocketHandler('Listen', (event) => {
|
|
||||||
if (store.state.radios.current && store.state.radios.running) {
|
|
||||||
const current = store.state.radios.current
|
|
||||||
|
|
||||||
if (current?.clientOnly) {
|
|
||||||
CLIENT_RADIOS[current.type].handleListen(current, event as ListenWSEvent, store)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Time ago
|
// Time ago
|
||||||
// TODO (wvffle): Migrate to useTimeAgo
|
// TODO (wvffle): Migrate to useTimeAgo
|
||||||
useIntervalFn(() => {
|
useIntervalFn(() => {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
||||||
|
import { MaybeRef } from "@vueuse/core"
|
||||||
|
import { Howl } from "howler"
|
||||||
|
import { sortBy } from "lodash-es"
|
||||||
|
import { reactive, watchEffect, ref, unref } from "vue"
|
||||||
|
|
||||||
|
export interface CachedSound {
|
||||||
|
id: string
|
||||||
|
date: Date
|
||||||
|
sound: Howl
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (maxPreloaded: MaybeRef<number>) => {
|
||||||
|
const soundCache = reactive(new Map<string, CachedSound>())
|
||||||
|
const cleaningCache = ref(false)
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
let toRemove = soundCache.size - unref(maxPreloaded)
|
||||||
|
|
||||||
|
if (toRemove > 0 && !cleaningCache.value) {
|
||||||
|
cleaningCache.value = true
|
||||||
|
|
||||||
|
const excess = sortBy(soundCache.values(), [(cached: CachedSound) => cached.date])
|
||||||
|
// TODO (wvffle): Check if works
|
||||||
|
.slice(0, toRemove) as unknown as CachedSound[]
|
||||||
|
|
||||||
|
for (const cached of excess) {
|
||||||
|
soundCache.delete(cached.id)
|
||||||
|
cached.sound.unload()
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaningCache.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return soundCache
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Track } from "~/types"
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
import updateQueryString from '~/composables/updateQueryString'
|
||||||
|
|
||||||
|
export interface TrackSource {
|
||||||
|
url: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (trackData: Track): TrackSource[] => {
|
||||||
|
const store = useStore()
|
||||||
|
const audio = document.createElement('audio')
|
||||||
|
|
||||||
|
const allowed = ['probably', 'maybe']
|
||||||
|
|
||||||
|
const sources = trackData.uploads
|
||||||
|
.filter(upload => {
|
||||||
|
const canPlay = audio.canPlayType(upload.mimetype)
|
||||||
|
return allowed.indexOf(canPlay) > -1
|
||||||
|
})
|
||||||
|
.map(upload => ({
|
||||||
|
type: upload.extension,
|
||||||
|
url: store.getters['instance/absoluteUrl'](upload.listen_url)
|
||||||
|
}))
|
||||||
|
|
||||||
|
// We always add a transcoded MP3 src at the end
|
||||||
|
// because transcoding is expensive, but we want browsers that do
|
||||||
|
// not support other codecs to be able to play it :)
|
||||||
|
sources.push({
|
||||||
|
type: 'mp3',
|
||||||
|
url: updateQueryString(
|
||||||
|
store.getters['instance/absoluteUrl'](trackData.listen_url),
|
||||||
|
'to',
|
||||||
|
'mp3'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const token = store.state.auth.scopedTokens.listen
|
||||||
|
if (store.state.auth.authenticated && token !== null) {
|
||||||
|
// we need to send the token directly in url
|
||||||
|
// so authentication can be checked by the backend
|
||||||
|
// because for audio files we cannot use the regular Authentication
|
||||||
|
// header
|
||||||
|
return sources.map(source => ({
|
||||||
|
...source,
|
||||||
|
url: updateQueryString(source.url, 'token', token)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources
|
||||||
|
}
|
|
@ -1,28 +1,62 @@
|
||||||
import { useStore } from "~/store"
|
import { computed, watchEffect } from "vue"
|
||||||
import { computed } from "vue"
|
import { Howler } from 'howler'
|
||||||
|
import useQueue from '~/composables/useQueue'
|
||||||
|
import toLinearVolumeScale from '~/composables/audio/toLinearVolumeScale'
|
||||||
|
import store from "~/store"
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const store = useStore()
|
|
||||||
const looping = computed(() => store.state.player.looping)
|
const looping = computed(() => store.state.player.looping)
|
||||||
const playing = computed(() => store.state.player.playing)
|
const playing = computed(() => store.state.player.playing)
|
||||||
const loading = computed(() => store.state.player.isLoadingAudio)
|
const loading = computed(() => store.state.player.isLoadingAudio)
|
||||||
const errored = computed(() => store.state.player.errored)
|
const errored = computed(() => store.state.player.errored)
|
||||||
const focused = computed(() => store.state.ui.queueFocused === 'player')
|
const focused = computed(() => store.state.ui.queueFocused === 'player')
|
||||||
|
|
||||||
const volume = computed(() => store.state.player.volume)
|
// Volume
|
||||||
|
const volume = computed({
|
||||||
|
get: () => store.state.player.volume,
|
||||||
|
set: (value) => store.commit('player/volume', value)
|
||||||
|
})
|
||||||
|
|
||||||
|
watchEffect(() => Howler.volume(toLinearVolumeScale(volume.value)))
|
||||||
|
|
||||||
|
// Time and duration
|
||||||
const duration = computed(() => store.state.player.duration)
|
const duration = computed(() => store.state.player.duration)
|
||||||
const currentTime = computed(() => store.state.player.currentTime)
|
const currentTime = computed(() => store.state.player.currentTime)
|
||||||
|
|
||||||
const durationFormatted = computed(() => store.getters['player/durationFormatted'])
|
const durationFormatted = computed(() => store.getters['player/durationFormatted'])
|
||||||
const currentTimeFormatted = computed(() => store.getters['player/currentTimeFormatted'])
|
const currentTimeFormatted = computed(() => store.getters['player/currentTimeFormatted'])
|
||||||
|
|
||||||
|
// Progress
|
||||||
const progress = computed(() => store.getters['player/progress'])
|
const progress = computed(() => store.getters['player/progress'])
|
||||||
const bufferProgress = computed(() => store.state.player.bufferProgress)
|
const bufferProgress = computed(() => store.state.player.bufferProgress)
|
||||||
|
|
||||||
|
// Controls
|
||||||
const pause = () => store.dispatch('player/pausePlayback')
|
const pause = () => store.dispatch('player/pausePlayback')
|
||||||
const resume = () => store.dispatch('player/resumePlayback')
|
const resume = () => store.dispatch('player/resumePlayback')
|
||||||
|
|
||||||
|
const { next } = useQueue()
|
||||||
|
const seek = (step: number) => {
|
||||||
|
// seek right
|
||||||
|
if (step > 0) {
|
||||||
|
if (currentTime.value + step < duration.value) {
|
||||||
|
store.dispatch('player/updateProgress', (currentTime.value + step))
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// seek left
|
||||||
|
const position = Math.max(currentTime.value + step, 0)
|
||||||
|
store.dispatch('player/updateProgress', position)
|
||||||
|
}
|
||||||
|
|
||||||
|
const togglePlayback = () => {
|
||||||
|
if (playing.value) return pause()
|
||||||
|
return resume()
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
looping,
|
looping,
|
||||||
playing,
|
playing,
|
||||||
|
@ -42,6 +76,8 @@ export default () => {
|
||||||
bufferProgress,
|
bufferProgress,
|
||||||
|
|
||||||
pause,
|
pause,
|
||||||
resume
|
resume,
|
||||||
|
seek,
|
||||||
|
togglePlayback
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,19 +1,21 @@
|
||||||
import { useTimeoutFn, useThrottleFn } from "@vueuse/core"
|
import { useTimeoutFn, useThrottleFn, useTimeAgo, useNow, whenever } from '@vueuse/core'
|
||||||
import { useTimeAgo, useNow } from '@vueuse/core'
|
import { Howler } from 'howler'
|
||||||
import { useGettext } from "vue3-gettext"
|
import { gettext } from '~/init/locale'
|
||||||
import { useStore } from "~/store"
|
|
||||||
import { ref, computed } from "vue"
|
import { ref, computed } from "vue"
|
||||||
import { Track } from "~/types"
|
import { Track } from "~/types"
|
||||||
import { sum } from 'lodash-es'
|
import { sum } from 'lodash-es'
|
||||||
|
import store from "~/store"
|
||||||
|
|
||||||
|
const { $pgettext } = gettext
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const store = useStore()
|
|
||||||
const { $pgettext } = useGettext()
|
|
||||||
|
|
||||||
const currentTrack = computed(() => store.getters['queue/currentTrack'])
|
const currentTrack = computed(() => store.getters['queue/currentTrack'])
|
||||||
const currentIndex = computed(() => store.state.queue.currentIndex)
|
const currentIndex = computed(() => store.state.queue.currentIndex)
|
||||||
const hasNext = computed(() => store.getters['queue/hasNext'])
|
const hasNext = computed(() => store.getters['queue/hasNext'])
|
||||||
|
const hasPrevious = computed(() => store.getters['queue/hasPrevious'])
|
||||||
|
|
||||||
const isEmpty = computed(() => store.getters['queue/isEmpty'])
|
const isEmpty = computed(() => store.getters['queue/isEmpty'])
|
||||||
|
whenever(isEmpty, () => Howler.unload())
|
||||||
|
|
||||||
const removeTrack = (index: number) => store.dispatch('queue/cleanTrack', index)
|
const removeTrack = (index: number) => store.dispatch('queue/cleanTrack', index)
|
||||||
const clear = () => store.dispatch('queue/clean')
|
const clear = () => store.dispatch('queue/clean')
|
||||||
|
@ -87,8 +89,11 @@ export default () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentTrack,
|
currentTrack,
|
||||||
|
currentIndex,
|
||||||
hasNext,
|
hasNext,
|
||||||
isEmpty,
|
hasPrevious,
|
||||||
|
isEmpty,
|
||||||
|
isShuffling,
|
||||||
|
|
||||||
removeTrack,
|
removeTrack,
|
||||||
clear,
|
clear,
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { InitModule } from '~/types'
|
||||||
|
import { Howl } from 'howler'
|
||||||
|
|
||||||
|
export const install: InitModule = ({ app }) => {
|
||||||
|
// TODO (wvffle): Check if it is needed
|
||||||
|
|
||||||
|
// this is needed to unlock audio playing under some browsers,
|
||||||
|
// cf https://github.com/goldfire/howler.js#mobilechrome-playback
|
||||||
|
// but we never actually load those audio files
|
||||||
|
const dummyAudio = new Howl({
|
||||||
|
preload: false,
|
||||||
|
autoplay: false,
|
||||||
|
src: ['noop.webm', 'noop.mp3']
|
||||||
|
})
|
||||||
|
|
||||||
|
return dummyAudio
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { InitModule } from '~/types'
|
||||||
|
import { whenever } from '@vueuse/core'
|
||||||
|
import useQueue from '~/composables/useQueue'
|
||||||
|
import usePlayer from '~/composables/usePlayer'
|
||||||
|
|
||||||
|
export const install: InitModule = ({ app }) => {
|
||||||
|
const { currentTrack, next, previous } = useQueue()
|
||||||
|
const { resume, pause, seek } = usePlayer()
|
||||||
|
|
||||||
|
// Add controls for notification drawer
|
||||||
|
if ('mediaSession' in navigator) {
|
||||||
|
navigator.mediaSession.setActionHandler('play', resume)
|
||||||
|
navigator.mediaSession.setActionHandler('pause', pause)
|
||||||
|
navigator.mediaSession.setActionHandler('seekforward', () => seek(5))
|
||||||
|
navigator.mediaSession.setActionHandler('seekbackward', () => seek(-5))
|
||||||
|
navigator.mediaSession.setActionHandler('nexttrack', next)
|
||||||
|
navigator.mediaSession.setActionHandler('previoustrack', previous)
|
||||||
|
|
||||||
|
// TODO (wvffle): set metadata to null when we don't have currentTrack?
|
||||||
|
// If the session is playing as a PWA, populate the notification
|
||||||
|
// with details from the track
|
||||||
|
whenever(currentTrack, () => {
|
||||||
|
const { title, artist, album } = currentTrack.value
|
||||||
|
|
||||||
|
const metadata: MediaMetadataInit = {
|
||||||
|
title,
|
||||||
|
artist: artist.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (album?.cover) {
|
||||||
|
metadata.album = album.title
|
||||||
|
metadata.artwork = [
|
||||||
|
{ src: album.cover.urls.original, sizes: '96x96', type: 'image/png' },
|
||||||
|
{ src: album.cover.urls.original, sizes: '128x128', type: 'image/png' },
|
||||||
|
{ src: album.cover.urls.original, sizes: '192x192', type: 'image/png' },
|
||||||
|
{ src: album.cover.urls.original, sizes: '256x256', type: 'image/png' },
|
||||||
|
{ src: album.cover.urls.original, sizes: '384x384', type: 'image/png' },
|
||||||
|
{ src: album.cover.urls.original, sizes: '512x512', type: 'image/png' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.mediaSession.metadata = new window.MediaMetadata(metadata)
|
||||||
|
}, { immediate: true })
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,14 @@
|
||||||
import { InitModule } from '~/types'
|
import {
|
||||||
|
InitModule,
|
||||||
|
ListenWSEvent,
|
||||||
|
PendingReviewEditsWSEvent,
|
||||||
|
PendingReviewReportsWSEvent,
|
||||||
|
PendingReviewRequestsWSEvent,
|
||||||
|
} from '~/types'
|
||||||
import { watchEffect, watch } from 'vue'
|
import { watchEffect, watch } from 'vue'
|
||||||
import { useWebSocket, whenever } from '@vueuse/core'
|
import { useWebSocket, whenever } from '@vueuse/core'
|
||||||
|
import useWebSocketHandler from '~/composables/useWebSocketHandler'
|
||||||
|
import { CLIENT_RADIOS } from '~/utils/clientRadios'
|
||||||
|
|
||||||
export const install: InitModule = ({ store }) => {
|
export const install: InitModule = ({ store }) => {
|
||||||
watch(() => store.state.instance.instanceUrl, () => {
|
watch(() => store.state.instance.instanceUrl, () => {
|
||||||
|
@ -25,4 +33,47 @@ export const install: InitModule = ({ store }) => {
|
||||||
console.log('Websocket status:', status.value)
|
console.log('Websocket status:', status.value)
|
||||||
})
|
})
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// WebSocket handlers
|
||||||
|
useWebSocketHandler('inbox.item_added', () => {
|
||||||
|
store.commit('ui/incrementNotifications', { type: 'inbox', count: 1 })
|
||||||
|
})
|
||||||
|
|
||||||
|
useWebSocketHandler('mutation.created', (event) => {
|
||||||
|
store.commit('ui/incrementNotifications', {
|
||||||
|
type: 'pendingReviewEdits',
|
||||||
|
value: (event as PendingReviewEditsWSEvent).pending_review_count
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
useWebSocketHandler('mutation.updated', (event) => {
|
||||||
|
store.commit('ui/incrementNotifications', {
|
||||||
|
type: 'pendingReviewEdits',
|
||||||
|
value: (event as PendingReviewEditsWSEvent).pending_review_count
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
useWebSocketHandler('report.created', (event) => {
|
||||||
|
store.commit('ui/incrementNotifications', {
|
||||||
|
type: 'pendingReviewReports',
|
||||||
|
value: (event as PendingReviewReportsWSEvent).unresolved_count
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
useWebSocketHandler('user_request.created', (event) => {
|
||||||
|
store.commit('ui/incrementNotifications', {
|
||||||
|
type: 'pendingReviewRequests',
|
||||||
|
value: (event as PendingReviewRequestsWSEvent).pending_count
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
useWebSocketHandler('Listen', (event) => {
|
||||||
|
if (store.state.radios.current && store.state.radios.running) {
|
||||||
|
const { current } = store.state.radios
|
||||||
|
|
||||||
|
if (current.clientOnly) {
|
||||||
|
CLIENT_RADIOS[current.type].handleListen(current, event as ListenWSEvent, store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { Store } from 'vuex'
|
import { Store } from 'vuex'
|
||||||
|
import { RootState } from '~/store'
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
$store: Store<any>
|
$store: Store<RootState>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ type NotificationsKey = 'inbox' | 'pendingReviewEdits' | 'pendingReviewReports'
|
||||||
export interface State {
|
export interface State {
|
||||||
currentLanguage: 'en_US' | keyof typeof availableLanguages
|
currentLanguage: 'en_US' | keyof typeof availableLanguages
|
||||||
selectedLanguage: boolean
|
selectedLanguage: boolean
|
||||||
queueFocused: null
|
queueFocused: null | 'queue' | 'player'
|
||||||
momentLocale: 'en'
|
momentLocale: 'en'
|
||||||
lastDate: Date
|
lastDate: Date
|
||||||
maxMessages: number
|
maxMessages: number
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { App } from 'vue'
|
||||||
import type { Store } from 'vuex'
|
import type { Store } from 'vuex'
|
||||||
import { Router } from 'vue-router'
|
import { Router } from 'vue-router'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
|
import { RootState } from '~/store'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -14,7 +15,7 @@ declare global {
|
||||||
export interface InitModuleContext {
|
export interface InitModuleContext {
|
||||||
app: App
|
app: App
|
||||||
router: Router
|
router: Router
|
||||||
store: Store<any>
|
store: Store<RootState>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InitModule = (ctx: InitModuleContext) => void
|
export type InitModule = (ctx: InitModuleContext) => void
|
||||||
|
@ -71,8 +72,12 @@ export interface Track {
|
||||||
|
|
||||||
album?: Album
|
album?: Album
|
||||||
artist?: Artist
|
artist?: Artist
|
||||||
|
|
||||||
|
// TODO (wvffle): Make sure it really has listen_url
|
||||||
|
listen_url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface Channel {
|
export interface Channel {
|
||||||
id: string
|
id: string
|
||||||
artist?: Artist
|
artist?: Artist
|
||||||
|
@ -171,6 +176,9 @@ export interface Upload {
|
||||||
source?: string
|
source?: string
|
||||||
uuid: string
|
uuid: string
|
||||||
duration?: number
|
duration?: number
|
||||||
|
mimetype: string
|
||||||
|
extension: string
|
||||||
|
listen_url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileSystem Logs
|
// FileSystem Logs
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { startCase } from 'lodash-es'
|
||||||
import { Store } from 'vuex'
|
import { Store } from 'vuex'
|
||||||
import { Router } from 'vue-router'
|
import { Router } from 'vue-router'
|
||||||
import { APIErrorResponse } from '~/types'
|
import { APIErrorResponse } from '~/types'
|
||||||
|
import { RootState } from '~/store'
|
||||||
|
|
||||||
export function setUpdate (obj: object, statuses: Record<string, unknown>, value: unknown) {
|
export function setUpdate (obj: object, statuses: Record<string, unknown>, value: unknown) {
|
||||||
for (const key of Object.keys(obj)) {
|
for (const key of Object.keys(obj)) {
|
||||||
|
@ -46,7 +47,7 @@ export function getCookie (name: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (wvffle): Use navigation guards
|
// TODO (wvffle): Use navigation guards
|
||||||
export async function checkRedirectToLogin (store: Store<any>, router: Router) {
|
export async function checkRedirectToLogin (store: Store<RootState>, router: Router) {
|
||||||
if (!store.state.auth.authenticated) {
|
if (!store.state.auth.authenticated) {
|
||||||
return router.push({ name: 'login', query: { next: router.currentRoute.value.fullPath } })
|
return router.push({ name: 'login', query: { next: router.currentRoute.value.fullPath } })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1390,6 +1390,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/howler@^2.2.7":
|
||||||
|
version "2.2.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/howler/-/howler-2.2.7.tgz#5acfbed57f9e1d99b8dabe1b824729e1c1ea1fae"
|
||||||
|
integrity sha512-PEZldwZqJJw1PWRTpupyC7ajVTZA8aHd8nB/Y0n6zRZi5u8ktYDntsHj13ltEiBRqWwF06pASxBEvCTxniG8eA==
|
||||||
|
|
||||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
|
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
|
||||||
|
|
Loading…
Reference in New Issue