755
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						| @@ -15,6 +15,7 @@ | ||||
|     "fast-levenshtein": "^3.0.0", | ||||
|     "fuse.js": "^6.4.6", | ||||
|     "qs": "^6.9.6", | ||||
|     "register-service-worker": "^1.7.1", | ||||
|     "typeface-roboto": "^1.1.13", | ||||
|     "v-jsoneditor": "^1.4.2", | ||||
|     "vue": "^2.6.11", | ||||
| @@ -30,6 +31,7 @@ | ||||
|     "@mdi/font": "^5.9.55", | ||||
|     "@vue/cli-plugin-babel": "^4.5.11", | ||||
|     "@vue/cli-plugin-eslint": "^4.5.11", | ||||
|     "@vue/cli-plugin-pwa": "~4.5.0", | ||||
|     "@vue/cli-service": "^4.5.12", | ||||
|     "babel-eslint": "^10.1.0", | ||||
|     "eslint": "^6.7.2", | ||||
| @@ -55,16 +57,16 @@ | ||||
|     }, | ||||
|     "rules": {} | ||||
|   }, | ||||
|   "browserslist": [ | ||||
|     "> 1%", | ||||
|     "last 2 versions", | ||||
|     "not dead" | ||||
|   ], | ||||
|   "prettier": { | ||||
|     "trailingComma": "es5", | ||||
|     "tabWidth": 2, | ||||
|     "semi": true, | ||||
|     "singleQuote": false, | ||||
|     "printWidth": 120 | ||||
|   } | ||||
|   }, | ||||
|   "browserslist": [ | ||||
|     "> 1%", | ||||
|     "last 2 versions", | ||||
|     "not dead" | ||||
|   ] | ||||
| } | ||||
|   | ||||
| Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/android-chrome-192x192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/android-chrome-512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 26 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/android-chrome-maskable-192x192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/android-chrome-maskable-512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 26 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/apple-touch-icon-120x120.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/apple-touch-icon-152x152.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/apple-touch-icon-180x180.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/apple-touch-icon-60x60.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/apple-touch-icon-76x76.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/apple-touch-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/favicon-16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 574 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/favicon-32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/msapplication-icon-144x144.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/img/icons/mstile-150x150.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										3
									
								
								frontend/public/img/icons/safari-pinned-tab.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256" version="1.1"> | ||||
| 	<path d="M 162.083 54.642 C 148.745 68.272, 137.170 80.703, 136.362 82.266 C 133.689 87.435, 133.522 94.130, 135.929 99.573 C 137.122 102.269, 139.070 105.510, 140.258 106.775 L 142.418 109.074 90.974 160.526 L 39.529 211.979 46.999 219.499 L 54.470 227.020 91.235 190.265 L 128 153.510 164.765 190.265 L 201.530 227.020 209 219.500 L 216.470 211.980 179.725 175.225 L 142.980 138.470 150.320 131.178 C 156.858 124.685, 157.808 124.063, 159.001 125.501 C 162.066 129.195, 168.873 132.163, 174.392 132.213 C 183.508 132.295, 186.374 130.174, 212.477 104.038 L 236.454 80.030 231.501 75.001 L 226.548 69.973 209.288 87.212 L 192.027 104.452 187 99.500 L 181.973 94.548 199.212 77.288 L 216.452 60.027 211.500 55 L 206.548 49.973 189.288 67.212 L 172.027 84.452 167 79.500 L 161.973 74.548 179.225 57.275 L 196.477 40.001 191.406 34.930 L 186.335 29.859 162.083 54.642 M 38.429 41.250 C 31.557 49.376, 28.011 62.815, 29.835 73.824 C 31.955 86.615, 34.508 90.093, 61.720 117.253 L 86.520 142.005 101.501 126.999 L 116.482 111.993 79.496 74.996 C 59.154 54.648, 42.210 38, 41.844 38 C 41.478 38, 39.941 39.462, 38.429 41.250" stroke="none" fill="black" fill-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.2 KiB | 
| @@ -1,21 +1,24 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|     <meta name="viewport" content="width=device-width,initial-scale=1.0"> | ||||
|     <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||||
|     <link rel="apple-touch-icon" sizes="256x256" href="<%= BASE_URL %>favicon.ico"> | ||||
|     <link rel="manifest" href='data:application/manifest+json,{"name":"Mealie","icons":[{"src":"<%= BASE_URL %>favicon.ico","sizes":"256x256","type":"image/png"}],"scope":"<%= BASE_URL %>","start_url":"<%= BASE_URL %>","display":"standalone"}'> | ||||
|     <title> Mealie </title> | ||||
|     <!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> --> | ||||
|     <!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> --> | ||||
|   </head> | ||||
|   <body> | ||||
|     <noscript> | ||||
|       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | ||||
|     </noscript> | ||||
|     <div id="app"></div> | ||||
|     <!-- built files will be auto injected --> | ||||
|   </body> | ||||
|  | ||||
| <head> | ||||
|   <meta charset="utf-8"> | ||||
|   <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|   <meta name="viewport" content="width=device-width,initial-scale=1.0"> | ||||
|   <meta name="description" content="Mealie is a self hosted recipe manager and meal planner."> | ||||
|   <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||||
|   <title> Mealie </title> | ||||
|   <!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> --> | ||||
|   <!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> --> | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|   <noscript> | ||||
|     <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. | ||||
|         Please enable it to continue.</strong> | ||||
|   </noscript> | ||||
|   <div id="app"></div> | ||||
|   <!-- built files will be auto injected --> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
							
								
								
									
										82
									
								
								frontend/public/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,82 @@ | ||||
