Baseline
This commit is contained in:
parent
85af4ec632
commit
3582b2281e
18 changed files with 660 additions and 84 deletions
83
package-lock.json
generated
83
package-lock.json
generated
|
|
@ -9,7 +9,8 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^3.2.13"
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
|
|
@ -3045,6 +3046,11 @@
|
|||
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
|
||||
"integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz",
|
||||
|
|
@ -11026,6 +11032,20 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
|
||||
"integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-style-loader": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||
|
|
@ -12108,7 +12128,8 @@
|
|||
"version": "7.21.0-placeholder-for-preset-env.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
|
||||
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@babel/plugin-syntax-async-generators": {
|
||||
"version": "7.8.4",
|
||||
|
|
@ -13691,7 +13712,8 @@
|
|||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz",
|
||||
"integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@vue/cli-service": {
|
||||
"version": "5.0.8",
|
||||
|
|
@ -13961,6 +13983,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@vue/devtools-api": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
|
||||
"integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
|
||||
},
|
||||
"@vue/reactivity": {
|
||||
"version": "3.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz",
|
||||
|
|
@ -14207,13 +14234,15 @@
|
|||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
|
||||
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "8.3.1",
|
||||
|
|
@ -14272,7 +14301,8 @@
|
|||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
|
|
@ -15079,7 +15109,8 @@
|
|||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
|
||||
"integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"css-loader": {
|
||||
"version": "6.8.1",
|
||||
|
|
@ -15265,7 +15296,8 @@
|
|||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz",
|
||||
"integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"csso": {
|
||||
"version": "4.2.0",
|
||||
|
|
@ -16787,7 +16819,8 @@
|
|||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
|
||||
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
|
|
@ -18174,25 +18207,29 @@
|
|||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz",
|
||||
"integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-discard-duplicates": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
|
||||
"integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-discard-empty": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz",
|
||||
"integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-discard-overridden": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz",
|
||||
"integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-loader": {
|
||||
"version": "6.2.1",
|
||||
|
|
@ -18297,7 +18334,8 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-modules-local-by-default": {
|
||||
"version": "4.0.3",
|
||||
|
|
@ -18332,7 +18370,8 @@
|
|||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz",
|
||||
"integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-normalize-display-values": {
|
||||
"version": "5.1.0",
|
||||
|
|
@ -19831,6 +19870,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
|
||||
"integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==",
|
||||
"requires": {
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
}
|
||||
},
|
||||
"vue-style-loader": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||
|
|
@ -20114,7 +20161,8 @@
|
|||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -20236,7 +20284,8 @@
|
|||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.8",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^3.2.13"
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
|
|
|
|||
52
src/App.vue
52
src/App.vue
|
|
@ -1,15 +1,27 @@
|
|||
<template>
|
||||
<img alt="Vue logo" src="./assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
<p>
|
||||
<ul class="nav">
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/" active-class="active">Actions</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/mealplan" active-class="active">Meal Plan</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/shopping" active-class="active">Shopping</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<router-view/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
HelloWorld
|
||||
computed: {
|
||||
currentRoute() {
|
||||
return this.$route.path
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -23,4 +35,32 @@ export default {
|
|||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
/* Make nav bar links buttons across top of screen */
|
||||
.nav {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
/* Style the links inside the navigation bar */
|
||||
.nav a {
|
||||
display: inline-block;
|
||||
color: #f2f2f2;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Have active route use different color */
|
||||
.nav a.active {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
2
src/assets/add-cart.svg
Normal file
2
src/assets/add-cart.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"><title/><path d="M12.2,9h1.6V6.8H16V5.2H13.8V3H12.2V5.2H10V6.8h2.2ZM20,5v5.5L7.45,12.72,5,3H1.25a1,1,0,0,0,0,2H3.47L6.7,18H20V16H8.26l-.33-1.33L22,12.18V5ZM7,19a1.5,1.5,0,1,0,1.5,1.5A1.5,1.5,0,0,0,7,19Zm12,0a1.5,1.5,0,1,0,1.5,1.5A1.5,1.5,0,0,0,19,19Z"/></svg>
|
||||
|
After Width: | Height: | Size: 499 B |
17
src/assets/add-recipe.svg
Normal file
17
src/assets/add-recipe.svg
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M384.337,0h-30.417h-42.775H201.802H27.219v512h326.701h30.417h100.445V0H384.337z M232.218,30.417h48.508v123.752
|
||||
l-24.254-14.734l-24.255,14.734V30.417z M57.636,481.583V30.417h144.165v177.82l54.672-33.213l54.671,33.213V30.417h42.775
|
||||
v451.166H57.636z M454.364,481.583h-70.027V30.417h19.806v229.986h30.417V30.417h19.805V481.583z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="404.143" y="312.557" width="30.417" height="116.883"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 769 B |
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB |
37
src/components/ActionItem.vue
Normal file
37
src/components/ActionItem.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<a @click="$emit('click')">
|
||||
<h2>{{ title }}</h2>
|
||||
<img :src="image" :alt="name" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ActionItem',
|
||||
props: ['title', 'image']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 20em;
|
||||
margin: 1em 1ex;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
</style>
|
||||
25
src/components/ActionsPage.vue
Normal file
25
src/components/ActionsPage.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div>
|
||||
<action-item title="Add to next shop" :image="require('@/assets/add-cart.svg')" @click="addToNextShop" />
|
||||
<action-item title="Add Recipe" :image="require('@/assets/add-recipe.svg')" @click="addRecipe" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ActionItem from './ActionItem.vue'
|
||||
|
||||
export default {
|
||||
name: 'ActionsPage',
|
||||
components: {
|
||||
ActionItem
|
||||
},
|
||||
methods: {
|
||||
addToNextShop() {
|
||||
console.log('Add to next shop')
|
||||
},
|
||||
addRecipe() {
|
||||
console.log('Add Recipe')
|
||||
this.$router.push('/recipes/add')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
19
src/components/EditMealPage.vue
Normal file
19
src/components/EditMealPage.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['id'],
|
||||
data() {
|
||||
return {
|
||||
meal: {
|
||||
date: new Date(),
|
||||
recipe: { name: 'Sar Tay' },
|
||||
chefs: [{ name: 'Ryan' }],
|
||||
consumers: [{ name: 'Ryan' }, { name: 'Jacob' }],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
114
src/components/EditRecipePage.vue
Normal file
114
src/components/EditRecipePage.vue
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<input class="recipe-link" type="text" v-model="link" placeholder="Link to Recipe" />
|
||||
<button @click="parseLink">Parse</button>
|
||||
</div>
|
||||
<div v-if="parsed && !recipe">
|
||||
<p>Recipe not found</p>
|
||||
</div>
|
||||
|
||||
<div v-if="parsed && recipe">
|
||||
<h1><input class="recipe-name" type="text" :value="recipe.name"></h1>
|
||||
<h3>Ingredients</h3>
|
||||
<ul>
|
||||
<li v-for="ingredient in recipe.ingredients" :key="ingredient.line">
|
||||
<ingredient-line
|
||||
:ingredient="ingredient"
|
||||
@update-ingredient="updateIngredient"
|
||||
@update-product-link="updateProduct" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
input {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: large;
|
||||
margin: 1ex 1em;
|
||||
}
|
||||
|
||||
input.recipe-link {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
input.recipe-name {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import data from '@/data.js'
|
||||
import IngredientLine from './IngredientLine.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: { type: Number, optional: true }
|
||||
},
|
||||
components: {
|
||||
IngredientLine
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
link: "",
|
||||
parsed: null,
|
||||
recipe: null,
|
||||
chefs: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.url) {
|
||||
this.link = this.$route.query.url
|
||||
data.parseRecipe(this.link).then((response) => {
|
||||
if (!response)
|
||||
{
|
||||
this.parsed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.recipe = response.recipe;
|
||||
this.parsed = response.raw;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseLink() {
|
||||
this.$router.push({ path: '/recipes/add', query: { url: this.link }})
|
||||
data.parseRecipe(this.link).then((response) => {
|
||||
this.recipe = response.recipe;
|
||||
this.parsed = response.raw;
|
||||
});
|
||||
},
|
||||
updateProduct(ingredient, product_link) {
|
||||
data.parseProduct(ingredient, product_link).then(product => {
|
||||
ingredient.product = product
|
||||
});
|
||||
},
|
||||
updateIngredient(ingredient, line) {
|
||||
data.parseIngredients([line]).then(function(newIngredients) {
|
||||
const newIngredient = newIngredients[0];
|
||||
ingredient.line = line;
|
||||
ingredient.name = newIngredient.name;
|
||||
ingredient.measure = newIngredient.measure;
|
||||
ingredient.preparation = newIngredient.preparation;
|
||||
if (newIngredient.product) {
|
||||
ingredient.product = newIngredient.product;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
||||
110
src/components/IngredientLine.vue
Normal file
110
src/components/IngredientLine.vue
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div class="ingredient-item">
|
||||
<!-- Full ingredient line text bar -->
|
||||
<div class="full-ingredient">
|
||||
<p class="product_teaser" style="flex: initial" v-if="ingredient.product && ingredient.product.img_small">
|
||||
<img :src="ingredient.product.img_small" />
|
||||
</p>
|
||||
<p>
|
||||
<input v-model="ingredientText" @keyup.enter="updateIngredient" @focusout="updateIngredient" placeholder="Enter an ingredient" />
|
||||
</p>
|
||||
<p>
|
||||
<input v-model="productLink" placeholder="Enter product link" @keyup.enter="updateProductLink" @focusout="updateProductLink" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Parse Results card -->
|
||||
<div class="ingredient-details" v-if="ingredient">
|
||||
<div class="parse-result">
|
||||
<p><small>Ingredient: </small><strong>{{ ingredient.name }}</strong></p>
|
||||
<p><small>Quantity: </small> <strong>{{ ingredient.measure.qty }}</strong></p>
|
||||
<p><small>Unit: </small> <strong>{{ ingredient.measure.unit }}</strong></p>
|
||||
<p><small>Preparation: </small> <strong>{{ ingredient.preparation }}</strong></p>
|
||||
</div>
|
||||
<div v-if="ingredient.product" class="product-result">
|
||||
<p><small>Product: </small><strong>{{ ingredient.product.name }}</strong></p>
|
||||
<p><img :src="ingredient.product.img_small" /></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
ingredient: { type: Object }
|
||||
},
|
||||
events: ['update-ingredient', 'update-product-link'],
|
||||
data() {
|
||||
return {
|
||||
ingredientText: this.ingredient.line,
|
||||
productLink: this.ingredient.product?.link ?? "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateIngredient() {
|
||||
if (this.ingredientText != this.ingredient.line)
|
||||
this.$emit('update-ingredient', this.ingredient, this.ingredientText);
|
||||
},
|
||||
updateProductLink() {
|
||||
if (this.productLink && this.productLink != this.ingredient.product?.link)
|
||||
this.$emit('update-product-link', this.ingredient, this.productLink);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.ingredient-item {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.full-ingredient {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.product_teaser img {
|
||||
max-width: 3em;
|
||||
max-height: 3em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.full-ingredient p {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.full-ingredient input {
|
||||
border: 0;
|
||||
font-size: larger;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ingredient-details {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.parse-result {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-result {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
39
src/components/MealCard.vue
Normal file
39
src/components/MealCard.vue
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div class="meal-card">
|
||||
<h3>{{ dayOfWeek }} <small>{{ date }}</small></h3>
|
||||
<h4>{{ meal.recipe.name }}</h4>
|
||||
<p> Cooked by {{ chef_names }}</p>
|
||||
<p> For {{ consumers_names }}</p>
|
||||
<a @click="$emit('edit-meal', meal)">Edit</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.meal-card {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MealCard',
|
||||
props: ['meal'],
|
||||
computed: {
|
||||
chef_names() {
|
||||
return this.meal.chefs.map(chef => chef.name).join(', ')
|
||||
},
|
||||
consumers_names() {
|
||||
return this.meal.consumers.map(consumer => consumer.name).join(', ')
|
||||
},
|
||||
date() {
|
||||
return this.meal.date.toLocaleDateString('en-au', { month: 'numeric', day: 'numeric' })
|
||||
},
|
||||
dayOfWeek() {
|
||||
return this.meal.date.toLocaleDateString('en-au', { weekday: 'long' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
83
src/components/MealPlanPage.vue
Normal file
83
src/components/MealPlanPage.vue
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div>
|
||||
<ul v-if="meals.length">
|
||||
<li v-for="meal in meals" :key="meal.id">
|
||||
<meal-card :meal="meal" @edit-meal="editmeal"/>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-if="!meals.length">
|
||||
<li>No meals planned</li>
|
||||
</ul>
|
||||
|
||||
<ul class="actions" v-if="selectedMeal">
|
||||
<li><router-link class="nav-link" to="/meal/{{meal.id}}/edit" active-class="active">Edit Meal</router-link></li>
|
||||
<li><router-link class="nav-link" to="/meal/{{meal.id}}/swap" active-class="active">Swap Day</router-link></li>
|
||||
<li><a @click="deleteSelectedMeal" class="button">Remove</a></li>
|
||||
<li><a @click="selectedMeal = null">Cancel</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.actions ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.actions li {
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
padding: 2ex;
|
||||
}
|
||||
|
||||
.actions li:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.actions li a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import MealCard from './MealCard.vue'
|
||||
|
||||
export default {
|
||||
name: 'MealPlanPage',
|
||||
components: {
|
||||
MealCard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
meals: [{
|
||||
'date': new Date(),
|
||||
'recipe': {'name': 'Sar Tay'},
|
||||
'chefs': [{
|
||||
'name': 'Ryan'
|
||||
}],
|
||||
'consumers': [{'name': 'Ryan'}, {'name': 'Jacob'}],
|
||||
}],
|
||||
selectedMeal: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
editmeal(meal) {
|
||||
console.log('editmeal', meal)
|
||||
this.selectedMeal = meal;
|
||||
},
|
||||
deleteSelectedMeal() {
|
||||
console.log('deleteSelectedMeal', this.selectedMeal)
|
||||
this.selectedMeal = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
5
src/components/ShoppingPage.vue
Normal file
5
src/components/ShoppingPage.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
Shopping
|
||||
</div>
|
||||
</template>
|
||||
19
src/components/SwapMealPage.vue
Normal file
19
src/components/SwapMealPage.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['id'],
|
||||
data() {
|
||||
return {
|
||||
meal: {
|
||||
date: new Date(),
|
||||
recipe: { name: 'Sar Tay' },
|
||||
chefs: [{ name: 'Ryan' }],
|
||||
consumers: [{ name: 'Ryan' }, { name: 'Jacob' }],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
36
src/data.js
Normal file
36
src/data.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
const BASE_URL = "http://localhost:8000"
|
||||
|
||||
export default {
|
||||
getMeals() {
|
||||
return fetch(BASE_URL + "/meals")
|
||||
.then(response => response.json())
|
||||
},
|
||||
getMeal(id) {
|
||||
return fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`)
|
||||
.then(response => response.json())
|
||||
},
|
||||
getRecipes() {
|
||||
},
|
||||
parseRecipe(url) {
|
||||
return fetch(BASE_URL + `/recipes/parse?url=${encodeURIComponent(url)}`)
|
||||
.then(response => response.json())
|
||||
},
|
||||
parseProduct(ingredient, url) {
|
||||
const body = {
|
||||
url, tags: [ingredient.name, ingredient.line],
|
||||
}
|
||||
|
||||
return fetch(BASE_URL + "/products", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json())
|
||||
},
|
||||
parseIngredients(lines) {
|
||||
const params = lines.map(line => "ingredients=" + encodeURIComponent(line)).join("&");
|
||||
return fetch(BASE_URL + "/recipes/ingredients/parse?" + params)
|
||||
.then(response => response.json())
|
||||
}
|
||||
}
|
||||
40
src/main.js
40
src/main.js
|
|
@ -1,4 +1,42 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
import ActionsPage from './components/ActionsPage.vue'
|
||||
import MealPlanPage from './components/MealPlanPage.vue'
|
||||
import ShoppingPage from './components/ShoppingPage.vue'
|
||||
import EditMealPage from './components/EditMealPage.vue'
|
||||
import SwapMealPage from './components/SwapMealPage.vue'
|
||||
import EditRecipePage from './components/EditRecipePage.vue'
|
||||
|
||||
// 2. Define some routes
|
||||
// Each route should map to a component.
|
||||
// We'll talk about nested routes later.
|
||||
const routes = [
|
||||
{ path: '/', component: ActionsPage },
|
||||
{ path: '/mealplan', component: MealPlanPage },
|
||||
{ path: '/shopping', component: ShoppingPage },
|
||||
{ path: '/meal/:id/edit', component: EditMealPage },
|
||||
{ path: '/meal/:id/swap', component: SwapMealPage },
|
||||
{ path: '/recipes/add', component: EditRecipePage },
|
||||
]
|
||||
|
||||
// 3. Create the router instance and pass the `routes` option
|
||||
// You can pass in additional options here, but let's
|
||||
// keep it simple for now.
|
||||
const router = createRouter({
|
||||
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
|
||||
history: createWebHashHistory(),
|
||||
routes, // short for `routes: routes`
|
||||
})
|
||||
|
||||
// 5. Create and mount the root instance.
|
||||
const app = createApp(App)
|
||||
// Make sure to _use_ the router instance to make the
|
||||
// whole app router-aware.
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
// Now the app has started!
|
||||
Loading…
Reference in a new issue