Basic PoC

This commit is contained in:
Lucas Verney 2017-10-22 21:20:58 +02:00
parent ff591aafb1
commit b600d97e44
11 changed files with 10559 additions and 169 deletions

View File

@ -45,5 +45,9 @@ module.exports = {
"ignorePropertyModificationsFor": ["state"] "ignorePropertyModificationsFor": ["state"]
} }
], ],
// enforce consistent line breaks inside function parentheses
'function-paren-newline': 0,
// Use dangling _
'no-underscore-dangle': 0,
} }
} };

10393
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,41 +16,45 @@
"dependencies": { "dependencies": {
"es6-promise": "^4.1.1", "es6-promise": "^4.1.1",
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"vue": "^2.4.2", "vue": "^2.5.2",
"vue-router": "^2.7.0", "vue-router": "^3.0.1",
"vuetify": "^0.15.7", "vuetify": "^0.16.9",
"vuex": "^2.4.1" "vuex": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^7.1.2", "autoprefixer": "^7.1.6",
"babel-core": "^6.22.1", "babel-core": "^6.22.1",
"babel-eslint": "^7.1.1", "babel-eslint": "^8.0.1",
"babel-loader": "^7.1.1", "babel-loader": "^7.1.1",
"babel-plugin-istanbul": "^4.1.1",
"babel-plugin-transform-runtime": "^6.22.0", "babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2", "babel-preset-env": "^1.6.1",
"babel-preset-stage-2": "^6.22.0", "babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0", "babel-register": "^6.22.0",
"chalk": "^2.0.1", "chai": "^4.1.2",
"connect-history-api-fallback": "^1.3.0", "chalk": "^2.2.0",
"copy-webpack-plugin": "^4.0.1", "chromedriver": "^2.33.2",
"connect-history-api-fallback": "^1.4.0",
"copy-webpack-plugin": "^4.2.0",
"cross-env": "^5.1.0",
"cross-spawn": "^5.0.1",
"css-loader": "^0.28.0", "css-loader": "^0.28.0",
"cssnano": "^3.10.0", "cssnano": "^3.10.0",
"eslint": "^3.19.0", "eslint": "^4.9.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-friendly-formatter": "^3.0.0", "eslint-friendly-formatter": "^3.0.0",
"eslint-import-resolver-webpack": "^0.8.1",
"eslint-loader": "^1.7.1", "eslint-loader": "^1.7.1",
"eslint-plugin-html": "^3.0.0", "eslint-plugin-html": "^3.0.0",
"eslint-config-airbnb-base": "^11.1.3", "eslint-plugin-import": "^2.8.0",
"eslint-import-resolver-webpack": "^0.8.1",
"eslint-plugin-import": "^2.2.0",
"eventsource-polyfill": "^0.9.6", "eventsource-polyfill": "^0.9.6",
"express": "^4.14.1", "express": "^4.16.2",
"extract-text-webpack-plugin": "^2.0.0", "extract-text-webpack-plugin": "^3.0.1",
"file-loader": "^0.11.1", "file-loader": "^1.1.5",
"friendly-errors-webpack-plugin": "^1.1.3", "friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0", "html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3", "http-proxy-middleware": "^0.17.3",
"webpack-bundle-analyzer": "^2.2.1", "inject-loader": "^3.0.0",
"cross-env": "^5.0.1",
"karma": "^1.4.1", "karma": "^1.4.1",
"karma-coverage": "^1.1.1", "karma-coverage": "^1.1.1",
"karma-mocha": "^1.3.0", "karma-mocha": "^1.3.0",
@ -59,31 +63,27 @@
"karma-sinon-chai": "^1.3.1", "karma-sinon-chai": "^1.3.1",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.31", "karma-spec-reporter": "0.0.31",
"karma-webpack": "^2.0.2", "karma-webpack": "^2.0.5",
"mocha": "^3.2.0", "mocha": "^4.0.1",
"chai": "^3.5.0",
"sinon": "^2.1.0",
"sinon-chai": "^2.8.0",
"inject-loader": "^3.0.0",
"babel-plugin-istanbul": "^4.1.1",
"phantomjs-prebuilt": "^2.1.14",
"chromedriver": "^2.27.2",
"cross-spawn": "^5.0.1",
"nightwatch": "^0.9.12", "nightwatch": "^0.9.12",
"selenium-server": "^3.0.1", "opn": "^5.1.0",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"phantomjs-prebuilt": "^2.1.14",
"rimraf": "^2.6.0",
"selenium-server": "^3.6.0",
"semver": "^5.3.0", "semver": "^5.3.0",
"shelljs": "^0.7.6", "shelljs": "^0.7.6",
"opn": "^5.1.0", "sinon": "^4.0.1",
"optimize-css-assets-webpack-plugin": "^2.0.0", "sinon-chai": "^2.14.0",
"ora": "^1.2.0", "url-loader": "^0.6.2",
"rimraf": "^2.6.0", "vue-loader": "^13.3.0",
"url-loader": "^0.5.8",
"vue-loader": "^13.0.4",
"vue-style-loader": "^3.0.1", "vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.4.2", "vue-template-compiler": "^2.5.2",
"webpack": "^2.6.1", "webpack": "^3.8.1",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0", "webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0", "webpack-hot-middleware": "^2.20.0",
"webpack-merge": "^4.1.0" "webpack-merge": "^4.1.0"
}, },
"engines": { "engines": {

View File

@ -1,23 +1,18 @@
<template> <template>
<v-app toolbar> <v-app toolbar>
<NavigationDrawer v-model="isDrawerVisible"/> <NavigationDrawer v-model="isDrawerVisible"/>
<v-toolbar class="indigo" dark fixed> <v-toolbar color="indigo" dark fixed app clipped-left>
<v-toolbar-side-icon @click.stop="showHideDrawer"></v-toolbar-side-icon> <v-toolbar-side-icon @click.stop="showHideDrawer"></v-toolbar-side-icon>
<v-toolbar-title>HungerGames</v-toolbar-title> <v-toolbar-title>HungerGames</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-menu bottom left offset-y> <v-btn icon>
<v-btn icon slot="activator"> <v-icon>skip_next</v-icon>
<v-icon>more_vert</v-icon>
</v-btn> </v-btn>
<v-list>
<v-list-tile>
<v-list-tile-title>Skip quest</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</v-toolbar> </v-toolbar>
<main> <main>
<v-content>
<router-view></router-view> <router-view></router-view>
</v-content>
</main> </main>
</v-app> </v-app>
</template> </template>

View File

@ -3,44 +3,84 @@ require('isomorphic-fetch');
export const BASEURL = 'https://world.openfoodfacts.org/'; export const BASEURL = 'https://world.openfoodfacts.org/';
function _fetchFromOFFApi(filters) {
let url = BASEURL;
filters.forEach((filter) => {
url += `/${filter}`;
});
return fetch(`${url}.json`);
}
function missingCategories() { function missingCategories() {
return fetch( return _fetchFromOFFApi([
`${BASEURL}state/categories-to-be-completed.json`, 'state/product-name-completed',
) 'state/brands-completed',
'state/photos-validated',
'state/categories-to-be-completed',
])
// Parse JSON
.then(response => response.json()) .then(response => response.json())
// Keep only useful data
.then(response => response.products.map(product => ({ .then(response => response.products.map(product => ({
id: product.id, id: product.id,
name: product.product_name, name: product.product_name,
icon: product.image_front_url, icon: product.image_front_url,
brands: product.brands, brands: product.brands,
predictedCategories: {
'en:fresh-foods': {
name: 'Fresh foods',
isOK: true,
},
'en:meats': {
name: 'Meats',
isOK: true,
},
'en:prepared-meats': {
name: 'Prepared meats',
isOK: true,
},
'en:hams': {
name: 'Hams',
isOK: true,
},
'fr:charcuteries-crues': {
name: 'Charcuteries crues',
isOK: true,
},
'en:beverages': {
name: 'Beverages',
isOK: true,
},
},
}))) })))
// Predict categories
.then(response => fetch(
'http://localhost:4242/predict',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(response),
},
))
// Parse JSON
.then(response => response.json())
// Parse data, strip products without any predicted category
.then(response => response.data.filter(
product => product.predictedCategories.length > 0),
)
// Augment predicted categories data
.then(response => response.map(
(product) => {
const augmentedProduct = product;
augmentedProduct.predictedCategories = product.predictedCategories.map(
category => ({ name: category, isOk: false }),
);
return augmentedProduct;
},
))
.catch(exc => console.error(`Unable to fetch products with missing categories: ${exc}.`)); .catch(exc => console.error(`Unable to fetch products with missing categories: ${exc}.`));
} }
export { missingCategories };
function missingBrands() {
return fetch(`${BASEURL}state/brands-to-be-completed.json`)
.then(response => response.json())
.then(response => response.products.map(product => ({
id: product.id,
name: product.product_name,
icon: product.image_front_url,
})))
.catch(exc => console.error(`Unable to fetch products with missing brands: ${exc}.`));
}
function missingProductName() {
return fetch(`${BASEURL}state/product-name-to-be-completed.json`)
.then(response => response.json())
.then(response => response.products.map(product => ({
id: product.id,
brands: product.brands,
icon: product.image_front_url,
})))
.catch(exc => console.error(`Unable to fetch products with missing product name: ${exc}.`));
}
export { missingBrands, missingCategories, missingProductName };

View File

@ -2,6 +2,7 @@
<v-navigation-drawer <v-navigation-drawer
persistent persistent
clipped clipped
app
enable-resize-watcher enable-resize-watcher
v-model="isActive" v-model="isActive"
> >
@ -14,6 +15,14 @@
<v-list-tile-title>Play!</v-list-tile-title> <v-list-tile-title>Play!</v-list-tile-title>
</v-list-tile-content> </v-list-tile-content>
</v-list-tile> </v-list-tile>
<v-list-tile :to="{ name: 'About' }" exact>
<v-list-tile-action>
<v-icon>info</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>About</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>
</template> </template>

View File

@ -13,6 +13,7 @@
</v-avatar> </v-avatar>
<v-card> <v-card>
<v-card-text> <v-card-text>
<!-- TODO: Preload images -->
<img style="width: 100%;" :src="data.icon" /> <img style="width: 100%;" :src="data.icon" />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -20,21 +21,21 @@
</v-flex> </v-flex>
<v-flex xs7 class="text-xs-center"> <v-flex xs7 class="text-xs-center">
<div> <div>
<div class="headline">{{ data.brands }}</div> <div class="headline">{{ data.brands || 'Unknown' }}</div>
<h2 class="title">{{ data.name }}</h2> <h2 class="title">{{ data.name || 'Unkown' }}</h2>
</div> </div>
</v-flex> </v-flex>
</v-layout> </v-layout>
<v-divider /> <v-divider />
<v-layout row> <v-layout row>
<v-flex xs12> <v-flex xs12>
<h2 class="title">Unselect all incorrect categories:</h2> <h2 class="title">Select all correct categories:</h2>
</v-flex> </v-flex>
</v-layout> </v-layout>
<v-layout row wrap> <v-layout row wrap>
<v-btn <v-btn
v-for="(category, key) in data.predictedCategories" :key="key" v-for="(category, key) in data.predictedCategories" :key="key"
round primary dark :class="{ green: category.isOK, red: !category.isOK }" round dark :class="{ green: category.isOK, red: !category.isOK }"
@click.stop="data.predictedCategories[key].isOK = !data.predictedCategories[key].isOK" @click.stop="data.predictedCategories[key].isOK = !data.predictedCategories[key].isOK"
> >
{{ category.name }} {{ category.name }}
@ -69,4 +70,8 @@ export default {
.pointable:hover { .pointable:hover {
opacity: 0.8; opacity: 0.8;
} }
.icon {
border-radius: 0;
}
</style> </style>

View File

@ -1,5 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import Router from 'vue-router'; import Router from 'vue-router';
import About from '@/views/About';
import Quest from '@/views/Quest'; import Quest from '@/views/Quest';
Vue.use(Router); Vue.use(Router);
@ -8,8 +9,13 @@ export default new Router({
routes: [ routes: [
{ {
path: '/', path: '/',
name: 'Quest', name: 'Home',
component: Quest, component: Quest,
}, },
{
path: '/about',
name: 'About',
component: About,
},
], ],
}); });