| { | ||||
|   "name": "Mealie", | ||||
|   "short_name": "Mealie", | ||||
|   "icons": [ | ||||
|     { | ||||
|       "src": "./img/icons/android-chrome-192x192.png", | ||||
|       "sizes": "192x192", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/android-chrome-512x512.png", | ||||
|       "sizes": "512x512", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/android-chrome-maskable-192x192.png", | ||||
|       "sizes": "192x192", | ||||
|       "type": "image/png", | ||||
|       "purpose": "maskable" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/android-chrome-maskable-512x512.png", | ||||
|       "sizes": "512x512", | ||||
|       "type": "image/png", | ||||
|       "purpose": "maskable" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/apple-touch-icon-60x60.png", | ||||
|       "sizes": "60x60", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/apple-touch-icon-76x76.png", | ||||
|       "sizes": "76x76", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/apple-touch-icon-120x120.png", | ||||
|       "sizes": "120x120", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/apple-touch-icon-152x152.png", | ||||
|       "sizes": "152x152", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/apple-touch-icon-180x180.png", | ||||
|       "sizes": "180x180", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/apple-touch-icon.png", | ||||
|       "sizes": "180x180", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/favicon-16x16.png", | ||||
|       "sizes": "16x16", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/favicon-32x32.png", | ||||
|       "sizes": "32x32", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/msapplication-icon-144x144.png", | ||||
|       "sizes": "144x144", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "./img/icons/mstile-150x150.png", | ||||
|       "sizes": "150x150", | ||||
|       "type": "image/png" | ||||
|     } | ||||
|   ], | ||||
|   "start_url": ".", | ||||
|   "display": "standalone", | ||||
|   "background_color": "#FFFFFF", | ||||
|   "theme_color": "#E58325" | ||||
| } | ||||
							
								
								
									
										2
									
								
								frontend/public/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| User-agent: * | ||||
