Merge varis music player

This commit is contained in:
Chizu 2023-03-19 03:03:33 +09:00
parent a0df90d7dc
commit 7f33b4de7d
17 changed files with 653 additions and 46 deletions

View file

@ -4,6 +4,7 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance
import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
import MusicPlayer from './components/music_player/music_player.vue'
import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import ModModal from './components/mod_modal/mod_modal.vue'
@ -29,7 +30,8 @@ export default {
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
ShoutPanel,
ShoutPanel,
MusicPlayer,
MediaModal,
SideDrawer,
MobilePostStatusButton,
@ -67,7 +69,7 @@ export default {
]
},
currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image },
userBackground () { return this.getBackgroundImage },
instanceBackground () {
return this.mergedConfig.hideInstanceWallpaper
? null
@ -81,7 +83,8 @@ export default {
}
}
},
shout () { return this.$store.state.shout.joined },
shout () { return this.$store.state.shout.joined },
song () { return this.$store.getters.hasSong },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
@ -115,7 +118,7 @@ export default {
},
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
...mapGetters(['mergedConfig'])
...mapGetters(['mergedConfig', 'getBackgroundImage'])
},
methods: {
updateMobileState () {

View file

@ -61,6 +61,12 @@
class="floating-shout mobile-hidden"
:class="{ '-left': shoutboxPosition }"
/>
<music-player
:floating="true"
v-if="song"
class="floating-music-player mobile-hidden"
:class="{ '-left': shoutboxPosition }"
/>
<MobilePostStatusButton />
<UserReportingModal />
<PostStatusModal />

View file

@ -422,6 +422,18 @@ const afterStoreSetup = async ({ store, i18n }) => {
}
})
// someone fix this please
router.beforeEach((to, from, next) => {
// If you are coming from a profile AND the song is playing AND you need to pause it AAAAND YOU ARENT GOING TO A USER PROFILE... pause it
if (from.name === 'user-profile' && store.getters.songState && !store.getters.mergedConfig.pauseOnProfileExit && to.name !== 'user-profile') {
store.dispatch('setSongState', false)
}
if (to.name !== 'user-profile') {
store.dispatch('resetBackgroundImage')
}
next()
})
const app = createApp(App)
app.use(router)

View file

@ -0,0 +1,95 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faHeadphones,
faTimes,
faPlay,
faPause,
faVolumeUp,
faVolumeOff,
faStepForward,
faStepBackward
} from '@fortawesome/free-solid-svg-icons'
library.add(
faHeadphones,
faTimes,
faPlay,
faPause,
faVolumeUp,
faVolumeOff,
faStepForward,
faStepBackward
)
const musicPlayer = {
props: ['floating'],
data () {
return {
currentMessage: '',
collapsed: true,
volume: 0,
progress: 0,
muted: false
}
},
computed: {
getCurrentSong () {
return this.$store.getters.hasSong
},
isPlaying () {
return this.$store.getters.songState
},
defaultVolume () {
return this.$store.getters.mergedConfig.defaultProfileMusicVolume
}
},
methods: {
nextSong () {
this.$store.dispatch('playlistNext')
},
prevSong () {
this.$store.dispatch('playlistPrev')
},
seekPosition () {
this.$store.dispatch('setSongPosition', this.progress)
},
togglePanel () {
this.collapsed = !this.collapsed
},
togglePlay () {
this.$store.dispatch('setSongState', !this.isPlaying)
},
toggleMute () {
if (this.muted) {
this.$store.dispatch('setSongVolume', this.defaultVolume)
} else {
this.$store.dispatch('setSongVolume', 0)
}
this.muted = !this.muted
},
setVolume () {
this.$store.dispatch('setSongVolume', this.volume)
},
clearQueue () {
this.$store.dispatch('setBackgroundMusic', null)
this.togglePanel()
},
created () {
this.progress = 0
this.volume = this.defaultVolume
setInterval(() => {
if (this.$store.getters.hasSong) {
this.progress = this.$store.getters.songProgress
if (this.progress === 100) {
this.$store.dispatch('playlistNext')
}
}
}, 1000)
},
beforeDestroy () {
clearInterval()
}
}
}
export default musicPlayer

View file

