Basic PoC
This commit is contained in:
parent
ff591aafb1
commit
b600d97e44
@ -45,5 +45,9 @@ module.exports = {
|
||||
"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
10393
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
78
package.json
78
package.json
@ -16,41 +16,45 @@
|
||||
"dependencies": {
|
||||
"es6-promise": "^4.1.1",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"vue": "^2.4.2",
|
||||
"vue-router": "^2.7.0",
|
||||
"vuetify": "^0.15.7",
|
||||
"vuex": "^2.4.1"
|
||||
"vue": "^2.5.2",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuetify": "^0.16.9",
|
||||
"vuex": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"autoprefixer": "^7.1.6",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-eslint": "^8.0.1",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-istanbul": "^4.1.1",
|
||||
"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-register": "^6.22.0",
|
||||
"chalk": "^2.0.1",
|
||||
"connect-history-api-fallback": "^1.3.0",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"chai": "^4.1.2",
|
||||
"chalk": "^2.2.0",
|
||||
"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",
|
||||
"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-import-resolver-webpack": "^0.8.1",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-html": "^3.0.0",
|
||||
"eslint-config-airbnb-base": "^11.1.3",
|
||||
"eslint-import-resolver-webpack": "^0.8.1",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"express": "^4.14.1",
|
||||
"extract-text-webpack-plugin": "^2.0.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"express": "^4.16.2",
|
||||
"extract-text-webpack-plugin": "^3.0.1",
|
||||
"file-loader": "^1.1.5",
|
||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"http-proxy-middleware": "^0.17.3",
|
||||
"webpack-bundle-analyzer": "^2.2.1",
|
||||
"cross-env": "^5.0.1",
|
||||
"inject-loader": "^3.0.0",
|
||||
"karma": "^1.4.1",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-mocha": "^1.3.0",
|
||||
@ -59,31 +63,27 @@
|
||||
"karma-sinon-chai": "^1.3.1",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.31",
|
||||
"karma-webpack": "^2.0.2",
|
||||
"mocha": "^3.2.0",
|
||||
"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",
|
||||
"karma-webpack": "^2.0.5",
|
||||
"mocha": "^4.0.1",
|
||||
"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",
|
||||
"shelljs": "^0.7.6",
|
||||
"opn": "^5.1.0",
|
||||
"optimize-css-assets-webpack-plugin": "^2.0.0",
|
||||
"ora": "^1.2.0",
|
||||
"rimraf": "^2.6.0",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-loader": "^13.0.4",
|
||||
"sinon": "^4.0.1",
|
||||
"sinon-chai": "^2.14.0",
|
||||
"url-loader": "^0.6.2",
|
||||
"vue-loader": "^13.3.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.4.2",
|
||||
"webpack": "^2.6.1",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"webpack": "^3.8.1",
|
||||
"webpack-bundle-analyzer": "^2.2.1",
|
||||
"webpack-dev-middleware": "^1.10.0",
|
||||
"webpack-hot-middleware": "^2.18.0",
|
||||
"webpack-hot-middleware": "^2.20.0",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
|
19
src/App.vue
19
src/App.vue
@ -1,23 +1,18 @@
|
||||
<template>
|
||||
<v-app toolbar>
|
||||
<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-title>HungerGames</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu bottom left offset-y>
|
||||
<v-btn icon slot="activator">
|
||||
<v-icon>more_vert</v-icon>
|
||||
</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-btn icon>
|
||||
<v-icon>skip_next</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<main>
|
||||
<router-view></router-view>
|
||||
<v-content>
|
||||
<router-view></router-view>
|
||||
</v-content>
|
||||
</main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
100
src/api/index.js
100
src/api/index.js
@ -3,44 +3,84 @@ require('isomorphic-fetch');
|
||||
|
||||
export const BASEURL = 'https://world.openfoodfacts.org/';
|
||||
|
||||
function _fetchFromOFFApi(filters) {
|
||||
let url = BASEURL;
|
||||
filters.forEach((filter) => {
|
||||
url += `/${filter}`;
|
||||
});
|
||||
|
||||
return fetch(`${url}.json`);
|
||||
}
|
||||
|
||||
function missingCategories() {
|
||||
return fetch(
|
||||
`${BASEURL}state/categories-to-be-completed.json`,
|
||||
)
|
||||
return _fetchFromOFFApi([
|
||||
'state/product-name-completed',
|
||||
'state/brands-completed',
|
||||
'state/photos-validated',
|
||||
'state/categories-to-be-completed',
|
||||
])
|
||||
// Parse JSON
|
||||
.then(response => response.json())
|
||||
// Keep only useful data
|
||||
.then(response => response.products.map(product => ({
|
||||
id: product.id,
|
||||
name: product.product_name,
|
||||
icon: product.image_front_url,
|
||||
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}.`));
|
||||
}
|
||||
|
||||
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 };
|
||||
|
@ -2,6 +2,7 @@
|
||||
<v-navigation-drawer
|
||||
persistent
|
||||
clipped
|
||||
app
|
||||
enable-resize-watcher
|
||||
v-model="isActive"
|
||||
>
|
||||
@ -14,6 +15,14 @@
|
||||
<v-list-tile-title>Play!</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</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-navigation-drawer>
|
||||
</template>
|
||||
|
@ -13,6 +13,7 @@
|
||||
</v-avatar>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<!-- TODO: Preload images -->
|
||||
<img style="width: 100%;" :src="data.icon" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@ -20,21 +21,21 @@
|
||||
</v-flex>
|
||||
<v-flex xs7 class="text-xs-center">
|
||||
<div>
|
||||
<div class="headline">{{ data.brands }}</div>
|
||||
<h2 class="title">{{ data.name }}</h2>
|
||||
<div class="headline">{{ data.brands || 'Unknown' }}</div>
|
||||
<h2 class="title">{{ data.name || 'Unkown' }}</h2>
|
||||
</div>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-divider />
|
||||
<v-layout row>
|
||||
<v-flex xs12>
|
||||
<h2 class="title">Unselect all incorrect categories:</h2>
|
||||
<h2 class="title">Select all correct categories:</h2>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap>
|
||||
<v-btn
|
||||
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"
|
||||
>
|
||||
{{ category.name }}
|
||||
@ -69,4 +70,8 @@ export default {
|
||||
.pointable:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.icon {
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
import About from '@/views/About';
|
||||
import Quest from '@/views/Quest';
|
||||
|
||||
Vue.use(Router);
|
||||
@ -8,8 +9,13 @@ export default new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Quest',
|
||||
name: 'Home',
|
||||
component: Quest,
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: About,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
19
src/views/About.vue
Normal file
19
src/views/About.vue
Normal 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>
|
@ -6,6 +6,7 @@
|
||||
import QuestMissingCategories from '@/components/QuestMissingCategories/index';
|
||||
|
||||
|
||||
// TODO: Changing route should not flash old product
|
||||
export default {
|
||||
components: {
|
||||
QuestMissingCategories,
|
||||
@ -13,10 +14,6 @@ export default {
|
||||
created() {
|
||||
this.fetchData();
|
||||
},
|
||||
watch: {
|
||||
// Fetch again when the component is updated
|
||||
$route: 'fetchData',
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.$store.dispatch('preloadQuests');
|
||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user