Browse Source

Huge refactor:
- Let router redirect the user (beforeEach)
- Added back button in scene selection and in experiment
- Fixed host configurator card
- Added GDPR notice + validation check
- Added GDPR consent reset
- Router system logic enhancements

rigwild 1 year ago
parent
commit
7daa709378

+ 67 - 74
src/App.vue

@@ -6,85 +6,70 @@
     </div>
     <!--/ Application cache reset button -->
 
-    <v-slide-y-transition mode="out-in">
-      <!-- Loading screen -->
-      <loader v-if="loadingMessage" :message="loadingMessage" />
-      <!--/ Loading screen -->
+    <div v-if="!loadingMessage && isGdprValidated && isHostConfigured">
+      <!-- Sidebar menu -->
+      <v-navigation-drawer
+        v-model="drawer"
+        clipped
+        fixed
+        app
+      >
+        <v-list dense>
+          <v-list-tile to="/experiments" exact>
+            <v-list-tile-action>
+              <v-icon>library_books</v-icon>
+            </v-list-tile-action>
+            <v-list-tile-content>
+              <v-list-tile-title>List of experiments</v-list-tile-title>
+            </v-list-tile-content>
+          </v-list-tile>
 
-      <!-- Host connection configuration -->
-      <host-config v-else-if="!isHostConfigured" />
-      <!--/ Host connection configuration -->
+          <v-list-tile @click="loadScenes">
+            <v-list-tile-action>
+              <v-icon>refresh</v-icon>
+            </v-list-tile-action>
+            <v-list-tile-content>
+              <v-list-tile-title>Refresh list of scenes</v-list-tile-title>
+            </v-list-tile-content>
+          </v-list-tile>
+        </v-list>
+      </v-navigation-drawer>
+      <!--/ Sidebar menu -->
 
+      <!-- Top bar -->
+      <v-toolbar app fixed clipped-left>
+        <v-toolbar-side-icon @click.stop="drawer = !drawer" />
+        <v-toolbar-title>Web experiment</v-toolbar-title>
+      </v-toolbar>
+      <!--/ Top bar -->
+    </div>
 
-      <div v-else>
-        <!-- Sidebar menu -->
-        <v-navigation-drawer
-          v-model="drawer"
-          clipped
-          fixed
-          app
-        >
-          <v-list dense>
-            <v-list-tile to="/experimentsList" exact>
-              <v-list-tile-action>
-                <v-icon>library_books</v-icon>
-              </v-list-tile-action>
-              <v-list-tile-content>
-                <v-list-tile-title>List of experiments</v-list-tile-title>
-              </v-list-tile-content>
-            </v-list-tile>
-
-            <v-list-tile @click="loadScenes">
-              <v-list-tile-action>
-                <v-icon>refresh</v-icon>
-              </v-list-tile-action>
-              <v-list-tile-content>
-                <v-list-tile-title>Refresh list of scenes</v-list-tile-title>
-              </v-list-tile-content>
-            </v-list-tile>
-          </v-list>
-        </v-navigation-drawer>
-        <!--/ Sidebar menu -->
-
-        <!-- Top bar -->
-        <v-toolbar app fixed clipped-left>
-          <v-toolbar-side-icon @click.stop="drawer = !drawer" />
-          <v-toolbar-title>Web experiment</v-toolbar-title>
-        </v-toolbar>
-        <!--/ Top bar -->
-
-        <!-- Pages content -->
-        <v-content>
-          <v-container fill-height>
-            <v-layout justify-center>
-              <v-flex xs12>
-                <v-scroll-x-reverse-transition mode="out-in">
-                  <!-- View injected here -->
-                  <router-view />
-                <!--/ View injected here -->
-                </v-scroll-x-reverse-transition>
-              </v-flex>
-            </v-layout>
-          </v-container>
-        </v-content>
-        <!--/ Pages content -->
-      </div>
-    </v-slide-y-transition>
+    <!-- Pages content -->
+    <v-content>
+      <v-container fill-height>
+        <v-layout justify-center>
+          <v-flex xs12>
+            <v-scroll-x-reverse-transition mode="out-in">
+              <!-- View injected here -->
+              <router-view />
+              <!--/ View injected here -->
+            </v-scroll-x-reverse-transition>
+          </v-flex>
+        </v-layout>
+      </v-container>
+    </v-content>
+    <!--/ Pages content -->
   </v-app>
 </template>
 
 <script>
 import './style.css'
 import ResetAppButton from '@/components/ResetAppButton.vue'
