Browse Source

Merge branch 'release/v0.2.5'

Samuel Delepoulle 5 years ago
parent
commit
d03cd44fd0

+ 10 - 0
DOCUMENTATION/00-home.md

@@ -0,0 +1,10 @@
+# Antoine_Internship
+
+## Summary
+
+| # | Documentation | Description |
+| --- | ------------- | ----------- |
+| 01 | [Application file tree](./01-application-file-tree.md) | Details how the application is stuctured |
+| 02 | [npm scripts](./02-npm-scripts.md) | Details the available npm scripts |
+| 03 | [npm dependencies](./03-npm-dependencies.md) | Details the npm dependencies |
+| 04 | [Create an experiment](./04-create-an-experiment.md) | Details how to fully create an experiment |

+ 210 - 0
DOCUMENTATION/01-application-file-tree.md

@@ -0,0 +1,210 @@
+# Application file tree
+
+This wiki page details how the application file tree is structured.
+
+## Summary
+
+ - [Project's general file structure](#projects-general-file-structure)
+ - [`/docs` directory](#docs-directory)
+ - [`/dist` directory](#dist-directory)
+ - [`/images` directory](#images-directory)
+ - [`/logs` directory](#logs-directory)
+ - [`/server` directory](#server-directory)
+ - [`/src` directory](#src-directory)
+ - [`/test` directory](#test-directory)
+
+---
+
+## Project's general file structure
+
+The following file tree represents the full application.
+
+```console
+.                                    || 
+├── babel.config.js                  || 
+├── back.Dockerfile                  || Back-end Docker configuration
+├── cleanExtracts.js                 || Extracts remover service
+├── config.js                        || Application configuration
+├── config.messagesId.js             || Database message identifiers
+├── data                             || Database (Docker)
+│   └── ...                          || 
+├── docs                             || Generated API documentation
+│   └── ...                          || 
+├── docker-compose.frontapp_only.yml || General Docker configuration for a front-only instance
+├── docker-compose.yml               || General Docker configuration
+├── DOCUMENTATION                    || This documentation
+│   └── ...                          || 
+├── dist                             || Generated Front-end files (build)
+│   └── ...                          || 
+├── experimentConfig.default.js      || Default experiments configuration
+├── experimentConfig.js              || Experiments configuration
+├── front.Dockerfile                 || Front-end Docker configuration
+├── images                           || Experiments images (Can be configured)
+│   └── ...                          || 
+├── index.js                         || Server + Extracts remover service starter
+├── jsconfig.json                    || 
+├── LICENSE                          || Application license
+├── logs                             || Application logs
+│   └── ...                          || 
+├── package.json                     || Project's scripts, dependencies and configuration
+├── public                           || Static front-end files
+│   ├── favicon.ico                  || Website favicon
+│   └── index.html                   || 
+├── README.md                        || 
+├── server                           || Server, database and WebSockets server code
+│   └── ...                          || 
+├── src                              || Front-end code
+│   └── ...                          || 
+├── test                             || API automated tests
+│   └── ...                          || 
+├── webhook_deploy_gogs.js           || Continuous integration script
+└── yarn.lock                        || Project's dependencies versions lock file
+```
+
+## `/docs` directory
+
+The `/docs` directory contains the generated API documentation. This documentation is accessible through `/doc` on the website and describes every routes of the API.
+
+To generate the documentation, use the following npm script:
+
+```sh
+yarn doc
+```
+
+## `/dist` directory
+
+The `/dist` directory contains the build output of the Front-end of the application. It is bundled with WebPack to contain only HTML/CSS/JavaScript files.
+
+To generate the Front-end files, use the following npm script:
+
+```sh
+yarn app:build
+```
+
+## `/images` directory
+
+The `/images` directory contains all the images available for the experiments.
+
+This path is the default one, but you can configure it by setting an absolute path with the `IMAGES_PATH` environment variable when running the application.
+
+```console
+└── images                                               ||
+    ├── Appart1opt02                                     || A scene directory
+    │   ├── appartAopt_00890.png                         || Scene images
+    │   ├── ...                                          || 
+    │   └── extracts                                     || Images extracts cut by the server
+    │       ├── x4_y4                                    || A cutting configuration (4 and 4 cuts on x and y axis)
+    │       │   ├── zone00001                            || Image extract zone 1 (top left of image)
+    │       │   │   ├── Appart1opt02_zone00001_00020.png || The actual extract (x4_y4 config, zone 1, quality 20)
+    │       │   │   └── ...                              || 
+    │       │   └── ...                                  || 
+    │       └── ...                                      || 
+    └── ...                                              || 
+```
+
+## `/logs` directory
+
+The `/logs` directory contains all the logs of the application.
+
+```console
+└── logs                                 || 
+    ├── db.error.log                     || Database errors
+    ├── db.log                           || Database logs
+    ├── extractsRemoverService.error.log || Extracts remover service errors
+    ├── extractsRemoverService.log       || Extracts remover service logs
+    ├── server.combined.log              || Server combined logs
+    ├── server.error.log                 || Server errors
+    ├── ws.error.log                     || WebSockets server errors
+    └── ws.log                           || WebSockets server logs
+```
+
+## `/server` directory
+
+The [`/server`](../server) directory contains the Back-end: server, database and WebSockets server code.
+
+```console
+└── server                    || 
+    ├── database              || Database link
+    │   ├── controllers       || Database controllers (Functions)
+    │   │   └── Data.js       || 
+    │   ├── index.js          || Database link module: connects to database and links models
+    │   └── models            || Database models (Schemas, types)
+    │       └── Data.js       || 
+    ├── functions.js          || Common utils for the server
+    ├── index.js              || Server module: links server, database and WebSockets server
+    ├── routes                || All API routes
+    │   ├── index.js          || Router module to inject all routes
+    │   ├── getImage.js       || 
+    │   └── ...               || 
+    ├── webSocket             || WebSockets server
+    │   ├── index.js          || Websockets server module: starts server
+    │   └── messageHandler.js || Handle WebSockets messages (JSON)
+    └── winston.config.js     || All the server's logging configuration
+```
+
+## `/src` directory
+
+The [`/src`](../src) directory contains the Front-end code.
+
+```console
+└── src                                              || 
+    ├── App.vue                                      || Main template, defines how the page looks
+    ├── components                                   || Common components
+    │   ├── ExperimentsComponents                    || Experiment-specific components
+    │   │   ├── ExtractConfiguration.vue             || Extracts configurator
+    │   │   └── ExtractsToImage.vue                  || Shows multiple extracts as a unique image
+    │   ├── Loader.vue                               || Loading component
+    │   ├── ResetAppButton.vue                       || Global application reset button
+    │   └── ToastMessage.vue                         || Show a temporary toast message
+    ├── config.utils.js                              || Some utils related to experiments configuration files
+    ├── functions.js                                 || Common utils
+    ├── main.js                                      || Vue modules and plugins register
+    ├── mixins                                       || Experiments bases: contains utils for a type of experiment
+    │   ├── ExperimentBaseAreSameImages.vue          || Images versus based experiment base
+    │   ├── ExperimentBaseExtracts.vue               || Extracts-based experiment base
+    │   └── ExperimentBase.vue                       || Global experiment base, used in every experiments
+    ├── plugins                                      || Vue.js plugins
+    │   └── vuetify.js                               || Vuetify design framework
+    ├── router                                       || Vue-router (routing logic)
+    │   ├── experiments.js                           || Experiment-specific routing logic
+    │   └── index.js                                 || Main routing logic
+    ├── store                                        || VueX store (global state management)
+    │   ├── actions.js                               || Store actions (can be async)
+    │   ├── getters.js                               || Compute store state values
+    │   ├── index.js                                 || Store module
+    │   ├── mutations.js                             || Store mutations (must be synchronous and atomic)
+    │   └── state.js                                 || Store initial state
+    ├── style.css                                    || Global application style
+    └── views                                        || Application views (pages)
+        ├── Experiments                              || Experiments
+        │   ├── AreSameImagesRandom.vue              || Two random quality images (versus)
+        │   ├── AreSameImagesReferenceOneExtract.vue || Two reference images, one has a random quality extract (versus)
+        │   ├── AreSameImagesReference.vue           || One random quality image, one reference image (versus)
+        │   └── MatchExtractsWithReference.vue       || Lowest quality extracts, match them to reference image
+        ├── ExperimentsList.vue                      || A list of all available experiments
+        ├── ExperimentValidated.vue                  || The experiment ending screen
+        ├── GdprNotice.vue                           || The GDPR notice
+        ├── HostConfig.vue                           || The application host configuration
+        └── SelectExperimentScene.vue                || Experiment scene selector
+```
+
+## `/test` directory
+
+The [`/test`](../test) directory contains the automated tests of the application.
+
+```console
+└── test                         ||
+    ├── api                      || API tests
+    │   ├── getImage.js          || A test file
+    │   ├── ...                  || 
+    │   ├── _test_functions.js   || Common utils for the tests
+    │   ├── _test_setup_start.js || Script called before running all the tests
+    │   └── _test_setup_stop.js  || Script called after running all the tests
+    └── images_test              || Some experiment images copied for tests
+        └── ...                  || 
+```
+
+To run these automated tests, use the following npm script:
+```sh
+yarn app:build
+```

+ 72 - 0
DOCUMENTATION/02-npm-scripts.md

@@ -0,0 +1,72 @@
+# npm scripts
+
+This wiki page details the available npm scripts. These scripts are located in the `scripts` category of [`/package.json`](../package.json).
+
+Keep in mind that this documentation uses the `yarn` command but using `npm` instead will have the same effect.
+
+## Summary
+
+ - [`server:start`](#serverstart)
+ - [`server:start:no-delete-extracts`](#serverstartno-delete-extracts)
+ - [`server:lint`](#serverlint)
+ - [`app:dev`](#appdev)
+ - [`app:build`](#appbuild)
+ - [`app:lint`](#applint)
+ - [`doc`](#doc)
+ - [`test`](#test)
+
+---
+
+## `server:start`
+Start the server with the extract remover service.
+```sh
+yarn server:start
+```
+
+## `server:start:no-delete-extracts`
+Start the server without the extract remover service.
+```sh
+yarn server:start
+```
+
+## `server:lint`
+Lint and autofix the server files.
+
+This will follow the [`/.eslintrc.js`](../.eslintrc.js) ESLint configuration. It will warn you for syntax misuse, indentation errors or such.
+```sh
+yarn server:lint
+```
+
+## `app:dev`
+Start the web application on an hot-reload server. Development mode only. Do not use in production as it is less performant and not optimized
+```sh
+yarn app:dev
+```
+
+## `app:build`
+Build the web application files to the `/dist` directory. Generated files (HTML/CSS/JavaScript) are optimized for production.
+```sh
+yarn app:build
+```
+
+## `app:lint`
+Lint and autofix the web application files.
+
+This will follow the [`/.eslintrc.js`](../.eslintrc.js) ESLint configuration. It will warn you for syntax misuse, indentation errors or such.
+```sh
+yarn app:lint
+```
+
+## `doc`
+Generate the API documentation to the `/doc` directory. When starting the server, this documentation will be available at `/doc` url.
+```sh
+yarn doc
+```
+
+## `test`
+Run the API automated tests. It will check for routes/WebSockets server/database errors.
+
+These tests are automatically ran when using Docker.
+```sh
+yarn test
+```

+ 64 - 0
DOCUMENTATION/03-npm-dependencies.md

@@ -0,0 +1,64 @@
+# npm dependencies
+
+This wiki page details the npm dependencies. These dependencies are located in `dependencies` and `devDependencies`categories of [`/package.json`](../package.json).
+
+## Summary
+
+ - [Production dependencies](#production-dependencies)
+ - [Development dependencies](#development-dependencies)
+
+---
+
+## Production dependencies
+These dependencies are mostly used by the server.
+
+| Name | Link | Description |
+| --- | --- | --- |
+| `@hapi/boom` | [npm](https://www.npmjs.com/package/@hapi/boom) | Better API HTTP errors |
+| `body-parser` | [npm](https://www.npmjs.com/package/body-parser) | Parse HTTP request body |
+| `compression` | [npm](https://www.npmjs.com/package/compression) | Turn HTTP server's gzip compression on |
+| `core-js` | [npm](https://www.npmjs.com/package/core-js) | Dependency of Babel (in `@vue/cli-service`) |
+| `cors` | [npm](https://www.npmjs.com/package/cors) | Turn Cross-Origin Resource Sharing (CORS) HTTP header on |
+| `cron` | [npm](https://www.npmjs.com/package/cron) | Cron integrated in Node.js, used for the extracts remover service |
+| `esm` | [npm](https://www.npmjs.com/package/esm) | Node.js JavaScript module loader. Used for the server |
+| `express` | [npm](https://www.npmjs.com/package/express) | API framework |
+| `helmet` | [npm](https://www.npmjs.com/package/helmet) | Turn security-related HTTP headers on |
+| `mongoose` | [npm](https://www.npmjs.com/package/mongoose) | MongoDB database driver |
+| `morgan` | [npm](https://www.npmjs.com/package/morgan) | API logger |
+| `serve-static` | [npm](https://www.npmjs.com/package/serve-static) | Serve and cache images |
+| `sharp` | [npm](https://www.npmjs.com/package/sharp) | Node.js image processing library |
+| `ua-parser-js` | [npm](https://www.npmjs.com/package/ua-parser-js) | Parse User Agent strings |
+| `winston` | [npm](https://www.npmjs.com/package/winston) | Server logger |
+| `ws` | [npm](https://www.npmjs.com/package/ws) | Node.js WebSockets server |
+
+## Development dependencies
+These dependencies are here for developers only. They contain the web application setup, optimized builders and API automated tests. It also contains API documentation generator and `ESLint` with its plugins.
+
+When the web application files are builded, `Vue.js` and its plugins are not necessary. In fact, the build output will contain portable HTML, CSS and JavaScript.
+
+| Name | Link | Description |
+| --- | --- | --- |
+| `@vue/cli-plugin-babel` | [npm](https://www.npmjs.com/package/@vue/cli-plugin-babel) | Just a plugin to use `ESLint` with latest JavaScript features |
+| `@vue/cli-plugin-eslint` | [npm](https://www.npmjs.com/package/@vue/cli-plugin-eslint) | Just a plugin to use `ESLint` with `Vue.js` |
+| `@vue/cli-service` | [npm](https://www.npmjs.com/package/@vue/cli-service) | Hot-reload development `Vue.js` development server. Builds and optimize files for production |
+| `@vue/eslint-config-standard` | [npm](https://www.npmjs.com/package/@vue/eslint-config-standard) | A base configuration for `ESLint` |
+| `apidoc` | [npm](https://www.npmjs.com/package/apidoc) | API documentation generator |
+| `ava` | [npm](https://www.npmjs.com/package/ava) | Automated tests |
+| `babel-eslint` | [npm](https://www.npmjs.com/package/babel-eslint) | Just a plugin to use `ESLint` with latest JavaScript features |
+| `deepmerge` | [npm](https://www.npmjs.com/package/deepmerge) | Merge JavaScript objects into one unique object. Used for the configuration system |
+| `eslint` | [npm](https://www.npmjs.com/package/eslint) | JavaScript code linter. Will warn you for syntax misuse, indentation errors or such  |
+| `eslint-plugin-vue` | [npm](https://www.npmjs.com/package/eslint-plugin-vue) | Just a plugin to use `ESLint` with `Vue.js` |
+| `fs-extra` | [npm](https://www.npmjs.com/package/fs-extra) | Better file system library than Node.js native one. Used in automated tests |
+| `material-design-icons-iconfont` | [npm](https://www.npmjs.com/package/material-design-icons-iconfont) | Web application icons |
+| `stylus` | [npm](https://www.npmjs.com/package/stylus) | Scriptable CSS, used by `Vuetify` |
+| `stylus-loader` | [npm](https://www.npmjs.com/package/stylus-loader) | Build stylus-styles files, used by `Vuetify` |
+| `supertest` | [npm](https://www.npmjs.com/package/supertest) | HTTP requests test library |
+| `vue` | [npm](https://www.npmjs.com/package/vue) | `Vue.js` JavaScript framework |
+| `vue-cli-plugin-vuetify` | [npm](https://www.npmjs.com/package/vue-cli-plugin-vuetify) | Just a plugin to use `Vuetify` in `Vue.js` |
+| `vue-native-websocket` | [npm](https://www.npmjs.com/package/vue-native-websocket) | WebSockets client library, plugged to `Vuex` |
+| `vue-router` | [npm](https://www.npmjs.com/package/vue-router) | Web application routing system |
+| `vue-template-compiler` | [npm](https://www.npmjs.com/package/vue-template-compiler) | Build `Vue.js` `.vue` files |
+| `vuetify` | [npm](https://www.npmjs.com/package/vuetify) | `Vue.js` specific CSS framework |
+| `vuetify-loader` | [npm](https://www.npmjs.com/package/vuetify-loader) | Load `Vuetify` |
+| `vuex` | [npm](https://www.npmjs.com/package/vuex) | `Vue.js` global store |
+| `vuex-persist` | [npm](https://www.npmjs.com/package/vuex-persist) | Persist global store in browser's cache, used to save configuration and progression data |

+ 140 - 0
DOCUMENTATION/04-create-an-experiment.md

@@ -0,0 +1,140 @@
+# Create an experiment
+
+This wiki page details how to fully create an experiment.
+
+## Summary
+
+ - [Prerequisites](#prerequisites)
+ - [Experiment file tree](#experiment-file-tree)
+ - [Experiment creation](#experiment-creation)
+   - [Experiment initialization](#experiment-initialization)
+   - [Experiment configuration](#experiment-configuration)
+ - [Experiment mixins API](#experiment-mixins-api)
+   - [ExperimentBase](#experimentbase)
+   - [ExperimentBaseAreSameImages](#experimentbasearesameimages)
+   - [ExperimentBaseExtracts](#experimentbaseextracts)
+
+---
+
+## Prerequisites
+To learn how to create an experiment, you first need to know how [Vue.js](https://vuejs.org/v2/guide/) works.
+
+If you want to use the same style as the application, here is the [Vuetify documentation](https://vuetifyjs.com/en/getting-started/quick-start).
+
+## Experiment file tree
+This is a more experiment-focused file tree.
+
+The complete `/src` directory file tree can be found here: [Application file tree - `/src` directory](./01-application-file-tree.md#src-directory).
+
+```console
+└── src                                              || 
+    ├── components                                   || Common components
+    │   └── ExperimentsComponents                    || Experiment-specific components
+    │       ├── ExtractConfiguration.vue             || Extracts configurator
+    │       └── ExtractsToImage.vue                  || Shows multiple extracts as a unique image
+    ├── functions.js                                 || Common utils
+    ├── mixins                                       || Experiments bases: contains utils for a type of experiment
+    │   ├── ExperimentBaseAreSameImages.vue          || Images versus based experiment base
+    │   ├── ExperimentBaseExtracts.vue               || Extracts-based experiment base
+    │   └── ExperimentBase.vue                       || Global experiment base, used in every experiments
+    ├── router                                       || Vue-router (routing logic)
+    │   ├── experiments.js                           || Experiment-specific routing logic
+    │   └── index.js                                 || Main routing logic
+    └── views                                        || Application views (pages)
+        └── Experiments                              || Experiments
+            ├── AreSameImagesRandom.vue              || Two random quality images (versus)
+            ├── AreSameImagesReferenceOneExtract.vue || Two reference images, one has a random quality extract (versus)
+            ├── AreSameImagesReference.vue           || One random quality image, one reference image (versus)
+            └── MatchExtractsWithReference.vue       || Lowest quality extracts, match them to reference image
+```
+
+## Experiment creation
+
+### Experiment initialization
+When creating an experiment
+
+##### 1. Choose an experiment name, it must be in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case)
+##### 2. Create a new `<YourExperimentName>.vue` file in the [`/src/views/Experiments`](../src/views/Experiments) directory
+
+##### 3. Link your experiment to the [Vue.js routing system](https://router.vuejs.org/)
+Create a new entry in the [`/src/router/experiments.js`](../src/router/experiments.js) file following this structure:
+
+```js
+{
+  path: '/experiments/YourExperimentName/:sceneName',
+  name: 'YourExperimentName',
+  component: () => import('@/views/Experiments/YourExperimentName'),
+  props: true,
+  meta: {
+    fullName: 'The visible name of your experiment'
+  }
+}
+```
+
+##### 4. Choose or create the experiment mixin
+`TODO: finish this part`
+
+An experiment mixin is a subset of functions or data that can be used in a type of experiment.
+
+
+##### 5. Actual experiment creation
+Copy the minimal experiment template [`/src/views/Experiments/_template.vue`](../src/views/Experiments/_template.vue) content to your new experiment.
+
+You are all set! You can now develop your own experiment.
+See [Experiment mixins API](#experiment-mixins-api) to check data and methods available from your selected parent mixins using the `this` keyword in any Vue methods/templates.
+
+### Experiment configuration
+`TODO: finish this part`
+
+## Experiment mixins API
+### ExperimentBase
+File: [`/src/mixins/ExperimentBase.vue`](../src/mixins/ExperimentBase.vue)
+
+Extends: []
+
+This mixin is mandatory. If you want to create a new mixin, use this mixin in your mixin.
+
+When drawing the tree from top mixins to the experiment itself, `ExperimentBase` must be the very top parent.
+
+| Data | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+| `loadingMessage` | `String` | `null` | Message to display while loading the application. `null` = not loading
+| `loadingErrorMessage` |  `String` | `null` | Message to display when the application failed to load. `null` = no loading errror
+| `qualities` | `Number[]` | `null` | List of qualities for the current scene
+
+| Method | Return type | Description |
+| ------ | ----------- | ----------- |
+| `loadProgress()` | `void` | Load progress from store into local state |
+| `saveProgress()` | `void` | Save progress from local state into store |
+| `loadConfig()` | `void` | Load a config object into the local state |
+| `finishExperiment()` | `void` | Finish an experiment, sending full data to the server . Don't forget to surcharge this function when using this mixin to add more data! |
+| `getQualitiesList()` | `Promise<void>` | Load qualities list from the API |
+| `getImage(quality: Number)` | `Promise<Object>` | Load an image from the API |
+| `sendMessage({ msgId: String, msg: Object })` | `void` | Send a message using WebSockets, your message will be stored in the database. Message IDs are listed in [`/config.messageId.js`](../config.messageId.js) |
+| `setExperimentFinished([done=true: Boolean])` | `void` | Set the current experiment as finished in the browser's cache |
+
+### ExperimentBaseAreSameImages
+File: [`/src/mixins/ExperimentBaseAreSameImages.vue`](../src/mixins/ExperimentBaseAreSameImages.vue)
+
+Extends: [[ExperimentBase](#experimentbase)]
+
+| Data | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+
+| Method | Return type | Description |
+| ------ | ----------- | ----------- |
+
+`TODO: finish this part`
+
+### ExperimentBaseExtracts
+File: [`/src/mixins/ExperimentBaseExtracts.vue`](../src/mixins/ExperimentBaseExtracts.vue)
+
+Extends: [[ExperimentBase](#experimentbase)]
+
+| Data | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+
+| Method | Return type | Description |
+| ------ | ----------- | ----------- |
+
+`TODO: finish this part`

+ 65 - 0
src/components/ExperimentBlock.vue

@@ -0,0 +1,65 @@
+<template>
+  <div>
+    <v-container grid-list-md text-xs-center fluid>
+      <v-layout row wrap>
+        <!-- Experiment header -->
+        <v-flex xs12>
+          <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>
+
+          <h2>Experiment "{{ $route.meta.fullName }}"</h2>
+          <h3>{{ sceneName }}</h3>
+
+          <slot name="header"></slot>
+        </v-flex>
+        <!--/ Experiment header -->
+
+        <!-- Loading screen -->
+        <loader v-if="loadingMessage" :message="loadingMessage" />
+        <!--/ Loading screen -->
+
+        <!-- Experiment -->
+        <template v-else-if="!loadingErrorMessage">
+          <slot name="content"></slot>
+        </template>
+        <!--/ Experiment -->
+      </v-layout>
+    </v-container>
+  </div>
+</template>
+
+
+<script>
+import Loader from '@/components/Loader.vue'
+
+export default {
+  name: 'ExperimentBlock',
+  components: {
+    Loader
+  },
+  props: {
+    experimentName: {
+      type: String,
+      required: true
+    },
+    sceneName: {
+      type: String,
+      required: true
+    },
+    loadingMessage: {
+      type: String,
+      required: false,
+      default: null
+    },
+    loadingErrorMessage: {
+      type: String,
+      required: false,
+      default: null
+    }
+  }
+}
+</script>

+ 5 - 2
src/mixins/ExperimentBase.vue

@@ -76,6 +76,10 @@ export default {
       Object.assign(this.$data, config)
     },
 
+    setExperimentFinished(done = true) {
+      this.setExperimentDone({ experimentName: this.experimentName, sceneName: this.sceneName, done })
+    },
+
     // 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() {
@@ -83,7 +87,7 @@ export default {
       obj.loadingMessage = undefined
       obj.loadingErrorMessage = undefined
       this.sendMessage({ msgId: experimentMsgId.VALIDATED, msg: obj })
-      this.setExperimentDone({ experimentName: this.experimentName, sceneName: this.sceneName, done: true })
+      this.setExperimentFinished()
       this.$router.push(`/experiments/${this.experimentName}/${this.sceneName}/validated`)
     },
 
@@ -95,7 +99,6 @@ export default {
       const URI = `${this.getHostURI}${API_ROUTES.listSceneQualities(this.sceneName)}`
       const { data } = await fetch(URI).then(res => res.json())
       this.qualities = data
-      this.saveProgress()
     },
 
 

+ 1 - 1
src/mixins/ExperimentBaseAreSameImages.vue

@@ -110,7 +110,7 @@ export default {
         sceneName: this.sceneName
       }
       this.sendMessage({ msgId: experimentMsgId.VALIDATED, msg: obj })
-      this.setExperimentDone({ experimentName: this.experimentName, sceneName: this.sceneName, done: true })
+      this.setExperimentFinished()
       this.$router.push(`/experiments/${this.experimentName}/${this.sceneName}/validated`)
     }
   }

+ 1 - 1
src/mixins/ExperimentBaseExtracts.vue

@@ -209,7 +209,7 @@ export default {
         referenceImage: this.referenceImage
       }
       this.sendMessage({ msgId: experimentMsgId.VALIDATED, msg: obj })
-      this.setExperimentDone({ experimentName: this.experimentName, sceneName: this.sceneName, done: true })
+      this.setExperimentFinished()
       this.$router.push(`/experiments/${this.experimentName}/${this.sceneName}/validated`)
     }
   }

+ 9 - 0
src/router/experiments.js

@@ -34,5 +34,14 @@ export default [
     meta: {
       fullName: 'Are images the same ? (Both are reference images but one contains a random quality extract)'
     }
+  },
+  {
+    path: '/experiments/PercentQualityRandom/:sceneName',
+    name: 'PercentQualityRandom',
+    component: () => import('@/views/Experiments/PercentQualityRandom'),
+    props: true,
+    meta: {
+      fullName: 'Choose a score for quality'
+    }
   }
 ]

+ 52 - 67
src/views/Experiments/AreSameImagesRandom.vue

@@ -1,84 +1,69 @@
 <template>
-  <div>
-    <v-container grid-list-md text-xs-center fluid>
-      <v-layout row wrap>
-        <v-flex xs12>
-          <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>
+  <ExperimentBlock
+    :experiment-name="experimentName"
+    :scene-name="sceneName"
+    :loading-message="loadingMessage"
+    :loading-error-message="loadingErrorMessage"
+  >
+    <template v-slot:header></template>
+    <template v-slot:content>
+      <v-flex xs12 sm6>
+        <v-card dark color="primary">
+          <v-card-text class="px-0">Image 1</v-card-text>
 
-          <h2>Experiment "{{ $route.meta.fullName }}"</h2>
-          <h3>{{ sceneName }}</h3>
-        </v-flex>
-        <!-- Loading screen -->
-        <loader v-if="loadingMessage" :message="loadingMessage" />
-        <!--/ Loading screen -->
+          <v-img v-if="image1 && image1.link" :src="image1.link">
+            <template v-slot:placeholder>
+              <v-layout fill-height align-center justify-center ma-0>
+                <v-progress-circular indeterminate color="grey lighten-5" />
+              </v-layout>
+            </template>
+          </v-img>
+        </v-card>
+      </v-flex>
+      <v-flex sm6 xs12>
+        <v-card dark color="primary">
+          <v-card-text>Image 2</v-card-text>
 
-        <!-- Experiment -->
-        <template v-else-if="!loadingErrorMessage">
-          <v-flex xs12 sm6>
-            <v-card dark color="primary">
-              <v-card-text class="px-0">Image 1</v-card-text>
+          <v-img v-if="image2 && image2.link" :src="image2.link" @load="scrollToChoiceButtons">
+            <template v-slot:placeholder>
+              <v-layout fill-height align-center justify-center ma-0>
+                <v-progress-circular indeterminate color="grey lighten-5" />
+              </v-layout>
+            </template>
+          </v-img>
+        </v-card>
+      </v-flex>
 
-              <v-img v-if="image1 && image1.link" :src="image1.link">
-                <template v-slot:placeholder>
-                  <v-layout fill-height align-center justify-center ma-0>
-                    <v-progress-circular indeterminate color="grey lighten-5" />
-                  </v-layout>
-                </template>
-              </v-img>
-            </v-card>
-          </v-flex>
-          <v-flex sm6 xs12>
-            <v-card dark color="primary">
-              <v-card-text>Image 2</v-card-text>
 
-              <v-img v-if="image2 && image2.link" :src="image2.link" @load="scrollToChoiceButtons">
-                <template v-slot:placeholder>
-                  <v-layout fill-height align-center justify-center ma-0>
-                    <v-progress-circular indeterminate color="grey lighten-5" />
-                  </v-layout>
-                </template>
-              </v-img>
-            </v-card>
-          </v-flex>
-
-
-          <!-- Experiment validation button -->
-          <v-layout justify-center align-content-center>
-            <div id="choice">
-              <v-container grid-list-md text-xs-center fluid>
-                <h2>Test {{ testCount }} / {{ maxTestCount }}</h2>
-                <v-layout row wrap>
-                  <v-flex sm6 xs12>
-                    <v-btn @click="areTheSameAction(false, getRandomTest)" color="error" large>Images are NOT the same</v-btn>
-                  </v-flex>
-                  <v-flex sm6 xs12>
-                    <v-btn @click="areTheSameAction(true, getRandomTest)" color="success" large>Images are the same</v-btn>
-                  </v-flex>
-                </v-layout>
-              </v-container>
-            </div>
-          </v-layout>
-          <!--/ Experiment validation button -->
-        </template>
-        <!--/ Experiment -->
+      <!-- Experiment validation button -->
+      <v-layout justify-center align-content-center>
+        <div id="choice">
+          <v-container grid-list-md text-xs-center fluid>
+            <h2>Test {{ testCount }} / {{ maxTestCount }}</h2>
+            <v-layout row wrap>
+              <v-flex sm6 xs12>
+                <v-btn @click="areTheSameAction(false, getRandomTest)" color="error" large>Images are NOT the same</v-btn>
+              </v-flex>
+              <v-flex sm6 xs12>
+                <v-btn @click="areTheSameAction(true, getRandomTest)" color="success" large>Images are the same</v-btn>
+              </v-flex>
+            </v-layout>
+          </v-container>
+        </div>
       </v-layout>
-    </v-container>
-  </div>
+      <!--/ Experiment validation button -->
+    </template>
+  </ExperimentBlock>
 </template>
 
 <script>
+import ExperimentBlock from '@/components/ExperimentBlock.vue'
 import ExperimentBaseAreTheSame from '@/mixins/ExperimentBaseAreSameImages'
-import Loader from '@/components/Loader.vue'
 
 export default {
   name: 'AreSameImagesRandom',
   components: {
-    Loader
+    ExperimentBlock
   },
   mixins: [ExperimentBaseAreTheSame],
 

+ 52 - 67
src/views/Experiments/AreSameImagesReference.vue

@@ -1,84 +1,69 @@
 <template>
-  <div>
-    <v-container grid-list-md text-xs-center fluid>
-      <v-layout row wrap>
-        <v-flex xs12>
-          <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>
+  <ExperimentBlock
+    :experiment-name="experimentName"
+    :scene-name="sceneName"
+    :loading-message="loadingMessage"
+    :loading-error-message="loadingErrorMessage"
+  >
+    <template v-slot:header></template>
+    <template v-slot:content>
+      <v-flex xs12 sm6>
+        <v-card dark color="primary">
+          <v-card-text class="px-0">Image 1</v-card-text>
 
-          <h2>Experiment "{{ $route.meta.fullName }}"</h2>
-          <h3>{{ sceneName }}</h3>
-        </v-flex>
-        <!-- Loading screen -->
-        <loader v-if="loadingMessage" :message="loadingMessage" />
-        <!--/ Loading screen -->
+          <v-img v-if="image1 && image1.link" :src="image1.link">
+            <template v-slot:placeholder>
+              <v-layout fill-height align-center justify-center ma-0>
+                <v-progress-circular indeterminate color="grey lighten-5" />
+              </v-layout>
+            </template>
+          </v-img>
+        </v-card>
+      </v-flex>
+      <v-flex sm6 xs12>
+        <v-card dark color="primary">
+          <v-card-text>Image 2</v-card-text>
 
-        <!-- Experiment -->
-        <template v-else-if="!loadingErrorMessage">
-          <v-flex xs12 sm6>
-            <v-card dark color="primary">
-              <v-card-text class="px-0">Image 1</v-card-text>
+          <v-img v-if="image2 && image2.link" :src="image2.link" @load="scrollToChoiceButtons">
+            <template v-slot:placeholder>
+              <v-layout fill-height align-center justify-center ma-0>
+                <v-progress-circular indeterminate color="grey lighten-5" />
+              </v-layout>
+            </template>
+          </v-img>
+        </v-card>
+      </v-flex>
 
-              <v-img v-if="image1 && image1.link" :src="image1.link">
-                <template v-slot:placeholder>
-                  <v-layout fill-height align-center justify-center ma-0>
-                    <v-progress-circular indeterminate color="grey lighten-5" />
-                  </v-layout>
-                </template>
-              </v-img>
-            </v-card>
-          </v-flex>
-          <v-flex sm6 xs12>
-            <v-card dark color="primary">
-              <v-card-text>Image 2</v-card-text>
 
-              <v-img v-if="image2 && image2.link" :src="image2.link" @load="scrollToChoiceButtons">
-                <template v-slot:placeholder>
-                  <v-layout fill-height align-center justify-center ma-0>
-                    <v-progress-circular indeterminate color="grey lighten-5" />
-                  </v-layout>
-                </template>
-              </v-img>
-            </v-card>
-          </v-flex>
-
-
-          <!-- Experiment validation button -->
-          <v-layout justify-center align-content-center>
-            <div id="choice">
-              <v-container grid-list-md text-xs-center fluid>
-                <h2>Test {{ testCount }} / {{ maxTestCount }}</h2>
-                <v-layout row wrap>
-                  <v-flex sm6 xs12>
-                    <v-btn @click="areTheSameAction(false, getReferenceTest)" color="error" large>Images are NOT the same</v-btn>
-                  </v-flex>
-                  <v-flex sm6 xs12>
-                    <v-btn @click="areTheSameAction(true, getReferenceTest)" color="success" large>Images are the same</v-btn>
-                  </v-flex>
-                </v-layout>
-              </v-container>
-            </div>
-          </v-layout>
-          <!--/ Experiment validation button -->
-        </template>
-        <!--/ Experiment -->
+      <!-- Experiment validation button -->
+      <v-layout justify-center align-content-center>
+        <div id="choice">
+          <v-container grid-list-md text-xs-center fluid>
+            <h2>Test {{ testCount }} / {{ maxTestCount }}</h2>
+            <v-layout row wrap>
+              <v-flex sm6 xs12>
+                <v-btn @click="areTheSameAction(false, getReferenceTest)" color="error" large>Images are NOT the same</v-btn>
+              </v-flex>
+              <v-flex sm6 xs12>
+                <v-btn @click="areTheSameAction(true, getReferenceTest)" color="success" large>Images are the same</v-btn>
+              </v-flex>
+            </v-layout>
+          </v-container>
+        </div>
       </v-layout>
-    </v-container>
-  </div>
+      <!--/ Experiment validation button -->
+    </template>
+  </ExperimentBlock>
 </template>
 
 <script>
+import ExperimentBlock from '@/components/ExperimentBlock.vue'
 import ExperimentBaseAreSameImages from '@/mixins/ExperimentBaseAreSameImages'
-import Loader from '@/components/Loader.vue'
 
 export default {
   name: 'AreSameImagesReference',
   components: {
-    Loader
+    ExperimentBlock
   },
   mixins: [ExperimentBaseAreSameImages],
 

+ 61 - 76
src/views/Experiments/AreSameImagesReferenceOneExtract.vue

@@ -1,92 +1,77 @@
 <template>
-  <div>
-    <v-container grid-list-md text-xs-center fluid>
-      <v-layout row wrap>
-        <v-flex xs12>
-          <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>
-
-          <h2>Experiment "{{ $route.meta.fullName }}"</h2>
-          <h3>{{ sceneName }}</h3>
-        </v-flex>
-        <!-- Loading screen -->
-        <loader v-if="loadingMessage" :message="loadingMessage" />
-        <!--/ Loading screen -->
-
-        <!-- Experiment -->
-        <template v-else-if="!loadingErrorMessage && image1 && image2">
-          <v-flex xs12 sm6>
-            <v-card dark color="primary">
-              <v-card-text class="px-0">Image 1</v-card-text>
-
-              <v-container v-if="imageOneExtractPosition === 'left'" class="pa-1">
-                <ExtractsToImage :extracts="image1" :extract-config="extractConfig" />
-              </v-container>
-              <v-img v-else :src="image2.link" @load="scrollToChoiceButtons">
-                <template v-slot:placeholder>
-                  <v-layout fill-height align-center justify-center ma-0>
-                    <v-progress-circular indeterminate color="grey lighten-5" />
-                  </v-layout>
-                </template>
-              </v-img>
-            </v-card>
-          </v-flex>
-          <v-flex xs12 sm6>
-            <v-card dark color="primary">
-              <v-card-text>Image 2</v-card-text>
-              <v-container v-if="imageOneExtractPosition === 'right'" class="pa-1">
-                <ExtractsToImage :extracts="image1" :extract-config="extractConfig" />
-              </v-container>
-              <v-img v-else :src="image2.link" @load="scrollToChoiceButtons">
-                <template v-slot:placeholder>
-                  <v-layout fill-height align-center justify-center ma-0>
-                    <v-progress-circular indeterminate color="grey lighten-5" />
-                  </v-layout>
-                </template>
-              </v-img>
-            </v-card>
-          </v-flex>
-
-
-          <!-- Experiment validation button -->
-          <v-layout justify-center align-content-center>
-            <div id="choice">
-              <v-container grid-list-md text-xs-center fluid>
-                <h2>Test {{ testCount }} / {{ maxTestCount }}</h2>
-                <v-layout row wrap>
-                  <v-flex sm6 xs12>
-                    <v-btn @click="areTheSameActionLocal(false)" color="error" large>Images are NOT the same</v-btn>
-                  </v-flex>
-                  <v-flex sm6 xs12>
-                    <v-btn @click="areTheSameActionLocal(true)" color="success" large>Images are the same</v-btn>
-                  </v-flex>
-                </v-layout>
-              </v-container>
-            </div>
-          </v-layout>
-          <!--/ Experiment validation button -->
-        </template>
-        <!--/ Experiment -->
+  <ExperimentBlock
+    :experiment-name="experimentName"
+    :scene-name="sceneName"
+    :loading-message="loadingMessage"
+    :loading-error-message="loadingErrorMessage"
+  >
+    <template v-slot:header></template>
+    <template v-if="image1 && image2" v-slot:content>
+      <v-flex xs12 sm6>
+        <v-card dark color="primary">
+          <v-card-text class="px-0">Image 1</v-card-text>
+
+          <v-container v-if="imageOneExtractPosition === 'left'" class="pa-1">
+            <ExtractsToImage :extracts="image1" :extract-config="extractConfig" />
+          </v-container>
+          <v-img v-else :src="image2.link" @load="scrollToChoiceButtons">
+            <template v-slot:placeholder>
+              <v-layout fill-height align-center justify-center ma-0>
+                <v-progress-circular indeterminate color="grey lighten-5" />
+              </v-layout>
+            </template>
+          </v-img>
+        </v-card>
+      </v-flex>
+      <v-flex xs12 sm6>
+        <v-card dark color="primary">
+          <v-card-text>Image 2</v-card-text>
+          <v-container v-if="imageOneExtractPosition === 'right'" class="pa-1">
+            <ExtractsToImage :extracts="image1" :extract-config="extractConfig" />
+          </v-container>
+          <v-img v-else :src="image2.link" @load="scrollToChoiceButtons">
+            <template v-slot:placeholder>
+              <v-layout fill-height align-center justify-center ma-0>
+                <v-progress-circular indeterminate color="grey lighten-5" />
+              </v-layout>
+            </template>
+          </v-img>
+        </v-card>
+      </v-flex>
+
+
+      <!-- Experiment validation button -->
+      <v-layout justify-center align-content-center>
+        <div id="choice">
+          <v-container grid-list-md text-xs-center fluid>
+            <h2>Test {{ testCount }} / {{ maxTestCount }}</h2>
+            <v-layout row wrap>
+              <v-flex sm6 xs12>
+                <v-btn @click="areTheSameActionLocal(false)" color="error" large>Images are NOT the same</v-btn>
+              </v-flex>
+              <v-flex sm6 xs12>
+                <v-btn @click="areTheSameActionLocal(true)" color="success" large>Images are the same</v-btn>
+              </v-flex>
+            </v-layout>
+          </v-container>
+        </div>
       </v-layout>
-    </v-container>
-  </div>
+      <!--/ Experiment validation button -->
+    </template>
+  </ExperimentBlock>
 </template>
 
 <script>
+import ExperimentBlock from '@/components/ExperimentBlock.vue'
 import ExperimentBaseExtracts from '@/mixins/ExperimentBaseExtracts'
 import ExperimentBaseAreSameImages from '@/mixins/ExperimentBaseAreSameImages'
-import Loader from '@/components/Loader'
 import ExtractsToImage from '@/components/ExperimentsComponents/ExtractsToImage'
 import { rand } from '@/functions'
 
 export default {
   name: 'AreSameImagesReferenceOneExtract',
   components: {
-    Loader,
+    ExperimentBlock,
     ExtractsToImage
   },
   mixins: [

+ 71 - 85
src/views/Experiments/MatchExtractsWithReference.vue

@@ -1,103 +1,89 @@
 <template>
-  <div>
-    <v-container grid-list-md text-xs-center fluid>
-      <v-layout row wrap>
-        <v-flex xs12>
-          <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>
+  <ExperimentBlock
+    :experiment-name="experimentName"
+    :scene-name="sceneName"
+    :loading-message="loadingMessage"
+    :loading-error-message="loadingErrorMessage"
+  >
+    <template v-slot:header>
+      <!-- Extract configuration -->
+      <extract-configuration v-if="lockConfig === false" @setExtractConfig="setExtractConfig($event, $refs.configurator)" :loading-error-message="loadingErrorMessage" ref="configurator" />
+      <!--/ Extract configuration -->
+    </template>
 
-          <h2>Experiment "{{ $route.meta.fullName }}"</h2>
-          <h3>{{ sceneName }}</h3>
+    <template v-slot:content>
+      <v-flex xs12 sm6>
+        <v-card dark color="primary">
+          <v-card-text class="px-0">Experiment image</v-card-text>
 
-          <!-- Extract configuration -->
-          <extract-configuration v-if="lockConfig === false" @setExtractConfig="setExtractConfig($event, $refs.configurator)" :loading-error-message="loadingErrorMessage" ref="configurator" />
-          <!--/ Extract configuration -->
-        </v-flex>
-        <!-- Loading screen -->
-        <loader v-if="loadingMessage" :message="loadingMessage" />
-        <!--/ Loading screen -->
-
-        <!-- Experiment -->
-        <template v-else-if="!loadingErrorMessage">
-          <v-flex xs12 sm6>
-            <v-card dark color="primary">
-              <v-card-text class="px-0">Experiment image</v-card-text>
-
-              <v-container class="pa-1">
-                <template v-for="i in extractConfig.y">
-                  <v-layout row wrap :key="`row-${i}`">
-                    <v-flex
-                      v-for="(anExtract, index) in extracts.slice(extractConfig.x * (i - 1), (extractConfig.x * i))"
-                      :key="`extract-${i}-${extractConfig.x}-${extractConfig.y}-${index}-${anExtract.quality}`"
-                      class="pa-0"
+          <v-container class="pa-1">
+            <template v-for="i in extractConfig.y">
+              <v-layout row wrap :key="`row-${i}`">
+                <v-flex
+                  v-for="(anExtract, index) in extracts.slice(extractConfig.x * (i - 1), (extractConfig.x * i))"
+                  :key="`extract-${i}-${extractConfig.x}-${extractConfig.y}-${index}-${anExtract.quality}`"
+                  class="pa-0"
+                >
+                  <v-card flat tile class="d-flex height100">
+                    <div
+                      v-if="anExtract.loading"
+                      class="img-extract-loader"
+                      @click.right.prevent
                     >
-                      <v-card flat tile class="d-flex height100">
-                        <div
-                          v-if="anExtract.loading"
-                          class="img-extract-loader"
-                          @click.right.prevent
-                        >
-                          <v-progress-circular
-                            :indeterminate="true"
-                          />
-                        </div>
-                        <v-img
-                          v-else
-                          :src="anExtract.link"
-                          @click.left.prevent="extractAction($event, anExtract)"
-                          @click.right.prevent="extractAction($event, anExtract)"
-                          class="cursor"
-                          :class="{ 'extract-hover-border': showHoverBorder === true }"
+                      <v-progress-circular
+                        :indeterminate="true"
+                      />
+                    </div>
+                    <v-img
+                      v-else
+                      :src="anExtract.link"
+                      @click.left.prevent="extractAction($event, anExtract)"
+                      @click.right.prevent="extractAction($event, anExtract)"
+                      class="cursor"
+                      :class="{ 'extract-hover-border': showHoverBorder === true }"
+                    >
+                      <template v-slot:placeholder>
+                        <v-layout
+                          fill-height
+                          align-center
+                          justify-center
+                          ma-0
                         >
-                          <template v-slot:placeholder>
-                            <v-layout
-                              fill-height
-                              align-center
-                              justify-center
-                              ma-0
-                            >
-                              <v-progress-circular indeterminate color="grey lighten-5" />
-                            </v-layout>
-                          </template>
-                        </v-img>
-                      </v-card>
-                    </v-flex>
-                  </v-layout>
-                </template>
-              </v-container>
-            </v-card>
-          </v-flex>
-          <v-flex sm6 xs12>
-            <v-card dark color="primary">
-              <v-card-text>Reference image</v-card-text>
-              <v-img v-if="referenceImage" :src="referenceImage" />
-            </v-card>
-          </v-flex>
-          <!-- Experiment validation button -->
-          <v-layout justify-end align-content-end>
-            <v-btn @click="finishExperiment" color="primary" large right>Finish experiment</v-btn>
-          </v-layout>
-          <!--/ Experiment validation button -->
-        </template>
-        <!--/ Experiment -->
+                          <v-progress-circular indeterminate color="grey lighten-5" />
+                        </v-layout>
+                      </template>
+                    </v-img>
+                  </v-card>
+                </v-flex>
+              </v-layout>
+            </template>
+          </v-container>
+        </v-card>
+      </v-flex>
+      <v-flex sm6 xs12>
+        <v-card dark color="primary">
+          <v-card-text>Reference image</v-card-text>
+          <v-img v-if="referenceImage" :src="referenceImage" />
+        </v-card>
+      </v-flex>
+      <!-- Experiment validation button -->
+      <v-layout justify-end align-content-end>
+        <v-btn @click="finishExperiment" color="primary" large right>Finish experiment</v-btn>
       </v-layout>
-    </v-container>
-  </div>
+      <!--/ Experiment validation button -->
+    </template>
+  </ExperimentBlock>
 </template>
 
 <script>
+import ExperimentBlock from '@/components/ExperimentBlock.vue'
 import ExperimentBaseExtracts from '@/mixins/ExperimentBaseExtracts'
-import Loader from '@/components/Loader.vue'
 import ExtractConfiguration from '@/components/ExperimentsComponents/ExtractConfiguration.vue'
 
 export default {
   name: 'MatchExtractsWithReference',
   components: {
-    Loader,
+    ExperimentBlock,
     ExtractConfiguration
   },
   mixins: [ExperimentBaseExtracts],

+ 155 - 0
src/views/Experiments/PercentQualityRandom.vue

@@ -0,0 +1,155 @@
+<template>
+  <ExperimentBlock
+    :experiment-name="experimentName"
+    :scene-name="sceneName"
+    :loading-message="loadingMessage"
+    :loading-error-message="loadingErrorMessage"
+  >
+    <template v-slot:header>
+      <!-- ## Template to place in the header (example: Extract-configurator) ## -->
+    </template>
+
+    <template v-slot:content>
+      <!-- ## Actual experiment template ## -->
+
+      <!-- Image -->
+      <v-flex xs6>
+        <v-card dark color="primary">
+          <v-card-text class="px-0">Image 1</v-card-text>
+
+          <v-img v-if="image1 && image1.link" :src="image1.link">
+            <template v-slot:placeholder>
+              <v-layout fill-height align-center justify-center ma-0>
+                <v-progress-circular indeterminate color="grey lighten-5" />
+              </v-layout>
+            </template>
+          </v-img>
+        </v-card>
+      </v-flex>
+
+      <!-- Quality Slider -->
+      <v-flex xs12>
+        <v-subheader class="pl-0">Mark from 0 to 100 how high you think the quality is</v-subheader>
+        <v-slider
+          v-model="selectedQuality"
+          thumb-label
+        />
+      </v-flex>
+
+
+      <!-- Experiment validation button -->
+      <v-layout justify-center align-content-center>
+        <div id="choice">
+          <v-container grid-list-md text-xs-center fluid>
+            <h2>Test {{ testCount }} / {{ maxTestCount }}</h2>
+            <v-layout row wrap>
+              <v-flex sm12 xs12>
+                <v-btn @click="nextRandomImage()" color="primary" large>Validate quality</v-btn>
+              </v-flex>
+            </v-layout>
+          </v-container>
+        </div>
+      </v-layout>
+      <!--/ Experiment validation button -->
+    </template>
+  </ExperimentBlock>
+</template>
+
+<script>
+import ExperimentBlock from '@/components/ExperimentBlock.vue'
+import ExperimentBase from '@/mixins/ExperimentBase.vue'
+import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
+import { rand } from '@/functions'
+
+export default {
+  name: 'PercentQualityRandom', // experiment filename
+  components: {
+    ExperimentBlock
+  },
+  mixins: [ExperimentBase],
+  data() {
+    return {
+      experimentName: 'PercentQualityRandom', // experiment filename
+      image1: null,
+      selectedQuality: 50,
+      testCount: 1,
+      maxTestCount: 10
+    }
+  },
+
+  // When experiment is loaded, this function is ran
+  async mounted() {
+    // Load config and progress for this scene to local state
+    this.loadConfig()
+    this.loadProgress()
+
+    // ## Do your experiment initialization stuff here ##
+    this.loadingMessage = 'Loading experiment data...'
+    this.loadingErrorMessage = null
+    try {
+      // Load scene qualities list
+      await this.getQualitiesList()
+
+      if (!this.image1) await this.getTest()
+    }
+    catch (err) {
+      console.error(err)
+      this.loadingErrorMessage = err.message
+      return
+    }
+    finally {
+      this.loadingMessage = null
+    }
+    // ##/ Do your experiment initialization stuff here ##
+
+    // Save progress from local state into store
+    this.saveProgress()
+  },
+
+  // List of experiment-specific methods
+  methods: {
+    // load image
+    async getTest() {
+      let randomQuality = this.qualities[rand(0, this.qualities.length - 1)]
+      let image = await this.getImage(randomQuality)
+      image.link = this.getHostURI + image.link
+      this.image1 = image
+      this.selectedQuality = 50
+    },
+
+    async nextRandomImage() {
+      this.loadingMessage = 'Loading new test...'
+      this.loadingErrorMessage = null
+      try {
+        this.testCount++
+
+        const experimentalData = {
+          image1: this.image1,
+          selectedQuality: this.selectedQuality,
+          experimentName: this.experimentName,
+          sceneName: this.sceneName
+        }
+        this.sendMessage({ msgId: experimentMsgId.DATA, msg: experimentalData })
+
+        await this.getTest()
+
+        // Experiment end
+        if (this.testCount > this.maxTestCount) return this.finishExperiment()
+      }
+      catch (err) {
+        console.error('Failed to load new test', err)
+        this.loadingErrorMessage = 'Failed to load new test. ' + err.message
+      }
+      finally {
+        this.loadingMessage = null
+        this.saveProgress()
+      }
+    }
+  }
+}
+</script>
+
+
+<style scoped>
+/* Experiment-specific style (CSS) */
+</style>

+ 69 - 0
src/views/Experiments/_template.vue

@@ -0,0 +1,69 @@
+<template>
+  <ExperimentBlock
+    :experiment-name="experimentName"
+    :scene-name="sceneName"
+    :loading-message="loadingMessage"
+    :loading-error-message="loadingErrorMessage"
+  >
+    <template v-slot:header>
+      <!-- ## Template to place in the header (example: Extract-configurator) ## -->
+    </template>
+
+    <template v-slot:content>
+      <!-- ## Actual experiment template ## -->
+    </template>
+  </ExperimentBlock>
+</template>
+
+<script>
+import ExperimentBlock from '@/components/ExperimentBlock.vue'
+import ExperimentBaseAreTheSame from '@/mixins/ExperimentBaseAreSameImages'
+
+export default {
+  name: 'YourExperimentName',
+  components: {
+    ExperimentBlock
+  },
+  mixins: [ExperimentBaseAreTheSame],
+  data() {
+    return {
+      experimentName: 'YourExperimentName'
+    }
+  },
+
+  // When experiment is loaded, this function is ran
+  async mounted() {
+    // Load config and progress for this scene to local state
+    this.loadConfig()
+    this.loadProgress()
+
+    // ## Do your experiment initialization stuff here ##
+    this.loadingMessage = 'Loading experiment data...'
+    this.loadingErrorMessage = null
+    try {
+      // Load scene qualities list
+      await this.getQualitiesList()
+    }
+    catch (err) {
+      console.error(err)
+      this.loadingErrorMessage = err.message
+      return
+    }
+    finally {
+      this.loadingMessage = null
+    }
+    // ##/ Do your experiment initialization stuff here ##
+
+    // Save progress from local state into store
+    this.saveProgress()
+  },
+
+  // List of experiment-specific methods
+  methods: {}
+}
+</script>
+
+
+<style scoped>
+/* Experiment-specific style (CSS) */
+</style>