Merge branch '327-versatile-front' into 'develop'
Resolve "Make it possible to point the front-end to custom API urls" Closes #327 See merge request funkwhale/funkwhale!269
This commit is contained in:
commit
7d60155b0f
|
@ -7,11 +7,53 @@ variables:
|
||||||
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- review
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
|
review:
|
||||||
|
stage: review
|
||||||
|
image: node:9
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
before_script:
|
||||||
|
- cd front
|
||||||
|
script:
|
||||||
|
- yarn install
|
||||||
|
# this is to ensure we don't have any errors in the output,
|
||||||
|
# cf https://code.eliotberriot.com/funkwhale/funkwhale/issues/169
|
||||||
|
- INSTANCE_URL=$REVIEW_INSTANCE_URL yarn run build | tee /dev/stderr | (! grep -i 'ERROR in')
|
||||||
|
- mkdir -p /static/$CI_BUILD_REF_SLUG
|
||||||
|
- cp -r dist/* /static/$CI_BUILD_REF_SLUG
|
||||||
|
cache:
|
||||||
|
key: "$CI_PROJECT_ID__front_dependencies"
|
||||||
|
paths:
|
||||||
|
- front/node_modules
|
||||||
|
- front/yarn.lock
|
||||||
|
environment:
|
||||||
|
name: review/$CI_BUILD_REF_NAME
|
||||||
|
url: http://$CI_BUILD_REF_SLUG.$REVIEW_DOMAIN
|
||||||
|
on_stop: stop_review
|
||||||
|
only:
|
||||||
|
- branches@funkwhale/funkwhale
|
||||||
|
tags:
|
||||||
|
- funkwhale-review
|
||||||
|
|
||||||
|
stop_review:
|
||||||
|
stage: review
|
||||||
|
script:
|
||||||
|
- rm -rf /static/$CI_BUILD_REF_SLUG/
|
||||||
|
variables:
|
||||||
|
GIT_STRATEGY: none
|
||||||
|
when: manual
|
||||||
|
environment:
|
||||||
|
name: review/$CI_BUILD_REF_NAME
|
||||||
|
action: stop
|
||||||
|
tags:
|
||||||
|
- funkwhale-review
|
||||||
|
|
||||||
black:
|
black:
|
||||||
image: python:3.6
|
image: python:3.6
|
||||||
stage: lint
|
stage: lint
|
||||||
|
|
36
CONTRIBUTING
36
CONTRIBUTING
|
@ -12,6 +12,42 @@ This document will guide you through common operations such as:
|
||||||
- Writing unit tests to validate your work
|
- Writing unit tests to validate your work
|
||||||
- Submit your work
|
- Submit your work
|
||||||
|
|
||||||
|
A quick path to contribute on the front-end
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
The next sections of this document include a full installation guide to help
|
||||||
|
you setup a local, development version of Funkwhale. If you only want to fix small things
|
||||||
|
on the front-end, and don't want to manage a full development environment, there is anoter way.
|
||||||
|
|
||||||
|
As the front-end can work with any Funkwhale server, you can work with the front-end only,
|
||||||
|
and make it talk with an existing instance (like the demo one, or you own instance, if you have one).
|
||||||
|
|
||||||
|
If even that is too much for you, you can also make your changes without any development environment,
|
||||||
|
and open a merge request. We will be able to to review your work easily by spawning automatically a
|
||||||
|
live version of your changes, thanks to Gitlab Review apps.
|
||||||
|
|
||||||
|
Setup front-end only development environment
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
1. Clone the repository::
|
||||||
|
|
||||||
|
git clone ssh://git@code.eliotberriot.com:2222/funkwhale/funkwhale.git
|
||||||
|
cd funkwhale
|
||||||
|
cd front
|
||||||
|
|
||||||
|
2. Install [nodejs](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/lang/en/docs/install/#debian-stable)
|
||||||
|
3. Install the dependencies::
|
||||||
|
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
4. Launch the development server::
|
||||||
|
|
||||||
|
# this will serve the front-end on http://localhost:8000
|
||||||
|
WEBPACK_DEVSERVER_PORT=8000 yarn dev
|
||||||
|
|
||||||
|
5. Make the front-end talk with an existing server (like https://demo.funkwhale.audio),
|
||||||
|
by clicking on the corresponding link in the footer
|
||||||
|
6. Start hacking!
|
||||||
|
|
||||||
Setup your development environment
|
Setup your development environment
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
Funkwhale's front-end can now point to any instance (#327)
|
||||||
|
|
||||||
|
Removed front-end and back-end coupling
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Eventhough Funkwhale's front-end has always been a Single Page Application,
|
||||||
|
talking to an API, it was only able to talk to an API on the same domain.
|
||||||
|
|
||||||
|
There was no real technical justification behind this (only lazyness), and it was
|
||||||
|
also blocking interesting use cases:
|
||||||
|
|
||||||
|
- Use multiple customized versions of the front-end with the same instance
|
||||||
|
- Use a customized version of the front-end with multiple instances
|
||||||
|
- Use a locally hosted front-end with a remote API, which is especially useful in development
|
||||||
|
|
||||||
|
From now on, Funkwhale's front-end can connect to any Funkwhale server. You can
|
||||||
|
change the server you are connecting to in the footer.
|
||||||
|
|
||||||
|
Fixing this also unlocked a really interesting feature in our development/review workflow:
|
||||||
|
by leveraging Gitlab CI and review apps, we are now able to deploy automatically live versions of
|
||||||
|
a merge request, making it possible for anyone to review front-end changes easily, without
|
||||||
|
the need to install a local environment.
|
|
@ -1,4 +1,5 @@
|
||||||
|
let url = process.env.INSTANCE_URL || '/'
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NODE_ENV: '"production"',
|
NODE_ENV: '"production"',
|
||||||
BACKEND_URL: '"/"'
|
INSTANCE_URL: `"${url}"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,71 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<sidebar></sidebar>
|
<div class="ui main text container instance-chooser" v-if="!$store.state.instance.instanceUrl">
|
||||||
<service-messages v-if="messages.length > 0" />
|
<div class="ui padded segment">
|
||||||
<router-view :key="$route.fullPath"></router-view>
|
<h1 class="ui header">{{ $t('Choose your instance') }}</h1>
|
||||||
<div class="ui fitted divider"></div>
|
<form class="ui form" @submit.prevent="$store.dispatch('instance/setUrl', instanceUrl)">
|
||||||
<div id="footer" class="ui vertical footer segment">
|
<p>{{ $t('You need to select an instance in order to continue') }}</p>
|
||||||
<div class="ui container">
|
<div class="ui action input">
|
||||||
<div class="ui stackable equal height stackable grid">
|
<input type="text" v-model="instanceUrl">
|
||||||
<div class="three wide column">
|
<button type="submit" class="ui button">{{ $t('Submit') }}</button>
|
||||||
<i18next tag="h4" class="ui header" path="Links"></i18next>
|
</div>
|
||||||
<div class="ui link list">
|
<p>{{ $t('Suggested choices') }}</p>
|
||||||
<router-link class="item" to="/about">
|
<div class="ui bulleted list">
|
||||||
<i18next path="About this instance" />
|
<div class="ui item" v-for="url in suggestedInstances">
|
||||||
</router-link>
|
<a @click="instanceUrl = url">{{ url }}</a>
|
||||||
<a href="https://funkwhale.audio" class="item" target="_blank">{{ $t('Official website') }}</a>
|
|
||||||
<a href="https://docs.funkwhale.audio" class="item" target="_blank">{{ $t('Documentation') }}</a>
|
|
||||||
<a href="https://code.eliotberriot.com/funkwhale/funkwhale" class="item" target="_blank">
|
|
||||||
<template v-if="version">{{ $t('Source code ({% version %})', {version: version}) }}</template>
|
|
||||||
<template v-else>{{ $t('Source code') }}</template>
|
|
||||||
</a>
|
|
||||||
<a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank">{{ $t('Issue tracker') }}</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ten wide column">
|
</form>
|
||||||
<i18next tag="h4" class="ui header" path="About funkwhale" />
|
</div>
|
||||||
<p>
|
</div>
|
||||||
<i18next path="Funkwhale is a free and open-source project run by volunteers. You can help us improve the platform by reporting bugs, suggesting features and share the project with your friends!"/>
|
<template v-else>
|
||||||
</p>
|
<sidebar></sidebar>
|
||||||
<p>
|
<service-messages v-if="messages.length > 0" />
|
||||||
<i18next path="The funkwhale logo was kindly designed and provided by Francis Gading."/>
|
<router-view :key="$route.fullPath"></router-view>
|
||||||
</p>
|
<div class="ui fitted divider"></div>
|
||||||
|
<div id="footer" class="ui vertical footer segment">
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui stackable equal height stackable grid">
|
||||||
|
<div class="three wide column">
|
||||||
|
<i18next tag="h4" class="ui header" path="Links"></i18next>
|
||||||
|
<div class="ui link list">
|
||||||
|
<router-link class="item" to="/about">
|
||||||
|
<i18next path="About this instance" />
|
||||||
|
</router-link>
|
||||||
|
<a href="https://funkwhale.audio" class="item" target="_blank">{{ $t('Official website') }}</a>
|
||||||
|
<a href="https://docs.funkwhale.audio" class="item" target="_blank">{{ $t('Documentation') }}</a>
|
||||||
|
<a href="https://code.eliotberriot.com/funkwhale/funkwhale" class="item" target="_blank">
|
||||||
|
<template v-if="version">{{ $t('Source code ({% version %})', {version: version}) }}</template>
|
||||||
|
<template v-else>{{ $t('Source code') }}</template>
|
||||||
|
</a>
|
||||||
|
<a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank">{{ $t('Issue tracker') }}</a>
|
||||||
|
<a @click="switchInstance" class="item" >
|
||||||
|
{{ $t('Use another instance') }}
|
||||||
|
<template v-if="$store.state.instance.instanceUrl !== '/'">
|
||||||
|
<br>
|
||||||
|
({{ $store.state.instance.instanceUrl }})
|
||||||
|
</template>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ten wide column">
|
||||||
|
<i18next tag="h4" class="ui header" path="About funkwhale" />
|
||||||
|
<p>
|
||||||
|
<i18next path="Funkwhale is a free and open-source project run by volunteers. You can help us improve the platform by reporting bugs, suggesting features and share the project with your friends!"/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i18next path="The funkwhale logo was kindly designed and provided by Francis Gading."/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<raven
|
||||||
<raven
|
v-if="$store.state.instance.settings.raven.front_enabled.value"
|
||||||
v-if="$store.state.instance.settings.raven.front_enabled.value"
|
:dsn="$store.state.instance.settings.raven.front_dsn.value">
|
||||||
:dsn="$store.state.instance.settings.raven.front_dsn.value">
|
</raven>
|
||||||
</raven>
|
<playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal>
|
||||||
<playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -63,17 +90,22 @@ export default {
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
nodeinfo: null
|
nodeinfo: null,
|
||||||
|
instanceUrl: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.$store.dispatch('instance/fetchSettings')
|
|
||||||
let self = this
|
let self = this
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
// used to redraw ago dates every minute
|
// used to redraw ago dates every minute
|
||||||
self.$store.commit('ui/computeLastDate')
|
self.$store.commit('ui/computeLastDate')
|
||||||
}, 1000 * 60)
|
}, 1000 * 60)
|
||||||
this.fetchNodeInfo()
|
if (this.$store.state.instance.instanceUrl) {
|
||||||
|
this.$store.commit('instance/instanceUrl', this.$store.state.instance.instanceUrl)
|
||||||
|
this.$store.dispatch('auth/check')
|
||||||
|
this.$store.dispatch('instance/fetchSettings')
|
||||||
|
this.fetchNodeInfo()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchNodeInfo () {
|
fetchNodeInfo () {
|
||||||
|
@ -81,18 +113,38 @@ export default {
|
||||||
axios.get('instance/nodeinfo/2.0/').then(response => {
|
axios.get('instance/nodeinfo/2.0/').then(response => {
|
||||||
self.nodeinfo = response.data
|
self.nodeinfo = response.data
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
switchInstance () {
|
||||||
|
let confirm = window.confirm(this.$t('This will erase your local data and disconnect you, do you want to continue?'))
|
||||||
|
if (confirm) {
|
||||||
|
this.$store.commit('instance/instanceUrl', null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
messages: state => state.ui.messages
|
messages: state => state.ui.messages
|
||||||
}),
|
}),
|
||||||
|
suggestedInstances () {
|
||||||
|
let rootUrl = (
|
||||||
|
window.location.protocol + '//' + window.location.hostname +
|
||||||
|
(window.location.port ? ':' + window.location.port : '')
|
||||||
|
)
|
||||||
|
let instances = [rootUrl, 'https://demo.funkwhale.audio']
|
||||||
|
return instances
|
||||||
|
},
|
||||||
version () {
|
version () {
|
||||||
if (!this.nodeinfo) {
|
if (!this.nodeinfo) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return _.get(this.nodeinfo, 'software.version')
|
return _.get(this.nodeinfo, 'software.version')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$store.state.instance.instanceUrl' () {
|
||||||
|
this.$store.dispatch('instance/fetchSettings')
|
||||||
|
this.fetchNodeInfo()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -116,6 +168,11 @@ html, body {
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.instance-chooser {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
.main.pusher, .footer {
|
.main.pusher, .footer {
|
||||||
@include media(">desktop") {
|
@include media(">desktop") {
|
||||||
margin-left: 350px !important;
|
margin-left: 350px !important;
|
||||||
|
@ -173,7 +230,7 @@ html, body {
|
||||||
.ui.icon.header .circular.icon {
|
.ui.icon.header .circular.icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.segment-content .button{
|
.segment-content .button{
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import config from '@/config'
|
|
||||||
|
|
||||||
var Album = {
|
var Album = {
|
||||||
clean (album) {
|
clean (album) {
|
||||||
// we manually rebind the album and artist to each child track
|
// we manually rebind the album and artist to each child track
|
||||||
|
@ -21,21 +19,6 @@ var Artist = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default {
|
export default {
|
||||||
absoluteUrl (url) {
|
|
||||||
if (url.startsWith('http')) {
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
if (url.startsWith('/')) {
|
|
||||||
let rootUrl = (
|
|
||||||
window.location.protocol + '//' + window.location.hostname +
|
|
||||||
(window.location.port ? ':' + window.location.port : '')
|
|
||||||
)
|
|
||||||
return rootUrl + url
|
|
||||||
} else {
|
|
||||||
return config.BACKEND_URL + url
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Artist: Artist,
|
Artist: Artist,
|
||||||
Album: Album
|
Album: Album
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import backend from './backend'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getCover (track) {
|
|
||||||
return backend.absoluteUrl(track.album.cover)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -120,7 +120,7 @@
|
||||||
<tr @click="$store.dispatch('queue/currentIndex', index)" v-for="(track, index) in queue.tracks" :key="index" :class="[{'active': index === queue.currentIndex}]">
|
<tr @click="$store.dispatch('queue/currentIndex', index)" v-for="(track, index) in queue.tracks" :key="index" :class="[{'active': index === queue.currentIndex}]">
|
||||||
<td class="right aligned">{{ index + 1}}</td>
|
<td class="right aligned">{{ index + 1}}</td>
|
||||||
<td class="center aligned">
|
<td class="center aligned">
|
||||||
<img class="ui mini image" v-if="track.album.cover" :src="backend.absoluteUrl(track.album.cover)">
|
<img class="ui mini image" v-if="track.album.cover" :src="$store.getters['instance/absoluteUrl'](track.album.cover)">
|
||||||
<img class="ui mini image" v-else src="../assets/audio/default-cover.png">
|
<img class="ui mini image" v-else src="../assets/audio/default-cover.png">
|
||||||
</td>
|
</td>
|
||||||
<td colspan="4">
|
<td colspan="4">
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div v-if="currentTrack" class="track-area ui unstackable items">
|
<div v-if="currentTrack" class="track-area ui unstackable items">
|
||||||
<div class="ui inverted item">
|
<div class="ui inverted item">
|
||||||
<div class="ui tiny image">
|
<div class="ui tiny image">
|
||||||
<img ref="cover" @load="updateBackground" v-if="currentTrack.album.cover" :src="Track.getCover(currentTrack)">
|
<img ref="cover" @load="updateBackground" v-if="currentTrack.album.cover" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover)">
|
||||||
<img v-else src="../../assets/audio/default-cover.png">
|
<img v-else src="../../assets/audio/default-cover.png">
|
||||||
</div>
|
</div>
|
||||||
<div class="middle aligned content">
|
<div class="middle aligned content">
|
||||||
|
@ -143,7 +143,6 @@ import {mapState, mapGetters, mapActions} from 'vuex'
|
||||||
import GlobalEvents from '@/components/utils/global-events'
|
import GlobalEvents from '@/components/utils/global-events'
|
||||||
import ColorThief from '@/vendor/color-thief'
|
import ColorThief from '@/vendor/color-thief'
|
||||||
|
|
||||||
import Track from '@/audio/track'
|
|
||||||
import AudioTrack from '@/components/audio/Track'
|
import AudioTrack from '@/components/audio/Track'
|
||||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
||||||
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
||||||
|
@ -162,7 +161,6 @@ export default {
|
||||||
isShuffling: false,
|
isShuffling: false,
|
||||||
renderAudio: true,
|
renderAudio: true,
|
||||||
sliderVolume: this.volume,
|
sliderVolume: this.volume,
|
||||||
Track: Track,
|
|
||||||
defaultAmbiantColors: defaultAmbiantColors,
|
defaultAmbiantColors: defaultAmbiantColors,
|
||||||
ambiantColors: defaultAmbiantColors
|
ambiantColors: defaultAmbiantColors
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import jQuery from 'jquery'
|
import jQuery from 'jquery'
|
||||||
import config from '@/config'
|
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
|
||||||
const SEARCH_URL = config.API_URL + 'search?query={query}'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mounted () {
|
mounted () {
|
||||||
let self = this
|
let self = this
|
||||||
|
@ -94,7 +91,7 @@ export default {
|
||||||
})
|
})
|
||||||
return {results: results}
|
return {results: results}
|
||||||
},
|
},
|
||||||
url: SEARCH_URL
|
url: this.$store.getters['instance/absoluteUrl']('search?query={query}')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let sources = [
|
let sources = [
|
||||||
{type: file.mimetype, url: file.path}
|
{type: file.mimetype, url: this.$store.getters['instance/absoluteUrl'](file.path)}
|
||||||
]
|
]
|
||||||
if (this.$store.state.auth.authenticated) {
|
if (this.$store.state.auth.authenticated) {
|
||||||
// we need to send the token directly in url
|
// we need to send the token directly in url
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="ui card">
|
<div class="ui card">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="right floated tiny ui image">
|
<div class="right floated tiny ui image">
|
||||||
<img v-if="album.cover" v-lazy="backend.absoluteUrl(album.cover)">
|
<img v-if="album.cover" v-lazy="$store.getters['instance/absoluteUrl'](album.cover)">
|
||||||
<img v-else src="../../../assets/audio/default-cover.png">
|
<img v-else src="../../../assets/audio/default-cover.png">
|
||||||
</div>
|
</div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="album in albums">
|
<tr v-for="album in albums">
|
||||||
<td>
|
<td>
|
||||||
<img class="ui mini image" v-if="album.cover" :src="backend.absoluteUrl(album.cover)">
|
<img class="ui mini image" v-if="album.cover" :src="$store.getters['instance/absoluteUrl'](album.cover)">
|
||||||
<img class="ui mini image" v-else src="../../../assets/audio/default-cover.png">
|
<img class="ui mini image" v-else src="../../../assets/audio/default-cover.png">
|
||||||
</td>
|
</td>
|
||||||
<td colspan="4">
|
<td colspan="4">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<play-button class="basic icon" :discrete="true" :track="track"></play-button>
|
<play-button class="basic icon" :discrete="true" :track="track"></play-button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<img class="ui mini image" v-if="track.album.cover" v-lazy="backend.absoluteUrl(track.album.cover)">
|
<img class="ui mini image" v-if="track.album.cover" v-lazy="$store.getters['instance/absoluteUrl'](track.album.cover)">
|
||||||
<img class="ui mini image" v-else src="../../..//assets/audio/default-cover.png">
|
<img class="ui mini image" v-else src="../../..//assets/audio/default-cover.png">
|
||||||
</td>
|
</td>
|
||||||
<td colspan="6">
|
<td colspan="6">
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<pre>
|
<pre>
|
||||||
export PRIVATE_TOKEN="{{ $store.state.auth.token }}"
|
export PRIVATE_TOKEN="{{ $store.state.auth.token }}"
|
||||||
<template v-for="track in tracks"><template v-if="track.files.length > 0">
|
<template v-for="track in tracks"><template v-if="track.files.length > 0">
|
||||||
curl -G -o "{{ track.files[0].filename }}" <template v-if="$store.state.auth.authenticated">--header "Authorization: JWT $PRIVATE_TOKEN"</template> "{{ backend.absoluteUrl(track.files[0].path) }}"</template></template>
|
curl -G -o "{{ track.files[0].filename }}" <template v-if="$store.state.auth.authenticated">--header "Authorization: JWT $PRIVATE_TOKEN"</template> "{{ $store.getters['instance/absoluteUrl'](track.files[0].path) }}"</template></template>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default {
|
||||||
if (!this.album.cover) {
|
if (!this.album.cover) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return 'background-image: url(' + backend.absoluteUrl(this.album.cover) + ')'
|
return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.album.cover) + ')'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -127,7 +127,7 @@ export default {
|
||||||
if (!this.cover) {
|
if (!this.cover) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return 'background-image: url(' + backend.absoluteUrl(this.cover) + ')'
|
return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.cover) + ')'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -108,7 +108,6 @@ import time from '@/utils/time'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import url from '@/utils/url'
|
import url from '@/utils/url'
|
||||||
import logger from '@/logging'
|
import logger from '@/logging'
|
||||||
import backend from '@/audio/backend'
|
|
||||||
import PlayButton from '@/components/audio/PlayButton'
|
import PlayButton from '@/components/audio/PlayButton'
|
||||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
||||||
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
||||||
|
@ -169,7 +168,7 @@ export default {
|
||||||
},
|
},
|
||||||
downloadUrl () {
|
downloadUrl () {
|
||||||
if (this.track.files.length > 0) {
|
if (this.track.files.length > 0) {
|
||||||
let u = backend.absoluteUrl(this.track.files[0].path)
|
let u = this.$store.getters['instance/absoluteUrl'](this.track.files[0].path)
|
||||||
if (this.$store.state.auth.authenticated) {
|
if (this.$store.state.auth.authenticated) {
|
||||||
u = url.updateQueryString(u, 'jwt', this.$store.state.auth.token)
|
u = url.updateQueryString(u, 'jwt', this.$store.state.auth.token)
|
||||||
}
|
}
|
||||||
|
@ -191,7 +190,7 @@ export default {
|
||||||
if (!this.cover) {
|
if (!this.cover) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return 'background-image: url(' + backend.absoluteUrl(this.cover) + ')'
|
return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.cover) + ')'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -63,7 +63,6 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import config from '@/config'
|
|
||||||
import $ from 'jquery'
|
import $ from 'jquery'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
checkResult: null,
|
checkResult: null,
|
||||||
showCandidadesModal: false,
|
showCandidadesModal: false,
|
||||||
exclude: config.not
|
exclude: this.config.not
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
|
|
@ -43,8 +43,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
import backend from '@/audio/backend'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -72,7 +70,7 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getUrl (code) {
|
getUrl (code) {
|
||||||
return backend.absoluteUrl(this.$router.resolve({name: 'signup', query: {invitation: code.toUpperCase()}}).href)
|
return this.$store.getters['instance/absoluteUrl'](this.$router.resolve({name: 'signup', query: {invitation: code.toUpperCase()}}).href)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import jQuery from 'jquery'
|
import jQuery from 'jquery'
|
||||||
import config from '@/config'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -117,7 +116,7 @@ export default {
|
||||||
})[0]
|
})[0]
|
||||||
},
|
},
|
||||||
searchUrl: function () {
|
searchUrl: function () {
|
||||||
return config.API_URL + 'providers/musicbrainz/search/' + this.currentTypeObject.value + 's/?query={query}'
|
return this.$store.getters['instance/absoluteUrl']('providers/musicbrainz/search/' + this.currentTypeObject.value + 's/?query={query}')
|
||||||
},
|
},
|
||||||
types: function () {
|
types: function () {
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
class Config {
|
|
||||||
constructor () {
|
|
||||||
this.BACKEND_URL = process.env.BACKEND_URL
|
|
||||||
this.API_URL = this.BACKEND_URL + 'api/v1/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Config()
|
|
|
@ -15,7 +15,6 @@ import i18next from 'i18next'
|
||||||
import i18nextFetch from 'i18next-fetch-backend'
|
import i18nextFetch from 'i18next-fetch-backend'
|
||||||
import VueI18Next from '@panter/vue-i18next'
|
import VueI18Next from '@panter/vue-i18next'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import config from './config'
|
|
||||||
import { sync } from 'vuex-router-sync'
|
import { sync } from 'vuex-router-sync'
|
||||||
import filters from '@/filters' // eslint-disable-line
|
import filters from '@/filters' // eslint-disable-line
|
||||||
import globals from '@/components/globals' // eslint-disable-line
|
import globals from '@/components/globals' // eslint-disable-line
|
||||||
|
@ -56,8 +55,6 @@ Vue.directive('title', {
|
||||||
document.title = parts.join(' - ')
|
document.title = parts.join(' - ')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
axios.defaults.baseURL = config.API_URL
|
|
||||||
axios.interceptors.request.use(function (config) {
|
axios.interceptors.request.use(function (config) {
|
||||||
// Do something before request is sent
|
// Do something before request is sent
|
||||||
if (store.state.auth.token) {
|
if (store.state.auth.token) {
|
||||||
|
@ -104,7 +101,6 @@ axios.interceptors.response.use(function (response) {
|
||||||
// Do something with response error
|
// Do something with response error
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
})
|
})
|
||||||
store.dispatch('auth/check')
|
|
||||||
|
|
||||||
// i18n
|
// i18n
|
||||||
i18next
|
i18next
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default new Vuex.Store({
|
||||||
}),
|
}),
|
||||||
createPersistedState({
|
createPersistedState({
|
||||||
key: 'instance',
|
key: 'instance',
|
||||||
paths: ['instance.events']
|
paths: ['instance.events', 'instance.instanceUrl']
|
||||||
}),
|
}),
|
||||||
createPersistedState({
|
createPersistedState({
|
||||||
key: 'radios',
|
key: 'radios',
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
maxEvents: 200,
|
maxEvents: 200,
|
||||||
|
instanceUrl: process.env.INSTANCE_URL,
|
||||||
events: [],
|
events: [],
|
||||||
settings: {
|
settings: {
|
||||||
instance: {
|
instance: {
|
||||||
|
@ -51,9 +52,46 @@ export default {
|
||||||
},
|
},
|
||||||
events: (state, value) => {
|
events: (state, value) => {
|
||||||
state.events = value
|
state.events = value
|
||||||
|
},
|
||||||
|
instanceUrl: (state, value) => {
|
||||||
|
state.instanceUrl = value
|
||||||
|
if (!value) {
|
||||||
|
axios.defaults.baseURL = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let apiUrl
|
||||||
|
let suffix = 'api/v1/'
|
||||||
|
if (state.instanceUrl.endsWith('/')) {
|
||||||
|
apiUrl = state.instanceUrl + suffix
|
||||||
|
} else {
|
||||||
|
apiUrl = state.instanceUrl + '/' + suffix
|
||||||
|
}
|
||||||
|
axios.defaults.baseURL = apiUrl
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
absoluteUrl: (state) => (relativeUrl) => {
|
||||||
|
if (relativeUrl.startsWith('http')) {
|
||||||
|
return relativeUrl
|
||||||
|
}
|
||||||
|
return state.instanceUrl + relativeUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
setUrl ({commit, dispatch}, url) {
|
||||||
|
commit('instanceUrl', url)
|
||||||
|
let modules = [
|
||||||
|
'auth',
|
||||||
|
'favorites',
|
||||||
|
'player',
|
||||||
|
'playlists',
|
||||||
|
'queue',
|
||||||
|
'radios'
|
||||||
|
]
|
||||||
|
modules.forEach(m => {
|
||||||
|
commit(`${m}/reset`, null, {root: true})
|
||||||
|
})
|
||||||
|
},
|
||||||
// Send a request to the login URL and save the returned JWT
|
// Send a request to the login URL and save the returned JWT
|
||||||
fetchSettings ({commit}, payload) {
|
fetchSettings ({commit}, payload) {
|
||||||
return axios.get('instance/settings/').then(response => {
|
return axios.get('instance/settings/').then(response => {
|
||||||
|
|
Loading…
Reference in New Issue