-import Loader from '@/components/Loader.vue'
-import HostConfig from '@/components/HostConfig.vue'
 import { mapGetters, mapActions } from 'vuex'
 
 export default {
   components: {
-    ResetAppButton,
-    Loader,
-    HostConfig
+    ResetAppButton
   },
   data() {
     return {
@@ -96,20 +81,29 @@ export default {
     }
   },
   computed: {
-    ...mapGetters(['isHostConfigured', 'areScenesLoaded'])
+    ...mapGetters(['isGdprValidated', 'isHostConfigured', 'areScenesLoaded'])
   },
   watch: {
+    isGdprValidated(isValidated) {
+      if (isValidated) this.APP_LOADER()
+    },
     isHostConfigured(isConfigured) {
-      if (isConfigured) this.loadScenes()
+      if (isConfigured) this.APP_LOADER()
     }
   },
-  async mounted() {
-    this.setAppUniqueId()
-    if (this.isHostConfigured) await this.loadWebSocket()
-    if (this.isHostConfigured && !this.areScenesLoaded) await this.loadScenes()
+  mounted() {
+    this.APP_LOADER()
   },
   methods: {
-    ...mapActions(['setAppUniqueId', 'loadScenesList', 'connectToWs']),
+    ...mapActions(['loadScenesList', 'connectToWs']),
+
+    // Main app function that redirect the user where he needs to be at
+    async APP_LOADER() {
+      if (this.isGdprValidated && this.isHostConfigured) {
+        await this.loadWebSocket()
+        if (!this.areScenesLoaded) await this.loadScenes()
+      }
+    },
 
     async load(fn, loadingMessage) {
       try {
@@ -132,7 +126,6 @@ export default {
     loadWebSocket() {
       return this.load(this.connectToWs, 'Connecting to WebSocket server...')
     }
-
   }
 }
 </script>

+ 1 - 0
src/components/ResetAppButton.vue

@@ -73,6 +73,7 @@ export default {
 
       selectedItems: [],
       items: [
+        { text: 'GDPR consent', value: 'gdprConsent' },
         { text: 'Host configuration', value: 'hostConfig' },
         { text: 'Progression', value: 'progression' }
       ]

+ 30 - 0
src/main.js

@@ -7,6 +7,7 @@ import VueNativeSock from 'vue-native-websocket'
 
 Vue.config.productionTip = false
 
+// Connect the WebSocket client to the store
 Vue.use(VueNativeSock, 'ws://example.com', {
   store,
   connectManually: true,
@@ -16,6 +17,35 @@ Vue.use(VueNativeSock, 'ws://example.com', {
 })
 store.$socket = Vue.prototype.$socket
 
+// A function loaded before each route change
+router.beforeEach((to, from, next) => {
+  // Redirect from config pages if already configured
+  if (to.path === '/gdpr' && store.getters.isGdprValidated)
+    return next('/hostConfig')
+
+  if (to.path === '/hostConfig' && store.getters.isHostConfigured)
+    return next('/experiments')
+
+
+  // Redirect to configuration pages
+  // Check GDPR before doing anything and redirect if necessary
+  if (!store.getters.isGdprValidated) {
+    if (to.path !== '/gdpr') return next('/gdpr')
+    return next()
+  }
+
+  // Identify the user
+  store.dispatch('setAppUniqueId')
+
+  // Redirect if the host is not configured
+  if (!store.getters.isHostConfigured) {
+    if (to.path !== '/hostConfig')
+      return next('/hostConfig')
+    return next()
+  }
+  next()
+})
+
 new Vue({
   router,
   store,

+ 15 - 4
src/router/index.js

@@ -1,6 +1,7 @@
 import Vue from 'vue'
 import Router from 'vue-router'
-import ExperimentsList from '@/views/ExperimentsList.vue'
+import GdprNotice from '@/views/GdprNotice.vue'
+import HostConfig from '@/views/HostConfig.vue'
 import Experiments from './experiments'
 
 Vue.use(Router)
@@ -9,12 +10,22 @@ export default new Router({
   routes: [
     {
       path: '/',
-      redirect: '/experimentsList'
+      redirect: 'gdpr'
     },
     {
-      path: '/experimentsList',
+      path: '/gdpr',
+      name: 'GdprNotice',
+      component: GdprNotice
+    },
+    {
+      path: '/hostConfig',
+      name: 'HostConfig',
+      component: HostConfig
+    },
+    {
+      path: '/experiments',
       name: 'ExperimentsList',
-      component: ExperimentsList
+      component: () => import('@/views/ExperimentsList.vue')
     },
     {
       path: '/experiments/:experimentName',

+ 11 - 2
src/store/actions.js

@@ -1,13 +1,21 @@
 import Vue from 'vue'
+import router from '../router'
 import { API_ROUTES, buildURI, buildWsURI, delay } from '../functions'
 
 export default {
+  setGdprValidated({ state, commit }) {
+    if (!state.gdprConsent) {
+      commit('setGdprValidated')
+      router.push('/hostConfig')
+    }
+  },
+
   setAppUniqueId({ state, commit }) {
     if (!state.uuid) commit('setAppUniqueId')
   },
 
-  resetApp({ commit }, { hostConfig = false, progression = false }) {
-    commit('resetApp', { hostConfig, progression })
+  resetApp({ commit }, { gdprConsent = false, hostConfig = false, progression = false }) {
+    commit('resetApp', { gdprConsent, hostConfig, progression })
   },
 
   async setHostConfig({ state, commit }, { ssl, host, port }) {
@@ -33,6 +41,7 @@ export default {
 
         // Configuration is valid
         commit('setHostConfig', { ssl, host, port })
+        router.push('/experiments')
       })
       .catch(err => {
         // Host not reachable or invalid HTTP status code

+ 4 - 0
src/store/getters.js

@@ -1,6 +1,10 @@
 import { buildURI, buildWsURI } from '../functions'
 
 export default {
+  isGdprValidated(state) {
+    if (!state) return
+    return state.gdprConsent
+  },
   isHostConfigured(state) {
     if (!state) return
     return !!(state.hostConfig.ssl !== null && state.hostConfig.host && state.hostConfig.port)

+ 1 - 0
src/store/index.js

@@ -13,6 +13,7 @@ const vuexLocal = new VuexPersistence({
   key: 'webexpe-state',
   reducer: state => ({
     uuid: state.uuid,
+    gdprConsent: state.gdprConsent,
     hostConfig: state.hostConfig,
     scenesList: state.scenesList,
     progression: state.progression

+ 9 - 3
src/store/mutations.js

@@ -24,19 +24,25 @@ const createProgressionObj = (state, scenes) => {
 }
 
 export default {
+  setGdprValidated(state) {
+    state.gdprConsent = true
+  },
+
   setAppUniqueId(state) {
     state.uuid = [...Array(30)].map(() => Math.random().toString(36)[2]).join('')
   },
 
-  resetApp(state, { hostConfig, progression }) {
+  resetApp(state, { gdprConsent, hostConfig, progression }) {
+    const defaultStateObj = defaultState()
+    if (gdprConsent) state.gdprConsent = false
     if (hostConfig) {
       if (state.socket.isConnected)
         this._vm.$disconnect()
-      state.hostConfig = defaultState().hostConfig
+      state.hostConfig = defaultStateObj.hostConfig
     }
     if (progression) {
       // Reset progression and recreate the progression object
-      state.progression = defaultState().progression
+      state.progression = defaultStateObj.progression
       createProgressionObj(state, state.scenesList)
     }
   },

+ 1 - 0
src/store/state.js

@@ -1,6 +1,7 @@
 // Deep copy to not mutate it with the store (default state is needed when reloading after a refresh)
 export default () => JSON.parse(JSON.stringify({
   uuid: null,
+  gdprConsent: false,
   hostConfig: {
     ssl: null,
     host: null,

+ 8 - 1
src/views/Experiments/WithReference.vue

@@ -3,7 +3,14 @@
     <v-container grid-list-md text-xs-center fluid>
       <v-layout row wrap>
         <v-flex xs12>
-          <h1>Experiment with reference</h1>
+          <v-layout justify-start>
+            <v-btn flat exact :to="`/experiments/${experimentName}`">
+              <v-icon left>arrow_back</v-icon>
+              Back to scene selection
+            </v-btn>
+          </v-layout>
+
+          <h1>Experiment with reference - {{ sceneName }}</h1>
           <!-- Extract configuration -->
           <extract-configuration @setConfig="setConfig($event, $refs.configurator)" :loading-error-message="loadingErrorMessage" ref="configurator" />
           <!--/ Extract configuration -->

+ 34 - 0
src/views/GdprNotice.vue

@@ -0,0 +1,34 @@
+<template>
+  <v-layout justify-center align-center>
+    <v-flex xs12 sm5>
+      <v-card>
+        <v-container fluid fill-height>
+          <v-layout column align-center>
+            <v-card-title class="headline d-block text-md-center font-weight-bold">GDPR notice</v-card-title>
+
+            <v-card-text>
+              We collect data...
+              <br><br>
+              Reprehenderit consectetur proident culpa id quis irure. Culpa adipisicing Lorem esse adipisicing ullamco nostrud mollit excepteur. Ut adipisicing labore commodo irure cillum incididunt reprehenderit do minim ea. Adipisicing nisi laboris laboris eu commodo anim Lorem tempor mollit. Aliqua minim magna incididunt cillum dolore irure. Nostrud quis aute consectetur id. Dolor cillum aute dolor irure id consectetur commodo pariatur elit irure pariatur occaecat consequat cupidatat.
+              <br><br>
+              Consequat est laboris aliqua culpa fugiat Lorem ad sit nisi. Dolore exercitation voluptate ex labore id proident nulla nulla incididunt deserunt laborum do ipsum. Consectetur fugiat Lorem pariatur ea irure cillum dolor voluptate velit tempor ex reprehenderit proident.
+            </v-card-text>
+
+            <v-btn color="primary" @click="setGdprValidated">I accept the data collection</v-btn>
+          </v-layout>
+        </v-container>
+      </v-card>
+    </v-flex>
+  </v-layout>
+</template>
+
+<script>
+import { mapActions } from 'vuex'
+
+export default {
+  name: 'GdprNotice',
+  methods: {
+    ...mapActions(['setGdprValidated'])
+  }
+}
+</script>

+ 7 - 11
src/components/HostConfig.vue

@@ -2,11 +2,11 @@
   <v-layout justify-center align-center>
     <v-flex xs12 sm5>
       <v-card>
-        <v-content>
-          <v-container fluid fill-height>
-            <v-layout style="flex-direction: column; text-align: center">
-              <h1>Host configuration</h1>
+        <v-container fluid fill-height>
+          <v-layout column align-center>
+            <v-card-title class="headline d-block text-md-center font-weight-bold">Host configuration</v-card-title>
 
+            <v-card-text>
               <v-slide-y-transition mode="out-in">
                 <loader v-if="loadingMessage" :message="loadingMessage" />
                 <v-form v-else ref="form">
@@ -43,9 +43,9 @@
                   </v-slide-y-transition>
                 </v-form>
               </v-slide-y-transition>
-            </v-layout>
-          </v-container>
-        </v-content>
+            </v-card-text>
+          </v-layout>
+        </v-container>
       </v-card>
     </v-flex>
   </v-layout>
@@ -79,10 +79,6 @@ export default {
     }
   },
 
-  mounted() {
-    this.$router.push('/')
-  },
-
   methods: {
     ...mapActions(['setHostConfig']),
     reset() {

+ 7 - 0
src/views/SelectExperimentScene.vue

@@ -1,5 +1,12 @@
 <template>
   <div>
+    <v-layout justify-start>
+      <v-btn flat exact :to="`/experiments`">
+        <v-icon left>arrow_back</v-icon>
+        Back to experiment selection
+      </v-btn>
+    </v-layout>
+
     Select a scene for the experiment "{{ experimentName }}"
 
     <v-card>