This commit is contained in:
Arthur
2019-04-20 15:15:32 +02:00
parent d8ac11921f
commit 2e7553b70c
46 changed files with 1871 additions and 721 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,13 @@
"dependencies": {
"core-js": "^2.6.5",
"vue": "^2.6.6",
"vue-router": "^3.0.2",
"vuetify": "^1.5.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.5.0",
"@vue/cli-plugin-eslint": "^3.5.0",
"@vue/cli-service": "^3.5.0",
"@vue/cli-service": "^3.6.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",

View File

@@ -8,6 +8,8 @@
<title>frontend</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">
</head>
<body>
<noscript>

View File

@@ -1,27 +1,119 @@
<template>
<v-app>
<ToolBar/>
<v-toolbar app tabs>
<v-toolbar-side-icon
@click.stop="showNav = !showNav"
></v-toolbar-side-icon>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon
v-for="(tool, idx) in tools"
:key="`tool-${idx}`"
>
<v-icon
:color="tool.color ? tool.color : 'dark'"
v-html="tool.icon"
></v-icon>
</v-btn>
<template v-slot:extension>
<router-view name="extension"></router-view>
</template>
</v-toolbar>
<v-navigation-drawer
fixed
app
clipped
v-model="showNav"
>
<v-list dense>
<v-list-tile
v-for="link in links"
:key="link.path"
:to="link.path"
>
<v-list-tile-avatar>
<v-icon v-html="link.icon"
></v-icon>
</v-list-tile-avatar>
<v-list-tile-title v-html="link.text"
></v-list-tile-title>
</v-list-tile>
</v-list>
</v-navigation-drawer>
<v-content>
<HelloWorld/>
<router-view></router-view>
</v-content>
</v-app>
</template>
<script>
import HelloWorld from './components/HelloWorld'
import ToolBar from './components/ToolBar'
import NotFound from './routes/NotFound'
export const RecipeCategories = {
0: "Petit-déjeuner",
1: "Entrée",
2: "Plat",
3: "Dessert"
}
// *** TOOLS ***
//
// * create_recipe: go to creation page
// * import_recipe: go to import page
// ---- Recipe list
// * category_tabs: navigate through categories
// * search: show input inside extension
// ---- Recipe details
// * go_back: go back to previous page
// * edit: edit fields of model details
// * add_to_menu: show menu slot picker in bottom sheet
// ---- Planning
// * complete_menu: ask for automatic completion of menu
export default {
name: 'App',
components: {
HelloWorld,
ToolBar
},
data () {
return {
//
links: [],
categories: RecipeCategories,
showNav: null,//
}
},
created () {
// Build navigation links from router.js config
for (var idx in this.$router.options.routes) {
var route = this.$router.options.routes[idx];
this.links.push({
path: route.path,
text: route.meta.title,
icon: route.meta.icon,
})
}
},
computed: {
tools: function() {
return [{icon: 'add'},]
},
// TODO: move this inside routing events
title: function() {
var title = "CookAssistant"
for (var key in this.$route.matched) {
var route = this.$route.matched[key];
if (route.hasOwnProperty('meta')) {
if (route.meta.title != undefined) {
title = route.meta.title
}
}
}
return title;
},
}
}
</script>
<style>
html { overflow-y: auto }
</style>

153
frontend/src/api.js Normal file
View File

@@ -0,0 +1,153 @@
/*
TODO:
Implement 'create'/'edit'/'read' modes with generic
configuration retrieved from OPTIONS request (using api.js)
'read' would use GET methods
'edit' would use PUT actions
'create' would use POST action
*/
const baseUrl = "http://localhost:8000/api"
/*
Check that the response status is what's expected.
Returns an object with :
* success: bool
* data: response data
*/
const processStatusCode = function(expected_code) {
return function(response) {
const status_code = response.status;
let data;
if (expected_code != 204) {
data = response.json();
} else {
data = null
}
return Promise.all([status_code, data])
.then(ret => ({
success: ret[0] == expected_code,
data: ret[1]
}))
}
}
/*
Returns data on success or throw an error with data.
*/
const processSuccess = function(result) {
if (!result.success) {
throw result.data;
}
return result.data;
}
const _headers = {
'Content-Type': 'application/json'
}
const _request = {
get (url) {
return fetch(
url,
{ method: 'GET',
})
.then(processStatusCode(200))
.then(processSuccess);
},
post (url, data) {
return fetch(
url,
{ method: 'POST',
body: JSON.stringify(data),
headers: _headers,
})
.then(processStatusCode(201))
.then(processSuccess)
},
put (url, data) {
return fetch(
url,
{ method: 'PUT',
body: JSON.stringify(data),
headers: _headers,
})
.then(processStatusCode(200))
.then(processSuccess)
},
delete(url) {
return fetch(
url,
{ method: 'DELETE',
headers: _headers, })
.then(processStatusCode(204))
.then(processSuccess)
},
options (url) {
return fetch(
url,
{ method: 'OPTIONS',
})
.then(processStatusCode(200))
.then(processSuccess)
}
}
const mountEndPoint = function(url) {
return {
read (id) {
return _request.get(url + id + '/')
},
create (data) {
return _request.post(url, data)
},
edit (id, data) {
return _request.put(url + id + '/', data)
},
delete (id) {
return _request.delete(url + id + '/')
},
options() { return _request.options(url) }
}
}
const mountNestedEndPoint = function(url) {
const getUrl = function(parent_id) {
// TODO: Make it safe against url injection !
return url.replace('*', parent_id)
}
return {
read (parent_id, id) {
return _request.get(getUrl(parent_id) + id + '/')
},
create (parent_id, data) {
return _request.post(
getUrl(parent_id),
data,
)
},
edit (parent_id, id, data) {
return _request.put(
getUrl(parent_id) + id + '/',
data,
)
},
delete (parent_id, id) {
return _request.delete( getUrl(parent_id) + id + '/' )
},
options () {
return _request.options( getUrl(1) )
},
list (parent_id) {
return _request.get( getUrl(parent_id) )
}
}
}
export default {
recipes: mountEndPoint(`${baseUrl}/recips/`),
ingredients: mountNestedEndPoint(
`${baseUrl}/recips/*/amounts/`
),
}

View File

@@ -0,0 +1,40 @@
<template>
<!-- Categories list -->
<span>
<v-tabs v-if="showTabs" :mandatory="false">
<v-tab v-for="key in Object.keys(categories)"
:key="`cat-${key}`"
:to="`/recipes/category/${key}`">
{{ categories[key] }}
</v-tab>
</v-tabs>
<template v-else>
<v-toolbar-title>
Détails
</v-toolbar-title>
<v-btn v-show="!showTabs"
icon
@click="$router.go(-1)"
><v-icon>arrow_back</v-icon></v-btn>
</template>
</span>
</template>
<script>
import { RecipeCategories } from '../App'
export default {
data: () => ({
categories: RecipeCategories,
}),
computed: {
showTabs: function() {
return !(this.$route.path.startsWith("/recipes/id"))
},
}
}
</script>
<style scoped>
</style>

View File

@@ -1,147 +0,0 @@
<template>
<v-container>
<v-layout
text-xs-center
wrap
>
<v-flex xs12>
<v-img
:src="require('../assets/logo.svg')"
class="my-3"
contain
height="200"
></v-img>
</v-flex>
<v-flex mb-4>
<h1 class="display-2 font-weight-bold mb-3">
Welcome to Vuetify
</h1>
<p class="subheading font-weight-regular">
For help and collaboration with other Vuetify developers,
<br>please join our online
<a href="https://community.vuetifyjs.com" target="_blank">Discord Community</a>
</p>
</v-flex>
<v-flex
mb-5
xs12
>
<h2 class="headline font-weight-bold mb-3">What's next?</h2>
<v-layout justify-center>
<a
v-for="(next, i) in whatsNext"
:key="i"
:href="next.href"
class="subheading mx-3"
target="_blank"
>
{{ next.text }}
</a>
</v-layout>
</v-flex>
<v-flex
xs12
mb-5
>
<h2 class="headline font-weight-bold mb-3">Important Links</h2>
<v-layout justify-center>
<a
v-for="(link, i) in importantLinks"
:key="i"
:href="link.href"
class="subheading mx-3"
target="_blank"
>
{{ link.text }}
</a>
</v-layout>
</v-flex>
<v-flex
xs12
mb-5
>
<h2 class="headline font-weight-bold mb-3">Ecosystem</h2>
<v-layout justify-center>
<a
v-for="(eco, i) in ecosystem"
:key="i"
:href="eco.href"
class="subheading mx-3"
target="_blank"
>
{{ eco.text }}
</a>
</v-layout>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
data: () => ({
ecosystem: [
{
text: 'vuetify-loader',
href: 'https://github.com/vuetifyjs/vuetify-loader'
},
{
text: 'github',
href: 'https://github.com/vuetifyjs/vuetify'
},
{
text: 'awesome-vuetify',
href: 'https://github.com/vuetifyjs/awesome-vuetify'
}
],
importantLinks: [
{
text: 'Documentation',
href: 'https://vuetifyjs.com'
},
{
text: 'Chat',
href: 'https://community.vuetifyjs.com'
},
{
text: 'Made with Vuetify',
href: 'https://madewithvuetifyjs.com'
},
{
text: 'Twitter',
href: 'https://twitter.com/vuetifyjs'
},
{
text: 'Articles',
href: 'https://medium.com/vuetify'
}
],
whatsNext: [
{
text: 'Explore components',
href: 'https://vuetifyjs.com/components/api-explorer'
},
{
text: 'Select a layout',
href: 'https://vuetifyjs.com/layout/pre-defined'
},
{
text: 'Frequently Asked Questions',
href: 'https://vuetifyjs.com/getting-started/frequently-asked-questions'
}
]
})
}
</script>
<style>
</style>

