maintenance (structure): Full revamp of the project code structure
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
@ -10,7 +10,7 @@ Call it an FTP client, an S3 viewer or a Dropbox like web app, Nuage leverages y
|
||||
|
||||
* Demo
|
||||
[[https://nuage.kerjean.me][Try]] before install
|
||||
[[https://raw.githubusercontent.com/mickael-kerjean/nuage/master/server/public/img/photo.jpg]]
|
||||
[[https://raw.githubusercontent.com/mickael-kerjean/nuage/master/.assets/img/photo.jpg]]
|
||||
* Features
|
||||
- manage your files directly from your browser
|
||||
- listen to music
|
||||
|
||||
@ -27,17 +27,6 @@ body, html{
|
||||
}
|
||||
a{color: inherit; text-decoration: none;}
|
||||
|
||||
.scroll-y{
|
||||
overflow-y: scroll!important;
|
||||
overflow-x: hidden!important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.scroll-x{
|
||||
overflow-x: scroll!important;
|
||||
overflow-y: hidden!important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
select{-moz-appearance: none;}
|
||||
select:-moz-focusring {
|
||||
color: inherit;
|
||||
11
client/assets/css/videojs-custom.css
Normal file
@ -0,0 +1,11 @@
|
||||
.video-js{outline: none;}
|
||||
|
||||
.video-js .vjs-big-play-button:before, .video-js .vjs-control:before, .video-js .vjs-modal-dialog, .vjs-modal-dialog .vjs-modal-dialog-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%; }
|
||||
|
||||
.video-js .vjs-big-play-button:before, .video-js .vjs-control:before {
|
||||
text-align: center; }
|
||||
|
Before Width: | Height: | Size: 676 B After Width: | Height: | Size: 676 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 747 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 730 B After Width: | Height: | Size: 730 B |
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 706 B |
|
Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 707 B |
|
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
|
Before Width: | Height: | Size: 904 B After Width: | Height: | Size: 904 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
42
client/components/alert.js
Normal file
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Input, Button, Modal, NgIf } from './';
|
||||
import './prompt.scss';
|
||||
|
||||
export class Alert extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
modal_appear: false
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit(e){
|
||||
e.preventDefault();
|
||||
this.props.onConfirm();
|
||||
this.setState({modal_appear: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal isActive={this.state.modal_appear} onQuit={this.onSubmit.bind(this)}>
|
||||
<div className="component_alert">
|
||||
<p>
|
||||
{this.props.message}
|
||||
</p>
|
||||
<form id="key_manager" onSubmit={this.onSubmit.bind(this)}>
|
||||
<div className="buttons">
|
||||
<Button type="submit" onClick={this.onSubmit.bind(this)}>OK</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Alert.propTypes = {
|
||||
message: PropTypes.string.isRequired,
|
||||
onConfirm: PropTypes.func
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
import React from 'react';
|
||||
@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom'
|
||||
import { theme, to_rgba } from '../utilities/theme';
|
||||
import { NgIf, Icon } from '../utilities/';
|
||||
import { EventEmitter, EventReceiver } from '../data';
|
||||
import { NgIf, Icon, EventEmitter, EventReceiver } from './';
|
||||
|
||||
export class BreadCrumb extends React.Component {
|
||||
constructor(props){
|
||||
@ -63,8 +61,8 @@ BreadCrumb.propTypes = {
|
||||
|
||||
|
||||
const BreadCrumbContainer = (props) => {
|
||||
let style1 = {background: theme.component.breadcrumb.bg, margin: '0 0 0px 0', padding: '6px 0', boxShadow: '0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2)', zIndex: '1000', position: 'relative'};
|
||||
let style2 = {margin: '0 auto', width: '95%', maxWidth: '800px', padding: '0', color: theme.component.breadcrumb.color};
|
||||
let style1 = {background: 'white', margin: '0 0 0px 0', padding: '6px 0', boxShadow: '0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2)', zIndex: '1000', position: 'relative'};
|
||||
let style2 = {margin: '0 auto', width: '95%', maxWidth: '800px', padding: '0', color: 'rgba(#6f6f6f, 0.8)'};
|
||||
return (
|
||||
<div className={props.className} style={style1}>
|
||||
<ul style={style2}>
|
||||
@ -144,7 +142,7 @@ export class PathElementWrapper extends React.Component {
|
||||
render(){
|
||||
let style = {
|
||||
cursor: this.props.isLast ? '' : 'pointer',
|
||||
background: this.state.hover && this.props.isLast !== true? theme.effects.hover : 'inherit',
|
||||
background: this.state.hover && this.props.isLast !== true? '#f5f5f5' : 'inherit',
|
||||
borderRadius: '1px',
|
||||
fontSize: '18px',
|
||||
display: 'inline-block',
|
||||
@ -152,8 +150,8 @@ export class PathElementWrapper extends React.Component {
|
||||
fontWeight: this.props.isLast ? '100': ''
|
||||
};
|
||||
if(this.props.highlight === true){
|
||||
style.background = theme.effects.selected;
|
||||
style.border = '2px solid '+theme.colors.primary;
|
||||
style.background = '#c5e2f1';
|
||||
style.border = '2px solid #9AD1ED';
|
||||
style.borderRadius = '2px';
|
||||
style.padding = '2px 20px';
|
||||
}
|
||||
@ -175,7 +173,7 @@ export class PathElement extends PathElementWrapper {
|
||||
|
||||
render(highlight = false){
|
||||
return (
|
||||
<div style={{display: 'inline-block', color: this.props.isLast? theme.component.breadcrumb.last : 'inherit'}}>
|
||||
<div style={{display: 'inline-block', color: this.props.isLast? '#6f6f6f' : 'inherit'}}>
|
||||
<PathElementWrapper highlight={highlight} {...this.props} />
|
||||
</div>
|
||||
)
|
||||
|
||||
0
client/components/breadcrumb.scss
Normal file
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { theme } from './theme';
|
||||
|
||||
import './buttons.scss';
|
||||
import './button.scss';
|
||||
|
||||
export class Button extends React.Component {
|
||||
constructor(props){
|
||||
@ -21,4 +20,5 @@ export class Button extends React.Component {
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
theme: PropTypes.string
|
||||
};
|
||||
@ -1,6 +1,4 @@
|
||||
import React from 'react';
|
||||
import {theme} from './theme';
|
||||
|
||||
import './card.scss';
|
||||
|
||||
export class Card extends React.Component {
|
||||
@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export class Connect extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
CONNECT
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -34,7 +34,6 @@ function emit(event, payload){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function EventReceiver(WrappedComponent){
|
||||
let id = Math.random().toString();
|
||||
|
||||
@ -53,8 +52,6 @@ export function EventReceiver(WrappedComponent){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function EventEmitter(WrappedComponent) {
|
||||
return class extends React.Component {
|
||||
emit(){
|
||||
63
client/components/icon.js
Normal file
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import './icon.scss';
|
||||
|
||||
import img_folder from "../assets/img/folder.svg";
|
||||
import img_file from "../assets/img/file.svg";
|
||||
import img_loader from "../assets/img/loader.svg";
|
||||
import img_save from "../assets/img/save.svg";
|
||||
import img_power from "../assets/img/power.svg";
|
||||
import img_edit from "../assets/img/edit.svg";
|
||||
import img_delete from "../assets/img/delete.svg";
|
||||
import img_bucket from "../assets/img/bucket.svg";
|
||||
import img_link from "../assets/img/link.svg";
|
||||
import img_loading from "../assets/img/loader.svg";
|
||||
import img_download from "../assets/img/download.svg";
|
||||
import img_play from "../assets/img/play.svg";
|
||||
import img_pause from "../assets/img/pause.svg";
|
||||
import img_error from "../assets/img/error.svg";
|
||||
import img_loading_white from "../assets/img/loader_white.svg";
|
||||
|
||||
export const Icon = (props) => {
|
||||
let img;
|
||||
if(props.name === 'directory'){
|
||||
img = img_folder;
|
||||
}else if(props.name === 'file'){
|
||||
img = img_file;
|
||||
}else if(props.name === 'loader'){
|
||||
img = img_loader;
|
||||
}else if(props.name === 'save'){
|
||||
img = img_save;
|
||||
}else if(props.name === 'power'){
|
||||
img = img_power;
|
||||
}else if(props.name === 'edit'){
|
||||
img = img_edit;
|
||||
}else if(props.name === 'delete'){
|
||||
img = img_delete;
|
||||
}else if(props.name === 'bucket'){
|
||||
img = img_bucket;
|
||||
}else if(props.name === 'link'){
|
||||
img = img_link;
|
||||
}else if(props.name === 'loading'){
|
||||
img = img_loader;
|
||||
}else if(props.name === 'download'){
|
||||
img = img_download;
|
||||
}else if(props.name === 'play'){
|
||||
img = img_play;
|
||||
}else if(props.name === 'pause'){
|
||||
img = img_pause;
|
||||
}else if(props.name === 'error'){
|
||||
img = img_error;
|
||||
}else if(props.name === 'loading_white'){
|
||||
img = img_loading_white;
|
||||
}else{
|
||||
throw('unknown icon');
|
||||
}
|
||||
|
||||
return (
|
||||
<img className="component_icon"
|
||||
style={props.style}
|
||||
onClick={props.onClick}
|
||||
src={img}
|
||||
alt={props.name}/>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,21 @@
|
||||
export { BreadCrumb } from './breadcrumb';
|
||||
export { FileSystem } from './filesystem';
|
||||
export { Connect } from './connect';
|
||||
export { EventEmitter, EventReceiver } from './events';
|
||||
export { BreadCrumb, PathElement } from './breadcrumb';
|
||||
export { Input } from './input';
|
||||
export { Textarea } from './textarea';
|
||||
export { Button } from './button';
|
||||
export { Container } from './container';
|
||||
export { NgIf } from './ngif';
|
||||
export { Card } from './card';
|
||||
export { Loader } from './loader';
|
||||
export { Error } from './error';
|
||||
export { Fab } from './fab';
|
||||
export { Icon } from './icon';
|
||||
export { Notification } from './notification';
|
||||
export { Uploader } from './uploader';
|
||||
export { Bundle } from './bundle';
|
||||
export { Modal } from './modal';
|
||||
export { Prompt } from './prompt';
|
||||
export { Alert } from './alert';
|
||||
//export { Connect } from './connect';
|
||||
// Those are commented because they will delivered as a separate chunk
|
||||
// export { Editor } from './editor';
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { theme } from './theme';
|
||||
import { Input } from './input';
|
||||
import { Button } from './button';
|
||||
import { NgIf } from './ngif';
|
||||
import './modal.scss';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Input, Button, NgIf } from './';
|
||||
import './modal.scss';
|
||||
|
||||
export class Modal extends React.Component {
|
||||
constructor(props){
|
||||
@ -19,5 +19,5 @@ export class NgIf extends React.Component {
|
||||
}
|
||||
|
||||
NgIf.propTypes = {
|
||||
cond: PropTypes.bool
|
||||
cond: PropTypes.bool.isRequired
|
||||
};
|
||||
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NgIf } from './';
|
||||
import { theme } from './theme';
|
||||
|
||||
import { NgIf } from './';
|
||||
import './notification.scss';
|
||||
|
||||
export class Notification extends React.Component {
|
||||
57
client/components/prompt.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Input, Button, Modal, NgIf } from './';
|
||||
import './prompt.scss';
|
||||
|
||||
export class Prompt extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
modal_appear: false,
|
||||
error: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props){
|
||||
if(props.error !== this.state.error){
|
||||
this.setState({error: props.error});
|
||||
}
|
||||
}
|
||||
|
||||
onCancel(should_clear){
|
||||
this.setState({modal_appear: false});
|
||||
}
|
||||
|
||||
onSubmit(e){
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal isActive={this.state.modal_appear} onQuit={this.onCancel.bind(this)}>
|
||||
<div className="component_prompt">
|
||||
<p className="message">
|
||||
{this.props.message}
|
||||
</p>
|
||||
<form onSubmit={this.onSubmit.bind(this)}>
|
||||
<Input autoFocus={true} value={this.state.key} type={this.props.type || 'text'} onChange={this.onKeyChange.bind(this)} autoComplete="new-password" />
|
||||
|
||||
<div className="error">{this.props.error} </div>
|
||||
|
||||
<div className="buttons">
|
||||
<Button type="button" onClick={this.onCancel.bind(this)}>CANCEL</Button>
|
||||
<Button type="submit" theme="secondary" onClick={this.onSubmit.bind(this)}>OK</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Prompt.propTypes = {
|
||||
type: PropTypes.string,
|
||||
message: PropTypes.string.isRequired,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
};
|
||||
5
client/components/prompt.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.component_prompt{
|
||||
.message{
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { theme } from './theme';
|
||||
|
||||
import './textarea.scss';
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
export { Files } from './api';
|
||||
export { Session } from './api';
|
||||
export { invalidate } from './tools';
|
||||
export { opener } from './mimetype';
|
||||
export { EventEmitter, EventReceiver } from './events';
|
||||
@ -25,6 +25,7 @@ export function invalidate(url){
|
||||
throw 'invalidation error';
|
||||
}
|
||||
}
|
||||
|
||||
export function http_get(url, cache_expire = 0, type = 'json'){
|
||||
if(cache_expire > 0 && cache[url] && cache[url].date > new Date().getTime()){
|
||||
return new Promise((done) => done(cache[url].data));
|
||||
46
client/helpers/backpressure.js
Normal file
@ -0,0 +1,46 @@
|
||||
export function debounce(func, wait, immediate) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
};
|
||||
|
||||
export function throttle(func, wait, options) {
|
||||
var context, args, result;
|
||||
var timeout = null;
|
||||
var previous = 0;
|
||||
if (!options) options = {};
|
||||
var later = function() {
|
||||
previous = options.leading === false ? 0 : Date.now();
|
||||
timeout = null;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
};
|
||||
return function() {
|
||||
var now = Date.now();
|
||||
if (!previous && options.leading === false) previous = now;
|
||||
var remaining = wait - (now - previous);
|
||||
context = this;
|
||||
args = arguments;
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
previous = now;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
8
client/helpers/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
export { URL_HOME, goToHome, URL_FILES, goToFiles, URL_VIEWER, goToViewer, URL_LOGIN, goToLogin, URL_LOGOUT, goToLogout } from './navigate';
|
||||
export { opener } from './mimetype';
|
||||
export { debounce, throttle } from './backpressure';
|
||||
export { encrypt, decrypt } from './crypto'
|
||||
export { pathBuilder } from './path';
|
||||
export { memory } from './memory';
|
||||
export { prepare } from './navigate';
|
||||
export { invalidate, http_get, http_post, http_delete } from './ajax';
|
||||
@ -29,8 +29,6 @@ export function opener(file){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const db = {
|
||||
'pdf': 'application/pdf',
|
||||
'csv': 'text/csv',
|
||||
@ -1,5 +1,3 @@
|
||||
//import { history } from '../history';
|
||||
|
||||
export const URL_HOME = '/';
|
||||
export function goToHome(history){
|
||||
history.push(URL_HOME);
|
||||
@ -30,13 +30,8 @@
|
||||
<meta name="theme-color" content="#f2f2f2">
|
||||
|
||||
<meta name="description" content="browse your files in the cloud">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
<link rel="stylesheet" href="/css/codemirror.css">
|
||||
<link rel="stylesheet" href="/css/codemirror-foldgutter.css">
|
||||
<link rel="stylesheet" href="/css/videojs-sublime-skin.css">
|
||||
<link rel="stylesheet" href="/css/video-js.css">
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -2,13 +2,14 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Router from './router';
|
||||
|
||||
import './assets/css/reset.scss';
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/cache.js').then(function(registration) {
|
||||
}).catch(function(error) {
|
||||
navigator.serviceWorker.register('/cache.js').catch(function(error) {
|
||||
console.log('ServiceWorker registration failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
ReactDOM.render(<Router/>, document.getElementById('main'));
|
||||
ReactDOM.render(<Router/>, document.getElementById('main'));
|
||||
};
|
||||
|
||||
@ -1,18 +1,6 @@
|
||||
import { http_get, http_post, http_delete, invalidate } from './tools';
|
||||
import { prepare } from '../utilities/navigate';
|
||||
import { http_get, http_post, invalidate, prepare } from '../helpers/';
|
||||
import Path from 'path';
|
||||
|
||||
function invalidate_ls(path, exact = true){
|
||||
let url = '/api/files/ls?path='.replace(/([^a-zA-Z0-9])/g, '\\$1');
|
||||
let reg = new RegExp(url + prepare(Path.dirname(path)+'.*'));
|
||||
return invalidate(reg);
|
||||
}
|
||||
function invalidate_cat(path, exact = true){
|
||||
let url = '/api/files/cat?path='.replace(/([^a-zA-Z0-9])/g, '\\$1');
|
||||
let reg = new RegExp(url + prepare(path)+ (exact? '' : '.*'));
|
||||
return invalidate(reg);
|
||||
}
|
||||
|
||||
class FileSystem{
|
||||
ls(path, cache = 120){
|
||||
let url = '/api/files/ls?path='+prepare(path);
|
||||
@ -73,35 +61,15 @@ class FileSystem{
|
||||
}
|
||||
}
|
||||
|
||||
class SessionManager{
|
||||
isLogged(){
|
||||
let url = '/api/session'
|
||||
return http_get(url);
|
||||
}
|
||||
|
||||
url(type){
|
||||
if(type === 'dropbox'){
|
||||
let url = '/api/session/auth/dropbox';
|
||||
return http_get(url);
|
||||
}else if(type === 'gdrive'){
|
||||
let url = '/api/session/auth/gdrive';
|
||||
return http_get(url);
|
||||
}else{
|
||||
return Promise.error({message: 'not authorization backend for: '+type, code: 'UNKNOWN_PROVIDER'})
|
||||
}
|
||||
}
|
||||
|
||||
authenticate(params){
|
||||
let url = '/api/session';
|
||||
return http_post(url, params);
|
||||
}
|
||||
|
||||
logout(){
|
||||
let url = '/api/session';
|
||||
return http_delete(url);
|
||||
}
|
||||
function invalidate_ls(path, exact = true){
|
||||
let url = '/api/files/ls?path='.replace(/([^a-zA-Z0-9])/g, '\\$1');
|
||||
let reg = new RegExp(url + prepare(Path.dirname(path)+'.*'));
|
||||
return invalidate(reg);
|
||||
}
|
||||
function invalidate_cat(path, exact = true){
|
||||
let url = '/api/files/cat?path='.replace(/([^a-zA-Z0-9])/g, '\\$1');
|
||||
let reg = new RegExp(url + prepare(path)+ (exact? '' : '.*'));
|
||||
return invalidate(reg);
|
||||
}
|
||||
|
||||
|
||||
export const Files = new FileSystem();
|
||||
export const Session = new SessionManager();
|
||||
2
client/model/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { Files } from './files';
|
||||
export { Session } from './session';
|
||||
32
client/model/session.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { http_get, http_post, http_delete } from '../helpers/';
|
||||
|
||||
class SessionManager{
|
||||
isLogged(){
|
||||
let url = '/api/session'
|
||||
return http_get(url);
|
||||
}
|
||||
|
||||
url(type){
|
||||
if(type === 'dropbox'){
|
||||
let url = '/api/session/auth/dropbox';
|
||||
return http_get(url);
|
||||
}else if(type === 'gdrive'){
|
||||
let url = '/api/session/auth/gdrive';
|
||||
return http_get(url);
|
||||
}else{
|
||||
return Promise.error({message: 'not authorization backend for: '+type, code: 'UNKNOWN_PROVIDER'})
|
||||
}
|
||||
}
|
||||
|
||||
authenticate(params){
|
||||
let url = '/api/session';
|
||||
return http_post(url, params);
|
||||
}
|
||||
|
||||
logout(){
|
||||
let url = '/api/session';
|
||||
return http_delete(url);
|
||||
}
|
||||
}
|
||||
|
||||
export const Session = new SessionManager();
|
||||
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
|
||||
import config from '../../config.js';
|
||||
import { Container, NgIf, Loader, Notification, theme } from '../utilities/';
|
||||
import { Session, invalidate } from '../data';
|
||||
import { ForkMe, RememberMe, Credentials, Form } from './connectpage/';
|
||||
|
||||
import './connectpage.scss';
|
||||
import { Session } from '../model/';
|
||||
import { Container, NgIf, Loader, Notification } from '../components/';
|
||||
import { ForkMe, RememberMe, Credentials, Form } from './connectpage/';
|
||||
import { invalidate } from '../helpers/';
|
||||
import config from '../../config.js';
|
||||
|
||||
|
||||
export class ConnectPage extends React.Component {
|
||||
|
||||
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
|
||||
import { Input, Button, Modal, NgIf } from '../../utilities';
|
||||
import { encrypt, decrypt, memory } from '../../utilities';
|
||||
import { Input, Button, NgIf, Modal } from '../../components/';
|
||||
import { encrypt, decrypt, memory } from '../../helpers/';
|
||||
import './credentials.scss';
|
||||
|
||||
export class Credentials extends React.Component {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Container, Card, NgIf, Input, Button, Textarea, Loader, Notification, encrypt, decrypt, theme, Prompt } from '../../utilities';
|
||||
import { Session, invalidate, password } from '../../data';
|
||||
import { Container, Card, NgIf, Input, Button, Textarea, Loader, Notification, Prompt } from '../../components/';
|
||||
import { invalidate, encrypt, decrypt } from '../../helpers/';
|
||||
import { Session } from '../../model/';
|
||||
|
||||
import './form.scss';
|
||||
import img_drive from '../../assets/google-drive.png';
|
||||
import img_dropbox from '../../assets/dropbox.png';
|
||||
import img_drive from '../../assets/img/google-drive.png';
|
||||
import img_dropbox from '../../assets/img/dropbox.png';
|
||||
|
||||
export class Form extends React.Component {
|
||||
constructor(props){
|
||||
|
||||
@ -62,3 +62,9 @@
|
||||
transition: all 0.3s ease-out;
|
||||
transition-delay: 0.5s;
|
||||
}
|
||||
|
||||
.scroll-x{
|
||||
overflow-x: auto!important;
|
||||
overflow-y: hidden!important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import { FileSystem } from '../components/';
|
||||
import BreadCrumb from './filespage/breadcrumb';
|
||||
import { Files, EventReceiver } from '../data/';
|
||||
import { NgIf, Loader, Error, debounce, goToFiles, goToViewer, Uploader } from '../utilities';
|
||||
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import { default as TouchBackend } from 'react-dnd-touch-backend';
|
||||
import Path from 'path';
|
||||
|
||||
import './filespage.scss';
|
||||
import { Files } from '../model/';
|
||||
import { NgIf, Loader, Error, Uploader, EventReceiver } from '../components/';
|
||||
import { debounce, goToFiles, goToViewer } from '../helpers/';
|
||||
import { BreadCrumb, FileSystem } from './filespage/';
|
||||
|
||||
@EventReceiver
|
||||
@DragDropContext(('ontouchstart' in window)? HTML5Backend : HTML5Backend)
|
||||
@ -25,7 +25,13 @@ export class FilesPage extends React.Component {
|
||||
this.resetHeight = debounce(this.resetHeight.bind(this), 100);
|
||||
this.goToFiles = goToFiles.bind(null, this.props.history);
|
||||
this.goToViewer = goToViewer.bind(null, this.props.history);
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
this.onPathUpdate(this.state.path, 'directory', true);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
// subscriptions
|
||||
this.props.subscribe('file.select', this.onPathUpdate.bind(this));
|
||||
this.props.subscribe('file.upload', this.onUpload.bind(this));
|
||||
@ -33,15 +39,9 @@ export class FilesPage extends React.Component {
|
||||
this.props.subscribe('file.rename', this.onRename.bind(this));
|
||||
this.props.subscribe('file.delete', this.onDelete.bind(this));
|
||||
this.props.subscribe('file.refresh', this.onRefresh.bind(this));
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
this.onPathUpdate(this.state.path, 'directory', true)
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.resetHeight();
|
||||
this.setState({error: false});
|
||||
this.hideError();
|
||||
window.addEventListener("resize", this.resetHeight);
|
||||
}
|
||||
|
||||
@ -55,28 +55,29 @@ export class FilesPage extends React.Component {
|
||||
window.removeEventListener("resize", this.resetHeight);
|
||||
}
|
||||
|
||||
|
||||
hideError(){
|
||||
this.setState({error: false});
|
||||
}
|
||||
|
||||
onRefresh(path = this.state.path){
|
||||
this.setState({error: false})
|
||||
this.setState({error: false});
|
||||
return Files.ls(path).then((files) => {
|
||||
this.setState({files: files, loading: false})
|
||||
this.setState({files: files, loading: false});
|
||||
}).catch((error) => {
|
||||
this.setState({error: error});
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
onPathUpdate(path, type = 'directory', withLoader = true){
|
||||
window.path = this.props.history;
|
||||
if(type === 'file'){
|
||||
this.props.history.push('/view'+path)
|
||||
this.props.history.push('/view'+path);
|
||||
}else{
|
||||
this.setState({path: path, loading: withLoader});
|
||||
if(path !== this.state.path){
|
||||
this.props.history.push('/files'+path)
|
||||
this.props.history.push('/files'+path);
|
||||
}
|
||||
return this.onRefresh(path)
|
||||
return this.onRefresh(path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +87,7 @@ export class FilesPage extends React.Component {
|
||||
}else if(type === 'directory'){
|
||||
return Files.mkdir(path);
|
||||
}else{
|
||||
return Promise.reject({message: 'internal error: can\'t create a '+type.toString(), code: 'UNKNOWN_TYPE'})
|
||||
return Promise.reject({message: 'internal error: can\'t create a '+type.toString(), code: 'UNKNOWN_TYPE'});
|
||||
}
|
||||
}
|
||||
onRename(from, to, type){
|
||||
@ -107,16 +108,16 @@ export class FilesPage extends React.Component {
|
||||
size: file.size,
|
||||
icon: 'loading',
|
||||
virtual: true
|
||||
}
|
||||
};
|
||||
});
|
||||
const files = JSON.parse(JSON.stringify(this.state.files));
|
||||
this.setState({files: [].concat(newfiles, files)});
|
||||
return Promise.resolve(_files);
|
||||
}
|
||||
};
|
||||
|
||||
const processFile = (file) => {
|
||||
return this.onCreate(Path.join(path, file.name), 'file', file);
|
||||
}
|
||||
};
|
||||
|
||||
const updateUI = (filename) => {
|
||||
const files = JSON.parse(JSON.stringify(this.state.files))
|
||||
@ -131,10 +132,10 @@ export class FilesPage extends React.Component {
|
||||
})
|
||||
.filter((file) => {
|
||||
return file === null? false : true;
|
||||
})
|
||||
});
|
||||
this.setState({files: files});
|
||||
return Promise.resolve('ok')
|
||||
}
|
||||
return Promise.resolve('ok');
|
||||
};
|
||||
|
||||
const showError = (filename, err) => {
|
||||
if(err && err.code === 'CANCELLED'){ return }
|
||||
@ -148,14 +149,14 @@ export class FilesPage extends React.Component {
|
||||
return file;
|
||||
});
|
||||
this.setState({files: files});
|
||||
return Promise.resolve('ok')
|
||||
}
|
||||
return Promise.resolve('ok');
|
||||
};
|
||||
|
||||
function generator(arr){
|
||||
let store = arr;
|
||||
return {
|
||||
next: function(){
|
||||
return store.pop()
|
||||
return store.pop();
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -166,7 +167,7 @@ export class FilesPage extends React.Component {
|
||||
return processFile(file)
|
||||
.then((ok) => updateUI(file.name))
|
||||
.then(() => job(it))
|
||||
.catch((err) => showError(file.name, err))
|
||||
.catch((err) => showError(file.name, err));
|
||||
}else{
|
||||
return Promise.resolve('ok');
|
||||
}
|
||||
@ -185,7 +186,6 @@ export class FilesPage extends React.Component {
|
||||
.then((res) => Promise.resolve('ok'));
|
||||
}
|
||||
|
||||
|
||||
resetHeight(){
|
||||
this.setState({
|
||||
height: document.body.clientHeight - document.querySelector('.breadcrumb').offsetHeight
|
||||
@ -195,23 +195,23 @@ export class FilesPage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<BreadCrumb className="breadcrumb" path={this.state.path} />
|
||||
<div style={{height: this.state.height+'px'}} className="scroll-y">
|
||||
<NgIf cond={!this.state.loading} style={{padding: '5px 0 20px 0', height: '100%', boxSizing: 'border-box'}}>
|
||||
<FileSystem path={this.state.path} files={this.state.files} />
|
||||
<Uploader path={this.state.path} />
|
||||
<div className="component_page_filespage">
|
||||
<BreadCrumb className="breadcrumb" path={this.state.path} />
|
||||
<div style={{height: this.state.height+'px'}} className="scroll-y">
|
||||
<NgIf className="container" cond={!this.state.loading}>
|
||||
<FileSystem path={this.state.path} files={this.state.files} />
|
||||
<Uploader path={this.state.path} />
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.loading}>
|
||||
<NgIf cond={this.state.error === false}>
|
||||
<Loader/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.loading}>
|
||||
<NgIf cond={this.state.error === false}>
|
||||
<Loader/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.error !== false} onClick={this.componentDidMount.bind(this)} style={{cursor: 'pointer'}}>
|
||||
<Error err={this.state.error}/>
|
||||
</NgIf>
|
||||
<NgIf className="error" cond={this.state.error !== false} onClick={this.componentDidMount.bind(this)}>
|
||||
<Error err={this.state.error}/>
|
||||
</NgIf>
|
||||
</div>
|
||||
</NgIf>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
16
client/pages/filespage.scss
Normal file
@ -0,0 +1,16 @@
|
||||
.component_page_filespage{
|
||||
.error{
|
||||
cursor: pointer;
|
||||
}
|
||||
.container{
|
||||
padding: 5px 0 20px 0;
|
||||
height: 100%;
|
||||
boxSizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-y{
|
||||
overflow-y: auto!important;
|
||||
overflow-x: hidden!important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EventEmitter } from '../../data';
|
||||
import { BreadCrumb, PathElement } from '../../components/breadcrumb';
|
||||
import { pathBuilder } from '../../utilities';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
|
||||
import { EventEmitter, BreadCrumb, PathElement } from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
|
||||
export default class BreadCrumbTargettable extends BreadCrumb{
|
||||
export class BreadCrumbTargettable extends BreadCrumb{
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Card, NgIf, Icon, pathBuilder, theme } from '../../utilities';
|
||||
import { EventEmitter } from '../../data';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
|
||||
import './existingthing.scss';
|
||||
import { Card, NgIf, Icon, EventEmitter} from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
|
||||
const fileSource = {
|
||||
beginDrag(props, monitor, component) {
|
||||
return {
|
||||
@ -24,15 +26,15 @@ const fileSource = {
|
||||
component.setState({icon: 'loading', message: null}, function(){
|
||||
props.emit.apply(component, ['file.rename'].concat(result.args))
|
||||
.then((ok) => {
|
||||
component.setState({appear: false})
|
||||
component.setState({appear: false});
|
||||
})
|
||||
.catch(err => {
|
||||
if(err && err.code === 'CANCELLED'){ return }
|
||||
component.setState({icon: 'error', message: err.message})
|
||||
})
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
component.setState({icon: 'error', message: err.message});
|
||||
});
|
||||
});
|
||||
}else{
|
||||
throw 'unknown action'
|
||||
throw 'unknown action';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +55,7 @@ const fileTarget = {
|
||||
|
||||
let from = pathBuilder(props.path, src.name, src.type);
|
||||
let to = pathBuilder(props.path, './'+dest.name+'/'+src.name, src.type);
|
||||
return {action: 'rename', args: [from, to, src.type], ctx: 'existingfile'}
|
||||
return {action: 'rename', args: [from, to, src.type], ctx: 'existingfile'};
|
||||
}
|
||||
};
|
||||
|
||||
@ -90,15 +92,16 @@ export class ExistingThing extends React.Component {
|
||||
hover: null,
|
||||
message: null,
|
||||
icon: props.file.type,
|
||||
filename: props.file.name
|
||||
filename: props.file.name,
|
||||
request_delete: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props){
|
||||
this.setState({
|
||||
filename: props.file.name,
|
||||
message: props.file.message || null,
|
||||
})
|
||||
message: props.file.message || null
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -106,7 +109,7 @@ export class ExistingThing extends React.Component {
|
||||
if(this.state.icon !== 'loading'){
|
||||
this.props.emit('file.select', pathBuilder(this.props.path, this.props.file.name, this.props.file.type), this.props.file.type)
|
||||
.catch((err) => {
|
||||
if(err && err.code === 'CANCELLED'){ return }
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
this.setState({icon: 'error', message: err.message});
|
||||
});
|
||||
}
|
||||
@ -123,14 +126,17 @@ export class ExistingThing extends React.Component {
|
||||
)
|
||||
.then((ok) => this.props.emit('file.refresh', this.props.path))
|
||||
.catch((err) => {
|
||||
if(err && err.code === 'CANCELLED'){ return }
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
this.setState({icon: 'error', message: err.message, filename: oldFilename});
|
||||
});
|
||||
}
|
||||
|
||||
onDelete(filename){
|
||||
onDeleteRequest(filename){
|
||||
let toConfirm = this.props.file.name.length > 16? this.props.file.name.substring(0, 10).toLowerCase() : this.props.file.name;
|
||||
let answer = prompt('Confirm by tapping "'+toConfirm+'"');
|
||||
console.log(answer);
|
||||
}
|
||||
onDeleteConfirm(filename, toConfirm, answer){
|
||||
if(answer === toConfirm){
|
||||
this.setState({icon: 'loading'});
|
||||
this.props.emit(
|
||||
@ -138,9 +144,9 @@ export class ExistingThing extends React.Component {
|
||||
pathBuilder(this.props.path, this.props.file.name),
|
||||
this.props.file.type
|
||||
).then((ok) => {
|
||||
this.setState({appear: false})
|
||||
this.setState({appear: false});
|
||||
}).catch((err) => {
|
||||
if(err && err.code === 'CANCELLED'){ return }
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
this.setState({icon: 'error', message: err.message});
|
||||
});
|
||||
}
|
||||
@ -153,14 +159,14 @@ export class ExistingThing extends React.Component {
|
||||
if(this.props.isDragging) { dragStyle.opacity = 0.15; }
|
||||
|
||||
if(this.state.hover === true){
|
||||
dragStyle.background = theme.effects.hover;
|
||||
dragStyle.background = '#f5f5f5';
|
||||
}
|
||||
if((this.props.fileIsOver && this.props.canDropFile) || (this.props.nativeFileIsOver && this.props.canDropNativeFile)) {
|
||||
dragStyle.background = theme.effects.selected;
|
||||
dragStyle.background = '#c5e2f1';
|
||||
}
|
||||
|
||||
return connectDragSource(connectDropNativeFile(connectDropFile(
|
||||
<div>
|
||||
<div className="component_existingthing">
|
||||
<NgIf cond={this.state.appear}>
|
||||
<Card onClick={this.onSelect.bind(this)} onMouseEnter={() => this.setState({hover: true})} onMouseLeave={() => this.setState({hover: false})} style={dragStyle}>
|
||||
<DateTime show={this.state.hover !== true || this.state.icon === 'loading'} timestamp={this.props.file.time} background={dragStyle.background}/>
|
||||
@ -171,16 +177,19 @@ export class ExistingThing extends React.Component {
|
||||
background={dragStyle.background}
|
||||
show={this.state.hover === true && this.state.icon !== 'loading' && !('ontouchstart' in window)}
|
||||
onRename={this.onRename.bind(this)}
|
||||
onDelete={this.onDelete.bind(this)} />
|
||||
onDelete={this.onDeleteRequest.bind(this)} />
|
||||
<FileSize type={this.props.file.type} size={this.props.file.size} />
|
||||
<Message message={this.state.message} />
|
||||
</Card>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.request_delete}>
|
||||
|
||||
</NgIf>
|
||||
</div>
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// <Prompt message="" onCancel={() => {}} onConfirm={this.onDeleteConfirm.bind(this, this.state.filename, 'test')} />
|
||||
ExistingThing.PropTypes = {
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
@ -192,21 +201,21 @@ ExistingThing.PropTypes = {
|
||||
|
||||
class Updater extends React.Component {
|
||||
constructor(props){
|
||||
super(props)
|
||||
super(props);
|
||||
this.state = {
|
||||
editing: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onRename(e){
|
||||
e.preventDefault();
|
||||
this.props.onRename(this.state.editing);
|
||||
this.setState({editing: null})
|
||||
this.setState({editing: null});
|
||||
}
|
||||
|
||||
onDelete(e){
|
||||
e.stopPropagation();
|
||||
this.props.onDelete()
|
||||
this.props.onDelete();
|
||||
}
|
||||
|
||||
|
||||
@ -229,7 +238,7 @@ class Updater extends React.Component {
|
||||
inline: {display: 'inline'},
|
||||
el: {float: 'right', color: '#6f6f6f', height: '22px', background: this.props.background || 'white', margin: '0 -10px', padding: '0 10px', position: 'relative'},
|
||||
margin: {marginRight: '10px'}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div style={{display: 'inline'}}>
|
||||
<NgIf cond={this.props.show} style={style.el}>
|
||||
|
||||
3
client/pages/filespage/existingthing.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.component_existingthing{
|
||||
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Path from 'path';
|
||||
import { Container, NgIf } from '../utilities';
|
||||
import { NewThing, ExistingThing, FileZone } from '../pages/filespage/';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
import Path from 'path';
|
||||
|
||||
|
||||
import { Container, NgIf } from '../../components/';
|
||||
import { NewThing } from './newthing';
|
||||
import { ExistingThing } from './existingthing';
|
||||
import { FileZone } from './filezone';
|
||||
|
||||
@DropTarget('__NATIVE_FILE__', {}, (connect, monitor) => ({
|
||||
connectDropFile: connect.dropTarget(),
|
||||
@ -18,7 +19,7 @@ export class FileSystem extends React.Component {
|
||||
creating: null,
|
||||
access_right: this._findAccessRight(props.files),
|
||||
sort: 'type'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_findAccessRight(files){
|
||||
@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
import { EventEmitter } from '../../data';
|
||||
import { theme, to_rgba } from '../../utilities';
|
||||
|
||||
import { EventEmitter } from '../../components/';
|
||||
|
||||
@EventEmitter
|
||||
@DropTarget('__NATIVE_FILE__', {
|
||||
@ -29,8 +28,8 @@ export class FileZone extends React.Component{
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
if(this.props.fileIsOver){
|
||||
style.background = to_rgba(theme.colors.primary, 0.5);
|
||||
style.border = '2px dashed '+theme.colors.primary;
|
||||
style.background = '#B4EBFF';
|
||||
style.border = '2px dashed #9AD1ED';
|
||||
style.color = 'white'
|
||||
}
|
||||
return this.props.connectDropFile(
|
||||
@ -43,4 +42,3 @@ export class FileZone extends React.Component{
|
||||
FileZone.PropTypes = {
|
||||
path: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
export { NewThing } from './newthing';
|
||||
export { ExistingThing } from './existingthing';
|
||||
export { FileZone } from './filezone';
|
||||
export { FileSystem } from './filesystem.js';
|
||||
export { BreadCrumbTargettable as BreadCrumb } from './breadcrumb';
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EventEmitter } from '../../data';
|
||||
import { Card, NgIf, Icon, pathBuilder } from '../../utilities';
|
||||
|
||||
import { Card, NgIf, Icon, EventEmitter } from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
|
||||
@EventEmitter
|
||||
export class NewThing extends React.Component {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Session } from '../data';
|
||||
import { Loader } from '../utilities';
|
||||
|
||||
import { Session } from '../model/';
|
||||
import { Loader } from '../components/';
|
||||
|
||||
export class HomePage extends React.Component {
|
||||
constructor(props){
|
||||
|
||||
@ -2,6 +2,5 @@ export { HomePage } from './homepage';
|
||||
export { ConnectPage } from './connectpage';
|
||||
export { LogoutPage } from './logout';
|
||||
export { NotFoundPage } from './notfound';
|
||||
// Those are commented because they will delivered as a separate chunk
|
||||
// export { FilesPage } from './filespage';
|
||||
// export { ViewerPage } from './viewerpage';
|
||||
export { FilesPage } from './filespage';
|
||||
export { ViewerPage } from './viewerpage';
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Session, invalidate } from '../data';
|
||||
import { Loader } from '../utilities';
|
||||
|
||||
import { Session } from '../model/';
|
||||
import { Loader } from '../components/';
|
||||
import { invalidate } from '../helpers/';
|
||||
|
||||
export class LogoutPage extends React.Component {
|
||||
constructor(props){
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { BreadCrumb } from '../components/';
|
||||
import { Bundle, debounce, NgIf, Loader, Error, Container } from '../utilities/';
|
||||
import { Files, opener, EventReceiver, EventEmitter } from '../data/';
|
||||
import { AudioPlayer, FileDownloader, ImageViewer, PDFViewer } from './viewerpage/';
|
||||
import Path from 'path';
|
||||
|
||||
import { Files } from '../model/';
|
||||
import { BreadCrumb, Bundle, NgIf, Loader, Error, Container, EventReceiver, EventEmitter } from '../components/';
|
||||
import { debounce, opener } from '../helpers/';
|
||||
import { AudioPlayer, FileDownloader, ImageViewer, PDFViewer } from './viewerpage/';
|
||||
|
||||
const VideoPlayer = (props) => (
|
||||
<Bundle loader={import(/* webpackChunkName: "video" */"../pages/viewerpage/videoplayer")} symbol="VideoPlayer">
|
||||
@ -17,7 +17,6 @@ const IDE = (props) => (
|
||||
</Bundle>
|
||||
);
|
||||
|
||||
|
||||
@EventReceiver
|
||||
export class ViewerPage extends React.Component {
|
||||
constructor(props){
|
||||
@ -66,35 +65,35 @@ export class ViewerPage extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribe('file.select')
|
||||
this.props.unsubscribe('file.select');
|
||||
window.removeEventListener("resize", this.resetHeight);
|
||||
}
|
||||
|
||||
|
||||
save(file){
|
||||
this.setState({isSaving: true})
|
||||
this.setState({isSaving: true});
|
||||
Files.save(this.state.path, file)
|
||||
.then(() => {
|
||||
this.setState({isSaving: false})
|
||||
this.setState({needSaving: false})
|
||||
this.setState({isSaving: false});
|
||||
this.setState({needSaving: false});
|
||||
})
|
||||
.catch((err) => {
|
||||
if(err && err.code === 'CANCELLED'){ return }
|
||||
this.setState({isSaving: false})
|
||||
let message = "Oups, something went wrong"
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
this.setState({isSaving: false});
|
||||
let message = "Oups, something went wrong";
|
||||
if(err.message){
|
||||
message += ':\n'+err.message
|
||||
message += ':\n'+err.message;
|
||||
}
|
||||
alert(message);
|
||||
});
|
||||
}
|
||||
|
||||
onPathUpdate(path){
|
||||
this.props.history.push('/files'+path)
|
||||
this.props.history.push('/files'+path);
|
||||
}
|
||||
|
||||
needSaving(bool){
|
||||
this.setState({needSaving: bool})
|
||||
this.setState({needSaving: bool});
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { MenuBar } from './menubar';
|
||||
import { NgIf, Icon, theme } from '../../utilities/'
|
||||
import WaveSurfer from 'wavesurfer.js';
|
||||
|
||||
import { MenuBar } from './menubar';
|
||||
import { NgIf, Icon } from '../../components/'
|
||||
|
||||
export class AudioPlayer extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
@ -83,7 +84,7 @@ export class AudioPlayer extends React.Component {
|
||||
<NgIf cond={this.state.loading === true}>
|
||||
<Icon name="loading" />
|
||||
</NgIf>
|
||||
<div style={{background: '#f1f1f1', boxShadow: theme.effects.shadow, opacity: this.state.loading? '0' : '1', position: 'relative'}}>
|
||||
<div style={{background: '#f1f1f1', boxShadow: 'rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px', opacity: this.state.loading? '0' : '1', position: 'relative'}}>
|
||||
<div style={{position: 'absolute', top: '10px', right: '10px', zIndex: '2', height: '30px'}}>
|
||||
<NgIf cond={this.state.isPlaying === false} style={{display: 'inline'}}>
|
||||
<span style={{cursor: 'pointer'}} onClick={this.onPlay.bind(this)}><Icon name="play"/></span>
|
||||
|
||||
@ -2,6 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import CodeMirror from 'codemirror/lib/codemirror';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import './editor.scss';
|
||||
window.CodeMirror = CodeMirror;
|
||||
|
||||
// keybinding
|
||||
@ -17,7 +19,7 @@ import 'codemirror/addon/dialog/dialog.js';
|
||||
// code folding
|
||||
import 'codemirror/addon/fold/foldcode';
|
||||
import 'codemirror/addon/fold/foldgutter';
|
||||
|
||||
import 'codemirror/addon/fold/foldgutter.css';
|
||||
|
||||
export class Editor extends React.Component {
|
||||
constructor(props){
|
||||
@ -108,7 +110,7 @@ export class Editor extends React.Component {
|
||||
else if(ext === 'markdown' || ext === 'md'){mode = 'markdown'; }
|
||||
else if(ext === 'pl' || ext === 'pm'){mode = 'perl'; }
|
||||
else if(ext === 'clj'){ mode = 'clojure'; }
|
||||
else if(ext === 'el' || ext === 'lisp' || ext === 'cl'){ mode = 'lisp'; }
|
||||
else if(ext === 'el' || ext === 'lisp' || ext === 'cl' || ext === 'emacs'){ mode = 'lisp'; }
|
||||
else if(ext === 'Dockerfile'){ mode = 'dockerfile'}
|
||||
else if(ext === 'R'){ mode = 'r'; }
|
||||
else if(ext === 'Makefile'){ mode = 'cmake'; }
|
||||
@ -126,7 +128,7 @@ export class Editor extends React.Component {
|
||||
mode = 'text';
|
||||
}
|
||||
|
||||
return import(/* webpackChunkName: "editor" */'../pages/viewerpage/editor/'+mode)
|
||||
return import(/* webpackChunkName: "editor" */'./editor/'+mode)
|
||||
.then((module) => Promise.resolve(module.default));
|
||||
}
|
||||
|
||||
@ -142,4 +144,4 @@ Editor.propTypes = {
|
||||
filename: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
onSave: PropTypes.func
|
||||
}
|
||||
};
|
||||
57
client/pages/viewerpage/editor.scss
Normal file
@ -0,0 +1,57 @@
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
|
||||
/* SEARCH */
|
||||
.CodeMirror-dialog {
|
||||
position: fixed;
|
||||
left: 0; right: 0;
|
||||
background: #525659;
|
||||
z-index: 15;
|
||||
padding: 5px .8em;
|
||||
overflow: hidden;
|
||||
color: #e2e2e2;
|
||||
box-shadow: 2px 2px 2px rgba(0,0,0,0.5)
|
||||
}
|
||||
.CodeMirror-dialog-top {
|
||||
border-bottom: 1px solid #eee;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-bottom {
|
||||
border-top: 1px solid #eee;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
width: 20em;
|
||||
color: white;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog button {
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll { -webkit-overflow-scrolling: touch; }
|
||||
|
||||
.cm-s-default .cm-header {color: #3E7AA6; font-size: 1.15em;}
|
||||
.cm-s-default .cm-header.cm-org-level-star{color: #6f6f6f; position: relative; top: 2px;}
|
||||
.cm-s-default .cm-header.cm-org-todo{color: #FF8355;}
|
||||
.cm-s-default .cm-header.cm-org-done{color: #3BB27C;}
|
||||
.cm-s-default .cm-link{color: #555!important;}
|
||||
.cm-s-default .cm-url{color: #555!important;}
|
||||
|
||||
|
||||
.cm-s-default .cm-keyword {color: #3E7AA6;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #6f6f6f;}
|
||||
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MenuBar } from './menubar';
|
||||
import { theme, NgIf, Icon } from '../../utilities/'
|
||||
import { NgIf, Icon } from '../../components/';
|
||||
|
||||
export class FileDownloader extends React.Component{
|
||||
constructor(props){
|
||||
@ -27,7 +28,7 @@ export class FileDownloader extends React.Component{
|
||||
render(){
|
||||
return (
|
||||
<div style={{textAlign: 'center', background: '#525659', height: '100%'}}>
|
||||
<div style={{padding: '15px 20px', background: '#323639', borderRadius: '2px', color: 'inherit', boxShadow: theme.effects.shadow, display: 'inline-block', marginTop: '50px'}}>
|
||||
<div style={{padding: '15px 20px', background: '#323639', borderRadius: '2px', color: 'inherit', boxShadow: 'rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px', display: 'inline-block', marginTop: '50px'}}>
|
||||
<a download={this.props.filename} href={this.props.data}>
|
||||
<NgIf onClick={this.onClick.bind(this)} cond={!this.state.loading} style={{fontSize: '17px', display: 'inline-block'}}>
|
||||
DOWNLOAD
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Editor } from '../../components/editor';
|
||||
import { NgIf, Fab, Icon } from '../../utilities/'
|
||||
import { MenuBar } from './menubar';
|
||||
|
||||
import { NgIf, Fab, Icon } from '../../components/';
|
||||
import { MenuBar } from './menubar';
|
||||
import { Editor } from './editor';
|
||||
|
||||
export class IDE extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
contentToSave: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onContentUpdate(text){
|
||||
this.props.needSaving(true);
|
||||
this.setState({contentToSave: text})
|
||||
this.setState({contentToSave: text});
|
||||
}
|
||||
|
||||
save(){
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { theme } from '../../utilities/';
|
||||
|
||||
import { MenuBar } from './menubar';
|
||||
|
||||
export const ImageViewer = (props) => {
|
||||
@ -7,7 +7,7 @@ export const ImageViewer = (props) => {
|
||||
<div style={{height: '100%'}}>
|
||||
<MenuBar title={props.filename} download={props.data} />
|
||||
<div style={{textAlign: 'center', background: '#525659', height: 'calc(100% - 34px)', overflow: 'hidden', padding: '20px', boxSizing: 'border-box'}}>
|
||||
<img src={props.data} style={{maxHeight: '100%', maxWidth: '100%', minHeight: '100px', background: '#f1f1f1', boxShadow: theme.effects.shadow}} />
|
||||
<img src={props.data} style={{maxHeight: '100%', maxWidth: '100%', minHeight: '100px', background: '#f1f1f1', boxShadow: 'rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px'}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -2,6 +2,6 @@ export { AudioPlayer } from './audioplayer';
|
||||
export { FileDownloader } from './filedownloader';
|
||||
export { ImageViewer } from './imageviewer';
|
||||
export { PDFViewer } from './pdfviewer';
|
||||
// Those are commented because they will delivered as a separate chunk
|
||||
// export { VideoPlayer } from './videoplayer';
|
||||
// export { IDE } from './ide';
|
||||
// Those are commented because they will be delivered as a separate chunk
|
||||
//export { VideoPlayer } from './videoplayer';
|
||||
//export { IDE } from './ide';
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { theme, Container, NgIf, Icon } from '../../utilities/';
|
||||
|
||||
import { Container, NgIf, Icon } from '../../components/';
|
||||
|
||||
export class MenuBar extends React.Component{
|
||||
constructor(props){
|
||||
@ -26,7 +27,7 @@ export class MenuBar extends React.Component{
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div style={{background: '#313538', color: '#f1f1f1', boxShadow: theme.effects.shadow_small}}>
|
||||
<div style={{background: '#313538', color: '#f1f1f1', boxShadow: 'rgba(0, 0, 0, 0.14) 2px 2px 2px 0px'}}>
|
||||
<Container style={{padding: '9px 0', textAlign: 'center', color: '#f1f1f1', fontSize: '0.9em'}}>
|
||||
<NgIf cond={this.props.hasOwnProperty('download')} style={{float: 'right', height: '1em'}}>
|
||||
<NgIf cond={!this.state.loading} style={{display: 'inline'}}>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { MenuBar } from './menubar';
|
||||
import { theme } from '../../utilities/';
|
||||
import videojs from 'video.js';
|
||||
import 'video.js/dist/video-js.css';
|
||||
|
||||
export class VideoPlayer extends React.Component {
|
||||
constructor(props){
|
||||
@ -31,7 +31,7 @@ export class VideoPlayer extends React.Component {
|
||||
<MenuBar title={this.props.filename} download={this.props.data} />
|
||||
<div style={{padding: '20px'}}>
|
||||
<div style={{maxWidth: '800px', width: '100%', margin: '0 auto'}}>
|
||||
<video ref={ node => this.videoNode = node } className="video-js my-skin" style={{boxShadow: theme.effects.shadow}}></video>
|
||||
<video ref={ node => this.videoNode = node } className="video-js my-skin" style={{boxShadow: 'rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px'}}></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,29 +1,34 @@
|
||||
import React from 'react'
|
||||
import { BrowserRouter, Route, IndexRoute } from 'react-router-dom'
|
||||
import { NotFoundPage, ConnectPage, HomePage, LogoutPage } from './pages/';
|
||||
import { Bundle, URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT } from './utilities/';
|
||||
import React from 'react';
|
||||
import { BrowserRouter, Route, IndexRoute, Switch } from 'react-router-dom';
|
||||
import { NotFoundPage, ConnectPage, HomePage, LogoutPage, FilesPage, ViewerPage } from './pages/';
|
||||
import { Bundle, URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT } from './helpers/';
|
||||
|
||||
const FilesPage = (props) => (
|
||||
<Bundle loader={import(/* webpackChunkName: "route" */ "./pages/filespage")} symbol="FilesPage">
|
||||
{(Comp) => <Comp {...props}/>}
|
||||
</Bundle>
|
||||
);
|
||||
const ViewerPage = (props) => (
|
||||
<Bundle loader={import(/* webpackChunkName: "route" */"./pages/viewerpage")} symbol="ViewerPage">
|
||||
{(Comp) => <Comp {...props}/>}
|
||||
</Bundle>
|
||||
);
|
||||
// import {FilesPage} from './pages/filespage';
|
||||
// import {ViewerPage} from './pages/viewerpage';
|
||||
// const FilesPage = (props) => (
|
||||
// <Bundle loader={import(/* webpackChunkName: "route" */ "./pages/filespage")} symbol="FilesPage">
|
||||
// {(Comp) => <Comp {...props}/>}
|
||||
// </Bundle>
|
||||
// );
|
||||
// const ViewerPage = (props) => (
|
||||
// <Bundle loader={import(/* webpackChunkName: "route" */"./pages/viewerpage")} symbol="ViewerPage">
|
||||
// {(Comp) => <Comp {...props}/>}
|
||||
// </Bundle>
|
||||
// );
|
||||
|
||||
export default class AppRouter extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<Route exact path="/" component={HomePage} />
|
||||
<Route path="/login" component={ConnectPage} />
|
||||
<Route path="/files/:path*" component={FilesPage} />
|
||||
<Route path="/view/:path*" component={ViewerPage} />
|
||||
<Route path="/logout" component={LogoutPage} />
|
||||
<Switch>
|
||||
<Route exact path="/" component={HomePage} />
|
||||
<Route path="/login" component={ConnectPage} />
|
||||
<Route path="/files/:path*" component={FilesPage} />
|
||||
<Route path="/view/:path*" component={ViewerPage} />
|
||||
<Route path="/logout" component={LogoutPage} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||