diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 206bb50cc4a08fd53c59dcfa64c7f44427f2c823..4005c8911d6107b04c4686fc931459d382d49ddd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,11 +7,53 @@ variables: stages: + - review - lint - test - build - 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: image: python:3.6 stage: lint diff --git a/CONTRIBUTING b/CONTRIBUTING index 33f2c07478b2535a86bafed379860ca6ea17ad73..6fb76a56c08d0699dc4f69318552d7ef0f5b3de8 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -12,6 +12,42 @@ This document will guide you through common operations such as: - Writing unit tests to validate 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 ---------------------------------- diff --git a/changes/changelog.d/327.feature b/changes/changelog.d/327.feature new file mode 100644 index 0000000000000000000000000000000000000000..8e22e6542c64f31bb69a20e0bb094704c9433805 --- /dev/null +++ b/changes/changelog.d/327.feature @@ -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. diff --git a/front/config/prod.env.js b/front/config/prod.env.js index decfe36154adc59fbf4a432cecac77119bbcdbf7..40cf48973416fbe1cfaa181e54821b51730f5398 100644 --- a/front/config/prod.env.js +++ b/front/config/prod.env.js @@ -1,4 +1,5 @@ +let url = process.env.INSTANCE_URL || '/' module.exports = { NODE_ENV: '"production"', - BACKEND_URL: '"/"' + INSTANCE_URL: `"${url}"` } diff --git a/front/src/App.vue b/front/src/App.vue index 2eb673ab4bf1800920111616091aa80cffba1c08..56dbe0aad41f7af3d382202832ab6828bb431837 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -1,44 +1,71 @@ <template> <div id="app"> - <sidebar></sidebar> - <service-messages v-if="messages.length > 0" /> - <router-view :key="$route.fullPath"></router-view> - <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> + <div class="ui main text container instance-chooser" v-if="!$store.state.instance.instanceUrl"> + <div class="ui padded segment"> + <h1 class="ui header">{{ $t('Choose your instance') }}</h1> + <form class="ui form" @submit.prevent="$store.dispatch('instance/setUrl', instanceUrl)"> + <p>{{ $t('You need to select an instance in order to continue') }}</p> + <div class="ui action input"> + <input type="text" v-model="instanceUrl"> + <button type="submit" class="ui button">{{ $t('Submit') }}</button> + </div> + <p>{{Â $t('Suggested choices') }}</p> + <div class="ui bulleted list"> + <div class="ui item" v-for="url in suggestedInstances"> + <a @click="instanceUrl = url">{{ url }}</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> + </form> + </div> + </div> + <template v-else> + <sidebar></sidebar> + <service-messages v-if="messages.length > 0" /> + <router-view :key="$route.fullPath"></router-view> + <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> - <raven - v-if="$store.state.instance.settings.raven.front_enabled.value" - :dsn="$store.state.instance.settings.raven.front_dsn.value"> - </raven> - <playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal> + <raven + v-if="$store.state.instance.settings.raven.front_enabled.value" + :dsn="$store.state.instance.settings.raven.front_dsn.value"> + </raven> + <playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal> + </template> </div> </template> @@ -63,17 +90,22 @@ export default { }, data () { return { - nodeinfo: null + nodeinfo: null, + instanceUrl: null } }, created () { - this.$store.dispatch('instance/fetchSettings') let self = this setInterval(() => { // used to redraw ago dates every minute self.$store.commit('ui/computeLastDate') }, 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: { fetchNodeInfo () { @@ -81,18 +113,38 @@ export default { axios.get('instance/nodeinfo/2.0/').then(response => { 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: { ...mapState({ 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 () { if (!this.nodeinfo) { return null } return _.get(this.nodeinfo, 'software.version') } + }, + watch: { + '$store.state.instance.instanceUrl' () { + this.$store.dispatch('instance/fetchSettings') + this.fetchNodeInfo() + } } } </script> @@ -116,6 +168,11 @@ html, body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + +.instance-chooser { + margin-top: 2em; +} + .main.pusher, .footer { @include media(">desktop") { margin-left: 350px !important; @@ -173,7 +230,7 @@ html, body { .ui.icon.header .circular.icon { display: flex; justify-content: center; - + } .segment-content .button{ diff --git a/front/src/audio/backend.js b/front/src/audio/backend.js index 619f3cefdbd7b08f9879be343ce246e41e86de0d..5a82719a3a0d403ce6ee264eed47e2f5926068f0 100644 --- a/front/src/audio/backend.js +++ b/front/src/audio/backend.js @@ -1,5 +1,3 @@ -import config from '@/config' - var Album = { clean (album) { // we manually rebind the album and artist to each child track @@ -21,21 +19,6 @@ var Artist = { } } 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, Album: Album - } diff --git a/front/src/audio/track.js b/front/src/audio/track.js deleted file mode 100644 index 9873b74ec5405bd941999df63e6071a3ac8056d3..0000000000000000000000000000000000000000 --- a/front/src/audio/track.js +++ /dev/null @@ -1,7 +0,0 @@ -import backend from './backend' - -export default { - getCover (track) { - return backend.absoluteUrl(track.album.cover) - } -} diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index 065a0a03a76b039100a1f930e1a346e07a5bfc36..5415e1b0e036e66b83cb72f4e8611b376550e15d 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -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}]"> <td class="right aligned">{{ index + 1}}</td> <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"> </td> <td colspan="4"> diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index 3c922e14ad323d75413d969ba7c033add19e92e0..1cc27970b47aabc5571047713d1ee45e3f7b853a 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -14,7 +14,7 @@ <div v-if="currentTrack" class="track-area ui unstackable items"> <div class="ui inverted item"> <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"> </div> <div class="middle aligned content"> @@ -143,7 +143,6 @@ import {mapState, mapGetters, mapActions} from 'vuex' import GlobalEvents from '@/components/utils/global-events' import ColorThief from '@/vendor/color-thief' -import Track from '@/audio/track' import AudioTrack from '@/components/audio/Track' import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon' import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon' @@ -162,7 +161,6 @@ export default { isShuffling: false, renderAudio: true, sliderVolume: this.volume, - Track: Track, defaultAmbiantColors: defaultAmbiantColors, ambiantColors: defaultAmbiantColors } diff --git a/front/src/components/audio/SearchBar.vue b/front/src/components/audio/SearchBar.vue index 99896d04beda7bc51a559fb10b8205a45e254610..9b6dc50e2343cf514fced16d4b46d9431494b6db 100644 --- a/front/src/components/audio/SearchBar.vue +++ b/front/src/components/audio/SearchBar.vue @@ -11,11 +11,8 @@ <script> import jQuery from 'jquery' -import config from '@/config' import router from '@/router' -const SEARCH_URL = config.API_URL + 'search?query={query}' - export default { mounted () { let self = this @@ -94,7 +91,7 @@ export default { }) return {results: results} }, - url: SEARCH_URL + url: this.$store.getters['instance/absoluteUrl']('search?query={query}') } }) } diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue index 366f104f1fc021fbe5478346439e94034b345e2a..2ded7cb0076f29b2f78956aae2ff8703fb2da9c8 100644 --- a/front/src/components/audio/Track.vue +++ b/front/src/components/audio/Track.vue @@ -49,7 +49,7 @@ export default { return [] } 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) { // we need to send the token directly in url diff --git a/front/src/components/audio/album/Card.vue b/front/src/components/audio/album/Card.vue index 6742dca4f9b94e88983ad6ec6d4396b11dc14648..3782771803edce6432a125957e9d10346b073fbd 100644 --- a/front/src/components/audio/album/Card.vue +++ b/front/src/components/audio/album/Card.vue @@ -2,7 +2,7 @@ <div class="ui card"> <div class="content"> <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"> </div> <div class="header"> diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue index a46506791e083eb5cc750c8f84918a6bc1fb2318..b19c5e12d727b6a32c6a57c04e9dab4b0b9c3134 100644 --- a/front/src/components/audio/artist/Card.vue +++ b/front/src/components/audio/artist/Card.vue @@ -11,7 +11,7 @@ <tbody> <tr v-for="album in albums"> <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"> </td> <td colspan="4"> diff --git a/front/src/components/audio/track/Row.vue b/front/src/components/audio/track/Row.vue index 8310e89c4a4ad6ab749f2386003f1c59ae7dccea..bd3ceb2aaa869350fa013ca36c709f16cc97cdbc 100644 --- a/front/src/components/audio/track/Row.vue +++ b/front/src/components/audio/track/Row.vue @@ -4,7 +4,7 @@ <play-button class="basic icon" :discrete="true" :track="track"></play-button> </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"> </td> <td colspan="6"> diff --git a/front/src/components/audio/track/Table.vue b/front/src/components/audio/track/Table.vue index 4559b3c41b59ccf798eec071b97c1ab55a81dceb..81869ff564af2f8b5ee5260535b7fda5f95f924e 100644 --- a/front/src/components/audio/track/Table.vue +++ b/front/src/components/audio/track/Table.vue @@ -35,7 +35,7 @@ <pre> export PRIVATE_TOKEN="{{ $store.state.auth.token }}" <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> </div> </div> diff --git a/front/src/components/library/Album.vue b/front/src/components/library/Album.vue index 1681d46e3b44466f022f18d22ffa3ba78e1643b7..9a4288b8ac46a8e584fa8dbf23bae3a96fa4e5cc 100644 --- a/front/src/components/library/Album.vue +++ b/front/src/components/library/Album.vue @@ -87,7 +87,7 @@ export default { if (!this.album.cover) { return '' } - return 'background-image: url(' + backend.absoluteUrl(this.album.cover) + ')' + return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.album.cover) + ')' } }, watch: { diff --git a/front/src/components/library/Artist.vue b/front/src/components/library/Artist.vue index 7d0a41d8988055316ecb4773103e129e970f92c7..171b80e8b7b1f9a2ad8c7a65e9dc3c227e6e1b5a 100644 --- a/front/src/components/library/Artist.vue +++ b/front/src/components/library/Artist.vue @@ -127,7 +127,7 @@ export default { if (!this.cover) { return '' } - return 'background-image: url(' + backend.absoluteUrl(this.cover) + ')' + return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.cover) + ')' } }, watch: { diff --git a/front/src/components/library/Track.vue b/front/src/components/library/Track.vue index 24acca75b809445ce711f9fb90e38a65f7c974dd..af364e94d6af39e4d459e1cbdc9ab105560d2f5f 100644 --- a/front/src/components/library/Track.vue +++ b/front/src/components/library/Track.vue @@ -108,7 +108,6 @@ import time from '@/utils/time' import axios from 'axios' import url from '@/utils/url' import logger from '@/logging' -import backend from '@/audio/backend' import PlayButton from '@/components/audio/PlayButton' import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon' import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon' @@ -169,7 +168,7 @@ export default { }, downloadUrl () { 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) { u = url.updateQueryString(u, 'jwt', this.$store.state.auth.token) } @@ -191,7 +190,7 @@ export default { if (!this.cover) { return '' } - return 'background-image: url(' + backend.absoluteUrl(this.cover) + ')' + return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.cover) + ')' } }, watch: { diff --git a/front/src/components/library/radios/Filter.vue b/front/src/components/library/radios/Filter.vue index b27c36077c113b801bf842f84ad868cfecc87542..0d268dc60faad8355650aed431ad4174d22ce464 100644 --- a/front/src/components/library/radios/Filter.vue +++ b/front/src/components/library/radios/Filter.vue @@ -63,7 +63,6 @@ </template> <script> import axios from 'axios' -import config from '@/config' import $ from 'jquery' import _ from 'lodash' @@ -86,7 +85,7 @@ export default { return { checkResult: null, showCandidadesModal: false, - exclude: config.not + exclude: this.config.not } }, mounted: function () { diff --git a/front/src/components/manage/users/InvitationForm.vue b/front/src/components/manage/users/InvitationForm.vue index 9429c1ae16294c49b91aa5a6ee43780a7b596c90..d9f0969e67853c841cbb0a10ebd9735792723d97 100644 --- a/front/src/components/manage/users/InvitationForm.vue +++ b/front/src/components/manage/users/InvitationForm.vue @@ -43,8 +43,6 @@ <script> import axios from 'axios' -import backend from '@/audio/backend' - export default { data () { return { @@ -72,7 +70,7 @@ export default { }) }, 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) } } } diff --git a/front/src/components/metadata/Search.vue b/front/src/components/metadata/Search.vue index 305aa7a3d6b3aa7e695c9613f566946c3e7be805..0379cb188ae7e8739ca8c2e5247856f9110dbfe7 100644 --- a/front/src/components/metadata/Search.vue +++ b/front/src/components/metadata/Search.vue @@ -22,7 +22,6 @@ <script> import jQuery from 'jquery' -import config from '@/config' export default { props: { @@ -117,7 +116,7 @@ export default { })[0] }, 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 () { return [ diff --git a/front/src/config.js b/front/src/config.js deleted file mode 100644 index 47d9d7b8b2cdc83ded7330e2400c48d4b72b8e2e..0000000000000000000000000000000000000000 --- a/front/src/config.js +++ /dev/null @@ -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() diff --git a/front/src/main.js b/front/src/main.js index 7973e4bb7e87222097c649f44fbba13c318a7256..181fd66b3b63197660794af819c24fa1751866fb 100644 --- a/front/src/main.js +++ b/front/src/main.js @@ -15,7 +15,6 @@ import i18next from 'i18next' import i18nextFetch from 'i18next-fetch-backend' import VueI18Next from '@panter/vue-i18next' import store from './store' -import config from './config' import { sync } from 'vuex-router-sync' import filters from '@/filters' // eslint-disable-line import globals from '@/components/globals' // eslint-disable-line @@ -56,8 +55,6 @@ Vue.directive('title', { document.title = parts.join(' - ') } }) - -axios.defaults.baseURL = config.API_URL axios.interceptors.request.use(function (config) { // Do something before request is sent if (store.state.auth.token) { @@ -104,7 +101,6 @@ axios.interceptors.response.use(function (response) { // Do something with response error return Promise.reject(error) }) -store.dispatch('auth/check') // i18n i18next diff --git a/front/src/store/index.js b/front/src/store/index.js index 298fa04ec13166fead7a12955636d3bc4340948a..0c2908d83d5cceee72997111f099aeda555d2337 100644 --- a/front/src/store/index.js +++ b/front/src/store/index.js @@ -34,7 +34,7 @@ export default new Vuex.Store({ }), createPersistedState({ key: 'instance', - paths: ['instance.events'] + paths: ['instance.events', 'instance.instanceUrl'] }), createPersistedState({ key: 'radios', diff --git a/front/src/store/instance.js b/front/src/store/instance.js index e78e804898c8c02a1f237297d3ab3dc653e62c0e..555bd82391fe56a4ec5d18baa71e0e64dceabf35 100644 --- a/front/src/store/instance.js +++ b/front/src/store/instance.js @@ -6,6 +6,7 @@ export default { namespaced: true, state: { maxEvents: 200, + instanceUrl: process.env.INSTANCE_URL, events: [], settings: { instance: { @@ -51,9 +52,46 @@ export default { }, events: (state, 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: { + 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 fetchSettings ({commit}, payload) { return axios.get('instance/settings/').then(response => {