View File

@@ -0,0 +1,26 @@
<template>
<v-select v-if="type == 'choice'"
:label="label"
placeholder="----"
:items="choices"
item-text="display_name"
:value="value"
@change="val => $emit('input', val)"
/>
<v-text-field v-else
:label="label"
:type="type"
:value="value"
@input="val => $emit('input', val)"
/>
</template>
<script>
//TODO: work with nested object fields...
export default {
props: ['value', 'label', 'read_only', 'required', 'type', 'choices'],
data () {
return {}
}
}
</script>

View File

@@ -1,37 +0,0 @@
<template>
<v-toolbar dark color="primary">
<!-- <v-toolbar-side-icon></v-toolbar-side-icon> -->
<v-toolbar-title class="white--text">
Cook Assistant
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>search</v-icon>
</v-btn>
<v-btn icon>
<v-icon>apps</v-icon>
</v-btn>
<v-btn icon>
<v-icon>refresh</v-icon>
</v-btn>
<v-btn icon>
<v-icon>more_vert</v-icon>
</v-btn>
</v-toolbar>
</template>
<script>
export default {
data: () => ({})
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,87 @@
<template>
<v-card-text class="py-1" v-if="!editing">
{{ item.display }}
</v-card-text>
<v-input v-else
:append-icon="append_icon"
@click:append="appendIconAction"
:messages="errors"
>
<v-container fluid class="pa-0 ma-0">
<v-layout>
<v-flex xs3>
<RField
v-bind="fields.amount"
v-model="item.amount"
@input="markUpdated"
/>
</v-flex>
<v-flex xs3>
<RField
v-bind="fields.unit"
v-model="item.unit"
@input="markUpdated"
/>
</v-flex>
<v-flex xs6>
<RField
v-bind="fields.ingredient.children.name"
v-model="item.ingredient.name"
@input="markUpdated"
/>
</v-flex>
</v-layout>
</v-container>
</v-input>
</template>
<script>
import api from '../../api.js'
import RField from '../RessourceField'
export default {
components: { RField },
props: ["item", "fields", "editing"],
data() {
return {
errors: [],
updated: false,
append_icon: "delete"
}
},
computed: {
isNew () {
return !this.item.ingredient.id
},
},
created () {
if (this.isNew) {
this.append_icon = "add"
}
},
methods: {
appendIconAction () {
if (this.isNew) {
this.$emit('create', this)
} else {
// TODO: should ask for confirmation
this.$emit('delete', this)
}
},
markUpdated () {
this.updated = true
}
},
watch: {
editing(newValue) {
if (newValue == false && this.updated) {
// TODO: check if there has been updates
this.$emit('save', this)
this.updated = false
}
},
},
}
</script>

View File

@@ -0,0 +1,103 @@
<template>
<v-card class="pa-1">
<v-card-title>
<strong>Ingrédients</strong>
</v-card-title>
<ingredient-details
v-for="ingdt in edited_ingdts"
:item="ingdt"
:fields="fields"
:editing="editing"
@delete="deleteItem"
@save="saveItem"
/>
<!-- New ingredient in editing mode -->
<p v-if="editing">
(+) <ingredient-details
:item="newItem()"
:fields="fields"
:editing="editing"
@create="createItem"
/>
</p>
</v-card>
</template>
<script>
import api from '../../api.js'
import Ingredient from './Ingredient'
function NullIngredient (recipe_id) {
this.recipe = recipe_id
this.ingredient = { name: "" }
this.amount = 0
this.unit = ""
this.display = ""
}
export default {
components: {
'ingredient-details': Ingredient,
},
props: ['ingredients', 'editing', 'recipe'],
data () {
return {
fields: {},
edited_ingdts: [],
}
},
mounted () {
// Copy the property to mutate it
api.ingredients.options()
.then(opts => {
this.fields = opts.actions.POST
})
console.log("get list:", this.recipe)
api.ingredients.list(this.recipe)
.then(data => this.edited_ingdts = data)
.catch(err => console.error("error reading ingdt:", err))
},
methods: {
newItem () {
return new NullIngredient(this.recipe)
},
createItem (comp) {
let item = comp.item
item.recipe = this.recipe
api.ingredients.create(
this.recipe,
item
)
.then(data => {
this.edited_ingdts.push(data)
//TODO: reset additionnal Ingredient
})
.catch(err => comp.errors.push(JSON.stringify(err)))
},
saveItem(comp) {
let item = comp.item
item.recipe = this.recipe // Inject recipe's id into data
api.ingredients.edit(
item.recipe,
item.ingredient.id,
item,
)
.then(data => {
// TODO: better identification for ingredients
// BUG: will not work if ingredient changes!!
let idx = this.edited_ingdts.indexOf(it => it.ingredient == data.ingredient)
this.edited_ingdts[idx] = data
})
.catch(err => comp.errors.push(JSON.stringify(err)))
},
deleteItem(comp) {
let item = comp.item
api.ingredients.delete(this.recipe, item.ingredient.id)
.then(() => {
var idx = this.edited_ingdts.indexOf(item)
this.edited_ingdts.splice(idx, 1)
})
.catch(err => comp.errors.push(JSON.stringify(err)))
}
}
}
</script>

View File

@@ -0,0 +1,194 @@
<!-- Details view of a Recipe
Has read/edit modes
-->
<template>
<v-container fluid>
<v-layout wrap>
<v-flex xs10> <!-- Heading -->
<v-alert v-for="(error,idx) in errors"
:key="`alert-${idx}`"
:value="true">
Error: {{ error }}
</v-alert>
<v-card-text v-if="!editing">
<h6 class="title pb-3">
{{item.name}}
</h6>
<p class="subheading pb-3">
{{categories[item.category]}}
</p>
</v-card-text>
<template v-else>
<RField
v-bind="fields.name"
v-model="item.name"
/>
<RField
v-bind="fields.category"
v-model="item.category"
/>
</template>
</v-flex>
<v-flex xs2> <!-- Buttons -->
<!-- Add to planning -->
<v-bottom-sheet v-show="!editing"
v-model="sheetSelectSlot"
>
<template v-slot:activator>
<v-btn
fab dark
color="success"
><v-icon>add</v-icon></v-btn>
</template>
<!-- Put a meal picker here... -->
<v-card elevation="10" style="min-height: 300px;">
<v-card-text>
Content of bottom sheet
</v-card-text>
</v-card>
</v-bottom-sheet>
<!-- Edit mode -->
<v-fab-transition>
<v-btn
fab dark
:small="!editing"
color="error"
@click="switchEdit"
>
<v-icon v-if="editing">save</v-icon>
<v-icon v-else>edit</v-icon>
</v-btn>
</v-fab-transition>
</v-flex>
<v-flex xs12 md4>
<IngredientList v-if="!isLoading"
:editing="editing"
:recipe="item.id"
:ingredients="item.ingredients"
/>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import RField from '../RessourceField'
import { RecipeCategories } from '../../App'
import api from '../../api.js'
import NotFound from '../../routes/NotFound'
const IngredientList = import('./IngredientList')
function NullRecipe() {
this.name = String();
this.category = Number();
this.ingredients = [];
}
export default {
components: {
IngredientList: () => ({
component: IngredientList,
loading: NotFound, //TODO: actual loading view
error: NotFound,
delay: 100,
timeout: 3000,
}),
RField,
},
data() {
// Transform categories for v-select component
var select_items = []
Object.keys(RecipeCategories)
.map(function(key) {
select_items.push({
text: RecipeCategories[key],
value: key,
});
});
return {
isLoading: true,
sheetSelectSlot: false,
editing: false,
select_items,
categories: RecipeCategories,
errors: [],
// Will be populated by initRecipeItem method
item: new NullRecipe,
}
},
methods: {
switchEdit: function() {
if (this.editing) {
var data = {
name: this.item.name,
category: this.item.category,
}
if (this.item.id) {
// Edit recipe
api.recipes.edit(this.item.id, data)
.then(data => {
this.item = data
this.editing = false
})
.catch(err => this.errors.push(err))
} else {
// Create recipe
api.recipes.create(data)
.then(data => {
this.item = data
this.editing = false
})
.catch(err => this.errors.push(err))
}
} else {
this.editing = true
}
},
initRecipeItem: function(id) {
// An id of 0 means we want a creation page
if (id == 0) {
this.item = new NullRecipe
this.editing = true
} else {
api.recipes.read(id)
.then(data => {
this.errors = []
this.item = data
})
.catch(err => {
this.errors.push(err)
this.item = new NullRecipe
})
.then(() => this.isLoading = false)
}
},
updateRecip: function(form_data) {
console.log(form_data);
var messages = [];
return messages;
}
},
created () {
api.recipes.options()
.then(data => this.fields = data.actions.POST)
.then(() => console.log(this.fields))
},
beforeRouteEnter (to, from, next) {
next(vm => {
const id = to.params.id;
vm.initRecipeItem(id);
})
},
beforeRouteUpdate (to, from, next) {
const id = to.params.id;
this.initRecipeItem(id);
next()
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,48 @@
/* List view of recipes */
<template>
<v-list>
<template v-for="(item, idx) in items">
<v-list-tile
:key="item.title"
:to="`/recipes/id/${item.id}`">
<v-list-tile-content>
{{item.name}}
</v-list-tile-content>
</v-list-tile>
<v-divider v-if="idx < (items.length - 1)"
:key="idx"></v-divider>
</template>
</v-list>
</template>
<script>
export default {
data() {
return {
// Recipes from db grouped by categories id
content: {
0: [], 1: [], 2: [], 3: []
}
}
},
computed: {
items: function() {
return this.content[this.$route.params.cat];
}
},
created: function() {
const vm = this;
fetch('http://localhost:8000/api/recips/')
.then((response) => response.json())
.then(function(data) {
for (var idx in data) {
var obj = data[idx];
vm.content[parseInt(obj.category)].push(obj);
}
});
}
}
</script>
<style scoped>
</style>

View File

@@ -1,9 +1,11 @@
import Vue from 'vue'
import './plugins/vuetify'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')

46
frontend/src/router.js Normal file
View File

@@ -0,0 +1,46 @@
import Vue from 'vue'
import Router from 'vue-router'
import Index from './routes/Index'
import NotFound from './routes/NotFound'
import Recipes from './routes/Recipes'
import Planning from './routes/Planning'
import RecipeDetails from './components/recipes/RecipeDetails'
import RecipeList from './components/recipes/RecipeList'
import CategoryTabs from './components/CategoryTabs'
Vue.use(Router)
const router = new Router({
routes: [
{ path: '/',
component: Index,
meta: { title: "Home", icon: "home" }
},
{ path: '/recipes',
components: { default: Recipes,
extension: CategoryTabs, },
meta: { title: "Recettes",
icon:"book" },
children: [
{ path: 'id/:id',
component: RecipeDetails,
meta: { subtitle: "Détails", } },
{ path: 'category/:cat',
component: RecipeList,
meta: { subtitle: "Liste", } },
{ path: '*',
component: NotFound }
]
},
{ path: '/planning',
component: Planning,
meta: { title: "Menu", icon: "calendar_today" },
}
]
})
export default router

View File

@@ -0,0 +1,21 @@
<template>
<section>
<v-img
:src="require('../assets/logo.svg')"
class="ma-3"
contain
height="200"
></v-img>
</section>
</template>
<script>
export default {
data: () => ({
})
}
</script>
<style>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<v-alert value="true">Not found</v-alert>
</template>
<script>
export default {
data: () => ({})
}
</script>

View File

@@ -0,0 +1,91 @@
<template>
<v-list two-line overflow-y>
<template v-for="(item, index) in items">
<v-subheader v-if="item.header"
:key="item.header"
>{{item.header}}</v-subheader>
<v-divider v-else-if="item.divider"></v-divider>
<v-list-tile v-else
:key="index">
<v-list-tile-content>
<v-list-tile-sub-title
>{{ item.subtitle }}</v-list-tile-sub-title>
<v-list-tile-title
>{{ item.title }}</v-list-tile-title>
</v-list-tile-content>
<v-list-tile-action v-show="item.title">
<v-btn icon color="primary" small
:to="`/recipes/id/${item.id}`"
><v-icon>remove_red_eye</v-icon></v-btn>
</v-list-tile-action>
<v-list-tile-action>
<v-btn icon color="warning" small>
<v-icon>
{{ item.title ? 'delete' : 'add' }}
</v-icon>
</v-btn>
</v-list-tile-action>
</v-list-tile>
</template>
</v-list>
</template>
<script>
const MockPlanning = {
"Lundi": {
"Midi": "Raclette",
"Soir": "Soupe",
},
"Mardi": {
"Midi": null,
"Soir": null,
},
"Mercredi": {
"Midi": null,
"Soir": null,
},
"Jeudi": {
"Midi": null,
"Soir": null,
},
"Vendredi": {
"Midi": null,
"Soir": null,
},
"Samedi": {
"Midi": null,
"Soir": null,
},
"Dimanche": {
"Midi": null,
"Soir": null,
},
}
export default {
data() {
var items = [];
for (var day in MockPlanning) {
items.push({ divider: true })
items.push({ header: day });
for (var meal in MockPlanning[day]) {
items.push({ id: 0, subtitle: meal, title: MockPlanning[day][meal] });
}
}
return {
items,
toolActions: [
{ icon: "find_replace", color: "success" },
],
planning: MockPlanning,
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,14 @@
<template>
<router-view></router-view>
</template>
<script>
export default {
data: () => ({
}),
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<HelloWorld />
</template>
<script>
import HelloWorld from '../components/HelloWorld'
export default {
components: {
HelloWorld
}
}
</script>

View File

@@ -1,7 +1,7 @@
const BundleTracker = require("webpack-bundle-tracker");
module.exports = {
baseUrl: "http://127.0.0.1:8080/",
publicPath: "http://localhost:8080/",
outputDir: './dist/',
chainWebpack: config => {
@@ -17,8 +17,8 @@ module.exports = {
.set('__STATIC__', 'static')
config.devServer
.public('http://0.0.0.0:8080')
.host('0.0.0.0')
.public('http://localhost:8080')
.host('localhost')
.port(8080)
.hotOnly(true)
.watchOptions({poll: 1000})

View File

@@ -1 +1 @@
{"status":"done","publicPath":"http://127.0.0.1:8080/","chunks":{"app":[{"name":"app.js","publicPath":"http://127.0.0.1:8080/app.js","path":"C:\\Users\\lecor\\Documents\\Dev\\Python\\cookAssistant\\frontend\\dist\\app.js"},{"name":"app.a2bf8f46c2ce5a8648c5.hot-update.js","publicPath":"http://127.0.0.1:8080/app.a2bf8f46c2ce5a8648c5.hot-update.js","path":"C:\\Users\\lecor\\Documents\\Dev\\Python\\cookAssistant\\frontend\\dist\\app.a2bf8f46c2ce5a8648c5.hot-update.js"}]},"error":"ModuleError","message":"Module Error (from ./node_modules/vue-loader/lib/loaders/templateLoader.js):\n(Emitted value instead of an instance of Error) \n\n Errors compiling template:\n\n tag <v-toolbar-items> has no matching end tag.\n\n 7 | </v-toolbar-title>\n 8 | <v-spacer></v-spacer>\n 9 | <v-toolbar-items>\n | ^^^^^^^^^^^^^^^^^\n 10 | <v-btn\n 11 | flat\n"}
{"status":"done","publicPath":"http://localhost:8080/","chunks":{"null":[{"name":"0.js","publicPath":"http://localhost:8080/0.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/0.js"},{"name":"0.880af34e5f0e1c2d5443.hot-update.js","publicPath":"http://localhost:8080/0.880af34e5f0e1c2d5443.hot-update.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/0.880af34e5f0e1c2d5443.hot-update.js"}],"app":[{"name":"app.js","publicPath":"http://localhost:8080/app.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/app.js"}]},"error":"ModuleBuildError","message":"Module build failed (from ./node_modules/babel-loader/lib/index.js):\nSyntaxError: /home/artus/Documents/cookAssistant/frontend/src/components/recipes/IngredientList.vue: Unexpected token (85:70)\n\n 83 | )\n 84 | .then(data => {\n> 85 | var idx = this.edited_ingdts.indexOf(i => i.ingredient.id ==)\n | ^\n 86 | })\n 87 | .catch(err => comp.errors.push(JSON.stringify(err)))\n 88 | },\n at Object.raise (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:3851:17)\n at Object.unexpected (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5167:16)\n at Object.parseExprAtom (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:6328:20)\n at Object.parseExprAtom (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:3570:20)\n at Object.parseExprSubscripts (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5914:23)\n at Object.parseMaybeUnary (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5894:21)\n at Object.parseExprOpBaseRightExpr (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5854:34)\n at Object.parseExprOpRightExpr (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5847:21)\n at Object.parseExprOp (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5826:27)\n at Object.parseExprOps (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5791:17)\n at Object.parseMaybeConditional (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5754:23)\n at Object.parseMaybeAssign (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:5701:21)\n at Object.parseFunctionBody (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:6891:24)\n at Object.parseArrowExpression (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:6851:10)\n at Object.parseExprAtom (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:6213:18)\n at Object.parseExprAtom (/home/artus/Documents/cookAssistant/frontend/node_modules/@babel/parser/lib/index.js:3570:20)"}