Parcourir la source

Merge branch 'release/v0.1.10'

rigwild il y a 5 ans
Parent
commit
f39cbe778e

+ 15 - 0
config.messagesId.js

@@ -0,0 +1,15 @@
+// List of IDs for messages sent using WebSockets
+
+// Message IDs for experiments events
+export const EXPERIMENT = {
+  // An experiment was started
+  STARTED: 'EXPERIMENT_STARTED',
+
+  // An experiment was started
+  DATA: 'EXPERIMENT_DATA',
+
+  // An experiment was validated
+  VALIDATED: 'EXPERIMENT_VALIDATED'
+}
+
+export default { EXPERIMENT }

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "expe-web",
-  "version": "0.1.9",
+  "version": "0.1.10",
   "private": true,
   "scripts": {
     "server:start": "node -r esm server/index.js",

+ 1 - 1
server/routes/getImage.js

@@ -117,7 +117,7 @@ const router = express.Router()
  * @param {string} sceneName the scene to get the image from
  * @param {number|"min"|"max"|"median"} quality the requested quality
  * @param {boolean} [nearestQuality=false] if selected quality not availabie, select the nearest one
- * @returns {Promise<Image>} the link and path to the image
+ * @returns {Promise<Image>} the image data
  */
 export const getImage = async (sceneName, quality, nearestQuality = false) => {
   const throwErrIfTrue = x => {

+ 2 - 1
server/routes/getImageExtracts.js

@@ -181,9 +181,10 @@ const cutImage = async (image, xExtracts, yExtracts) => {
 
       // Zone number of the extract `00020`
       const fileNameCount = (extracts.length + 1).toString().padStart(5, '0')
+      const fileNameQuality = image.quality.toString().padStart(5, '0')
 
       // File name of the extract : `Scene2_zone00199_100.png`
-      const extractName = `${image.sceneName}_zone${fileNameCount}_${image.quality}.${image.ext}`
+      const extractName = `${image.sceneName}_zone${fileNameCount}_${fileNameQuality}.${image.ext}`
 
       // Configured path to the image (Check defined convention)
       const pathToImage = [image.sceneName, extractsDirName, `x${xExtracts}_y${yExtracts}`, `zone${fileNameCount}`, extractName]

+ 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,

+ 11 - 1
src/mixins/ExperimentBase/index.vue

@@ -7,6 +7,7 @@
 <script>
 import { mapGetters, mapActions } from 'vuex'
 import { API_ROUTES } from '@/functions'
+import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
 
 export default {
   name: 'ExperimentBase',
@@ -29,6 +30,9 @@ export default {
     ...mapGetters(['getHostURI', 'getExperimentProgress', 'isExperimentDone'])
   },
   mounted() {
+    if (!this.getExperimentProgress({ experimentName: this.experimentName, sceneName: this.sceneName }).experimentName)
+      this.sendMessage({ msgId: experimentMsgId.STARTED })
+
     // Check if the experiment is already finished
     if (this.experimentName && this.sceneName && this.isExperimentDone({ experimentName: this.experimentName, sceneName: this.sceneName })) {
       console.warn('Redirected from experiment. You can\'t go back in an experiment after finishing it.')
@@ -56,7 +60,13 @@ export default {
       // console.log('Saved data from local state to store.', this.$data)
     },
 
-    async finishExperiment() {
+    // Finish an experiment, sending full data to the server
+    // Don't forget to surcharge this function when using this mixin to add more data
+    finishExperiment() {
+      const obj = Object.assign(this.$data, { sceneName: this.sceneName })
+      obj.loadingMessage = undefined
+      obj.loadingErrorMessage = undefined
+      this.sendMessage({ msgId: experimentMsgId.VALIDATED, msg: obj })
       this.setExperimentDone({ experimentName: this.experimentName, sceneName: this.sceneName, done: true })
       this.$router.push(`/experiments/${this.experimentName}`)
     },

+ 27 - 0
src/mixins/ExperimentBaseExtracts/index.vue

@@ -10,6 +10,7 @@ import ExperimentBase from '@/mixins/ExperimentBase'
 
 import { mapGetters } from 'vuex'
 import { API_ROUTES, findNearestUpper, findNearestLower } from '@/functions'
+import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
 
 export default {
   name: 'ExperimentBaseExtracts',
@@ -97,6 +98,9 @@ export default {
         this.extracts[index].nextQuality = findNearestUpper(data.info.image.quality, this.qualities)
         this.extracts[index].precQuality = findNearestLower(data.info.image.quality, this.qualities)
         this.extracts[index].loading = false
+
+        // Sending event to WebSocket server
+      // this.sendMessage({ msgId: experimentMsgId.DATA, msg: obj })
       }
       catch (err) {
         // TODO: toast message if fail
@@ -106,6 +110,29 @@ export default {
         this.extracts[index].loading = false
         this.saveProgress()
       }
+    },
+
+    // Finish an experiment, sending full data to the server
+    // Don't forget to surcharge this function when using this mixin to add more data
+    finishExperiment() {
+      const obj = {
+        experimentName: this.experimentName,
+        sceneName: this.sceneName,
+        extractConfig: this.extractConfig,
+        extracts: this.extracts.map(x => ({
+          index: x.index,
+          link: x.link,
+          nextQuality: x.nextQuality,
+          precQuality: x.precQuality,
+          quality: x.quality,
+          zone: x.zone
+        })),
+        qualities: this.qualities,
+        referenceImage: this.referenceImage
+      }
+      this.sendMessage({ msgId: experimentMsgId.VALIDATED, msg: obj })
+      this.setExperimentDone({ experimentName: this.experimentName, sceneName: this.sceneName, done: true })
+      this.$router.push(`/experiments/${this.experimentName}`)
     }
   }
 }

+ 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',

+ 13 - 4
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
@@ -52,8 +61,8 @@ export default {
     else throw new Error('Could not connect to WebSocket server. Host is not configured.')
   },
 
-  sendMessage(_, message) {
-    Vue.prototype.$socket.send(JSON.stringify(message) || message)
+  sendMessage(_, { msgId, msg = undefined }) {
+    Vue.prototype.$socket.send(JSON.stringify({ msgId, msg }))
   },
 
   async loadScenesList({ getters: { isHostConfigured, getHostURI }, commit }) {

+ 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>