maintenance (code): incremental improvement

This commit is contained in:
Mickael KERJEAN 2018-09-25 16:48:27 +10:00
parent 32f7bb8875
commit bde4079fb9
16 changed files with 151 additions and 76 deletions

View file

@ -0,0 +1,80 @@
import React from 'react';
import { Session } from '../model/';
import { Container, Loader } from '../components/';
import { memory } from '../helpers/';
import '../pages/error.scss';
export function LoggedInOnly(WrappedComponent){
memory.set('user::authenticated', false);
return class extends React.Component {
constructor(props){
super(props);
this.state = {
is_logged_in: memory.get('user::authenticated')
};
}
componentDidMount(){
if(this.state.is_logged_in === false){
Session.currentUser().then((res) => {
if(res.is_authenticated === false){
this.props.error({message: "Authentication Required"});
return;
}
memory.set('user::authenticated', true);
this.setState({is_logged_in: true});
}).catch((err) => {
if(err.code === "NO_INTERNET"){
this.setState({is_logged_in: true});
return;
}
this.props.error(err);
});
}
}
render(){
if(this.state.is_logged_in === true){
return <WrappedComponent {...this.props} />;
}
return null;
}
}
}
export function ErrorPage(WrappedComponent){
return class extends React.Component {
constructor(props){
super(props);
this.state = {
error: null
};
}
update(obj){
this.setState({error: obj});
}
render(){
if(this.state.error !== null){
const message = this.state.error.message || "There is nothing in here";
return (
<Container>
<div className="error-page">
<h1>Oops!</h1>
<h2>{message}</h2>
<p>{JSON.stringify(this.state.error)}</p>
</div>
</Container>
);
}
return (
<WrappedComponent error={this.update.bind(this)} {...this.props} />
);
}
}
}

View file

@ -19,6 +19,7 @@ export { Audio } from './audio';
export { Video } from './video';
export { Dropdown, DropdownButton, DropdownList, DropdownItem } from './dropdown';
export { MapShot } from './mapshot';
export { LoggedInOnly, ErrorPage } from './decorator';
//export { Connect } from './connect';
// Those are commented because they will delivered as a separate chunk
// export { Editor } from './editor';

View file

@ -108,7 +108,6 @@ function handle_error_response(xhr, err){
}else if(xhr.status === 500){
err({message: message || "Oups something went wrong with our servers", code: "INTERNAL_SERVER_ERROR"});
}else if(xhr.status === 401){
if(location.pathname !== '/login'){ location.pathname = "/login"; }
err({message: message || "Authentication error", code: "Unauthorized"});
}else if(xhr.status === 403){
err({message: message || "You can\'t do that", code: "Forbidden"});

View file

@ -3,11 +3,10 @@ function Memory(){
return {
get: function(key){
if(!data[key]) return null;
if(data[key] === undefined) return null;
return data[key];
},
set: function(key, value){
if(!data[key]) data[key] = {};
data[key] = value;
},
all: function(){

View file

@ -4,19 +4,15 @@ import Router from './router';
import './assets/css/reset.scss';
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/assets/worker/cache.js').catch(function(error) {
console.log('ServiceWorker registration failed:', error);
});
}
window.onload = () => {
ReactDOM.render(<Router/>, document.getElementById('main'));
};
window.log = function(){console.log.apply(this, arguments)};
window.addEventListener("DOMContentLoaded", () => {
const className = 'ontouchstart' in window ? 'touch-yes' : 'touch-no';
document.body.classList.add(className);
ReactDOM.render(<Router/>, document.getElementById('main'));
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/assets/worker/cache.js').catch(function(error) {
console.log('ServiceWorker registration failed:', error);
});
}
});

View file