19
src/views/About.vue Normal file
View File

@ -0,0 +1,19 @@
<template>
<v-container fluid>
<v-layout row>
<v-flex xs12>
<h2>About</h2>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12>
<p>TODO</p>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
};
</script>

View File

@ -6,6 +6,7 @@
import QuestMissingCategories from '@/components/QuestMissingCategories/index'; import QuestMissingCategories from '@/components/QuestMissingCategories/index';
// TODO: Changing route should not flash old product
export default { export default {
components: { components: {
QuestMissingCategories, QuestMissingCategories,
@ -13,10 +14,6 @@ export default {
created() { created() {
this.fetchData(); this.fetchData();
}, },
watch: {
// Fetch again when the component is updated
$route: 'fetchData',
},
methods: { methods: {
fetchData() { fetchData() {
this.$store.dispatch('preloadQuests'); this.$store.dispatch('preloadQuests');

View File

@ -1,78 +0,0 @@
<template>
<v-container fluid grid-list-lg class="text-xs-center">
<v-layout row mb-3 align-center>
<v-flex xs5>
<v-avatar size="100%">
<img class="icon" src="https://static.openfoodfacts.org/images/products/761/303/423/2465/front_fr.6.400.jpg" />
</v-avatar>
</v-flex>
<v-flex xs7 class="text-xs-center">
<div>
<div class="headline">Herta</div>
<h2 class="title">Le Bon Paris, À l'Étouffée (6 Tranches)</h2>
</div>
</v-flex>
</v-layout>
<v-divider />
<v-layout row>
<v-flex xs12>
<h2 class="title">Fix ingredients list:</h2>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs12>
<img style="max-width: 100%;" src="https://static.openfoodfacts.org/images/products/761/303/423/2465/ingredients_fr.8.400.jpg" />
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12>
<v-text-field
value="Jambon frais de porc, Bouillon (eau, couennes de porc, oignons, os de porc, carottes, sel, persil, ail, clou de girofle, poivre, laurier) ; sel, dextrose de maïs, arômes naturels, Conservateur (nitrite de sodium) ; Antioxydant (isoascorbate de sodium)."
class="input-group--focused"
multi-line
/>
</v-text-field>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12>
<v-btn>submit</v-btn>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
data() {
return {
items: {
'en:fresh-foods': {
name: 'Fresh foods',
isOK: true,
},
'en:meats': {
name: 'Meats',
isOK: true,
},
'en:prepared-meats': {
name: 'Prepared meats',
isOK: true,
},
'en:hams': {
name: 'Hams',
isOK: true,
},
'fr:charcuteries-crues': {
name: 'Charcuteries crûes',
isOK: false,
},
'en:beverages': {
name: 'Beverages',
isOK: false,
},
},
};
},
};
</script>