@ -0,0 +1,187 @@
<template>
<div
v-if="!collapsed || !floating"
class="music-panel"
>
<div class="panel panel-default">
<div
class="panel-heading timeline-heading"
:class="{ 'shout-heading': floating }"
@click.stop.prevent="togglePanel"
>
<div class="title">
{{ $t('music_player.title') }}
<FAIcon
v-if="floating"
icon="times"
class="close-icon"
/>
</div>
</div>
<div class="shout-window">
<div class="music-player-controls">
<div
class="music-player-button"
@click.stop.prevent="togglePlay"
>
<div
@click.stop.prevent="prevSong"
>
<FAIcon
icon="step-backward"
class="music-player-button"
/>
</div>
<div>
<FAIcon
v-if="isPlaying"
icon="pause"
class="music-player-button"
/>
<FAIcon
v-else
icon="play"
/>
</div>
<div
@click.stop.prevent="nextSong"
>
<FAIcon
icon="step-forward"
class="music-player-button"
/>
</div>
<div
@click.stop.prevent="toggleMute"
>
<FAIcon
v-if="!muted"
icon="volume-up"
/>
<FAIcon
v-else
icon="volume-off"
/>
</div>
</div>
<input
type="range"
v-model="volume"
min="0"
max="100"
@change.stop.prevent="setVolume"
/>
<FAIcon
icon="times"
class="close-icon"
@click.stop.prevent="clearQueue"
/>
</div>
<div class="music-player-timeline">
<input
class="seekbar"
type="range"
min="0"
max="100"
v-model="progress"
@change.stop.prevent="seekPosition"
/>
</div>
</div>
</div>
</div>
<div
v-else
class="music-panel"
>
<div class="panel panel-default">
<div
class="panel-heading -stub timeline-heading shout-heading"
@click.stop.prevent="togglePanel"
>
<div class="title">
<FAIcon
class="icon"
icon="headphones"
/>
{{ $t('music_player.title') }}
</div>
</div>
</div>
</div>
</template>
<script src="./music_player.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.floating-music-player {
position: fixed;
bottom: 5em;
z-index: 1000;
max-width: 25em;
&.-left {
left: 0.5em;
}
&:not(.-left) {
right: 0.5em;
}
}
.music-panel {
.shout-heading {
cursor: pointer;
.icon {
color: $fallback--text;
color: var(--panelText, $fallback--text);
margin-right: 0.5em;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
}
.title svg {
float: right;
}
}
.shout-window {
overflow-y: auto;
overflow-x: hidden;
max-height: 20em;
padding: 2em;
column-gap: 1em;
.music-player-controls {
display: inline-flex;
column-gap: 1em;
.music-player-button {
display: inline-flex;
column-gap: 2em;
}
}
.music-player-timeline {
padding-top: 1em;
.seekbar {
width: 100%
}
}
}
.shout-window-container {
height: 100%;
}
.music-panel {
.title {
display: flex;
justify-content: space-between;
}
}
}
</style>

View file

