service worker stuff work in progress
This commit is contained in:
parent
ec14338159
commit
47c690526e
File diff suppressed because one or more lines are too long
1
cookbook/static/vue/js/offline_view.js
Normal file
1
cookbook/static/vue/js/offline_view.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
cookbook/static/vue/offline_view.html
Normal file
1
cookbook/static/vue/offline_view.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="js/offline_view.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Recipes"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><div id="app"></div><script src="js/chunk-vendors.js"></script></body></html>
|
@ -1,4 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load webpack_loader %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
@ -15,14 +16,14 @@
|
|||||||
<h1 class="">Offline</h1>
|
<h1 class="">Offline</h1>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<span>{% trans 'You are currently offline!' %}</span>
|
<span>{% trans 'You are currently offline!' %}</span> <br/>
|
||||||
<span>{% trans 'This app does not (yet) support offline functionality. Please make sure to re-establish a network connection.' %}</span>
|
<span>{% trans 'The recipes listed below are available for offline viewing because you have recently viewed them. Keep in mind that data might be outdated.' %}</span>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<i class="fas fa-search fa-8x"></i>
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<offline-view recipe_id="5"></offline-view>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
@ -32,15 +33,15 @@
|
|||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
|
|
||||||
<script type="application/javascript">
|
<script src="{% url 'javascript-catalog' %}"></script>
|
||||||
|
|
||||||
caches.open('html').then(productsRuntimeCache => {
|
{% if debug %}
|
||||||
|
<script src="{% url 'js_reverse' %}"></script>
|
||||||
|
{% else %}
|
||||||
|
<script src="{% static 'django_js_reverse/reverse.js' %}"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
productsRuntimeCache.keys().then(keys => {
|
{% render_bundle 'chunk-vendors' %}
|
||||||
console.log(keys)
|
{% render_bundle 'offline_view' %}
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
File diff suppressed because one or more lines are too long
@ -12,7 +12,6 @@
|
|||||||
"bootstrap-vue": "^2.21.2",
|
"bootstrap-vue": "^2.21.2",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"register-service-worker": "^1.7.1",
|
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-multiselect": "^2.1.6",
|
"vue-multiselect": "^2.1.6",
|
||||||
"vue-template-compiler": "^2.6.12",
|
"vue-template-compiler": "^2.6.12",
|
||||||
@ -31,7 +30,8 @@
|
|||||||
"workbox-navigation-preload": "^6.0.2",
|
"workbox-navigation-preload": "^6.0.2",
|
||||||
"workbox-precaching": "^6.0.2",
|
"workbox-precaching": "^6.0.2",
|
||||||
"workbox-routing": "^6.0.2",
|
"workbox-routing": "^6.0.2",
|
||||||
"workbox-strategies": "^6.0.2"
|
"workbox-strategies": "^6.0.2",
|
||||||
|
"workbox-expiration": "^6.0.2"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
94
vue/src/apps/OfflineView/OfflineView.vue
Normal file
94
vue/src/apps/OfflineView/OfflineView.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{ _('Search') }}
|
||||||
|
<input type="text" v-model="filter" class="form-control">
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3" v-for="r in filtered_recipes" :key="r.id">
|
||||||
|
<b-card :title="r.name" tag="article">
|
||||||
|
<b-card-text>
|
||||||
|
<span class="text-muted">{{ formatDateTime(r.updated_at) }}</span>
|
||||||
|
{{ r.description }}
|
||||||
|
</b-card-text>
|
||||||
|
|
||||||
|
|
||||||
|
<b-button :href="resolveDjangoUrl('view_recipe', r.id)" variant="primary">{{ _('Open') }}</b-button>
|
||||||
|
</b-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue'
|
||||||
|
import {BootstrapVue} from 'bootstrap-vue'
|
||||||
|
|
||||||
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
import {GettextMixin, ResolveUrlMixin} from "@/utils/utils";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue)
|
||||||
|
Vue.prototype.moment = moment
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OfflineView',
|
||||||
|
mixins: [
|
||||||
|
ResolveUrlMixin,
|
||||||
|
GettextMixin
|
||||||
|
],
|
||||||
|
computed: {
|
||||||
|
filtered_recipes: function () {
|
||||||
|
let filtered_list = {}
|
||||||
|
this.recipes.forEach((recipe) => {
|
||||||
|
if (recipe.name.toLowerCase().includes(this.filter.toLowerCase())) {
|
||||||
|
if (recipe.id in filtered_list) {
|
||||||
|
if (recipe.updated_at > filtered_list[recipe.id].updated_at) {
|
||||||
|
filtered_list[recipe.id] = recipe
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filtered_list[recipe.id] = recipe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return filtered_list
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
recipes: [],
|
||||||
|
filter: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadRecipe()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatDateTime: function (datetime) {
|
||||||
|
moment.locale(window.navigator.language);
|
||||||
|
return moment(datetime).format('LLL')
|
||||||
|
},
|
||||||
|
loadRecipe: function () {
|
||||||
|
caches.open('api-recipe').then(productsRuntimeCache => {
|
||||||
|
productsRuntimeCache.keys().then(keys => {
|
||||||
|
keys.forEach((key) => {
|
||||||
|
caches.match(key).then((response) => {
|
||||||
|
response.json().then((json) => {
|
||||||
|
this.recipes.push(json)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
8
vue/src/apps/OfflineView/main.js
Normal file
8
vue/src/apps/OfflineView/main.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './OfflineView.vue'
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
render: h => h(App),
|
||||||
|
}).$mount('#app')
|
@ -1,32 +0,0 @@
|
|||||||
/* 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(
|
|
||||||
'App is being served from cache by a service worker.\n' +
|
|
||||||
'For more details, visit https://goo.gl/AFskqB'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
registered () {
|
|
||||||
console.log('Service worker has been registered.')
|
|
||||||
},
|
|
||||||
cached () {
|
|
||||||
console.log('Content has been cached for offline use.')
|
|
||||||
},
|
|
||||||
updatefound () {
|
|
||||||
console.log('New content is downloading.')
|
|
||||||
},
|
|
||||||
updated () {
|
|
||||||
console.log('New content is available; please refresh.')
|
|
||||||
},
|
|
||||||
offline () {
|
|
||||||
console.log('No internet connection found. App is running in offline mode.')
|
|
||||||
},
|
|
||||||
error (error) {
|
|
||||||
console.error('Error during service worker registration:', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -2,50 +2,43 @@
|
|||||||
import {precacheAndRoute} from 'workbox-precaching';
|
import {precacheAndRoute} from 'workbox-precaching';
|
||||||
import {registerRoute, setCatchHandler} from 'workbox-routing';
|
import {registerRoute, setCatchHandler} from 'workbox-routing';
|
||||||
import {CacheFirst, NetworkFirst, StaleWhileRevalidate} from 'workbox-strategies';
|
import {CacheFirst, NetworkFirst, StaleWhileRevalidate} from 'workbox-strategies';
|
||||||
|
import {ExpirationPlugin} from 'workbox-expiration';
|
||||||
|
|
||||||
|
|
||||||
const CACHE_NAME = 'offline-html';
|
const OFFLINE_CACHE_NAME = 'offline-html';
|
||||||
// This assumes /offline.html is a URL for your self-contained
|
const OFFLINE_PAGE_URL = '/offline/';
|
||||||
// (no external images or styles) offline page.
|
|
||||||
const FALLBACK_HTML_URL = '/offline/';
|
|
||||||
// Populate the cache with the offline HTML page when the
|
|
||||||
// service worker is installed.
|
|
||||||
self.addEventListener('install', async (event) => {
|
self.addEventListener('install', async (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.open(CACHE_NAME)
|
caches.open(OFFLINE_CACHE_NAME).then((cache) => cache.add(new Request(OFFLINE_PAGE_URL, {cache: "reload"})))
|
||||||
.then((cache) => cache.add(new Request(FALLBACK_HTML_URL, {cache: "reload"})))
|
);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// default handler if everything else fails
|
// default handler if everything else fails
|
||||||
setCatchHandler(({event}) => {
|
setCatchHandler(({event}) => {
|
||||||
// The FALLBACK_URL entries must be added to the cache ahead of time, either
|
switch (event.request.destination) {
|
||||||
// via runtime or precaching. If they are precached, then call
|
case 'document':
|
||||||
// `matchPrecache(FALLBACK_URL)` (from the `workbox-precaching` package)
|
console.log('Triggered fallback HTML')
|
||||||
// to get the response from the correct cache.
|
return caches.match(OFFLINE_PAGE_URL);
|
||||||
//
|
|
||||||
// Use event, request, and url to figure out how to respond.
|
|
||||||
// One approach would be to use request.destination, see
|
|
||||||
// https://medium.com/dev-channel/service-worker-caching-strategies-based-on-request-types-57411dd7652c
|
|
||||||
switch (event.request.destination) {
|
|
||||||
case 'document':
|
|
||||||
// If using precached URLs:
|
|
||||||
// return matchPrecache(FALLBACK_HTML_URL);
|
|
||||||
console.log('Triggered fallback HTML')
|
|
||||||
return caches.match(FALLBACK_HTML_URL);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// If we don't have a fallback, just return an error response.
|
console.log('Triggered response ERROR')
|
||||||
console.log('Triggered response ERROR')
|
return Response.error();
|
||||||
return Response.error();
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
precacheAndRoute(self.__WB_MANIFEST);
|
precacheAndRoute(self.__WB_MANIFEST);
|
||||||
|
|
||||||
registerRoute(
|
registerRoute(
|
||||||
({request}) => request.destination === 'image',
|
({request}) => request.destination === 'image',
|
||||||
new CacheFirst({cacheName: 'images'}),
|
new CacheFirst({
|
||||||
|
cacheName: 'images',
|
||||||
|
plugins: [
|
||||||
|
new ExpirationPlugin({
|
||||||
|
maxEntries: 20,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
registerRoute(
|
registerRoute(
|
||||||
@ -65,14 +58,51 @@ registerRoute(
|
|||||||
registerRoute(
|
registerRoute(
|
||||||
new RegExp('api/*'),
|
new RegExp('api/*'),
|
||||||
new NetworkFirst({
|
new NetworkFirst({
|
||||||
cacheName: 'api'
|
cacheName: 'api',
|
||||||
|
plugins: [
|
||||||
|
new ExpirationPlugin({
|
||||||
|
maxEntries: 50
|
||||||
|
}),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
registerRoute(
|
registerRoute(
|
||||||
({request}) => request.destination === 'document',
|
new RegExp('api/recipe/([0-9]+)'),
|
||||||
new NetworkFirst({
|
new NetworkFirst({
|
||||||
cacheName: 'html'
|
cacheName: 'api-recipe',
|
||||||
|
plugins: [
|
||||||
|
new ExpirationPlugin({
|
||||||
|
maxEntries: 50,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const matchHtml = ({url, request, event}) => {
|
||||||
|
if (request.destination === 'document') {
|
||||||
|
if (RegExp('view/recipe/*').test(url)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (RegExp('search/*').test(url)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (RegExp('plan/*').test(url)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
registerRoute(
|
||||||
|
matchHtml,
|
||||||
|
new NetworkFirst({
|
||||||
|
cacheName: 'html',
|
||||||
|
plugins: [
|
||||||
|
new ExpirationPlugin({
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 30,
|
||||||
|
}),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,6 +5,10 @@ const pages = {
|
|||||||
entry: './src/apps/RecipeView/main.js',
|
entry: './src/apps/RecipeView/main.js',
|
||||||
chunks: ['chunk-vendors']
|
chunks: ['chunk-vendors']
|
||||||
},
|
},
|
||||||
|
'offline_view': {
|
||||||
|
entry: './src/apps/OfflineView/main.js',
|
||||||
|
chunks: ['chunk-vendors']
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1 +1 @@
|
|||||||
{"status":"done","chunks":{"chunk-vendors":[{"name":"css/chunk-vendors.css","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\css\\chunk-vendors.css"},{"name":"js/chunk-vendors.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\chunk-vendors.js"}],"recipe_view":[{"name":"js/recipe_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\recipe_view.js"}]}}
|
{"status":"done","chunks":{"chunk-vendors":[{"name":"css/chunk-vendors.css","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\css\\chunk-vendors.css"},{"name":"js/chunk-vendors.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\chunk-vendors.js"}],"offline_view":[{"name":"js/offline_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\offline_view.js"}],"recipe_view":[{"name":"js/recipe_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\recipe_view.js"}]}}
|
@ -8865,6 +8865,13 @@ workbox-expiration@^5.1.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
workbox-core "^5.1.4"
|
workbox-core "^5.1.4"
|
||||||
|
|
||||||
|
workbox-expiration@^6.0.2:
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.0.2.tgz#ac01e8d17f48daa31dc0872c09ee6f4d2cf28ccb"
|
||||||
|
integrity sha512-6+nbR18cklAdI3BPT675ytftXPwnVbXGR8mPWNWTJtl5y2urRYv56ZOJLD7FBFVkZ8EjWiRhNP/A0fkxgdKtWQ==
|
||||||
|
dependencies:
|
||||||
|
workbox-core "^6.0.2"
|
||||||
|
|
||||||
workbox-google-analytics@^5.1.4:
|
workbox-google-analytics@^5.1.4:
|
||||||
version "5.1.4"
|
version "5.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz#b3376806b1ac7d7df8418304d379707195fa8517"
|
resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz#b3376806b1ac7d7df8418304d379707195fa8517"
|
||||||
|
Loading…
Reference in New Issue
Block a user