@ -1,7 +1,7 @@
import { http_get, http_post, http_delete } from '../helpers/';
class SessionManager{
isLoggedIn(){
currentUser(){
let url = '/api/session'
return http_get(url)
.then(data => data.result);

View file

@ -45,8 +45,10 @@ export class ConnectPage extends React.Component {
authenticate(params){
this.setState({loading: true});
Session.authenticate(params)
.then((path) => {
.then(Session.currentUser)
.then((user) => {
let url = '/files/';
let path = user.home
if(path){
path = path.replace(/^\/?(.*?)\/?$/, "$1");
if(path !== ""){

View file

@ -2,6 +2,7 @@
width: 80%;
max-width: 600px;
margin: 50px auto 0 auto;
flex-direction: column;
h1{margin: 5px 0; font-size: 3.1em;}
h2{margin: 10px 0; font-weight: normal;}

View file

@ -6,7 +6,7 @@ import './filespage.scss';
import './error.scss';
import { Files } from '../model/';
import { sort, onCreate, onRename, onDelete, onUpload, onSearch } from './filespage.helper';
import { NgIf, Loader, EventReceiver } from '../components/';
import { NgIf, Loader, EventReceiver, LoggedInOnly, ErrorPage } from '../components/';
import { notify, debounce, goToFiles, goToViewer, event, settings_get, settings_put } from '../helpers/';
import { BreadCrumb, FileSystem, FrequentlyAccess, Submenu } from './filespage/';
import InfiniteScroll from 'react-infinite-scroller';
@ -14,6 +14,8 @@ import InfiniteScroll from 'react-infinite-scroller';
const PAGE_NUMBER_INIT = 3;
const LOAD_PER_SCROLL = 24;
@ErrorPage
@LoggedInOnly
@EventReceiver
@DragDropContext(('ontouchstart' in window)? HTML5Backend : HTML5Backend)
export class FilesPage extends React.Component {
@ -30,8 +32,7 @@ export class FilesPage extends React.Component {
metadata: null,
frequents: [],
page_number: PAGE_NUMBER_INIT,
loading: true,
error: null
loading: true
};
this.goToFiles = goToFiles.bind(null, this.props.history);
@ -50,7 +51,6 @@ export class FilesPage extends React.Component {
this.props.subscribe('file.delete', onDelete.bind(this));
this.props.subscribe('file.refresh', this.onRefresh.bind(this));
window.addEventListener('keydown', this.toggleHiddenFilesVisibilityonCtrlK);
this.hideError();
}
componentWillUnmount() {
@ -76,10 +76,6 @@ export class FilesPage extends React.Component {
}
}
hideError(){
this.setState({error: null});
}
toggleHiddenFilesVisibilityonCtrlK(e){
if(e.keyCode === 72 && e.ctrlKey === true){
e.preventDefault();
@ -118,10 +114,9 @@ export class FilesPage extends React.Component {
notify.send(res, 'error');
}
}, (error) => {
this.setState({error: error});
this.props.error(error);
});
this.observers.push(observer);
this.setState({error: null});
if(path === "/"){
Files.frequents().then((s) => this.setState({frequents: s}));
}
@ -213,7 +208,7 @@ export class FilesPage extends React.Component {
<div className="scroll-y">
<InfiniteScroll pageStart={0} loader={$moreLoading} hasMore={this.state.files.length > 70}
initialLoad={false} useWindow={false} loadMore={this.loadMore.bind(this)} threshold={100}>
<NgIf className="container" cond={this.state.loading === false && this.state.error === null}>
<NgIf className="container" cond={!this.state.loading}>
<NgIf cond={this.state.path === '/'}>
<FrequentlyAccess files={this.state.frequents}/>
</NgIf>
@ -225,14 +220,9 @@ export class FilesPage extends React.Component {
</NgIf>
</NgIf>
</InfiniteScroll>
<NgIf cond={this.state.loading && this.state.error === null}>
<NgIf cond={!!this.state.loading}>
<Loader/>
</NgIf>
<NgIf cond={this.state.error !== null} className="error-page">
<h1>Oops!</h1>
<h2>It seems this directory doesn't exist</h2>
<p>{JSON.stringify(this.state.error)}</p>
</NgIf>
</div>
</div>
</div>

View file

@ -13,10 +13,14 @@ export class HomePage extends React.Component {
}
componentDidMount(){
Session.isLoggedIn()
Session.currentUser()
.then((res) => {
if(res === true){
this.setState({redirection: "/files"});
if(res && res.is_authenticated === true){
let url = "/files"
if(res.home){
url += res.home
}
this.setState({redirection: url});
}else{
this.setState({redirection: "/login"});
}

View file

@ -4,7 +4,7 @@ import Path from 'path';
import './viewerpage.scss';
import './error.scss';
import { Files } from '../model/';
import { BreadCrumb, Bundle, NgIf, Loader, Container, EventReceiver, EventEmitter } from '../components/';
import { BreadCrumb, Bundle, NgIf, Loader, Container, EventReceiver, EventEmitter, LoggedInOnly , ErrorPage } from '../components/';
import { debounce, opener, notify } from '../helpers/';
import { AudioPlayer, FileDownloader, ImageViewer, PDFViewer } from './viewerpage/';
@ -19,6 +19,8 @@ const IDE = (props) => (
</Bundle>
);
@ErrorPage
@LoggedInOnly
@EventReceiver
export class ViewerPage extends React.Component {
constructor(props){
@ -31,8 +33,7 @@ export class ViewerPage extends React.Component {
content: null,
needSaving: false,
isSaving: false,
loading: true,
error: null
loading: true
};
this.props.subscribe('file.select', this.onPathUpdate.bind(this));
}
@ -41,10 +42,10 @@ export class ViewerPage extends React.Component {
this.setState({
path: props.match.url.replace('/view', '') + (location.hash || ""),
filename: Path.basename(props.match.url.replace('/view', '')) || 'untitled.dat'
}, () => { this.componentWillMount(); });
}, () => { this.componentDidMount(); });
}
componentWillMount(){
componentDidMount(){
const metadata = () => {
return new Promise((done, err) => {
let app_opener = opener(this.state.path);
@ -54,7 +55,7 @@ export class ViewerPage extends React.Component {
opener: app_opener
}, () => done(app_opener));
}).catch(error => {
notify.send(err, 'error');
this.props.error(error);
err(error);
});
});
@ -67,15 +68,14 @@ export class ViewerPage extends React.Component {
if(err && err.code === 'BINARY_FILE'){
this.setState({opener: 'download', loading: false});
}else{
this.setState({error: err});
this.props.error(err);
}
});
}else{
this.setState({loading: false});
return;
}
this.setState({loading: false});
};
return metadata()
.then(data_fetch);
return metadata().then(data_fetch);
}
componentWillUnmount() {
@ -106,7 +106,7 @@ export class ViewerPage extends React.Component {
if(err && err.code === 'CANCELLED'){ return; }
this.setState({isSaving: false});
notify.send(err, 'error');
return Promise.reject();
return Promise.reject(err);
});
}
@ -125,7 +125,7 @@ export class ViewerPage extends React.Component {
<div className="component_page_viewerpage">
<BreadCrumb needSaving={this.state.needSaving} className="breadcrumb" path={this.state.path} />
<div className="page_container">
<NgIf cond={this.state.loading === false && this.state.error === null}>
<NgIf cond={this.state.loading === false}>
<NgIf cond={this.state.opener === 'editor'}>
<IDE needSavingUpdate={this.needSaving.bind(this)}
needSaving={this.state.needSaving}
@ -151,14 +151,9 @@ export class ViewerPage extends React.Component {
<FileDownloader data={this.state.url} filename={this.state.filename} />
</NgIf>
</NgIf>
<NgIf cond={this.state.loading === true && this.state.error === null}>
<NgIf cond={this.state.loading === true}>
<Loader/>
</NgIf>
<NgIf cond={this.state.error !== null} className="error-page">
<h1>Oops!</h1>
<h2>There is nothing in here</h2>
<p>{JSON.stringify(this.state.error)}</p>
</NgIf>
</div>
</div>
);

View file

@ -19,6 +19,9 @@ func NewBool(t bool) *bool {
}
func NewString(t string) *string {
if t == "" {
return nil
}
return &t
}

View file

@ -56,6 +56,13 @@ func FileLs(ctx App, res http.ResponseWriter, req *http.Request) {
}
func FileCat(ctx App, res http.ResponseWriter, req *http.Request) {
http.SetCookie(res, &http.Cookie{
Name: "download",
Value: "",
MaxAge: -1,
Path: "/",
})
path, err := pathBuilder(ctx, req.URL.Query().Get("path"))
if err != nil {
SendErrorResult(res, err)
@ -71,13 +78,6 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) {
return
}
http.SetCookie(res, &http.Cookie{
Name: "download",
Value: "",
MaxAge: -1,
Path: "/",
})
file, err = services.ProcessFileBeforeSend(file, &ctx, req, &res)
if err != nil {
SendErrorResult(res, err)

View file

@ -8,21 +8,28 @@ import (
"time"
)
func SessionIsValid(ctx App, res http.ResponseWriter, req *http.Request) {
type Session struct {
Home *string `json:"home,omitempty"`
IsAuth bool `json:"is_authenticated"`
}
func SessionGet(ctx App, res http.ResponseWriter, req *http.Request) {
r := Session {
IsAuth: false,
}
if ctx.Backend == nil {
SendSuccessResult(res, false)
SendSuccessResult(res, r)
return
}
if _, err := ctx.Backend.Ls("/"); err != nil {
SendSuccessResult(res, false)
home, err := model.GetHome(ctx.Backend)
if err != nil {
SendSuccessResult(res, r)
return
}
home, _ := model.GetHome(ctx.Backend)
if home == "" {
SendSuccessResult(res, true)
return
}
SendSuccessResult(res, true)
r.IsAuth = true
r.Home = NewString(home)
SendSuccessResult(res, r)
}
func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {

View file

@ -5,7 +5,6 @@ import (
. "github.com/mickael-kerjean/nuage/server/common"
"github.com/mickael-kerjean/nuage/server/model"
"net/http"
"log"
)
func ShareList(ctx App, res http.ResponseWriter, req *http.Request) {
@ -29,7 +28,6 @@ func ShareGet(ctx App, res http.ResponseWriter, req *http.Request) {
func ShareUpsert(ctx App, res http.ResponseWriter, req *http.Request) {
s := extractParams(req, &ctx)
log.Println("EXPIRE::", s.Expire, ctx.Body["expire"])
s.Path = NewStringFromInterface(ctx.Body["path"])
if err := model.ShareUpsert(&s); err != nil {

View file

@ -14,7 +14,7 @@ func Init(a *App) *http.Server {
// API
session := r.PathPrefix("/api/session").Subrouter()
session.HandleFunc("", APIHandler(SessionIsValid, *a)).Methods("GET")
session.HandleFunc("", APIHandler(SessionGet, *a)).Methods("GET")
session.HandleFunc("", APIHandler(SessionAuthenticate, *a)).Methods("POST")
session.HandleFunc("", APIHandler(SessionLogout, *a)).Methods("DELETE")
session.Handle("/auth/{service}", APIHandler(SessionOAuthBackend, *a)).Methods("GET")