@ -533,7 +533,56 @@
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.plf.profile_look_feel') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="allowCustomProfiles">
{{ $t('settings.plf.allow_custom_profile') }}
</BooleanSetting>
</li>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="allowProfileBackgroundDisplay"
:disabled="!allowCustomProfiles"
>
{{ $t('settings.plf.allow_profile_background') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="allowProfileMusicPlay"
:disabled="!allowCustomProfiles"
>
{{ $t('settings.plf.allow_profile_music') }}
</BooleanSetting>
</li>
<li>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="pauseOnProfileExit"
:disabled="!allowProfileMusicPlay || !allowCustomProfiles"
>
{{ $t('settings.plf.pause_profile_music') }}
</BooleanSetting>
</li>
<li>
<IntegerSetting
path="defaultProfileMusicVolume"
:min="0"
:max="100"
:disabled="!allowProfileMusicPlay || !allowCustomProfiles"
>
{{ $t('settings.plf.profile_music_volume') }}
</IntegerSetting>
</li>
</ul>
</li>
</ul>
</ul>
</div>
<div
v-if="user"
class="setting-item"

View file

@ -12,6 +12,7 @@ import InterfaceLanguageSwitcher from 'src/components/interface_language_switche
import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import localeService from 'src/services/locale/locale.service.js'
import statusPosterService from '../../../services/status_poster/status_poster.service'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -113,6 +114,22 @@ const ProfileTab = {
bannerImgSrc () {
const src = this.$store.state.users.currentUser.cover_photo
return (!src) ? this.defaultBanner : src
},
// How badly... will this fuck us :awoo_concern:
hasBackgroundMusic () {
for (let i = 0; i < this.user.fields.length; i++) {
if (this.user.fields[i].name.toLowerCase() === 'music') {
return i
}
}
return false
},
backgroundMusic () {
for (let i = 0; i < this.user.fields.length; i++) {
if (this.user.fields[i].name.toLowerCase() === 'music') {
return this.user.fields[i].value
}
}
}
},
methods: {
@ -159,6 +176,46 @@ const ProfileTab = {
uploadFile (slot, e) {
const file = e.target.files[0]
if (!file) { return }
if (this.overFileSizeLimit(slot, file)) { return }
// eslint-disable-next-line no-undef
const reader = new FileReader()
reader.onload = ({ target }) => {
const img = target.result
this[slot + 'Preview'] = img
this[slot] = file
}
reader.readAsDataURL(file)
},
uploadAudioFile (slot, e) {
const formData = new FormData()
const store = this.$store
const file = e.target.files[0]
if (!file) { return }
if (this.overFileSizeLimit(slot, file)) { return }
// Check if user has background music already, otherwise check if we aren't over the fields limit and create new
// save the index of the place for later incase it does exist
const musicIndex = this.hasBackgroundMusic
if (!musicIndex) {
if (this.newFields.length >= this.maxFields) {
return false
}
}
formData.append('file', file)
statusPosterService.uploadMedia({ store, formData })
.then((fileData) => {
// If we do not a music Index create one
// otherwise change the value of the current music index
if (!musicIndex) {
this.newFields.push({ name: 'Music', value: fileData.url })
this.updateProfile()
} else {
this.newFields[musicIndex].value = fileData.url
this.updateProfile()
}
})
},
overFileSizeLimit (slot, file) {
if (file.size > this.$store.state.instance[slot + 'limit']) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
@ -174,16 +231,9 @@ const ProfileTab = {
],
level: 'error'
})
return
return true
}
// eslint-disable-next-line no-undef
const reader = new FileReader()
reader.onload = ({ target }) => {
const img = target.result
this[slot + 'Preview'] = img
this[slot] = file
}
reader.readAsDataURL(file)
return false
},
resetAvatar () {
const confirmed = window.confirm(this.$t('settings.reset_avatar_confirm'))

View file

@ -157,33 +157,53 @@
/>
</div>
<div class="setting-item">
<h2>{{ $t('settings.profile_banner') }}</h2>
<div class="banner-background-preview">
<img :src="user.cover_photo">
<button
v-if="!isDefaultBanner"
class="button-unstyled reset-button"
:title="$t('settings.reset_profile_banner')"
@click="resetBanner"
>
<FAIcon
icon="times"
type="button"
<h2>{{ $t('settings.profile_customisation.customise') }}</h2>
<ul class="setting-list">
<li><h3>{{ $t('settings.profile_customisation.profile_background') }}</h3></li>
<li>
<div class="banner-background-preview">
<img :src="user.background_image">
<button
v-if="!isDefaultBackground"
class="button-unstyled reset-button"
:title="$t('settings.reset_profile_background')"
@click="resetBackground"
>
<FAIcon
icon="times"
type="button"
/>
</button>
</div>
<p>{{ $t('settings.set_new_profile_background') }}</p>
<img
v-if="backgroundPreview"
class="banner-background-preview"
:src="backgroundPreview"
>
<div>
<input
type="file"
@change="uploadFile('background', $event)"
>
</div>
</li>
<li><h3>{{ $t('settings.profile_customisation.profile_music') }}</h3></li>
<li>
<audio
v-if="hasBackgroundMusic"
:src="backgroundMusic"
controls
/>
</button>
</div>
<p>{{ $t('settings.set_new_profile_banner') }}</p>
<img
v-if="bannerPreview"
class="banner-background-preview"
:src="bannerPreview"
>
<div>
<input
type="file"
@change="uploadFile('banner', $event)"
>
</div>
<p>{{ $t('settings.set_new_profile_music') }}</p>
<div>
<input
type="file"
@change="uploadAudioFile('upload', $event)"
>
</div>
</li>
</ul>
<FAIcon
v-if="bannerUploading"
class="uploading"

View file

@ -80,7 +80,7 @@
.floating-shout {
position: fixed;
bottom: 0.5em;
z-index: 1000;
z-index: 1001;
max-width: 25em;
&.-left {

View file

@ -54,6 +54,7 @@ const UserProfile = {
tab: 'statuses',
followsTab: 'users',
footerRef: null,
backgroundMusic: null,
note: null,
noteLoading: false
}
@ -97,6 +98,16 @@ const UserProfile = {
followersTabVisible () {
return this.isUs || !this.user.hide_followers
},
hasMusic () {
const totalSongs = []
for (let i = 0; i < this.user.fields_text.length; i++) {
if (this.user.fields_text[i].name.toLowerCase() === 'music') {
totalSongs.push(this.user.fields_text[i].value)
}
}
if (totalSongs.length !== 0) { return totalSongs }
return false
},
currentUser () {
return this.$store.state.users.currentUser
},
@ -127,6 +138,7 @@ const UserProfile = {
load (userNameOrId) {
const loadById = (userId) => {
this.userId = userId
loadUserCustomisations(userId)
const timelines = ['user', 'favorites', 'replies', 'media']
timelines.forEach((timeline) => {
this.$store.commit('clearTimeline', { timeline: timeline })
@ -135,7 +147,22 @@ const UserProfile = {
// Fetch all pinned statuses immediately
this.$store.dispatch('fetchPinnedStatuses', userId)
}
const loadUserCustomisations = (userId) => {
const user = this.$store.getters.findUser(userId)
const { allowProfileMusicPlay: allowsMusic, allowProfileBackgroundDisplay: allowsImages } = this.$store.getters.mergedConfig
if (user.background_image !== null && allowsImages) {
this.$store.dispatch('setBackgroundImage', user.background_image)
}
const userMusic = this.hasMusic
if (Array.isArray(userMusic)) {
this.$store.dispatch('setPlaylist', { append: false, list: userMusic })
if (allowsMusic) {
this.$store.dispatch('setSongState', true)
}
}
}
// Reset view
this.userId = null
this.error = false

View file

@ -58,6 +58,9 @@
"shoutbox": {
"title": "Shoutbox"
},
"music_player": {
"title": "Music Player"
},
"chats": {
"chats": "Chats",
"delete": "Delete",
@ -691,11 +694,23 @@
"pause_on_unfocused": "Pause when tab is not focused",
"play_videos_in_modal": "Play videos in a popup frame",
"post_look_feel": "Posts Look & Feel",
"plf": {
"profile_look_feel": "Profile Look & Feel",
"allow_custom_profile": "Allow custom profiles",
"allow_profile_music": "Allow a profiles music to autoplay",
"allow_profile_background": "Allow a profiles background to display",
"pause_profile_music": "Continue music after exiting profile?",
"profile_music_volume": "Default Volume"
},
"post_status_content_type": "Default post content type",
"posts": "Posts",
"preload_images": "Preload images",
"presets": "Presets",
"profile_background": "Profile background",
"profile_customisation": {
"customise": "Profile customisation",
"profile_background": "Profile background",
"profile_music": "Profile music"
},
"profile_banner": "Profile banner",
"profile_fields": {
"add_field": "Add field",
@ -738,6 +753,7 @@
"set_new_mascot": "Set new mascot",
"set_new_profile_background": "Set new profile background",
"set_new_profile_banner": "Set new profile banner",
"set_new_profile_music": "Set new profile music",
"setting_changed": "Setting is different from default",
"setting_server_side": "This setting is tied to your profile and affects all sessions and clients",
"settings": "Settings",

View file

@ -23,6 +23,7 @@ import chatsModule from './modules/chats.js'
import announcementsModule from './modules/announcements.js'
import editStatusModule from './modules/editStatus.js'
import statusHistoryModule from './modules/statusHistory.js'
import musicPlayerModule from './modules/music_player.js'
import tagModule from './modules/tags.js'
import recentEmojisModule from './modules/recentEmojis.js'
@ -92,7 +93,7 @@ const persistedStateOptions = {
api: apiModule,
config: configModule,
serverSideConfig: serverSideConfigModule,
shout: shoutModule,
shout: shoutModule,
oauth: oauthModule,
authFlow: authFlowModule,
mediaViewer: mediaViewerModule,
@ -104,6 +105,7 @@ const persistedStateOptions = {
statusHistory: statusHistoryModule,
chats: chatsModule,
announcements: announcementsModule,
musicPlayer: musicPlayerModule,
tags: tagModule,
recentEmojis: recentEmojisModule,
},

View file

@ -25,6 +25,11 @@ export const defaultState = {
profileVersion: 0,
expertLevel: 0, // used to track which settings to show and hide
colors: {},
allowCustomProfiles: false,
allowProfileMusicPlay: false,
allowProfileBackgroundDisplay: false,
pauseOnProfileExit: true,
defaultProfileMusicVolume: 50,
theme: undefined,
customTheme: undefined,
customThemeSource: undefined,

View file

@ -19,7 +19,9 @@ const defaultState = {
layoutType: 'normal',
globalNotices: [],
layoutHeight: 0,
lastTimeline: null
lastTimeline: null,
backgroundImage: null,
currentUserBackgroundImage: null
}
const interfaceMod = {
@ -104,6 +106,17 @@ const interfaceMod = {
},
setLastTimeline (state, value) {
state.lastTimeline = value
},
setBackgroundImage (state, value) {
state.backgroundImage = value
},
// Should only be called on start
setCurrentUserBackgroundImage (state, value) {
state.backgroundImage = value
state.currentUserBackgroundImage = value
},
resetBackgroundImage (state) {
state.backgroundImage = state.currentUserBackgroundImage
}
},
actions: {
@ -192,6 +205,21 @@ const interfaceMod = {
},
setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value)
},
setBackgroundImage ({ commit }, value) {
commit('setBackgroundImage', value)
},
setCurrentUserBackgroundImage ({ commit }, value) {
commit('setBackgroundImage', value)
commit('setCurrentUserBackgroundImage', value)
},
resetBackgroundImage ({ commit }) {
commit('resetBackgroundImage')
}
},
getters: {
getBackgroundImage (state) {
return state.backgroundImage
}
}
}

106
src/modules/music_player.js Normal file
View file

@ -0,0 +1,106 @@
const musicPlayer = {
state: {
currentSong: null,
songState: null,
playlist: [],
playlistIndex: null,
volume: 0
},
mutations: {
incrementPlaylistIndex (state) {
if (state.playlistIndex >= state.playlist.length - 1) { return }
state.playlistIndex++
},
decrimentPlaylistIndex (state) {
if (state.playlistIndex <= 0) { return }
state.playlistIndex--
},
setPlaylist (state, value) {
if (!value.append) {
state.playlist = []
state.playlistIndex = 0
}
state.playlist = value.list
this.commit('setSong', state.playlist[state.playlistIndex])
},
setSong (state, value) {
if (state.currentSong != null) {
state.currentSong.pause()
delete state.currentSong
}
state.currentSong = new Audio(value)
},
setSongVolume (state, value) {
state.volume = value
state.currentSong.volume = state.volume / 100
},
setSongPosition (state, value) {
state.currentSong.currentTime = value * state.currentSong.duration / 100
},
setSongState (state, value) {
if (state.currentSong === null) { return }
state.songState = value
if (state.songState === true) {
state.currentSong.play()
.catch(() => {
state.songState = false
})
} else {
state.currentSong.pause()
}
}
},
actions: {
setBackgroundMusic ({ commit, getters }, value) {
commit('setSong', value)
commit('setSongVolume', getters.mergedConfig.defaultProfileMusicVolume)
},
setSongVolume ({ commit }, value) {
commit('setSongVolume', value)
},
setSongState ({ commit }, value) {
commit('setSongState', value)
},
setPlaylist ({ commit }, value) {
commit('setPlaylist', value)
},
setSongPosition ({ commit }, value) {
commit('setSongPosition', value)
},
playlistNext ({ commit, getters }) {
commit('incrementPlaylistIndex')
commit('setSong', getters.getPlaylist[getters.getPlaylistIndex])
commit('setSongState', true)
commit('setSongVolume', getters.getVolume)
},
playlistPrev ({ commit, getters }) {
commit('decrimentPlaylistIndex')
commit('setSong', getters.getPlaylist[getters.getPlaylistIndex])
commit('setSongState', true)
commit('setSongVolume', getters.getVolume)
}
},
getters: {
songState (state) {
return state.songState
},
songProgress (state) {
return Math.round(state.currentSong.currentTime / state.currentSong.duration * 100)
},
hasSong (state) {
return state.currentSong
},
getPlaylistIndex (state) {
return state.playlistIndex
},
getPlaylist (state) {
return state.playlist
},
getVolume (state) {
return state.volume
}
}
}
export default musicPlayer

View file

@ -766,8 +766,8 @@ const statuses = {
rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
.then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
},
search (store, { q, resolve, limit, offset, following, type }) {
return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following, type })
search (store, { q, resolve, limit, offset, following, type, accountId }) {
return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following, type, accountId })
.then((data) => {
store.commit('addNewUsers', data.accounts)
store.commit('addNewStatuses', { statuses: data.statuses })

View file

@ -602,6 +602,7 @@ const users = {
user.muteIds = []
user.domainMutes = []
commit('setCurrentUser', user)
commit('setCurrentUserBackgroundImage', user.background_image) // Set the background image to reset too
commit('addNewUsers', [user])
store.dispatch('fetchEmoji')