| Disallow: | ||||
| @@ -9,6 +9,17 @@ | ||||
|         </div> | ||||
|       </v-banner> | ||||
|       <GlobalSnackbar /> | ||||
|       <v-snackbar v-model="snackWithButtons" bottom left timeout="-1"> | ||||
|         {{ snackWithBtnText }} | ||||
|         <template v-slot:action="{ attrs }"> | ||||
|           <v-btn text color="primary" v-bind="attrs" @click.stop="refreshApp"> | ||||
|             {{ snackBtnText }} | ||||
|           </v-btn> | ||||
|           <v-btn icon class="ml-4" @click="snackWithButtons = false"> | ||||
|             <v-icon>{{ $globals.icons.close }}</v-icon> | ||||
|           </v-btn> | ||||
|         </template> | ||||
|       </v-snackbar> | ||||
|       <router-view></router-view> | ||||
|     </v-main> | ||||
|   </v-app> | ||||
| @@ -51,6 +62,29 @@ export default { | ||||
|     this.$store.dispatch("requestSiteSettings"); | ||||
|   }, | ||||
|  | ||||
|   data() { | ||||
|     return { | ||||
|       refreshing: false, | ||||
|       registration: null, | ||||
|       snackBtnText: "", | ||||
|       snackWithBtnText: "", | ||||
|       snackWithButtons: false, | ||||
|     }; | ||||
|   }, | ||||
|  | ||||
|   created() { | ||||
|     // Listen for swUpdated event and display refresh snackbar as required. | ||||
|     document.addEventListener("swUpdated", this.showRefreshUI, { once: true }); | ||||
|     // Refresh all open app tabs when a new service worker is installed. | ||||
|     if (navigator.serviceWorker) { | ||||
|       navigator.serviceWorker.addEventListener("controllerchange", () => { | ||||
|         if (this.refreshing) return; | ||||
|         this.refreshing = true; | ||||
|         window.location.reload(); | ||||
|       }); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   methods: { | ||||
|     // For Later! | ||||
|  | ||||
| @@ -70,6 +104,25 @@ export default { | ||||
|         this.darkModeSystemCheck(); | ||||
|       }); | ||||
|     }, | ||||
|  | ||||
|     showRefreshUI(e) { | ||||
|       // Display a snackbar inviting the user to refresh/reload the app due | ||||
|       // to an app update being available. | ||||
|       // The new service worker is installed, but not yet active. | ||||
|       // Store the ServiceWorkerRegistration instance for later use. | ||||
|       this.registration = e.detail; | ||||
|       this.snackBtnText = "Refresh"; | ||||
|       this.snackWithBtnText = "New version available!"; | ||||
|       this.snackWithButtons = true; | ||||
|     }, | ||||
|     refreshApp() { | ||||
|       this.snackWithButtons = false; | ||||
|       // Protect against missing registration.waiting. | ||||
|       if (!this.registration || !this.registration.waiting) { | ||||
|         return; | ||||
|       } | ||||
|       this.registration.waiting.postMessage("skipWaiting"); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { globals } from "@/utils/globals"; | ||||
| import i18n from "./i18n"; | ||||
| import "@mdi/font/css/materialdesignicons.css"; | ||||
| import "typeface-roboto/index.css"; | ||||
| import './registerServiceWorker' | ||||
|  | ||||
| Vue.config.productionTip = false; | ||||
| Vue.use(VueRouter); | ||||
|   | ||||
							
								
								
									
										39
									
								
								frontend/src/registerServiceWorker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | ||||
| /* eslint-disable no-console */ | ||||
|  | ||||
| import { register } from "register-service-worker"; | ||||
|  | ||||
| if (process.env.NODE_ENV === "production") { | ||||
|   register(`${process.env.BASE_URL}service-worker.js`, { | ||||
|     ready() { | ||||
|       console.log("Service worker is active."); | ||||
|     }, | ||||
|     registered(registration) { | ||||
|       console.log("Service worker has been registered."); | ||||
|  | ||||
|       // Routinely check for app updates by testing for a new service worker. | ||||
|       setInterval(() => { | ||||
|         registration.update(); | ||||
|       }, 1000 * 60 * 60); // hourly checks | ||||
|     }, | ||||
|     cached() { | ||||
|       console.log("Content has been cached for offline use."); | ||||
|     }, | ||||
|     updatefound() { | ||||
|       console.log("New content is downloading."); | ||||
|     }, | ||||
|     updated(registration) { | ||||
|       console.log("New content is available; please refresh."); | ||||
|  | ||||
|       // Add a custom event and dispatch it. | ||||
|       // Used to display of a 'refresh' banner following a service worker update. | ||||
|       // Set the event payload to the service worker registration object. | ||||
|       document.dispatchEvent(new CustomEvent("swUpdated", { detail: registration })); | ||||
|     }, | ||||
|     offline() { | ||||
|       console.log("No internet connection found. App is running in offline mode."); | ||||
|     }, | ||||
|     error(error) { | ||||
|       console.error("Error during service worker registration:", error); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										75
									
								
								frontend/src/sw.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,75 @@ | ||||
| /* eslint-disable no-undef, no-underscore-dangle, no-restricted-globals */ | ||||
|  | ||||
| self.addEventListener("install", event => { | ||||
|   event.waitUntil(preLoad()); | ||||
| }); | ||||
|  | ||||
| var preLoad = async () => { | ||||
|   console.log("Installing web app"); | ||||
|   const cache = await caches.open("offline"); | ||||
|   console.log("caching index and important routes"); | ||||
|   return await cache.addAll(["/", "/recipes/all"]); | ||||
| }; | ||||
|  | ||||
| self.addEventListener("fetch", event => { | ||||
|   event.respondWith( | ||||
|     checkResponse(event.request).catch(() => { | ||||
|       return returnFromCache(event.request); | ||||
|     }) | ||||
|   ); | ||||
|   event.waitUntil(addToCache(event.request)); | ||||
| }); | ||||
|  | ||||
| var checkResponse = request => { | ||||
|   return new Promise(function(fulfill, reject) { | ||||
|     fetch(request).then(function(response) { | ||||
|       if (response.status !== 404) { | ||||
|         fulfill(response); | ||||
|       } else { | ||||
|         reject(); | ||||
|       } | ||||
|     }, reject); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| var addToCache = async request => { | ||||
|   const cache = await caches.open("offline"); | ||||
|   const response = await fetch(request); | ||||
|   console.log(response.url + " was cached"); | ||||
|   return await cache.put(request, response); | ||||
| }; | ||||
|  | ||||
| var returnFromCache = async request => { | ||||
|   const cache = await caches.open("offline"); | ||||
|   const matching = await cache.match(request); | ||||
|   if (!matching || matching.status == 404) { | ||||
|     return cache.match("offline.html"); | ||||
|   } else { | ||||
|     return matching; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // This is the code piece that GenerateSW mode can't provide for us. | ||||
| // This code listens for the user's confirmation to update the app. | ||||
| self.addEventListener("message", e => { | ||||
|   if (!e.data) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   switch (e.data) { | ||||
|     case "skipWaiting": | ||||
|       self.skipWaiting(); | ||||
|       break; | ||||
|     default: | ||||
|       // NOOP | ||||
|       break; | ||||
|   } | ||||
| }); | ||||
|  | ||||
| workbox.core.clientsClaim(); // Vue CLI 4 and Workbox v4, else | ||||
| // workbox.clientsClaim(); // Vue CLI 3 and Workbox v3. | ||||
|  | ||||
| // The precaching code provided by Workbox. | ||||
| self.__precacheManifest = [].concat(self.__precacheManifest || []); | ||||
| // workbox.precaching.suppressWarnings(); // Only used with Vue CLI 3 and Workbox v3. | ||||
| workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); | ||||
| @@ -1,4 +1,5 @@ | ||||
| const path = require("path"); | ||||
| const manifestJSON = require("./public/manifest.json"); | ||||
| module.exports = { | ||||
|   transpileDependencies: ["vuetify"], | ||||
|   publicPath: process.env.NODE_ENV === "production" ? "/" : "/", | ||||
| @@ -26,4 +27,17 @@ module.exports = { | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   pwa: { | ||||
|     name: manifestJSON.short_name, | ||||
|     themeColor: manifestJSON.theme_color, | ||||
|     msTileColor: manifestJSON.background_color, | ||||
|     appleMobileWebAppCapable: "yes", | ||||
|     appleMobileWebAppStatusBarStyle: "black", | ||||
|  | ||||
|     workboxPluginMode: "InjectManifest", | ||||
|     workboxOptions: { | ||||
|       swSrc: "./src/sw.js", | ||||
|       swDest: "service-worker.js", | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|   | ||||