This commit is contained in:
jableader 2024-01-13 13:00:15 +11:00
parent 85af4ec632
commit 3582b2281e
18 changed files with 660 additions and 84 deletions

83
package-lock.json generated
View file

@ -9,7 +9,8 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"core-js": "^3.8.3", "core-js": "^3.8.3",
"vue": "^3.2.13" "vue": "^3.2.13",
"vue-router": "^4.2.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
@ -3045,6 +3046,11 @@
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
"dev": true "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": { "node_modules/@vue/reactivity": {
"version": "3.4.5", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz",
@ -11026,6 +11032,20 @@
"node": ">=8" "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": { "node_modules/vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "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", "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", "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==", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
"dev": true "dev": true,
"requires": {}
}, },
"@babel/plugin-syntax-async-generators": { "@babel/plugin-syntax-async-generators": {
"version": "7.8.4", "version": "7.8.4",
@ -13691,7 +13712,8 @@
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz",
"integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==", "integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==",
"dev": true "dev": true,
"requires": {}
}, },
"@vue/cli-service": { "@vue/cli-service": {
"version": "5.0.8", "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": { "@vue/reactivity": {
"version": "3.4.5", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz",
@ -14207,13 +14234,15 @@
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true "dev": true,
"requires": {}
}, },
"acorn-jsx": { "acorn-jsx": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true "dev": true,
"requires": {}
}, },
"acorn-walk": { "acorn-walk": {
"version": "8.3.1", "version": "8.3.1",
@ -14272,7 +14301,8 @@
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true "dev": true,
"requires": {}
}, },
"ansi-colors": { "ansi-colors": {
"version": "4.1.3", "version": "4.1.3",
@ -15079,7 +15109,8 @@
"version": "6.4.1", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
"integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==",
"dev": true "dev": true,
"requires": {}
}, },
"css-loader": { "css-loader": {
"version": "6.8.1", "version": "6.8.1",
@ -15265,7 +15296,8 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz",
"integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==",
"dev": true "dev": true,
"requires": {}
}, },
"csso": { "csso": {
"version": "4.2.0", "version": "4.2.0",
@ -16787,7 +16819,8 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true "dev": true,
"requires": {}
}, },
"ieee754": { "ieee754": {
"version": "1.2.1", "version": "1.2.1",
@ -18174,25 +18207,29 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz",
"integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-discard-duplicates": { "postcss-discard-duplicates": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
"integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-discard-empty": { "postcss-discard-empty": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz",
"integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-discard-overridden": { "postcss-discard-overridden": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz",
"integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-loader": { "postcss-loader": {
"version": "6.2.1", "version": "6.2.1",
@ -18297,7 +18334,8 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-modules-local-by-default": { "postcss-modules-local-by-default": {
"version": "4.0.3", "version": "4.0.3",
@ -18332,7 +18370,8 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz",
"integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-normalize-display-values": { "postcss-normalize-display-values": {
"version": "5.1.0", "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": { "vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -20114,7 +20161,8 @@
"version": "8.16.0", "version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"dev": true "dev": true,
"requires": {}
} }
} }
}, },
@ -20236,7 +20284,8 @@
"version": "7.5.9", "version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"dev": true "dev": true,
"requires": {}
}, },
"y18n": { "y18n": {
"version": "5.0.8", "version": "5.0.8",

View file

@ -9,7 +9,8 @@
}, },
"dependencies": { "dependencies": {
"core-js": "^3.8.3", "core-js": "^3.8.3",
"vue": "^3.2.13" "vue": "^3.2.13",
"vue-router": "^4.2.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",

View file

@ -1,15 +1,27 @@
<template> <template>
<img alt="Vue logo" src="./assets/logo.png"> <p>
<HelloWorld msg="Welcome to Your Vue.js App"/> <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> </template>
<script> <script>
import HelloWorld from './components/HelloWorld.vue'
export default { export default {
name: 'App', name: 'App',
components: { computed: {
HelloWorld currentRoute() {
return this.$route.path
}
} }
} }
</script> </script>
@ -23,4 +35,32 @@ export default {
color: #2c3e50; color: #2c3e50;
margin-top: 60px; 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> </style>

2
src/assets/add-cart.svg Normal file
View 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
View 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

View 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>

View 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>

View 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>

View 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>

View file

@ -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>

View 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>

View 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>

View 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>

View file

@ -0,0 +1,5 @@
<template>
<div>
Shopping
</div>
</template>

View 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
View 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())
}
}

View file

@ -1,4 +1,42 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import App from './App.vue' 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!