Commit 1d4f2e1d authored by Kasper Seweryn's avatar Kasper Seweryn 🥞 Committed by Kasper Seweryn
Browse files

Remove files commited by accident

parent 2dbd1174
<template>
<div
id="app"
:key="String($store.state.instance.instanceUrl)"
:class="[$store.state.ui.queueFocused ? 'queue-focused' : '',
{'has-bottom-player': $store.state.queue.tracks.length > 0}]"
>
<!-- here, we display custom stylesheets, if any -->
<link
v-for="url in customStylesheets"
:key="url"
rel="stylesheet"
property="stylesheet"
:href="url"
>
<sidebar
:width="width"
@show:set-instance-modal="showSetInstanceModal = !showSetInstanceModal"
@show:shortcuts-modal="showShortcutsModal = !showShortcutsModal"
/>
<set-instance-modal
:show="showSetInstanceModal"
@update:show="showSetInstanceModal = $event"
/>
<service-messages />
<transition name="queue">
<queue
v-if="$store.state.ui.queueFocused"
@touch-progress="$refs.player.setCurrentTime($event)"
/>
</transition>
<router-view
role="main"
:class="{hidden: $store.state.ui.queueFocused}"
/>
<player ref="player" />
<playlist-modal v-if="$store.state.auth.authenticated" />
<channel-upload-modal v-if="$store.state.auth.authenticated" />
<filter-modal v-if="$store.state.auth.authenticated" />
<report-modal />
<shortcuts-modal
:show="showShortcutsModal"
@update:show="showShortcutsModal = $event"
/>
<GlobalEvents @keydown.h.exact="showShortcutsModal = !showShortcutsModal" />
</div>
</template>
<script>
import axios from 'axios'
import { uniq, get } from 'lodash-es'
import { mapState, mapGetters } from 'vuex'
import { useWebSocket, whenever } from '@vueuse/core'
import GlobalEvents from '@/components/utils/global-events.vue'
import locales from './locales'
import { getClientOnlyRadio } from '@/radios'
import Player from '@/components/audio/Player.vue'
import Queue from '@/components/Queue.vue'
import PlaylistModal from '@/components/playlists/PlaylistModal.vue'
import ChannelUploadModal from '@/components/channels/UploadModal.vue'
import Sidebar from '@/components/Sidebar.vue'
import ServiceMessages from '@/components/ServiceMessages.vue'
import SetInstanceModal from '@/components/SetInstanceModal.vue'
import ShortcutsModal from '@/components/ShortcutsModal.vue'
import FilterModal from '@/components/moderation/FilterModal.vue'
import ReportModal from '@/components/moderation/ReportModal.vue'
import { watch, watchEffect } from '@vue/composition-api'
export default {
name: 'App',
components: {
Player,
Queue,
PlaylistModal,
ChannelUploadModal,
Sidebar,
ServiceMessages,
SetInstanceModal,
ShortcutsModal,
FilterModal,
ReportModal,
GlobalEvents
},
setup (props, { root }) {
const store = root.$store
const url = store.getters['instance/absoluteUrl']('api/v1/activity')
.replace(/^http/, 'ws')
const { data, status, open, close } = useWebSocket(url, {
autoReconnect: true,
immediate: false
})
watch(() => store.state.auth.authenticated, (authenticated) => {
if (authenticated) return open()
close()
})
whenever(data, () => {
store.dispatch('ui/websocketEvent', JSON.parse(data.value))
})
watchEffect(() => {
console.log('Websocket status:', status.value)
})
},
data () {
return {
instanceUrl: null,
showShortcutsModal: false,
showSetInstanceModal: false,
initialTitle: document.title,
width: window.innerWidth
}
},
computed: {
...mapState({
messages: state => state.ui.messages,
nodeinfo: state => state.instance.nodeinfo,
playing: state => state.player.playing,
bufferProgress: state => state.player.bufferProgress,
isLoadingAudio: state => state.player.isLoadingAudio,
serviceWorker: state => state.ui.serviceWorker
}),
...mapGetters({
hasNext: 'queue/hasNext',
currentTrack: 'queue/currentTrack',
progress: 'player/progress'
}),
labels () {
const play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Play track')
const pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Pause track')
const next = this.$pgettext('Sidebar/Player/Icon.Tooltip', 'Next track')
const expandQueue = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Expand queue')
return {
play,
pause,
next,
expandQueue
}
},
suggestedInstances () {
const instances = this.$store.state.instance.knownInstances.slice(0)
if (this.$store.state.instance.frontSettings.defaultServerUrl) {
let serverUrl = this.$store.state.instance.frontSettings.defaultServerUrl
if (!serverUrl.endsWith('/')) {
serverUrl = serverUrl + '/'
}
instances.push(serverUrl)
}
instances.push(this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio/')
return uniq(instances.filter((e) => { return e }))
},
version () {
if (!this.nodeinfo) {
return null
}
return get(this.nodeinfo, 'software.version')
},
customStylesheets () {
if (this.$store.state.instance.frontSettings) {
return this.$store.state.instance.frontSettings.additionalStylesheets || []
}
return null
},
matchDarkColorScheme () {
if (window.matchMedia) {
return window.matchMedia('(prefers-color-scheme: dark)')
}
return null
}
},
watch: {
'$store.state.instance.instanceUrl' (v) {
this.$store.dispatch('instance/fetchSettings')
this.fetchNodeInfo()
},
'$store.state.ui.theme': {
immediate: true,
handler (newValue) {
const matchesDark = this.matchDarkColorScheme
if (matchesDark) {
if (newValue === 'system') {
newValue = matchesDark.matches ? 'dark' : 'light'
matchesDark.addEventListener('change', this.handleThemeChange)
} else {
matchesDark.removeEventListener('change', this.handleThemeChange)
}
} else {
if (newValue === 'system') {
newValue = 'light'
}
}
this.setTheme(newValue)
}
},
'$store.state.ui.currentLanguage': {
immediate: true,
handler (newValue) {
const self = this
const htmlLocale = newValue.toLowerCase().replace('_', '-')
document.documentElement.setAttribute('lang', htmlLocale)
if (newValue === 'en_US') {
self.$language.current = 'noop'
self.$language.current = newValue
return self.$store.commit('ui/momentLocale', 'en')
}
}
},
currentTrack: {
immediate: true,
handler (newValue) {
this.updateDocumentTitle()
}
},
'$store.state.ui.pageTitle': {
immediate: true,
handler (newValue) {
this.updateDocumentTitle()
}
},
'serviceWorker.updateAvailable': {
handler (v) {
if (!v) {
return
}
const self = this
this.$store.commit('ui/addMessage', {
content: this.$pgettext('App/Message/Paragraph', 'A new version of the app is available.'),
date: new Date(),
key: 'refreshApp',
displayTime: 0,
classActions: 'bottom attached opaque',
actions: [
{
text: this.$pgettext('App/Message/Paragraph', 'Update'),
class: 'primary',
click: function () {
self.updateApp()
}
},
{
text: this.$pgettext('App/Message/Paragraph', 'Later'),
class: 'basic'
}
]
})
},
immediate: true
}
},
async created () {
if (navigator.serviceWorker) {
navigator.serviceWorker.addEventListener(
'controllerchange', () => {
if (this.serviceWorker.refreshing) return
this.$store.commit('ui/serviceWorker', {
refreshing: true
})
window.location.reload()
}
)
}
window.addEventListener('resize', this.handleResize)
this.handleResize()
const self = this
if (!this.$store.state.ui.selectedLanguage) {
this.autodetectLanguage()
}
setInterval(() => {
// used to redraw ago dates every minute
self.$store.commit('ui/computeLastDate')
}, 1000 * 60)
const urlParams = new URLSearchParams(window.location.search)
const serverUrl = urlParams.get('_server')
if (serverUrl) {
this.$store.commit('instance/instanceUrl', serverUrl)
}
const url = urlParams.get('_url')
if (url) {
await this.$router.replace(url)
} else if (!this.$store.state.instance.instanceUrl) {
// we have several way to guess the API server url. By order of precedence:
// 1. use the url provided in settings.json, if any
// 2. use the url specified when building via VUE_APP_INSTANCE_URL
// 3. use the current url
const defaultInstanceUrl =
this.$store.state.instance.frontSettings.defaultServerUrl ||
import.meta.env.VUE_APP_INSTANCE_URL || this.$store.getters['instance/defaultUrl']()
this.$store.commit('instance/instanceUrl', defaultInstanceUrl)
} else {
// needed to trigger initialization of axios / service worker
this.$store.commit('instance/instanceUrl', this.$store.state.instance.instanceUrl)
}
await this.fetchNodeInfo()
this.$store.dispatch('instance/fetchSettings')
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'inbox.item_added',
id: 'sidebarCount',
handler: this.incrementNotificationCountInSidebar
})
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'mutation.created',
id: 'sidebarReviewEditCount',
handler: this.incrementReviewEditCountInSidebar
})
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'mutation.updated',
id: 'sidebarReviewEditCount',
handler: this.incrementReviewEditCountInSidebar
})
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'report.created',
id: 'sidebarPendingReviewReportCount',
handler: this.incrementPendingReviewReportsCountInSidebar
})
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'user_request.created',
id: 'sidebarPendingReviewRequestCount',
handler: this.incrementPendingReviewRequestsCountInSidebar
})
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'Listen',
id: 'handleListen',
handler: this.handleListen
})
},
mounted () {
const self = this
// slight hack to allow use to have internal links in <translate> tags
// while preserving router behaviour
document.documentElement.addEventListener('click', function (event) {
if (!event.target.matches('a.internal')) return
self.$router.push(event.target.getAttribute('href'))
event.preventDefault()
}, false)
this.$nextTick(() => {
document.getElementById('fake-content').classList.add('loaded')
})
},
destroyed () {
this.$store.commit('ui/removeWebsocketEventHandler', {
eventName: 'inbox.item_added',
id: 'sidebarCount'
})
this.$store.commit('ui/removeWebsocketEventHandler', {
eventName: 'mutation.created',
id: 'sidebarReviewEditCount'
})
this.$store.commit('ui/removeWebsocketEventHandler', {
eventName: 'mutation.updated',
id: 'sidebarReviewEditCount'
})
this.$store.commit('ui/removeWebsocketEventHandler', {
eventName: 'mutation.updated',
id: 'sidebarPendingReviewReportCount'
})
this.$store.commit('ui/removeWebsocketEventHandler', {
eventName: 'user_request.created',
id: 'sidebarPendingReviewRequestCount'
})
this.$store.commit('ui/removeWebsocketEventHandler', {
eventName: 'Listen',
id: 'handleListen'
})
},
methods: {
incrementNotificationCountInSidebar (event) {
this.$store.commit('ui/incrementNotifications', { type: 'inbox', count: 1 })
},
incrementReviewEditCountInSidebar (event) {
this.$store.commit('ui/incrementNotifications', { type: 'pendingReviewEdits', value: event.pending_review_count })
},
incrementPendingReviewReportsCountInSidebar (event) {
this.$store.commit('ui/incrementNotifications', { type: 'pendingReviewReports', value: event.unresolved_count })
},
incrementPendingReviewRequestsCountInSidebar (event) {
this.$store.commit('ui/incrementNotifications', { type: 'pendingReviewRequests', value: event.pending_count })
},
handleListen (event) {
if (this.$store.state.radios.current && this.$store.state.radios.running) {
const current = this.$store.state.radios.current
if (current.clientOnly && current.type === 'account') {
getClientOnlyRadio(current).handleListen(current, event, this.$store)
}
}
},
async fetchNodeInfo () {
const response = await axios.get('instance/nodeinfo/2.0/')
this.$store.commit('instance/nodeinfo', response.data)
},
autodetectLanguage () {
const userLanguage = navigator.language || navigator.userLanguage
const available = locales.locales.map(e => { return e.code })
let candidate
const matching = available.filter((a) => {
return userLanguage.replace('-', '_') === a
})
const almostMatching = available.filter((a) => {
return userLanguage.replace('-', '_').split('_')[0] === a.split('_')[0]
})
if (matching.length > 0) {
candidate = matching[0]
} else if (almostMatching.length > 0) {
candidate = almostMatching[0]
} else {
return
}
this.$store.commit('ui/currentLanguage', candidate)
},
getTrackInformationText (track) {
const trackTitle = track.title
const albumArtist = (track.album) ? track.album.artist.name : null
const artistName = (
(track.artist) ? track.artist.name : albumArtist)
const text = `♫ ${trackTitle}${artistName} ♫`
return text
},
updateDocumentTitle () {
const parts = []
const currentTrackPart = (
(this.currentTrack)
? this.getTrackInformationText(this.currentTrack)
: null)
if (currentTrackPart) {
parts.push(currentTrackPart)
}
if (this.$store.state.ui.pageTitle) {
parts.push(this.$store.state.ui.pageTitle)
}
parts.push(this.initialTitle || 'Funkwhale')
document.title = parts.join('')
},
updateApp () {
this.$store.commit('ui/serviceWorker', { updateAvailable: false })
if (!this.serviceWorker.registration || !this.serviceWorker.registration.waiting) { return }
this.serviceWorker.registration.waiting.postMessage({ command: 'skipWaiting' })
},
handleResize () {
this.width = window.innerWidth
},
handleThemeChange (event) {
this.setTheme(event.matches ? 'dark' : 'light')
},
setTheme (theme) {
const oldTheme = (theme === 'light') ? 'dark' : 'light'
document.body.classList.remove(`theme-${oldTheme}`)
document.body.classList.add(`theme-${theme}`)
}
}
}
</script>
<style lang="scss">
@import "style/_main";
</style>
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
declare module '@vue/runtime-core' {
export interface GlobalComponents {
About: typeof import('./components/About.vue')['default']
AboutPod: typeof import('./components/AboutPod.vue')['default']
AccountsTable: typeof import('./components/manage/moderation/AccountsTable.vue')['default']
ActionFeedback: typeof import('./components/common/ActionFeedback.vue')['default']
ActionTable: typeof import('./components/common/ActionTable.vue')['default']
ActorAvatar: typeof import('./components/common/ActorAvatar.vue')['default']
ActorLink: typeof import('./components/common/ActorLink.vue')['default']
AjaxButton: typeof import('./components/common/AjaxButton.vue')['default']
AlbumBase: typeof import('./components/library/AlbumBase.vue')['default']
AlbumDetail: typeof import('./components/library/AlbumDetail.vue')['default']
AlbumDropdown: typeof import('./components/library/AlbumDropdown.vue')['default']
AlbumEdit: typeof import('./components/library/AlbumEdit.vue')['default']
AlbumForm: typeof import('./components/channels/AlbumForm.vue')['default']
AlbumModal: typeof import('./components/channels/AlbumModal.vue')['default']
Albums: typeof import('./components/library/Albums.vue')['default']
AlbumSelect: typeof import('./components/channels/AlbumSelect.vue')['default']
AlbumsTable: typeof import('./components/manage/library/AlbumsTable.vue')['default']
ApplicationEdit: typeof import('./components/auth/ApplicationEdit.vue')['default']
ApplicationForm: typeof import('./components/auth/ApplicationForm.vue')['default']
ApplicationNew: typeof import('./components/auth/ApplicationNew.vue')['default']
ArtistBase: typeof import('./components/library/ArtistBase.vue')['default']
ArtistDetail: typeof import('./components/library/ArtistDetail.vue')['default']
ArtistEdit: typeof import('./components/library/ArtistEdit.vue')['default']
ArtistLabel: typeof import('./components/audio/ArtistLabel.vue')['default']
Artists: typeof import('./components/library/Artists.vue')['default']
ArtistsTable: typeof import('./components/manage/library/ArtistsTable.vue')['default']
AttachmentInput: typeof import('./components/common/AttachmentInput.vue')['default']
Authorize: typeof import('./components/auth/Authorize.vue')['default']
Builder: typeof import('./components/library/radios/Builder.vue')['default']
Button: typeof import('./components/radios/Button.vue')['default']
Card: typeof import('./components/playlists/Card.vue')['default']
CardList: typeof import('./components/playlists/CardList.vue')['default']
ChannelCard: typeof import('./components/audio/ChannelCard.vue')['default']
ChannelEntries: typeof import('./components/audio/ChannelEntries.vue')['default']
ChannelEntryCard: typeof import('./components/audio/ChannelEntryCard.vue')['default']
ChannelForm: typeof import('./components/audio/ChannelForm.vue')['default']
ChannelSerieCard: typeof import('./components/audio/ChannelSerieCard.vue')['default']
ChannelSeries: typeof import('./components/audio/ChannelSeries.vue')['default']
ChannelsTable: typeof import('./components/manage/ChannelsTable.vue')['default']
ChannelsWidget: typeof import('./components/audio/ChannelsWidget.vue')['default']
CollapseLink: typeof import('./components/common/CollapseLink.vue')['default']
ContentForm: typeof import('./components/common/ContentForm.vue')['default']
CopyInput: typeof import('./components/common/CopyInput.vue')['default']
DangerousButton: typeof import('./components/common/DangerousButton.vue')['default']
DomainsTable: typeof import('./components/manage/moderation/DomainsTable.vue')['default']
Duration: typeof import('./components/common/Duration.vue')['default']
EditCard: typeof import('./components/library/EditCard.vue')['default']
EditDetail: typeof import('./components/library/EditDetail.vue')['default']
EditForm: typeof import('./components/library/EditForm.vue')['default']
EditList: typeof import('./components/library/EditList.vue')['default']
Editor: typeof import('./components/playlists/Editor.vue')['default']
EditsCardList: typeof import('./components/manage/library/EditsCardList.vue')['default']
EmbedWizard: typeof import('./components/audio/EmbedWizard.vue')['default']
EmptyState: typeof import('./components/common/EmptyState.vue')['default']
ExpandableDiv: typeof import('./components/common/ExpandableDiv.vue')['default']
FetchButton: typeof import('./components/federation/FetchButton.vue')['default']
FileUpload: typeof import('./components/library/FileUpload.vue')['default']
FileUploadWidget: typeof import('./components/library/FileUploadWidget.vue')['default']
Filter: typeof import('./components/library/radios/Filter.vue')['default']
FilterModal: typeof import('./components/moderation/FilterModal.vue')['default']
Footer: typeof import('./components/Footer.vue')['default']
Form: typeof import('./components/playlists/Form.vue')['default']
FsBrowser: typeof import('./components/library/FsBrowser.vue')['default']
FsLogs: typeof import('./components/library/FsLogs.vue')['default']
GlobalEvents: typeof import('./components/utils/global-events.vue')['default']
Home: typeof import('./components/Home.vue')['default']
HumanDate: typeof import('./components/common/HumanDate.vue')['default']
HumanDuration: typeof import('./components/common/HumanDuration.vue')['default']
ImportStatusModal: typeof import('./components/library/ImportStatusModal.vue')['default']
InlineSearchBar: typeof import('./components/common/InlineSearchBar.vue')['default']
InstancePolicyCard: typeof import('./components/manage/moderation/InstancePolicyCard.vue')['default']
InstancePolicyForm: typeof import('./components/manage/moderation/InstancePolicyForm.vue')['default']
InstancePolicyModal: typeof import('./components/manage/moderation/InstancePolicyModal.vue')['default']
InvitationForm: typeof import('./components/manage/users/InvitationForm.vue')['default']
InvitationsTable: typeof import('./components/manage/users/InvitationsTable.vue')['default']
LibrariesTable: typeof import('./components/manage/library/LibrariesTable.vue')['default']
Library: typeof import('./components/library/Library.vue')['default']
LibraryFollowButton: typeof import('./components/audio/LibraryFollowButton.vue')['default']
LibraryWidget: typeof import('./components/federation/LibraryWidget.vue')['default']
LicenseSelect: typeof import('./components/channels/LicenseSelect.vue')['default']
List: typeof import('./components/favorites/List.vue')['default']
LoginForm: typeof import('./components/auth/LoginForm.vue')['default']
LoginModal: typeof import('./components/common/LoginModal.vue')['default']
Logo: typeof import('./components/Logo.vue')['default']
LogoText: typeof import('./components/LogoText.vue')['default']
Logout: typeof import('./components/auth/Logout.vue')['default']
Message: typeof import('./components/common/Message.vue')['default']
MobileRow: typeof import('./components/audio/podcast/MobileRow.vue')['default']
Modal: typeof import('./components/semantic/Modal.vue')['default']
NoteForm: typeof import('./components/manage/moderation/NoteForm.vue')['default']
NotesThread: typeof import('./components/manage/moderation/NotesThread.vue')['default']
NotificationRow: typeof import('./components/notifications/NotificationRow.vue')['default']
Ordering: typeof import('./components/mixins/Ordering.vue')['default']
PageNotFound: typeof import('./components/PageNotFound.vue')['default']
Pagination: typeof import('./components/Pagination.vue')['default']
PasswordInput: typeof import('./components/forms/PasswordInput.vue')['default']
PlayButton: typeof import('./components/audio/PlayButton.vue')['default']
Player: typeof import('./components/audio/Player.vue')['default']
PlayIndicator: typeof import('./components/audio/track/PlayIndicator.vue')['default']
PlaylistModal: typeof import('./components/playlists/PlaylistModal.vue')['default']
PlayOptions: typeof import('./components/mixins/PlayOptions.vue')['default']
Plugin: typeof import('./components/auth/Plugin.vue')['default']
Podcasts: typeof import('./components/library/Podcasts.vue')['default']
Queue: typeof import('./components/Queue.vue')['default']
Radios: typeof import('./components/library/Radios.vue')['default']
RemoteSearchForm: typeof import('./components/RemoteSearchForm.vue')['default']
RenderedDescription: typeof import('./components/common/RenderedDescription.vue')['default']
Report: typeof import('./components/mixins/Report.vue')['default']
ReportCard: typeof import('./components/manage/moderation/ReportCard.vue')['default']
ReportCategoryDropdown: typeof import('./components/moderation/ReportCategoryDropdown.vue')['default']
ReportModal: typeof import('./components/moderation/ReportModal.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Row: typeof import('./components/audio/podcast/Row.vue')['default']
Search: typeof import('./components/audio/Search.vue')['default']
SearchBar: typeof import('./components/audio/SearchBar.vue')['default']
ServiceMessages: typeof import('./components/ServiceMessages.vue')['default']
SetInstanceModal: typeof import('./components/SetInstanceModal.vue')['default']
Settings: typeof import('./components/auth/Settings.vue')['default']
SettingsGroup: typeof import('./components/admin/SettingsGroup.vue')['default']
ShortcutsModal: typeof import('./components/ShortcutsModal.vue')['default']
Sidebar: typeof import('./components/Sidebar.vue')['default']
</