diff --git a/front/src/App.vue b/front/src/App.vue index 2eb673ab4bf1800920111616091aa80cffba1c08..73e46328a218106b801d7d94fb7f55ff1bca23f9 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -1,44 +1,65 @@ <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" target="_blank">{{ $t('Use another instance') }}</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 +84,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 +107,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 +162,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 +224,7 @@ html, body { .ui.icon.header .circular.icon { display: flex; justify-content: center; - + } .segment-content .button{ 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 => {