diff --git a/client/data/tools.js b/client/data/tools.js index 229cd0ff..562a4250 100644 --- a/client/data/tools.js +++ b/client/data/tools.js @@ -54,14 +54,18 @@ export function http_get(url, cache_expire = 0, type = 'json'){ } }else{ done(xhr.responseText); - } + } }else{ - err({status: xhr.status, message: xhr.responseText || 'Oups something went wrong'}); + if(navigator.onLine === false){ + err({status: xhr.status, code: "CONNECTION_LOST", message: 'Connection Lost'}); + }else{ + err({status: xhr.status, message: xhr.responseText || 'Oups something went wrong'}); + } } } } xhr.open('GET', url, true); - xhr.send(null); + xhr.send(null); }); } } @@ -78,7 +82,7 @@ export function http_post(url, data, type = 'json'){ } xhr.send(data); xhr.onload = function () { - if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.readyState === XMLHttpRequest.DONE) { if(xhr.status === 200){ try{ let data = JSON.parse(xhr.responseText); @@ -91,11 +95,15 @@ export function http_post(url, data, type = 'json'){ } }catch(error){ err({message: 'oups', trace: error}); - } + } }else{ - err({status: xhr.status, message: xhr.responseText || 'Oups something went wrong'}); + if(navigator.onLine === false){ + err({status: xhr.status, code: "CONNECTION_LOST", message: 'Connection Lost'}); + }else{ + err({status: xhr.status, message: xhr.responseText || 'Oups something went wrong'}); + } } - } + } } }); } @@ -106,7 +114,7 @@ export function http_delete(url){ xhr.open("DELETE", url, true); xhr.withCredentials = true; xhr.onload = function () { - if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.readyState === XMLHttpRequest.DONE) { if(xhr.status === 200){ try{ let data = JSON.parse(xhr.responseText); @@ -119,11 +127,15 @@ export function http_delete(url){ } }catch(error){ err({message: 'oups', trace: error}); - } + } }else{ - err({status: xhr.status, message: xhr.responseText || 'Oups something went wrong'}); + if(navigator.onLine === false){ + err({status: xhr.status, code: "CONNECTION_LOST", message: 'Connection Lost'}); + }else{ + err({status: xhr.status, message: xhr.responseText || 'Oups something went wrong'}); + } } - } + } } xhr.send(null); }); diff --git a/client/index.html b/client/index.html index 8b476b15..bd0dd5bf 100644 --- a/client/index.html +++ b/client/index.html @@ -1,23 +1,42 @@ - - - - - - - - - - Nuage - - - - -
- - - - - + + + Nuage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + diff --git a/client/index.js b/client/index.js index 52f063f0..dc491f05 100644 --- a/client/index.js +++ b/client/index.js @@ -2,6 +2,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Router from './router'; +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/cache.js').then(function(registration) { + }).catch(function(error) { + console.log('ServiceWorker registration failed:', error); + }); +} + window.onload = () => { ReactDOM.render(, document.getElementById('main')); }; diff --git a/client/pages/filespage.js b/client/pages/filespage.js index b55b78a4..04c6b502 100644 --- a/client/pages/filespage.js +++ b/client/pages/filespage.js @@ -12,7 +12,7 @@ import Path from 'path'; @EventReceiver @DragDropContext(('ontouchstart' in window)? HTML5Backend : HTML5Backend) -export class FilesPage extends React.Component { +export class FilesPage extends React.Component { constructor(props){ super(props); this.state = { @@ -37,6 +37,7 @@ export class FilesPage extends React.Component { componentWillMount(){ + this.setState({error: false}); this.onPathUpdate(this.state.path, 'directory', true) } @@ -58,9 +59,13 @@ export class FilesPage extends React.Component { onRefresh(path = this.state.path){ + this.setState({error: false}) return Files.ls(path).then((files) => { this.setState({files: files, loading: false}) - }); + }).catch((error) => { + this.setState({error: error}); + }) + } onPathUpdate(path, type = 'directory', withLoader = true){ @@ -104,7 +109,7 @@ export class FilesPage extends React.Component { icon: 'loading', virtual: true } - }); + }); const files = JSON.parse(JSON.stringify(this.state.files)); this.setState({files: [].concat(newfiles, files)}); return Promise.resolve(_files); @@ -150,7 +155,7 @@ export class FilesPage extends React.Component { } }; } - + function job(it){ let file = it.next(); if(file){ @@ -168,12 +173,12 @@ export class FilesPage extends React.Component { return job(it); })); } - + const poolSize = 10; return createFilesInUI(files) .then((files) => Promise.resolve(generator(files))) .then((it) => process(it, poolSize)) - .then((res) => Promise.resolve('ok')); + .then((res) => Promise.resolve('ok')); } @@ -183,9 +188,9 @@ export class FilesPage extends React.Component { }); } - + render() { - return ( + return (
@@ -196,8 +201,8 @@ export class FilesPage extends React.Component { - - + + @@ -206,6 +211,3 @@ export class FilesPage extends React.Component { ); } } - - - diff --git a/client/pages/viewerpage.js b/client/pages/viewerpage.js index cbb766db..0df0ceec 100644 --- a/client/pages/viewerpage.js +++ b/client/pages/viewerpage.js @@ -20,7 +20,7 @@ const IDE = (props) => ( @EventReceiver export class ViewerPage extends React.Component { - constructor(props){ + constructor(props){ super(props); this.state = { path: props.match.url.replace('/view', ''), @@ -37,6 +37,34 @@ export class ViewerPage extends React.Component { this.props.subscribe('file.select', this.onPathUpdate.bind(this)); } + componentWillMount(){ + this.setState({loading: true, error: false}); + let app = opener(this.state.path); + if(app === 'editor'){ + Files.cat(this.state.path).then((content) => { + this.setState({data: content, loading: false, opener: app}); + }).catch(err => { + if(err && err.code === 'CANCELLED'){ return } + if(err.code === 'BINARY_FILE'){ + Files.url(this.state.path).then((url) => { + this.setState({data: url, loading: false, opener: 'download'}); + }).catch(err => { + this.setState({error: err}); + }); + }else{ + this.setState({error: err}); + } + }); + }else{ + Files.url(this.state.path).then((url) => { + this.setState({data: url, loading: false, opener: app}); + }).catch(err => { + if(err && err.code === 'CANCELLED'){ return } + this.setState({error: err}); + }); + } + } + componentWillUnmount() { this.props.unsubscribe('file.select') window.removeEventListener("resize", this.resetHeight); @@ -72,32 +100,6 @@ export class ViewerPage extends React.Component { componentDidMount(){ this.resetHeight(); window.addEventListener("resize", this.resetHeight); - this.setState({loading: true}); - let app = opener(this.state.path); - if(app === 'editor'){ - Files.cat(this.state.path).then((content) => { - this.setState({data: content, loading: false, opener: app}); - }).catch(err => { - if(err && err.code === 'CANCELLED'){ return } - if(err.code === 'BINARY_FILE'){ - Files.url(this.state.path).then((url) => { - this.setState({data: url, loading: false, opener: 'download'}); - }).catch(err => { - this.setState({error: err}); - }); - }else{ - this.setState({error: err}); - } - }); - }else{ - Files.url(this.state.path).then((url) => { - this.setState({data: url, loading: false, opener: app}); - }).catch(err => { - console.log("ERROR", err) - if(err && err.code === 'CANCELLED'){ return } - this.setState({error: err}); - }); - } } resetHeight(){ @@ -140,13 +142,12 @@ export class ViewerPage extends React.Component { - + - +
); } } - diff --git a/server/public/cache.js b/server/public/cache.js new file mode 100644 index 00000000..1202151b --- /dev/null +++ b/server/public/cache.js @@ -0,0 +1,160 @@ +const CACHE_NAME = 'v0.0'; +const URLS_TO_CACHE = ['/', '/index.html']; + +self.addEventListener('fetch', function(event){ + if(is_a_ressource(event.request)){ + //console.log("> FETCH RESSOURCE", event.request.url) + return fetchRessource(event); + }else if(is_an_api_call(event.request)){ + //console.log("> FETCH API", event.request.url) + return fetchApi(event); + }else if(is_an_index(event.request)){ + //console.log("> FETCH INDEX", event.request.url) + return fetchIndex(event); + }else{ + //console.log("> FETCH FALLBACK", event.request.url) + return cacheFallback(event); + } +}); + +self.addEventListener('activate', function(event){ + //console.log("> ACTIVATE") + vacuum(event) +}); +self.addEventListener('install', function(event){ + //console.log("> INSTALL SERVICE WORKER", navigator) + if (self.skipWaiting) { self.skipWaiting(); } +}) + +//////////////////////////////////////// +// ASSETS AND RESSOURCES +//////////////////////////////////////// + +function is_a_ressource(request){ + return ['css', 'js', 'img', 'logo', 'manifest.json', 'favicon.ico'].indexOf(pathname(request)[0]) >= 0 ? true : false; +} + +/* + * cache agressively but refresh the cache if possible + */ +function fetchRessource(event){ + event.respondWith( + caches.open(CACHE_NAME).then(function(cache){ + return cache.match(event.request) + .then(function(response){ + if(response){ + fetchAndCache(event).catch(nil) + return response; + }else{ + return Promise.reject("OUPS"); + } + }) + .catch(function(err){ + return fetchAndCache(event); + }); + }) + ); + + 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 + const request = event.request.clone(); + + return fetch(request) + .then(function(response){ + if(!response){ 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 responseToCache = response.clone(); + caches.open(CACHE_NAME).then(function(cache){ + cache.put(event.request, responseToCache); + }); + return response; + }); + } +} + + + +//////////////////////////////////////// +// API CALL +//////////////////////////////////////// +function is_an_api_call(request){ + return pathname(request)[0] === 'api' ? true : false; +} + +function fetchApi(event){ +} + + + +//////////////////////////////////////// +// INDEX CALL +//////////////////////////////////////// +function is_an_index(request){ + return ['login', 'files', 'view', 'logout'].indexOf(pathname(request)[0]) >= 0? true : false; +} +function fetchIndex(event){ + event.request.url = host(event.request); + event.respondWith( + caches.open(CACHE_NAME).then(function(cache){ + return cache.match('/').then(function(response){ + return response || fetch('/').then(function(response) { + if(response && response.status === 200){ + cache.put('/', response.clone()); + } + return response; + }) + }) + }) + ) +} + + +//////////////////////////////////////// +// OTHER STUFF +//////////////////////////////////////// +function cacheFallback(event){ + event.respondWith( + caches.open(CACHE_NAME).then(function(cache){ + return cache.match(event.request).then(function(response){ + if(response){ + return response; + }else{ + return fetch(event.request.clone()) + .then(function(response){ + cache.put(event.request, response.clone()) + return response; + }); + } + }); + }) + ) +} + + +//////////////////////////////////////// +// 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 host(request){ + return request.url.replace(/(http[s]?\:\/\/[^\/]*\/).*/, '$1'); +} +function pathname(request){ + return request.url.replace(/^http[s]?:\/\/[^\/]*\//, '').split('/') +} +function nil(e){} diff --git a/server/public/img/logo.png b/server/public/img/logo.png index ac840fc1..6c05a3a6 100644 Binary files a/server/public/img/logo.png and b/server/public/img/logo.png differ diff --git a/server/public/logo/android-icon-144x144.png b/server/public/logo/android-icon-144x144.png new file mode 100644 index 00000000..7033af94 Binary files /dev/null and b/server/public/logo/android-icon-144x144.png differ diff --git a/server/public/logo/android-icon-192x192.png b/server/public/logo/android-icon-192x192.png new file mode 100644 index 00000000..f8ae45c0 Binary files /dev/null and b/server/public/logo/android-icon-192x192.png differ diff --git a/server/public/logo/android-icon-36x36.png b/server/public/logo/android-icon-36x36.png new file mode 100644 index 00000000..d6d0b1a7 Binary files /dev/null and b/server/public/logo/android-icon-36x36.png differ diff --git a/server/public/logo/android-icon-48x48.png b/server/public/logo/android-icon-48x48.png new file mode 100644 index 00000000..14041fdb Binary files /dev/null and b/server/public/logo/android-icon-48x48.png differ diff --git a/server/public/logo/android-icon-72x72.png b/server/public/logo/android-icon-72x72.png new file mode 100644 index 00000000..27b820c7 Binary files /dev/null and b/server/public/logo/android-icon-72x72.png differ diff --git a/server/public/logo/android-icon-96x96.png b/server/public/logo/android-icon-96x96.png new file mode 100644 index 00000000..ef5fc4db Binary files /dev/null and b/server/public/logo/android-icon-96x96.png differ diff --git a/server/public/logo/apple-icon-114x114.png b/server/public/logo/apple-icon-114x114.png new file mode 100644 index 00000000..3e426a95 Binary files /dev/null and b/server/public/logo/apple-icon-114x114.png differ diff --git a/server/public/logo/apple-icon-120x120.png b/server/public/logo/apple-icon-120x120.png new file mode 100644 index 00000000..ea07bc34 Binary files /dev/null and b/server/public/logo/apple-icon-120x120.png differ diff --git a/server/public/logo/apple-icon-144x144.png b/server/public/logo/apple-icon-144x144.png new file mode 100644 index 00000000..7033af94 Binary files /dev/null and b/server/public/logo/apple-icon-144x144.png differ diff --git a/server/public/logo/apple-icon-152x152.png b/server/public/logo/apple-icon-152x152.png new file mode 100644 index 00000000..f3421d97 Binary files /dev/null and b/server/public/logo/apple-icon-152x152.png differ diff --git a/server/public/logo/apple-icon-180x180.png b/server/public/logo/apple-icon-180x180.png new file mode 100644 index 00000000..4d560e2b Binary files /dev/null and b/server/public/logo/apple-icon-180x180.png differ diff --git a/server/public/logo/apple-icon-57x57.png b/server/public/logo/apple-icon-57x57.png new file mode 100644 index 00000000..8184032e Binary files /dev/null and b/server/public/logo/apple-icon-57x57.png differ diff --git a/server/public/logo/apple-icon-60x60.png b/server/public/logo/apple-icon-60x60.png new file mode 100644 index 00000000..059d0130 Binary files /dev/null and b/server/public/logo/apple-icon-60x60.png differ diff --git a/server/public/logo/apple-icon-72x72.png b/server/public/logo/apple-icon-72x72.png new file mode 100644 index 00000000..27b820c7 Binary files /dev/null and b/server/public/logo/apple-icon-72x72.png differ diff --git a/server/public/logo/apple-icon-76x76.png b/server/public/logo/apple-icon-76x76.png new file mode 100644 index 00000000..2ffe65db Binary files /dev/null and b/server/public/logo/apple-icon-76x76.png differ diff --git a/server/public/logo/apple-icon-precomposed.png b/server/public/logo/apple-icon-precomposed.png new file mode 100644 index 00000000..f16a581e Binary files /dev/null and b/server/public/logo/apple-icon-precomposed.png differ diff --git a/server/public/logo/apple-icon.png b/server/public/logo/apple-icon.png new file mode 100644 index 00000000..f16a581e Binary files /dev/null and b/server/public/logo/apple-icon.png differ diff --git a/server/public/logo/favicon-16x16.png b/server/public/logo/favicon-16x16.png new file mode 100644 index 00000000..50d12784 Binary files /dev/null and b/server/public/logo/favicon-16x16.png differ diff --git a/server/public/logo/favicon-32x32.png b/server/public/logo/favicon-32x32.png new file mode 100644 index 00000000..46fb6ecc Binary files /dev/null and b/server/public/logo/favicon-32x32.png differ diff --git a/server/public/logo/favicon-96x96.png b/server/public/logo/favicon-96x96.png new file mode 100644 index 00000000..78f9c963 Binary files /dev/null and b/server/public/logo/favicon-96x96.png differ diff --git a/server/public/logo/favicon.ico b/server/public/logo/favicon.ico new file mode 100644 index 00000000..70996732 Binary files /dev/null and b/server/public/logo/favicon.ico differ diff --git a/server/public/logo/logo_large.png b/server/public/logo/logo_large.png new file mode 100644 index 00000000..36cbb5d5 Binary files /dev/null and b/server/public/logo/logo_large.png differ diff --git a/server/public/logo/ms-icon-144x144.png b/server/public/logo/ms-icon-144x144.png new file mode 100644 index 00000000..7033af94 Binary files /dev/null and b/server/public/logo/ms-icon-144x144.png differ diff --git a/server/public/logo/ms-icon-150x150.png b/server/public/logo/ms-icon-150x150.png new file mode 100644 index 00000000..be78794c Binary files /dev/null and b/server/public/logo/ms-icon-150x150.png differ diff --git a/server/public/logo/ms-icon-310x310.png b/server/public/logo/ms-icon-310x310.png new file mode 100644 index 00000000..607c109e Binary files /dev/null and b/server/public/logo/ms-icon-310x310.png differ diff --git a/server/public/logo/ms-icon-70x70.png b/server/public/logo/ms-icon-70x70.png new file mode 100644 index 00000000..37d33a0c Binary files /dev/null and b/server/public/logo/ms-icon-70x70.png differ diff --git a/server/public/manifest.json b/server/public/manifest.json new file mode 100644 index 00000000..ac5d993c --- /dev/null +++ b/server/public/manifest.json @@ -0,0 +1,47 @@ +{ + "name": "Manage your files in the cloud with Nuage", + "short_name": "Nuage", + "icons": [ + { + "src": "logo/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "logo/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "logo/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "logo/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "logo/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "logo/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ], + "theme_color": "#9AD1ED", + "background_color": "#f2f2f2", + "orientation": "any", + "display": "standalone", + "start_url": "/" +}