mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 00:15:11 +01:00
improve (pwa): follow the lighthouse tool recommendations for PWAs
This commit is contained in:
parent
8c1952d59c
commit
8deedcd916
11 changed files with 160 additions and 174 deletions
|
|
@ -185,3 +185,12 @@ select:-moz-focusring {
|
|||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hidden{
|
||||
position:absolute;
|
||||
left:-10000px;
|
||||
top:auto;
|
||||
width:1px;
|
||||
height:1px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,19 +76,19 @@ BreadCrumb.propTypes = {
|
|||
const BreadCrumbContainer = (props) => {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<ul>
|
||||
<div className="ul">
|
||||
{props.children}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const Logout = (props) => {
|
||||
return (
|
||||
<li className="component_logout">
|
||||
<div className="li component_logout">
|
||||
<Link to="/logout">
|
||||
<Icon name="power"/>
|
||||
</Link>
|
||||
</li>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ export class PathElementWrapper extends React.Component {
|
|||
href += location.search;
|
||||
|
||||
return (
|
||||
<li className={className}>
|
||||
<div className={"li "+className}>
|
||||
<NgIf cond={this.props.isLast === false}>
|
||||
<Link to={href} className="label">
|
||||
<NgIf cond={this.props.path.minify !== true}>
|
||||
|
|
@ -163,7 +163,7 @@ export class PathElementWrapper extends React.Component {
|
|||
{this.limitSize(this.props.path.label)}
|
||||
<Saving needSaving={this.props.needSaving} />
|
||||
</NgIf>
|
||||
</li>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
z-index: 1000;
|
||||
padding: 4px 0;
|
||||
|
||||
ul{
|
||||
.ul{
|
||||
list-style-type: none;
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
max-width: 800px;
|
||||
padding: 0;
|
||||
> span{display: block; padding: 7px 0;}
|
||||
div, li{
|
||||
div, .li{
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,21 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
.then(render);
|
||||
}
|
||||
return removeLoader().then(render);
|
||||
}).catch((e) => {
|
||||
const msg = "Couldn't boot Filestash";
|
||||
Log.report(msg, location.href);
|
||||
return removeLoaderWithAnimation()
|
||||
});
|
||||
});
|
||||
|
||||
window.onerror = function (msg, url, lineNo, colNo, error) {
|
||||
Log.report(msg, url, lineNo, colNo, error)
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", function() {
|
||||
navigator.serviceWorker.register("/sw_cache.js").catch(function(err){
|
||||
console.error("ServiceWorker registration failed:", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@ class LogManager{
|
|||
}
|
||||
|
||||
report(msg, link, lineNo, columnNo, error){
|
||||
if(navigator.onLine === false) return Promise.resolve();
|
||||
let url = "/report?";
|
||||
url += "url="+encodeURIComponent(location.href)+"&";
|
||||
url += "error="+encodeURIComponent(error.message)+"&";
|
||||
url += "msg="+encodeURIComponent(msg)+"&";
|
||||
url += "from="+encodeURIComponent(link)+"&";
|
||||
url += "from.lineNo="+lineNo+"&";
|
||||
url += "from.columnNo="+columnNo;
|
||||
return http_post(url);
|
||||
if(error) url += "error="+encodeURIComponent(error.message)+"&";
|
||||
return http_post(url).catch();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export class Form extends React.Component {
|
|||
</label>
|
||||
);
|
||||
return (
|
||||
<label className={"no-select input_type_" + props.params["type"]}>
|
||||
<label htmlFor={props.params["id"]} className={"no-select input_type_" + props.params["type"]}>
|
||||
<div>
|
||||
{ $input }
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ export class Submenu extends React.Component {
|
|||
</label>
|
||||
<NgIf cond={this.state.search_input_visible !== null} type="inline">
|
||||
<input ref="$input" onBlur={this.closeIfEmpty.bind(this, false)} style={{"width": this.state.search_input_visible ? "180px" : "0px"}} value={this.state.search_keyword} onChange={(e) => this.onSearchKeypress(e.target.value, true)} type="text" id="search" placeholder="search" name="search" autoComplete="off" />
|
||||
<label htmlFor="search" className="hidden">search</label>
|
||||
</NgIf>
|
||||
</form>
|
||||
</NgIf>
|
||||
|
|
|
|||
|
|
@ -1,162 +0,0 @@
|
|||
const CACHE_NAME = 'v0.3';
|
||||
const DELAY_BEFORE_SENDING_CACHE = 2000;
|
||||
|
||||
/*
|
||||
* Control everything going through the wire, applying different
|
||||
* strategy for caching, fetching resources
|
||||
*/
|
||||
self.addEventListener('fetch', function(event){
|
||||
if(is_a_ressource(event.request)){
|
||||
return event.respondWith(smartCacheStrategy(event.request));
|
||||
}else if(is_an_api_call(event.request)){
|
||||
return event;
|
||||
}else if(is_an_index(event.request)){
|
||||
return event.respondWith(smartCacheStrategy(event.request))
|
||||
}else{
|
||||
return event;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* When a new service worker is coming in, we need to do a bit of
|
||||
* cleanup to get rid of the rotten cache
|
||||
*/
|
||||
self.addEventListener('activate', function(event){
|
||||
vacuum(event);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* When a newly installed service worker is coming in, we want to use it
|
||||
* straight away (make it active). By default it would be in a "waiting state"
|
||||
*/
|
||||
self.addEventListener('install', function(event){
|
||||
if (self.skipWaiting) { self.skipWaiting(); }
|
||||
});
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
// Test if what's the request is about
|
||||
////////////////////////////////////////
|
||||
|
||||
function is_a_ressource(request){
|
||||
return ['css', 'js', 'img', 'logo', 'manifest.json', 'favicon.ico'].indexOf(_pathname(request)[0]) >= 0 ? true : false;
|
||||
}
|
||||
|
||||
function is_an_api_call(request){
|
||||
return _pathname(request)[0] === 'api' ? true : false;
|
||||
}
|
||||
function is_an_index(request){
|
||||
return ['login', 'files', 'view', 'logout'].indexOf(_pathname(request)[0]) >= 0? true : false;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
// HELPERS
|
||||
////////////////////////////////////////
|
||||
|
||||
function vacuum(event){
|
||||
return event.waitUntil(
|
||||
caches.keys().then(function(cachesName){
|
||||
return Promise.all(cachesName.map(function(cacheName){
|
||||
if(cacheName !== CACHE_NAME){
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function _pathname(request){
|
||||
return request.url.replace(/^http[s]?:\/\/[^\/]*\//, '').split('/')
|
||||
}
|
||||
|
||||
/*
|
||||
* Loading Strategy:
|
||||
* use what's in cache first to make things faster but refresh it as we receive a response
|
||||
*/
|
||||
function smartCacheStrategy(request){
|
||||
return caches.open(CACHE_NAME).then(function(cache){
|
||||
return cache.match(request)
|
||||
.then(function(response){
|
||||
if(response && response.status === 200){
|
||||
fetchAndCache(request).catch(nil);
|
||||
response.headers.append('Content-Stale', 'yes');
|
||||
return response;
|
||||
}else{
|
||||
return Promise.reject("OUPS");
|
||||
}
|
||||
})
|
||||
.catch(function(err){
|
||||
return fetchAndCache(request);
|
||||
});
|
||||
}).catch(() => request);
|
||||
|
||||
|
||||
function fetchAndCache(_request){
|
||||
// A request is a stream and can only be consumed once. Since we are consuming this
|
||||
// once by cache and once by the browser for fetch, we need to clone the response as
|
||||
// seen on https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
|
||||
return fetch(_request.clone && _request.clone() || _request)
|
||||
.then(function(response){
|
||||
if(!response || response.status !== 200){ return response; }
|
||||
|
||||
// A response is a stream and can only because we want the browser to consume the
|
||||
// response as well as the cache consuming the response, we need to clone it
|
||||
const responseClone = response.clone();
|
||||
caches.open(CACHE_NAME).then(function(cache){
|
||||
cache.put(_request, responseClone);
|
||||
});
|
||||
return response;
|
||||
}).catch(() => _request);
|
||||
}
|
||||
function nil(e){}
|
||||
}
|
||||
|
||||
|
||||
// Broken as I didn't understood the Promise.race behavior correctly first ...
|
||||
// if nothing in cache it just brakes
|
||||
function networkFirstStrategy(request){
|
||||
return new Promise(function(done, error){
|
||||
cache(request.clone && request.clone() || request).then(function(response){
|
||||
if(!response || !response.headers) return;
|
||||
response.headers.append('Content-Stale', 'yes');
|
||||
done(response);
|
||||
});
|
||||
network(request.clone && request.clone() || request)
|
||||
.then(done)
|
||||
.catch(error);
|
||||
}).catch(() => request);
|
||||
|
||||
function network(request){
|
||||
return fetch(request)
|
||||
.then(function(response){
|
||||
if(!response || response.status !== 200) return Promise.reject(response);
|
||||
|
||||
const responseClone = response.clone();
|
||||
caches.open(CACHE_NAME).then(function(cache){
|
||||
cache.put(request, responseClone);
|
||||
});
|
||||
return Promise.resolve(response);
|
||||
})
|
||||
.catch(function(){
|
||||
return cache(request.clone && request.clone() || request)
|
||||
});
|
||||
}
|
||||
|
||||
function cache(_request){
|
||||
return timeout()
|
||||
.then(function(){ return caches.open(CACHE_NAME); })
|
||||
.then(function(_cache){ return _cache.match(_request); });
|
||||
|
||||
function timeout(){
|
||||
return new Promise(function(done) {
|
||||
setTimeout(function() {
|
||||
done();
|
||||
}, DELAY_BEFORE_SENDING_CACHE);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
123
client/worker/sw_cache.js
Normal file
123
client/worker/sw_cache.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
const CACHE_NAME = "v0.3";
|
||||
const DELAY_BEFORE_SENDING_CACHE = 2000;
|
||||
|
||||
/*
|
||||
* Control everything going through the wire, applying different
|
||||
* strategy for caching, fetching resources
|
||||
*/
|
||||
self.addEventListener("fetch", function(event){
|
||||
if(is_a_ressource(event.request)){
|
||||
return event.respondWith(cacheFirstStrategy(event));
|
||||
}else if(is_an_api_call(event.request)){
|
||||
return event;
|
||||
}else if(is_an_index(event.request)){
|
||||
return event.respondWith(cacheFirstStrategy(event));
|
||||
}else{
|
||||
return event;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* When a new service worker is coming in, we need to do a bit of
|
||||
* cleanup to get rid of the rotten cache
|
||||
*/
|
||||
self.addEventListener("activate", function(event){
|
||||
vacuum(event);
|
||||
});
|
||||
|
||||
self.addEventListener("error", function(err){
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
/*
|
||||
* When a newly installed service worker is coming in, we want to use it
|
||||
* straight away (make it active). By default it would be in a "waiting state"
|
||||
*/
|
||||
self.addEventListener("install", function(event){
|
||||
caches.open(CACHE_NAME).then(function(cache) {
|
||||
return cache.addAll([
|
||||
"/",
|
||||
"/api/config"
|
||||
]);
|
||||
});
|
||||
|
||||
if (self.skipWaiting) { self.skipWaiting(); }
|
||||
});
|
||||
|
||||
|
||||
|
||||
function is_a_ressource(request){
|
||||
const p = _pathname(request);
|
||||
if(["assets", "manifest.json", "favicon.ico"].indexOf(p[0]) !== -1){
|
||||
return true;
|
||||
} else if(p[0] === "api" && (p[1] === "config")){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function is_an_api_call(request){
|
||||
return _pathname(request)[0] === "api" ? true : false;
|
||||
}
|
||||
function is_an_index(request){
|
||||
return ["files", "view", "login", "logout", ""].indexOf(_pathname(request)[0]) >= 0? true : false;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
// HELPERS
|
||||
////////////////////////////////////////
|
||||
|
||||
function vacuum(event){
|
||||
return event.waitUntil(
|
||||
caches.keys().then(function(cachesName){
|
||||
return Promise.all(cachesName.map(function(cacheName){
|
||||
if(cacheName !== CACHE_NAME){
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function _pathname(request){
|
||||
return request.url.replace(/^http[s]?:\/\/[^\/]*\//, "").split("/")
|
||||
}
|
||||
|
||||
/*
|
||||
* strategy is cache first:
|
||||
* 1. use whatever is in the cache
|
||||
* 2. perform the network call to update the cache
|
||||
*/
|
||||
function cacheFirstStrategy(event){
|
||||
return caches.open(CACHE_NAME).then(function(cache){
|
||||
return cache.match(event.request).then(function(response){
|
||||
if(!response){
|
||||
return fetchAndCache(event);
|
||||
}
|
||||
|
||||
fetchAndCache(event).catch(nil);
|
||||
response.headers.append("Content-Stale", "yes");
|
||||
return response;
|
||||
});
|
||||
});
|
||||
|
||||
function fetchAndCache(event){
|
||||
// A request is a stream and can only be consumed once. Since we are consuming this
|
||||
// once by cache and once by the browser for fetch, we need to clone the response as
|
||||
// seen on https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
|
||||
return fetch(event.request)
|
||||
.then(function(response){
|
||||
if(!response || response.status !== 200){ return response; }
|
||||
|
||||
// A response is a stream and can only because we want the browser to consume the
|
||||
// response as well as the cache consuming the response, we need to clone it
|
||||
const responseClone = response.clone();
|
||||
caches.open(CACHE_NAME).then(function(cache){
|
||||
cache.put(event.request, responseClone);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
function nil(e){}
|
||||
}
|
||||
|
|
@ -93,6 +93,7 @@ func Init(a *App) {
|
|||
middlewares = []Middleware{ StaticHeaders }
|
||||
r.PathPrefix("/assets").Handler(http.HandlerFunc(NewMiddlewareChain(StaticHandler(FILE_ASSETS), middlewares, *a))).Methods("GET")
|
||||
r.HandleFunc("/favicon.ico", NewMiddlewareChain(StaticHandler(FILE_ASSETS + "/assets/logo/"), middlewares, *a)).Methods("GET")
|
||||
r.HandleFunc("/sw_cache.js", NewMiddlewareChain(StaticHandler(FILE_ASSETS + "/assets/worker/"), middlewares, *a)).Methods("GET")
|
||||
|
||||
// Other endpoints
|
||||
middlewares = []Middleware{ ApiHeaders }
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ let config = {
|
|||
chunkFilename: "assets/js/chunk_[name]_[id]_[chunkhash].js"
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
rules: [
|
||||
{
|
||||
test: path.join(__dirname, 'client'),
|
||||
use: ['babel-loader'],
|
||||
exclude: /node_modules/
|
||||
|
|
|
|||
Loading…
Reference in a new issue