mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 00:15:11 +01:00
improvement (UI): fixes + cleanup interface - #11
This commit is contained in:
parent
59afd3f0a5
commit
89bb4450f3
51 changed files with 3937 additions and 606 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -9,4 +9,5 @@ babel_cache/
|
|||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
.tern-port
|
||||
.tern-port
|
||||
.tern-project.js
|
||||
|
|
@ -9,9 +9,9 @@ before_install:
|
|||
- echo $DOCKER_PASSWORD | docker login -u=$DOCKER_USERNAME --password-stdin
|
||||
|
||||
script:
|
||||
- sed -i "s/application_url/$APPLICATION_URL/g" config.js
|
||||
- sed -i "s/gdrive_client_id/$GOOGLE_CLIENTID/" config.js
|
||||
- sed -i "s/gdrive_client_secret/$GOOGLE_CLIENTSECRET/" config.js
|
||||
- sed -i "s/dropbox_client_id/$DROPBOX_CLIENTID/" config.js
|
||||
- sed -i "s/application_url/$APPLICATION_URL/g" config_server.js
|
||||
- sed -i "s/gdrive_client_id/$GOOGLE_CLIENTID/" config_server.js
|
||||
- sed -i "s/gdrive_client_secret/$GOOGLE_CLIENTSECRET/" config_server.js
|
||||
- sed -i "s/dropbox_client_id/$DROPBOX_CLIENTID/" config_server.js
|
||||
- npm run image
|
||||
- npm run publish
|
||||
|
|
@ -4,16 +4,18 @@
|
|||
--emphasis: #375160;
|
||||
|
||||
--primary: #9AD1ED;
|
||||
--emphasis-primary: #2b71bc;
|
||||
--emphasis-primary: #c5e2f1;
|
||||
|
||||
--secondary: #466372;
|
||||
--emphasis-secondary: #466372;
|
||||
|
||||
--super-light: #ecf1f6;
|
||||
--light: #909090;
|
||||
--super-light: #f4f4f4;
|
||||
--error: #f26d6d;
|
||||
--success: #63d9b1;
|
||||
}
|
||||
|
||||
// --super-light: #ecf1f6;
|
||||
|
||||
html {
|
||||
font-family:"San Francisco","Roboto","Arial",sans-serif;
|
||||
|
|
@ -68,8 +70,6 @@ input[type="checkbox"]{position: relative; top: 1px; margin: 0; padding: 0;}
|
|||
border-color: rgb(154, 209, 237)!important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.drag-drop{
|
||||
z-index: 2;
|
||||
}
|
||||
|
|
@ -97,26 +97,3 @@ body, body > div, body > div > div, body > div > div > div{ height: 100%;}
|
|||
.login-form button.active{
|
||||
box-shadow: 0px 1px 5px rgba(0,0,0,0.20);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ANIMATION */
|
||||
.example-enter {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
.example-enter.example-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 500ms ease-in;
|
||||
}
|
||||
|
||||
.example-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.example-leave.example-leave-active {
|
||||
opacity: 0.01;
|
||||
transition: opacity 300ms ease-in;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<g fill-rule="evenodd" transform="matrix(.86667 0 0 .86667 -172.05 -864.43)" fill="#969696">
|
||||
<path d="m200.2 999.72c-0.28913 0-0.53125 0.2421-0.53125 0.53117v12.784c0 0.2985 0.23264 0.5312 0.53125 0.5312h15.091c0.2986 0 0.53124-0.2327 0.53124-0.5312l0.0004-10.474c0-0.2889-0.24211-0.5338-0.53124-0.5338l-7.5457 0.0005-2.3076-2.3078z" fill-rule="evenodd" fill="#6f6f6f"/>
|
||||
</g>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.0"
|
||||
id="svg7123"
|
||||
sodipodi:docname="folder.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
|
||||
<metadata
|
||||
id="metadata7129">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7127" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview7125"
|
||||
showgrid="false"
|
||||
inkscape:zoom="59"
|
||||
inkscape:cx="7.3533727"
|
||||
inkscape:cy="9.3706349"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg7123" />
|
||||
<g
|
||||
transform="matrix(0.86666431,0,0,0.86667,-172.04578,-864.32759)"
|
||||
id="g7121"
|
||||
style="fill:#75bbd9;fill-opacity:0.94117647;fill-rule:evenodd">
|
||||
<path
|
||||
d="m 200.2,999.72 c -0.28913,0 -0.53125,0.2421 -0.53125,0.5312 v 12.784 c 0,0.2985 0.23264,0.5312 0.53125,0.5312 h 15.091 c 0.2986,0 0.53124,-0.2327 0.53124,-0.5312 l 4e-4,-10.474 c 0,-0.2889 -0.24211,-0.5338 -0.53124,-0.5338 l -7.5457,5e-4 -2.3076,-2.30783 z"
|
||||
id="path7119"
|
||||
style="fill:#75bbd9;fill-opacity:0.94117647;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.86667,0,0,0.86667,-172.04692,-864.7834)"
|
||||
id="g7121-3"
|
||||
style="fill:#9ad1ed;fill-opacity:1;fill-rule:evenodd">
|
||||
<path
|
||||
d="m 200.2,999.72 c -0.28913,0 -0.53125,0.2421 -0.53125,0.5312 v 12.784 c 0,0.2985 0.23264,0.5312 0.53125,0.5312 h 15.091 c 0.2986,0 0.53124,-0.2327 0.53124,-0.5312 l 4e-4,-10.474 c 0,-0.2889 -0.24211,-0.5338 -0.53124,-0.5338 l -7.5457,5e-4 -2.3076,-2.30783 z"
|
||||
id="path7119-5"
|
||||
style="fill:#9ad1ed;fill-opacity:1;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 2.5 KiB |
|
|
@ -1,6 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<g fill-rule="evenodd" transform="matrix(.86667 0 0 .86667 -172.05 -864.43)" fill="#969696">
|
||||
<path d="m200.2 999.72c-0.28913 0-0.53125 0.2421-0.53125 0.53117v12.784c0 0.2985 0.23264 0.5312 0.53125 0.5312h15.091c0.2986 0 0.53124-0.2327 0.53124-0.5312l0.0004-10.474c0-0.2889-0.24211-0.5338-0.53124-0.5338l-7.5457 0.0005-2.3076-2.3078z" fill-rule="evenodd" fill="#6f6f6f"/>
|
||||
</g>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.0"
|
||||
id="svg7123"
|
||||
sodipodi:docname="link.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
|
||||
<metadata
|
||||
id="metadata7129">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7127" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview7125"
|
||||
showgrid="false"
|
||||
inkscape:zoom="59"
|
||||
inkscape:cx="4.3533727"
|
||||
inkscape:cy="5.9808044"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg7123" />
|
||||
<g
|
||||
transform="matrix(0.86666431,0,0,0.86667,-172.04578,-864.32759)"
|
||||
id="g7121"
|
||||
style="fill:#75bbd9;fill-opacity:0.94117647;fill-rule:evenodd">
|
||||
<path
|
||||
d="m 200.2,999.72 c -0.28913,0 -0.53125,0.2421 -0.53125,0.5312 v 12.784 c 0,0.2985 0.23264,0.5312 0.53125,0.5312 h 15.091 c 0.2986,0 0.53124,-0.2327 0.53124,-0.5312 l 4e-4,-10.474 c 0,-0.2889 -0.24211,-0.5338 -0.53124,-0.5338 l -7.5457,5e-4 -2.3076,-2.30783 z"
|
||||
id="path7119"
|
||||
style="fill:#75bbd9;fill-opacity:0.94117647;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.86667,0,0,0.86667,-172.04692,-864.7834)"
|
||||
id="g7121-3"
|
||||
style="fill:#9ad1ed;fill-opacity:1;fill-rule:evenodd">
|
||||
<path
|
||||
d="m 200.2,999.72 c -0.28913,0 -0.53125,0.2421 -0.53125,0.5312 v 12.784 c 0,0.2985 0.23264,0.5312 0.53125,0.5312 h 15.091 c 0.2986,0 0.53124,-0.2327 0.53124,-0.5312 l 4e-4,-10.474 c 0,-0.2889 -0.24211,-0.5338 -0.53124,-0.5338 l -7.5457,5e-4 -2.3076,-2.30783 z"
|
||||
id="path7119-5"
|
||||
style="fill:#9ad1ed;fill-opacity:1;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 2.5 KiB |
|
|
@ -66,41 +66,28 @@ BreadCrumb.propTypes = {
|
|||
|
||||
|
||||
const BreadCrumbContainer = (props) => {
|
||||
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}>
|
||||
<div className={props.className}>
|
||||
<ul>
|
||||
{props.children}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const Logout = (props) => {
|
||||
let style = {
|
||||
float: 'right',
|
||||
display: 'inline-block',
|
||||
padding: '5px 0px 5px 5px',
|
||||
margin: '0 0px'
|
||||
};
|
||||
return (
|
||||
<li style={style}>
|
||||
<li className="component_logout">
|
||||
<Link to="/logout">
|
||||
<Icon style={{height: '20px'}} name="power"/>
|
||||
<Icon name="power"/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
const Saving = (props) => {
|
||||
let style = {
|
||||
display: 'inline',
|
||||
padding: '0 3px'
|
||||
};
|
||||
|
||||
if(props.needSaving){
|
||||
return (
|
||||
<NgIf style={style} cond={props.needSaving === true && props.isLast === true}>
|
||||
<NgIf className="component_saving" cond={props.needSaving === true && props.isLast === true}>
|
||||
*
|
||||
</NgIf>
|
||||
);
|
||||
|
|
@ -111,7 +98,7 @@ const Saving = (props) => {
|
|||
|
||||
const Separator = (props) => {
|
||||
return (
|
||||
<NgIf cond={props.isLast === false} style={{position: 'relative', top: '3px', display: 'inline'}}>
|
||||
<NgIf cond={props.isLast === false} className="component_separator">
|
||||
<img width="16" height="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAA30lEQVQ4T63T7Q2CMBAG4OuVPdQNcAPdBCYwDdclCAQ3ACfRDXQDZQMHgNRcAoYApfWjv0jIPX3b3gn4wxJjI03TUAhRBkGwV0o9ffaYIEVRrJumuQHA3ReaILxzl+bCkNZ660ozi/QQIl4BoCKieAmyIlyU53lkjCld0CIyhIwxSmt9nEvkRLgoyzIuPggh4iRJqjHkhXTQAwBWUsqNUoq/38sL+TlJf7lf38ngdU5EFNme2adPFgGGrR2LiGcAqIko/LhjeXbatuVOraWUO58hnJ1iRKx8AetxXPHH/1+y62USursaSgAAAABJRU5ErkJggg=="/>
|
||||
</NgIf>
|
||||
);
|
||||
|
|
@ -127,41 +114,29 @@ export class PathElementWrapper extends React.Component {
|
|||
|
||||
onClick(){
|
||||
if(this.props.isLast === false){
|
||||
this.props.emit('file.select', this.props.path.full, 'directory')
|
||||
this.props.emit('file.select', this.props.path.full, 'directory');
|
||||
}
|
||||
}
|
||||
|
||||
toggleHover(shouldHover){
|
||||
if(('ontouchstart' in window) === false){
|
||||
this.setState({hover: shouldHover})
|
||||
this.setState({hover: shouldHover});
|
||||
}
|
||||
}
|
||||
|
||||
limitSize(str){
|
||||
if(str.length > 30){
|
||||
return str.substring(0,23)+'...'
|
||||
return str.substring(0,23)+'...';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
render(){
|
||||
let style = {
|
||||
cursor: this.props.isLast ? '' : 'pointer',
|
||||
background: this.state.hover && this.props.isLast !== true? '#f5f5f5' : 'inherit',
|
||||
borderRadius: '1px',
|
||||
fontSize: '18px',
|
||||
display: 'inline-block',
|
||||
padding: '4px 5px',
|
||||
fontWeight: this.props.isLast ? '100': ''
|
||||
};
|
||||
if(this.props.highlight === true){
|
||||
style.background = '#c5e2f1';
|
||||
style.border = '2px solid #9AD1ED';
|
||||
style.borderRadius = '2px';
|
||||
style.padding = '2px 20px';
|
||||
}
|
||||
let className = "component_path-element-wrapper";
|
||||
if(this.state.hover){ className += " hover"; }
|
||||
if(this.props.highlight) { className += " highlight";}
|
||||
return (
|
||||
<li onClick={this.onClick.bind(this)} style={style} onMouseEnter={this.toggleHover.bind(this, true)} onMouseLeave={this.toggleHover.bind(this, false)}>
|
||||
<li className={className} onClick={this.onClick.bind(this)} onMouseEnter={this.toggleHover.bind(this, true)} onMouseLeave={this.toggleHover.bind(this, false)}>
|
||||
{this.limitSize(this.props.path.label)}
|
||||
<Saving isLast={this.props.isLast} needSaving={this.props.needSaving} isSaving={false} />
|
||||
</li>
|
||||
|
|
@ -177,8 +152,12 @@ export class PathElement extends PathElementWrapper {
|
|||
}
|
||||
|
||||
render(highlight = false){
|
||||
let className = "component_path-element";
|
||||
if(this.props.isLast){
|
||||
className += " is-last";
|
||||
}
|
||||
return (
|
||||
<div style={{display: 'inline-block', color: this.props.isLast? '#6f6f6f' : 'inherit'}}>
|
||||
<div className={className}>
|
||||
<PathElementWrapper highlight={highlight} {...this.props} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,76 @@
|
|||
.component_breadcrumb{
|
||||
.breadcrumb{
|
||||
background: white;
|
||||
margin: 0 0 0px 0;
|
||||
padding: 8px 0;
|
||||
box-shadow: 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);
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
|
||||
ul{
|
||||
list-style-type: none;
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
max-width: 800px;
|
||||
padding: 0;
|
||||
span, div, li{display: inline-block;}
|
||||
}
|
||||
}
|
||||
.component_logout{
|
||||
float: right;
|
||||
display: inline-block;
|
||||
margin: 0 0px;
|
||||
padding: 3px 0;
|
||||
.component_icon{
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.component_saving{
|
||||
display: inline;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.component_separator{
|
||||
position: relative;
|
||||
top: 3px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.component_path-element{
|
||||
display: inline-block;
|
||||
color: var(--light);
|
||||
cursor: pointer;
|
||||
&.is-last{
|
||||
cursor: inherit;
|
||||
color: var(--color);
|
||||
font-weight: inherit;
|
||||
.component_path-element-wrapper.hover{
|
||||
cursor: inherit;
|
||||
background: inherit!important;
|
||||
}
|
||||
}
|
||||
|
||||
.component_path-element-wrapper{
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
padding: 2px 5px;
|
||||
border-radius: 2px;
|
||||
|
||||
&.hover{
|
||||
background: var(--super-light);
|
||||
}
|
||||
&.highlight{
|
||||
background: var(--emphasis-primary);
|
||||
border: 2px solid var(--primary);
|
||||
padding: 0px 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ANIMATION */
|
||||
.component_breadcrumb{
|
||||
.breadcrumb-leave{
|
||||
display: inline-block;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ export class Card extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const _className = this.props.className ? this.props.className+" box" : "box";
|
||||
return (
|
||||
<div {...this.props} className={this.props.className+" box"}>
|
||||
<div {...this.props} className={_className}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import React from 'react';
|
||||
import { NgIf } from './';
|
||||
|
||||
import "./error.scss";
|
||||
|
||||
export const Error = (props) => {
|
||||
let style = props.style || {};
|
||||
style.textAlign = 'center';
|
||||
style.marginTop = '50px';
|
||||
style.fontSize = '25px';
|
||||
style.fontStyle = 'italic';
|
||||
style.fontWeight = 100;
|
||||
return (
|
||||
<div style={style}>
|
||||
{props.err.message || "Oups something went wrong :/"}
|
||||
<NgIf cond={props.err.trace !== undefined} style={{fontSize: '12px', maxWidth: '500px', margin: '10px auto 0 auto'}}>
|
||||
<div className="component_error">
|
||||
{props.err.message || "Oups something went wrong :/"}
|
||||
<NgIf cond={props.err.trace !== undefined} className="trace">
|
||||
{JSON.stringify(props.err.trace)}
|
||||
</NgIf>
|
||||
</div>
|
||||
|
|
|
|||
13
client/components/error.scss
Normal file
13
client/components/error.scss
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.component_error{
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
font-size: 25px;
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
|
||||
.trace{
|
||||
fontSize: 12px;
|
||||
maxWidth: 500px;
|
||||
margin: 10px auto 0 auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,9 @@ export class Modal extends React.Component {
|
|||
this.setState({marginTop: this._marginTop()});
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
}
|
||||
|
||||
_marginTop(){
|
||||
let size = 300;
|
||||
const $box = document.querySelector('#modal-box > div');
|
||||
|
|
@ -35,7 +38,6 @@ export class Modal extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<ReactCSSTransitionGroup transitionName="modal"
|
||||
transitionLeaveTimeout={300}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,9 @@
|
|||
// box
|
||||
.modal-appear > div > div, .modal-enter > div > div{
|
||||
opacity: 0;
|
||||
transform-origin: top center;
|
||||
transform: translateY(10px);
|
||||
|
||||
}
|
||||
.modal-appear.modal-appear-active > div > div, .modal-enter.modal-enter-active > div > div{
|
||||
opacity: 1;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ export class NgIf extends React.Component {
|
|||
let clean_prop = Object.assign({}, this.props);
|
||||
delete clean_prop.cond;
|
||||
delete clean_prop.children;
|
||||
delete clean_prop.type;
|
||||
if(this.props.cond){
|
||||
return <div {...clean_prop}>{this.props.children}</div>;
|
||||
if(this.props.type === "inline"){
|
||||
return <span {...clean_prop}>{this.props.children}</span>;
|
||||
}else{
|
||||
return <div {...clean_prop}>{this.props.children}</div>;
|
||||
}
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,38 @@
|
|||
let cache = {};
|
||||
|
||||
// cleanup expired cache
|
||||
setInterval(() => {
|
||||
for(let key in cache){
|
||||
if(cache[key].date < new Date().getTime()){
|
||||
delete cache[key];
|
||||
}
|
||||
}
|
||||
}, 120*1000)
|
||||
|
||||
export function invalidate(url){
|
||||
if(url === undefined){ cache = {}; }
|
||||
else if(typeof url === 'string'){
|
||||
if(cache[url]){
|
||||
delete cache[url];
|
||||
}
|
||||
}else if(typeof url.exec === 'function'){ // regexp
|
||||
for(let key in cache){
|
||||
if(url.exec(key)){
|
||||
delete cache[key];
|
||||
}
|
||||
}
|
||||
}else{
|
||||
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));
|
||||
}else{
|
||||
if(cache[url]){ delete cache[url]; }
|
||||
return new Promise((done, err) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.withCredentials = true;
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if(xhr.status === 200){
|
||||
if(type === 'json'){
|
||||
try{
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if(data.status === 'ok'){
|
||||
if(cache_expire > 0){
|
||||
cache[url] = {data: data.results || data.result, date: new Date().getTime() + cache_expire * 1000};
|
||||
}
|
||||
done(data.results || data.result);
|
||||
}else if(data.status === 'redirect'){
|
||||
if(data.to === 'logout'){location.pathname = "/logout";}
|
||||
}else{
|
||||
err(data);
|
||||
}
|
||||
}catch(error){
|
||||
err({message: 'oups', trace: error});
|
||||
export function http_get(url, type = 'json'){
|
||||
return new Promise((done, err) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.withCredentials = true;
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if(xhr.status === 200){
|
||||
if(type === 'json'){
|
||||
try{
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if(data.status === 'ok'){
|
||||
done(data);
|
||||
}else if(data.status === 'redirect'){
|
||||
if(data.to === 'logout'){location.pathname = "/logout";}
|
||||
}else{
|
||||
err(data);
|
||||
}
|
||||
}else{
|
||||
done(xhr.responseText);
|
||||
}catch(error){
|
||||
err({message: 'oups', trace: error});
|
||||
}
|
||||
}else{
|
||||
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'});
|
||||
}
|
||||
done(xhr.responseText);
|
||||
}
|
||||
}else{
|
||||
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.open('GET', url, true);
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -88,7 +52,7 @@ export function http_post(url, data, type = 'json'){
|
|||
try{
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if(data.status === 'ok'){
|
||||
done(data.results || data.result);
|
||||
done(data);
|
||||
}else if(data.status === 'redirect'){
|
||||
if(data.to === 'logout'){location.pathname = "/logout";}
|
||||
}else{
|
||||
|
|
@ -120,7 +84,7 @@ export function http_delete(url){
|
|||
try{
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if(data.status === 'ok'){
|
||||
done(data.results || data.result);
|
||||
done(data);
|
||||
}else if(data.status === 'redirect'){
|
||||
if(data.to === 'logout'){location.pathname = "/logout";}
|
||||
}else{
|
||||
|
|
|
|||
184
client/helpers/cache.js
Normal file
184
client/helpers/cache.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
"use strict";
|
||||
// window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
// window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers
|
||||
// window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
|
||||
|
||||
function Data(){
|
||||
this.FILE_PATH = "file_path";
|
||||
this.FILE_CONTENT = "file_content";
|
||||
this.db_version = 'v1.1';
|
||||
this.db = null;
|
||||
this.intervalId = window.setInterval(this._vacuum.bind(this), 5000);
|
||||
this._init();
|
||||
}
|
||||
|
||||
Data.prototype._init = function(){
|
||||
const request = window.indexedDB.open('nuage', 1);
|
||||
request.onupgradeneeded = (e) => this._setup(e.target.result);
|
||||
|
||||
this.db = new Promise((done, err) => {
|
||||
request.onsuccess = (e) => {
|
||||
done(e.target.result);
|
||||
}
|
||||
request.onerror = err;
|
||||
});
|
||||
}
|
||||
|
||||
Data.prototype._setup = function(db){
|
||||
let store;
|
||||
if(!db.objectStoreNames.contains(this.FILE_PATH)){
|
||||
store = db.createObjectStore(this.FILE_PATH, {keyPath: "path"});
|
||||
}
|
||||
//store.createIndex("stale", ["last_access"])
|
||||
|
||||
if(!db.objectStoreNames.contains(this.FILE_CONTENT)){
|
||||
store = db.createObjectStore(this.FILE_CONTENT, {keyPath: "path"});
|
||||
}
|
||||
//store.createIndex("stale", ["last_access"])
|
||||
}
|
||||
|
||||
Data.prototype._vacuum = function(){
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch a record using its path, can be whether a file path or content
|
||||
*/
|
||||
Data.prototype.get = function(type, path, _should_update = true){
|
||||
if(type !== this.FILE_PATH && type !== this.FILE_CONTENT) return Promise.reject({});
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction(type, "readwrite");
|
||||
const store = tx.objectStore(type);
|
||||
const query = store.get(path);
|
||||
return new Promise((done, error) => {
|
||||
query.onsuccess = (e) => {
|
||||
let data = query.result || null;
|
||||
done(data);
|
||||
if(data && _should_update === true){
|
||||
requestAnimationFrame(() => {
|
||||
data.last_access = new Date();
|
||||
if(!data.access_count) data.access_count = 0;
|
||||
data.access_count += 1;
|
||||
this.put(type, data.path, data);
|
||||
});
|
||||
}
|
||||
};
|
||||
tx.onerror = error;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Data.prototype.put = function(type, path, data){
|
||||
if(type !== this.FILE_PATH && type !== this.FILE_CONTENT) return Promise.reject({});
|
||||
|
||||
return this.get(type, path, false)
|
||||
.then((res) => {
|
||||
let new_data;
|
||||
if(res === null){
|
||||
new_data = data;
|
||||
new_data.last_update = new Date();
|
||||
new_data.path = path;
|
||||
}else{
|
||||
new_data = Object.assign(res, data);
|
||||
new_data.last_update = new Date();
|
||||
}
|
||||
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction(type, "readwrite");
|
||||
const store = tx.objectStore(type);
|
||||
|
||||
return new Promise((done, error) => {
|
||||
let request = store.put(new_data);
|
||||
request.onsuccess = () => done(new_data.result || new_data.results);
|
||||
request.onerror = (e) => error(e);
|
||||
tx.onerror = (e) => error(e);
|
||||
tx.oncomplete = () => done(new_data.result || new_data.results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Data.prototype.remove = function(type, path, exact = true){
|
||||
return this.db.then((db) => {
|
||||
const tx = db.transaction(type, "readwrite");
|
||||
const store = tx.objectStore(type);
|
||||
|
||||
if(exact === true){
|
||||
const req = store.delete(path);
|
||||
return new Promise((done, err) => {
|
||||
req.onsuccess = () => done();
|
||||
req.onerror = err;
|
||||
});
|
||||
}else{
|
||||
const request = store.openCursor();
|
||||
return new Promise((done, err) => {
|
||||
request.onsuccess = function(event) {
|
||||
const cursor = event.target.result;
|
||||
if(cursor){
|
||||
if(cursor.value.path.indexOf(path) === 0){
|
||||
store.delete(cursor.value.path);
|
||||
}
|
||||
cursor.continue();
|
||||
}else{
|
||||
done();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Data.prototype.update_path = function(updater_fn){
|
||||
this.db.then((db) => {
|
||||
const tx = db.transaction(this.FILE_PATH, "readwrite");
|
||||
const store = tx.objectStore(this.FILE_PATH);
|
||||
const request = store.openCursor();
|
||||
request.onsuccess = function(event) {
|
||||
const cursor = event.target.result;
|
||||
if(cursor){
|
||||
updater_fn(cursor.value, store)
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
Data.prototype.update_content = function(updater_fn){
|
||||
this.db.then((db) => {
|
||||
const tx = db.transaction(this.FILE_CONTENT, "readwrite");
|
||||
const store = tx.objectStore(this.FILE_CONTENT);
|
||||
const request = store.openCursor();
|
||||
request.onsuccess = function(event) {
|
||||
const cursor = event.target.result;
|
||||
if(cursor){
|
||||
const action = updater_fn(cursor.value, store);
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Data.prototype.destroy = function(){
|
||||
this.db.then((db) => db.close())
|
||||
clearTimeout(this.intervalId);
|
||||
window.indexedDB.deleteDatabase('nuage');
|
||||
this._init();
|
||||
}
|
||||
|
||||
|
||||
// // test
|
||||
// cache = new Data();
|
||||
// cache.put(cache.FILE_PATH, '/', {a:3});
|
||||
// cache.get(cache.FILE_PATH, '/').then((r) => {
|
||||
// console.log(r);
|
||||
// cache.remove(cache.FILE_PATH, '/');
|
||||
// cache.get(cache.FILE_PATH, '/').then((r) => {
|
||||
// console.log(r);
|
||||
// //cache.destroy();
|
||||
// });
|
||||
// });
|
||||
|
||||
|
||||
export const cache = new Data();
|
||||
window.test = cache;
|
||||
19
client/helpers/events.js
Normal file
19
client/helpers/events.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
function Event(){
|
||||
this.fns = [];
|
||||
}
|
||||
Event.prototype.subscribe = function(name, fn){
|
||||
if(!name || typeof fn !== 'function') return;
|
||||
this.fns.push({key: name, fn: fn});
|
||||
}
|
||||
Event.prototype.unsubscribe = function(name){
|
||||
this.fns = this.fns.filter((data) => {
|
||||
return data.key === name ? false : true;
|
||||
});
|
||||
}
|
||||
Event.prototype.emit = function(name, payload){
|
||||
this.fns.map((data) => {
|
||||
if(data.key === name) data.fn(payload);
|
||||
});
|
||||
}
|
||||
|
||||
export const event = new Event();
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
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 { encrypt, decrypt } from './crypto';
|
||||
export { event } from './events';
|
||||
export { cache } from './cache';
|
||||
export { pathBuilder } from './path';
|
||||
export { memory } from './memory';
|
||||
export { prepare } from './navigate';
|
||||
|
|
|
|||
|
|
@ -13,3 +13,5 @@ if ('serviceWorker' in navigator) {
|
|||
window.onload = () => {
|
||||
ReactDOM.render(<Router/>, document.getElementById('main'));
|
||||
};
|
||||
|
||||
window.log = function(){console.log.apply(this, arguments)};
|
||||
|
|
|
|||
|
|
@ -1,75 +1,360 @@
|
|||
import { http_get, http_post, invalidate, prepare } from '../helpers/';
|
||||
"use strict";
|
||||
|
||||
import { http_get, http_post, prepare } from '../helpers/';
|
||||
import Path from 'path';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { cache } from '../helpers/';
|
||||
|
||||
class FileSystem{
|
||||
ls(path, cache = 120){
|
||||
let url = '/api/files/ls?path='+prepare(path);
|
||||
invalidate(path)
|
||||
return http_get(url, cache);
|
||||
constructor(){
|
||||
this.obs = null;
|
||||
this.current_path = null;
|
||||
}
|
||||
|
||||
ls(path, internal = false){
|
||||
this.current_path = path;
|
||||
this.obs && this.obs.complete();
|
||||
|
||||
return Observable.create((obs) => {
|
||||
this.obs = obs;
|
||||
this._ls_from_cache(path);
|
||||
|
||||
let keep_pulling_from_http = false;
|
||||
const fetch_from_http = (_path) => {
|
||||
return this._ls_from_http(_path)
|
||||
.then(() => new Promise((done, err) => {
|
||||
window.setTimeout(() => done(), 2000);
|
||||
}))
|
||||
.then(() => {
|
||||
return keep_pulling_from_http === true? fetch_from_http(_path) : Promise.resolve();
|
||||
});
|
||||
};
|
||||
fetch_from_http(path);
|
||||
|
||||
return () => {
|
||||
keep_pulling_from_http = false;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
_ls_from_http(path){
|
||||
const url = '/api/files/ls?path='+prepare(path);
|
||||
return http_get(url).then((response) => {
|
||||
return cache.get(cache.FILE_PATH, path, false).then((_files) => {
|
||||
if(_files && _files.results){
|
||||
let _files_virtual_to_keep = _files.results.filter((file) => file.icon === 'loading');
|
||||
// update file results
|
||||
for(let i=0; i<_files_virtual_to_keep.length; i++){
|
||||
for(let j=0; j<response.results.length; j++){
|
||||
if(response.results[j].name === _files_virtual_to_keep[i].name){
|
||||
response.results[j] = Object.assign({}, _files_virtual_to_keep[i]);
|
||||
_files_virtual_to_keep[i] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// add stuff that didn't exist in our response
|
||||
_files_virtual_to_keep = _files_virtual_to_keep.filter((e) => e);
|
||||
response.results = response.results.concat(_files_virtual_to_keep);
|
||||
}
|
||||
// publish
|
||||
cache.put(cache.FILE_PATH, path, {results: response.results});
|
||||
if(this.current_path === path) this.obs && this.obs.next(response.results);
|
||||
});
|
||||
}).catch((_err) => {
|
||||
// TODO: user is in offline mode, notify
|
||||
console.log(_err);
|
||||
});
|
||||
}
|
||||
_ls_from_cache(path){
|
||||
return cache.get(cache.FILE_PATH, path).then((_files) => {
|
||||
if(_files && _files.results){
|
||||
if(this.current_path === path){
|
||||
this.obs && this.obs.next(_files.results);
|
||||
}
|
||||
};
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
rm(path){
|
||||
let url = '/api/files/rm?path='+prepare(path);
|
||||
invalidate_ls(path), false;
|
||||
invalidate_cat(path, false);
|
||||
return http_get(url);
|
||||
const url = '/api/files/rm?path='+prepare(path);
|
||||
this._replace(path, 'loading');
|
||||
return http_get(url)
|
||||
.then((res) => {
|
||||
if(res.status === 'ok'){
|
||||
this._remove(path);
|
||||
cache.remove(cache.FILE_CONTENT, path, false);
|
||||
cache.remove(cache.FILE_PATH, Path.dirname(path) + "/", false);
|
||||
}else{
|
||||
this._replace(path, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mv(from, to){
|
||||
let url = '/api/files/mv?from='+prepare(from)+"&to="+prepare(to);
|
||||
invalidate_ls(from);
|
||||
invalidate_ls(to);
|
||||
invalidate_cat(from);
|
||||
return http_get(url);
|
||||
}
|
||||
|
||||
cat(path, cache = 60){
|
||||
let url = '/api/files/cat?path='+prepare(path);
|
||||
return http_get(url, cache, 'raw')
|
||||
cat(path){
|
||||
const url = '/api/files/cat?path='+prepare(path);
|
||||
return http_get(url, 'raw')
|
||||
.then((res) => cache.put(cache.FILE_CONTENT, path, {result: res}))
|
||||
.catch((res) => {
|
||||
return cache.get(cache.FILE_CONTENT, path)
|
||||
.then((_res) => {
|
||||
if(!_res || !_res.result) return Promise.reject(_res);
|
||||
return Promise.resolve(_res.result);
|
||||
})
|
||||
.catch(() => Promise.reject(res));
|
||||
});
|
||||
}
|
||||
url(path){
|
||||
let url = '/api/files/cat?path='+prepare(path);
|
||||
const url = '/api/files/cat?path='+prepare(path);
|
||||
return Promise.resolve(url);
|
||||
}
|
||||
|
||||
save(path, file){
|
||||
invalidate_ls(path);
|
||||
invalidate_cat(path);
|
||||
let url = '/api/files/cat?path='+prepare(path);
|
||||
let formData = new FormData();
|
||||
const url = '/api/files/cat?path='+prepare(path);
|
||||
let formData = new window.FormData();
|
||||
formData.append('file', file);
|
||||
return http_post(url, formData, 'multipart');
|
||||
this._replace(path, 'loading');
|
||||
cache.put(cache.FILE_CONTENT, path, file);
|
||||
return http_post(url, formData, 'multipart')
|
||||
.then((res)=> {
|
||||
res.status === 'ok'? this._replace(path) : this._replace(path, 'error');
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
}
|
||||
|
||||
mkdir(path){
|
||||
let url = '/api/files/mkdir?path='+prepare(path);
|
||||
invalidate_ls(path);
|
||||
return http_get(url);
|
||||
const url = '/api/files/mkdir?path='+prepare(path);
|
||||
this._add(path, 'loading');
|
||||
cache.remove(cache.FILE_PATH, Path.dirname(path) + "/");
|
||||
return http_get(url)
|
||||
.then((res) => {
|
||||
return res.status === 'ok'? this._replace(path) : this._replace(path, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
touch(path, file){
|
||||
invalidate_ls(path);
|
||||
this._add(path, 'loading');
|
||||
let req;
|
||||
if(file){
|
||||
let url = '/api/files/cat?path='+prepare(path);
|
||||
let formData = new FormData();
|
||||
const url = '/api/files/cat?path='+prepare(path);
|
||||
let formData = new window.FormData();
|
||||
formData.append('file', file);
|
||||
return http_post(url, formData, 'multipart');
|
||||
req = http_post(url, formData, 'multipart');
|
||||
}else{
|
||||
let url = '/api/files/touch?path='+prepare(path);
|
||||
return http_get(url)
|
||||
const url = '/api/files/touch?path='+prepare(path);
|
||||
req = http_get(url);
|
||||
}
|
||||
|
||||
return req
|
||||
.then((res) => {
|
||||
return res.status === 'ok'? this._replace(path) : this._replace(path, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
mv(from, to){
|
||||
const url = '/api/files/mv?from='+prepare(from)+"&to="+prepare(to);
|
||||
|
||||
ui_before_request(from, to)
|
||||
.then(() => this._ls_from_cache(Path.dirname(from)+"/"))
|
||||
.then(() => http_get(url)
|
||||
.then((res) => {
|
||||
if(res.status === 'ok'){
|
||||
ui_when_success(from, to)
|
||||
.then(() => this._ls_from_cache(Path.dirname(from)+"/"));
|
||||
}else{
|
||||
ui_when_fail(from, to)
|
||||
.then(() => this._ls_from_cache(Path.dirname(from)+"/"));
|
||||
}
|
||||
return Promise.resolve(res);
|
||||
}));
|
||||
|
||||
function ui_before_request(from, to){
|
||||
return update_from()
|
||||
.then((file) => {
|
||||
if(Path.dirname(from) !== Path.dirname(to)){
|
||||
return update_to(file);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
function update_from(){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(from)+"/", false)
|
||||
.then((res_from) => {
|
||||
let _file = {name: Path.basename(from), type: /\/$/.test(from) ? 'directory' : 'file'};
|
||||
res_from.results = res_from.results.map((file) => {
|
||||
if(file.name === Path.basename(from)){
|
||||
file.name = Path.basename(to);
|
||||
file.icon = 'loading';
|
||||
_file = file;
|
||||
}
|
||||
return file;
|
||||
});
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(from)+"/", res_from)
|
||||
.then(() => Promise.resolve(_file));
|
||||
});
|
||||
}
|
||||
function update_to(file){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(to)+"/", false).then((res_to) => {
|
||||
if(!res_to || !res_to.results) return Promise.resolve();
|
||||
res_to.results.push(file);
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(to)+"/", res_to);
|
||||
});
|
||||
}
|
||||
}
|
||||
function ui_when_fail(from, to){
|
||||
return update_from()
|
||||
.then((file) => {
|
||||
if(Path.dirname(from) !== Path.dirname(to)){
|
||||
return update_to();
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
function update_from(){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(from)+"/", false)
|
||||
.then((res_from) => {
|
||||
if(!res_from || !res_from.results) return Promise.reject();
|
||||
res_from.results = res_from.results.map((file) => {
|
||||
if(file.name === Path.basename(from)){
|
||||
file.icon = 'error';
|
||||
}
|
||||
return file;
|
||||
});
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(from)+"/", res_from)
|
||||
.then(() => Promise.resolve());
|
||||
});
|
||||
}
|
||||
|
||||
function update_to(){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(to)+"/", false)
|
||||
.then((res_to) => {
|
||||
if(!res_to || !res_to.results) return Promise.resolve();
|
||||
res_to.results = res_to.results.filter((file) => {
|
||||
if(file.name === Path.basename(to)){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(from)+"/", res_to);
|
||||
});
|
||||
}
|
||||
}
|
||||
function ui_when_success(from, to){
|
||||
if(Path.dirname(from) === Path.dirname(to)){
|
||||
this._replace(Path.dirname(from)+"/"+Path.basename(to), null);
|
||||
return Promise.resolve();
|
||||
}else{
|
||||
return update_from()
|
||||
.then(update_to)
|
||||
.then(update_related);
|
||||
}
|
||||
|
||||
function update_from(){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(from)+"/", false).then((res_from) => {
|
||||
if(!res_from || !res_from.results) return Promise.resolve();
|
||||
res_from.results = res_from.results.filter((file) => {
|
||||
if(file.name === Path.basename(to)){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(from)+"/", res_from);
|
||||
});
|
||||
}
|
||||
function update_to(){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(to)+"/", false).then((res_to) => {
|
||||
const target_already_exist = res_to && res_to.results ? true : false;
|
||||
if(target_already_exist){
|
||||
res_to.results = res_to.results.map((file) => {
|
||||
if(file.name === Path.basename(to)){
|
||||
delete file.icon;
|
||||
}
|
||||
return file;
|
||||
});
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(to)+"/", res_to);
|
||||
}else{
|
||||
const data = {results: [{
|
||||
name: Path.basename(to),
|
||||
type: /\/$/.test(to) ? 'directory' : 'file',
|
||||
time: (new Date()).getTime()
|
||||
}]};
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(to)+"/", data);
|
||||
}
|
||||
});
|
||||
}
|
||||
function update_related(){
|
||||
// manage nested directories when we try to rename a directory
|
||||
if(/\/$/.test(from) === true){
|
||||
return cache.update_path((data) => {
|
||||
if(data.path !== Path.dirname(to) + "/" && data.path !== Path.dirname(from) + "/" && data.path.indexOf(Path.dirname(from) + "/") === 0){
|
||||
const old_path = data.path;
|
||||
data.path = data.path.replace(Path.dirname(from) + "/", Path.dirname(to) + "/");
|
||||
return cache.remove(cache.FILE_PATH, old_path)
|
||||
.then(() => cache.put(cache.FILE_PATH, data.path, data));
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_replace(path, icon){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(path) + "/", false)
|
||||
.then((res) => {
|
||||
if(!res) return Promise.resolve();
|
||||
let files = res.results.map((file) => {
|
||||
if(file.name === Path.basename(path)){
|
||||
if(!icon) delete file.icon;
|
||||
if(icon) file.icon = icon;
|
||||
}
|
||||
return file;
|
||||
});
|
||||
res.results = files;
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(path) + "/", res)
|
||||
.then((res) => {
|
||||
this._ls_from_cache(Path.dirname(path)+"/");
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_add(path, icon){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(path) + "/", false)
|
||||
.then((res) => {
|
||||
if(!res) return Promise.resolve();
|
||||
let file = {
|
||||
name: Path.basename(path),
|
||||
type: /\/$/.test(path) ? 'directory' : 'file',
|
||||
};
|
||||
if(icon) file.icon = icon;
|
||||
res.results.push(file);
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(path) + "/", res)
|
||||
.then((res) => {
|
||||
this._ls_from_cache(Path.dirname(path)+"/");
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
_remove(path){
|
||||
return cache.get(cache.FILE_PATH, Path.dirname(path) + "/", false)
|
||||
.then((res) => {
|
||||
if(!res) return Promise.resolve();
|
||||
let files = res.results.filter((file) => {
|
||||
return file.name === Path.basename(path) ? false : true;
|
||||
});
|
||||
res.results = files;
|
||||
return cache.put(cache.FILE_PATH, Path.dirname(path) + "/", res)
|
||||
.then((res) => {
|
||||
this._ls_from_cache(Path.dirname(path)+"/");
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
window.Files = Files;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ 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';
|
||||
import { cache } from '../helpers/';
|
||||
import config from '../../config_client';
|
||||
|
||||
import { Alert, Prompt } from '../components/';
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ export class ConnectPage extends React.Component {
|
|||
this.setState({loading: true});
|
||||
Session.authenticate(params)
|
||||
.then((ok) => {
|
||||
invalidate();
|
||||
cache.destroy();
|
||||
const path = params.path && /^\//.test(params.path)? /\/$/.test(params.path) ? params.path : params.path+'/' : '/';
|
||||
this.props.history.push('/files'+path);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
.component_page_connect{
|
||||
background: var(--primary);
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ 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 { debounce, goToFiles, goToViewer, event } from '../helpers/';
|
||||
import { BreadCrumb, FileSystem } from './filespage/';
|
||||
|
||||
@EventReceiver
|
||||
|
|
@ -25,13 +25,12 @@ 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);
|
||||
this.observers = {ls: null};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.onPathUpdate(this.state.path, 'directory');
|
||||
|
||||
// subscriptions
|
||||
this.props.subscribe('file.select', this.onPathUpdate.bind(this));
|
||||
this.props.subscribe('file.upload', this.onUpload.bind(this));
|
||||
|
|
@ -53,6 +52,7 @@ export class FilesPage extends React.Component {
|
|||
this.props.unsubscribe('file.delete');
|
||||
this.props.unsubscribe('file.refresh');
|
||||
window.removeEventListener("resize", this.resetHeight);
|
||||
if(this.observers.ls) this.observers.ls.unsubscribe();
|
||||
}
|
||||
|
||||
hideError(){
|
||||
|
|
@ -60,24 +60,26 @@ 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) => {
|
||||
if(this.observers.ls) this.observers.ls.unsubscribe();
|
||||
this.observers.ls = Files.ls(path).subscribe((files) => {
|
||||
this.setState({files: files, loading: false})
|
||||
}, (error) => {
|
||||
console.log("ERROR", error);
|
||||
this.setState({error: error});
|
||||
});
|
||||
this.setState({error: false});
|
||||
}
|
||||
|
||||
onPathUpdate(path, type = 'directory', withLoader = true){
|
||||
window.path = this.props.history;
|
||||
onPathUpdate(path, type = 'directory'){
|
||||
window.timestamp = new Date();
|
||||
if(type === 'file'){
|
||||
this.props.history.push('/view'+path);
|
||||
}else{
|
||||
this.setState({path: path, loading: withLoader});
|
||||
this.setState({path: path, loading: true});
|
||||
this.onRefresh(path)
|
||||
if(path !== this.state.path){
|
||||
this.props.history.push('/files'+path);
|
||||
}
|
||||
return this.onRefresh(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.component_existingthing{
|
||||
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import Path from 'path';
|
|||
|
||||
import "./filesystem.scss";
|
||||
import { Container, NgIf } from '../../components/';
|
||||
import { NewThing } from './newthing';
|
||||
import { ExistingThing } from './existingthing';
|
||||
import { NewThing } from './thing-new';
|
||||
import { ExistingThing } from './thing-existing';
|
||||
import { FileZone } from './filezone';
|
||||
|
||||
@DropTarget('__NATIVE_FILE__', {}, (connect, monitor) => ({
|
||||
|
|
@ -44,40 +44,43 @@ export class FileSystem extends React.Component {
|
|||
}
|
||||
function sortByType(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
let idA = ['deleting', 'moving'].indexOf(fileA.state),
|
||||
idB = ['deleting', 'moving'].indexOf(fileB.state);
|
||||
|
||||
if(idA !== -1 && idB !== -1){ return 0; }
|
||||
else if(idA !== -1 && idB === -1){ return +1; }
|
||||
else if(idA === -1 && idB !== -1){ return -1; }
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(['directory', 'link'].indexOf(fileA.type) !== -1 && ['directory', 'link'].indexOf(fileB.type) !== -1 ){ return 0; }
|
||||
else if(['directory', 'link'].indexOf(fileA.type) !== -1 && ['directory', 'link'].indexOf(fileB.type) === -1){ return -1; }
|
||||
else if(['directory', 'link'].indexOf(fileA.type) === -1 && ['directory', 'link'].indexOf(fileB.type) !== -1){ return +1; }
|
||||
else{ return fileA.name.toLowerCase() > fileB.name.toLowerCase(); }
|
||||
if(['directory', 'link'].indexOf(fileA.type) === -1 && ['directory', 'link'].indexOf(fileB.type) !== -1){
|
||||
return +1;
|
||||
}else if(['directory', 'link'].indexOf(fileA.type) !== -1 && ['directory', 'link'].indexOf(fileB.type) === -1){
|
||||
return -1;
|
||||
}else{
|
||||
if(fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if(fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
else{
|
||||
return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortByName(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
let idA = ['deleting', 'moving'].indexOf(fileA.state),
|
||||
idB = ['deleting', 'moving'].indexOf(fileB.state);
|
||||
|
||||
if(idA !== -1 && idB !== -1){ return 0; }
|
||||
else if(idA !== -1 && idB === -1){ return +1; }
|
||||
else if(idA === -1 && idB !== -1){ return -1; }
|
||||
else{ return fileA.name.toLowerCase() > fileB.name.toLowerCase(); }
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
if(fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
|
||||
else if(fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
|
||||
else{
|
||||
return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortByDate(files){
|
||||
return files.sort((fileA, fileB) => {
|
||||
let idA = ['deleting', 'moving'].indexOf(fileA.state),
|
||||
idB = ['deleting', 'moving'].indexOf(fileB.state);
|
||||
|
||||
if(idA !== -1 && idB !== -1){ return 0; }
|
||||
else if(idA !== -1 && idB === -1){ return +1; }
|
||||
else if(idA === -1 && idB !== -1){ return -1; }
|
||||
else{ return fileB.time - fileA.time; }
|
||||
if(fileA.icon === 'loading' && fileB.icon !== 'loading'){return +1;}
|
||||
else if(fileA.icon !== 'loading' && fileB.icon === 'loading'){return -1;}
|
||||
else{
|
||||
return fileB.time - fileA.time;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
height: 100%;
|
||||
.list{
|
||||
clear: both;
|
||||
padding-bottom: 150px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.error{
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ import PropTypes from 'prop-types';
|
|||
import { DropTarget } from 'react-dnd';
|
||||
|
||||
import { EventEmitter } from '../../components/';
|
||||
import './filezone.scss';
|
||||
|
||||
@EventEmitter
|
||||
@DropTarget('__NATIVE_FILE__', {
|
||||
drop(props, monitor){
|
||||
let files = monitor.getItem().files
|
||||
let files = monitor.getItem().files;
|
||||
props.emit('file.upload', props.path, files);
|
||||
}
|
||||
}, (connect, monitor) => ({
|
||||
|
|
@ -16,29 +17,18 @@ import { EventEmitter } from '../../components/';
|
|||
}))
|
||||
export class FileZone extends React.Component{
|
||||
constructor(props){
|
||||
super(props)
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(){
|
||||
let style = {
|
||||
border: '2px dashed',
|
||||
padding: '25px 0',
|
||||
marginBottom: '10px',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
if(this.props.fileIsOver){
|
||||
style.background = '#B4EBFF';
|
||||
style.border = '2px dashed #9AD1ED';
|
||||
style.color = 'white'
|
||||
}
|
||||
return this.props.connectDropFile(
|
||||
<div style={style}>
|
||||
<div className={"component_filezone "+(this.props.fileIsOver ? "hover" : "")}>
|
||||
DROP HERE TO UPLOAD
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FileZone.PropTypes = {
|
||||
path: PropTypes.string.isRequired
|
||||
}
|
||||
|
|
|
|||
11
client/pages/filespage/filezone.scss
Normal file
11
client/pages/filespage/filezone.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.component_filezone{
|
||||
border: 2px dashed;
|
||||
padding: 25px 0;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
&.hover{
|
||||
background: var(--emphasis-primary);
|
||||
border: 2px dashed var(--primary);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Card, NgIf, Icon, EventEmitter } from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
|
||||
@EventEmitter
|
||||
export class NewThing extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
name: null,
|
||||
type: null,
|
||||
message: null,
|
||||
icon: null
|
||||
}
|
||||
}
|
||||
|
||||
onNew(type){
|
||||
this.setState({type: type, name: '', icon: type})
|
||||
}
|
||||
|
||||
onDelete(){
|
||||
this.setState({type: null, name: null, icon: null})
|
||||
}
|
||||
|
||||
onSave(e){
|
||||
e.preventDefault();
|
||||
if(this.state.name !== null){
|
||||
this.setState({icon: 'loading'})
|
||||
this.props.emit('file.create', pathBuilder(this.props.path, this.state.name, this.state.type), this.state.type)
|
||||
.then((ok) => this.props.emit('file.refresh', this.props.path))
|
||||
.then((ok) => {
|
||||
this.onDelete();
|
||||
return Promise.resolve('ok');
|
||||
})
|
||||
.catch(err => {
|
||||
if(err && err.code === 'CANCELLED'){ return }
|
||||
this.setState({message: err.message, icon: 'error'})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onSortUpdate(e){
|
||||
this.props.onSortUpdate(e.target.value);
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<div style={{fontSize: '15px', lineHeight: '15px', height: '15px', marginTop: '5px', color: 'rgba(0,0,0,0.4)', margin: '0 0 10px 0'}}>
|
||||
<NgIf cond={this.props.accessRight.can_create_file === true} onClick={this.onNew.bind(this, 'file')} style={{marginRight: '15px', cursor: 'pointer', display: 'inline'}}>New File</NgIf>
|
||||
<NgIf cond={this.props.accessRight.can_create_directory === true} onClick={this.onNew.bind(this, 'directory')} style={{cursor: 'pointer', display: 'inline'}}>New Directory</NgIf>
|
||||
<select value={this.props.sort} onChange={this.onSortUpdate.bind(this)} style={{float: 'right', color: 'rgba(0,0,0,0.4)', background: 'none', borderRadius: '5px', outline: 'none', border: '1px solid rgba(0,0,0,0.4)', fontSize: '12px'}}>
|
||||
<option value="type">Sort By Type</option>
|
||||
<option value="date">Sort By Date</option>
|
||||
<option value="name">Sort By Name</option>
|
||||
</select>
|
||||
</div>
|
||||
<NgIf cond={this.state.type !== null}>
|
||||
<Card>
|
||||
<Icon style={{width: '25px', height: '25px'}} name={this.state.icon} />
|
||||
<form onSubmit={this.onSave.bind(this)} style={{display: 'inline'}}>
|
||||
<input onChange={(e) => this.setState({name: e.target.value})} value={this.state.name} style={{outline: 'none'}} type="text" autoFocus/>
|
||||
</form>
|
||||
<NgIf cond={this.state.message !== null} style={{color: 'rgba(0,0,0,0.4)', fontSize: '0.9em', paddingLeft: '10px', display: 'inline'}}>
|
||||
{this.state.message}
|
||||
</NgIf>
|
||||
<div style={{float: 'right', height: '22px'}}>
|
||||
<Icon style={{width: '25px', height: '25px'}} name="delete" onClick={this.onDelete.bind(this)} />
|
||||
</div>
|
||||
</Card>
|
||||
</NgIf>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
NewThing.PropTypes = {
|
||||
accessRight: PropTypes.obj,
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
onSortUpdate: PropTypes.func.isRequired,
|
||||
sort: PropTypes.string.isRequired,
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
|
||||
import './existingthing.scss';
|
||||
import './thing.scss';
|
||||
import { Card, NgIf, Icon, EventEmitter, Prompt } from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
|
||||
|
|
@ -15,24 +15,13 @@ const fileSource = {
|
|||
};
|
||||
},
|
||||
canDrag(props, monitor){
|
||||
// would have been great to use forbid dragging while there's some actions happenning
|
||||
// but react-dnd won't give us the component in argument :(
|
||||
return true;
|
||||
return props.file.icon === 'loading'? false : true;
|
||||
},
|
||||
endDrag(props, monitor, component){
|
||||
if(monitor.didDrop() && component.state.icon !== 'loading'){
|
||||
let result = monitor.getDropResult();
|
||||
if(result.action === 'rename'){
|
||||
component.setState({icon: 'loading', message: null}, function(){
|
||||
props.emit.apply(component, ['file.rename'].concat(result.args))
|
||||
.then((ok) => {
|
||||
component.setState({appear: false});
|
||||
})
|
||||
.catch(err => {
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
component.setState({icon: 'error', message: err.message});
|
||||
});
|
||||
});
|
||||
props.emit.apply(component, ['file.rename'].concat(result.args));
|
||||
}else{
|
||||
throw 'unknown action';
|
||||
}
|
||||
|
|
@ -88,10 +77,8 @@ export class ExistingThing extends React.Component {
|
|||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
appear: true,
|
||||
hover: null,
|
||||
message: null,
|
||||
icon: props.file.type,
|
||||
filename: props.file.name,
|
||||
delete_request: false,
|
||||
delete_message: "Confirm by tapping \""+this._confirm_delete_text()+"\"",
|
||||
|
|
@ -99,38 +86,25 @@ export class ExistingThing extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props){
|
||||
this.setState({
|
||||
filename: props.file.name,
|
||||
message: props.file.message || null
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onSelect(){
|
||||
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; }
|
||||
this.setState({icon: 'error', message: err.message});
|
||||
});
|
||||
if(this.state.icon !== 'loading' && this.state.icon !== 'error'){
|
||||
this.props.emit(
|
||||
'file.select',
|
||||
pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
|
||||
this.props.file.type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onRename(newFilename){
|
||||
let oldFilename = this.props.file.name;
|
||||
this.setState({icon: 'loading', filename: newFilename});
|
||||
this.props.emit(
|
||||
'file.rename',
|
||||
pathBuilder(this.props.path, oldFilename),
|
||||
pathBuilder(this.props.path, newFilename),
|
||||
this.props.file.type
|
||||
)
|
||||
.then((ok) => this.props.emit('file.refresh', this.props.path))
|
||||
.catch((err) => {
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
this.setState({icon: 'error', message: err.message, filename: oldFilename});
|
||||
});
|
||||
if(this.state.icon !== 'loading' && this.state.icon !== 'error'){
|
||||
this.props.emit(
|
||||
'file.rename',
|
||||
pathBuilder(this.props.path, this.props.file.name),
|
||||
pathBuilder(this.props.path, newFilename),
|
||||
this.props.file.type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onDeleteRequest(filename){
|
||||
|
|
@ -143,12 +117,7 @@ export class ExistingThing extends React.Component {
|
|||
'file.delete',
|
||||
pathBuilder(this.props.path, this.props.file.name),
|
||||
this.props.file.type
|
||||
).then((ok) => {
|
||||
this.setState({appear: false});
|
||||
}).catch((err) => {
|
||||
if(err && err.code === 'CANCELLED'){ return; }
|
||||
this.setState({icon: 'error', message: err.message});
|
||||
});
|
||||
);
|
||||
}else{
|
||||
this.setState({delete_error: "Doesn't match"});
|
||||
}
|
||||
|
|
@ -163,33 +132,32 @@ export class ExistingThing extends React.Component {
|
|||
|
||||
render(highlight){
|
||||
const { connectDragSource, connectDropFile, connectDropNativeFile } = this.props;
|
||||
let dragStyle = {whiteSpace: 'nowrap'};
|
||||
if(this.props.isDragging) { dragStyle.opacity = 0.15; }
|
||||
|
||||
let className = "";
|
||||
if(this.props.isDragging) {
|
||||
className += "is-dragging ";
|
||||
}
|
||||
if(this.state.hover === true){
|
||||
dragStyle.background = '#f5f5f5';
|
||||
className += "mouse-is-hover ";
|
||||
}
|
||||
if((this.props.fileIsOver && this.props.canDropFile) || (this.props.nativeFileIsOver && this.props.canDropNativeFile)) {
|
||||
dragStyle.background = '#c5e2f1';
|
||||
className += "file-is-hover ";
|
||||
}
|
||||
className = className.trim();
|
||||
|
||||
return connectDragSource(connectDropNativeFile(connectDropFile(
|
||||
<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}/>
|
||||
<Updater filename={this.state.filename}
|
||||
icon={this.props.file.virtual? this.props.file.icon : this.state.icon}
|
||||
can_move={this.props.file.can_move !== false}
|
||||
can_delete={this.props.file.can_delete !== false}
|
||||
background={dragStyle.background}
|
||||
show={this.state.hover === true && this.state.icon !== 'loading' && !('ontouchstart' in window)}
|
||||
onRename={this.onRename.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>
|
||||
<div className="component_thing">
|
||||
<Card className={this.state.hover} onClick={this.onSelect.bind(this)} onMouseEnter={() => this.setState({hover: true})} onMouseLeave={() => this.setState({hover: false})} className={className}>
|
||||
<DateTime show={this.state.hover !== true || this.state.icon === 'loading'} timestamp={this.props.file.time} />
|
||||
<Updater filename={this.props.file.name}
|
||||
icon={this.props.file.icon || this.props.file.type}
|
||||
can_move={this.props.file.can_move !== false}
|
||||
can_delete={this.props.file.can_delete !== false}
|
||||
show={this.state.hover === true && this.state.icon !== 'loading' && !('ontouchstart' in window)}
|
||||
onRename={this.onRename.bind(this)}
|
||||
onDelete={this.onDeleteRequest.bind(this)} />
|
||||
<FileSize type={this.props.file.type} size={this.props.file.size} />
|
||||
<Message message={this.state.message} />
|
||||
</Card>
|
||||
<Prompt appear={this.state.delete_request} error={this.state.delete_error} message={this.state.delete_message} onCancel={this.onDeleteCancel.bind(this)} onSubmit={this.onDeleteConfirm.bind(this)}/>
|
||||
</div>
|
||||
)));
|
||||
|
|
@ -239,31 +207,26 @@ class Updater extends React.Component {
|
|||
}
|
||||
|
||||
render(){
|
||||
const style = {
|
||||
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}>
|
||||
<NgIf cond={this.props.can_move} style={style.inline}>
|
||||
<Icon name="edit" onClick={this.onRenameRequest.bind(this)} style={style.margin} style={{width: '25px', height: '25px'}} />
|
||||
<span className="component_updater">
|
||||
<NgIf className="action" cond={this.props.show}>
|
||||
<NgIf cond={this.props.can_move} type="inline">
|
||||
<Icon name="edit" onClick={this.onRenameRequest.bind(this)} className="component_updater--icon" />
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.can_delete !== false} style={style.inline}>
|
||||
<Icon name="delete" onClick={this.onDelete.bind(this)} style={{width: '25px', height: '25px'}} />
|
||||
<NgIf cond={this.props.can_delete !== false} type="inline">
|
||||
<Icon name="delete" onClick={this.onDelete.bind(this)} className="component_updater--icon"/>
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
<Icon style={{width: '25px', height: '25px'}} name={this.props.icon} />
|
||||
<span style={{padding: '5px', lineHeight: '22px'}}>
|
||||
<NgIf style={{display: 'inline'}} cond={this.state.editing === null}>{this.props.filename}</NgIf>
|
||||
<NgIf style={{display: 'inline'}} cond={this.state.editing !== null}>
|
||||
<form onClick={this.preventSelect} onSubmit={this.onRename.bind(this)} style={{display: 'inline'}}>
|
||||
<Icon className="component_updater--icon" name={this.props.icon} />
|
||||
<span className="file-details">
|
||||
<NgIf cond={this.state.editing === null} type='inline'>{this.props.filename}</NgIf>
|
||||
<NgIf cond={this.state.editing !== null} type='inline'>
|
||||
<form onClick={this.preventSelect} onSubmit={this.onRename.bind(this)}>
|
||||
<input value={this.state.editing} onChange={(e) => this.setState({editing: e.target.value})} autoFocus />
|
||||
</form>
|
||||
</NgIf>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -283,10 +246,8 @@ const DateTime = (props) => {
|
|||
}
|
||||
}
|
||||
|
||||
const style = {float: 'right', color: '#6f6f6f', lineHeight: '25px', background: props.background || 'white', margin: '0 -10px', padding: '0 10px', position: 'relative'};
|
||||
|
||||
return (
|
||||
<NgIf cond={props.show} style={style}>
|
||||
<NgIf cond={props.show} className="component_datetime">
|
||||
<span>{displayTime(props.timestamp)}</span>
|
||||
</NgIf>
|
||||
);
|
||||
|
|
@ -294,30 +255,30 @@ const DateTime = (props) => {
|
|||
|
||||
const FileSize = (props) => {
|
||||
function displaySize(bytes){
|
||||
if(bytes < 1024){
|
||||
return bytes+'B';
|
||||
if(Number.isNaN(bytes) || bytes === undefined){
|
||||
return "";
|
||||
}else if(bytes < 1024){
|
||||
return "("+bytes+'B'+")";
|
||||
}else if(bytes < 1048576){
|
||||
return Math.round(bytes/1024*10)/10+'KB';
|
||||
return "("+Math.round(bytes/1024*10)/10+'KB'+")";
|
||||
}else if(bytes < 1073741824){
|
||||
return Math.round(bytes/(1024*1024)*10)/10+'MB';
|
||||
return "("+Math.round(bytes/(1024*1024)*10)/10+'MB'+")";
|
||||
}else if(bytes < 1099511627776){
|
||||
return Math.round(bytes/(1024*1024*1024)*10)/10+'GB';
|
||||
return "("+Math.round(bytes/(1024*1024*1024)*10)/10+'GB'+")";
|
||||
}else{
|
||||
return Math.round(bytes/(1024*1024*1024*1024))+'TB';
|
||||
return "("+Math.round(bytes/(1024*1024*1024*1024))+'TB'+")";
|
||||
}
|
||||
}
|
||||
const style = {color: '#6f6f6f', fontSize: '0.85em'};
|
||||
|
||||
return (
|
||||
<NgIf cond={props.type === 'file'} style={{display: 'inline-block'}}>
|
||||
<span style={style}>({displaySize(props.size)})</span>
|
||||
<NgIf type="inline" className="component_filesize" cond={props.type === 'file'}>
|
||||
<span>{displaySize(props.size)}</span>
|
||||
</NgIf>
|
||||
);
|
||||
}
|
||||
const Message = (props) => {
|
||||
const style = {color: 'rgba(0,0,0,0.4)', fontSize: '0.9em', paddingLeft: '10px', display: 'inline'};
|
||||
return (
|
||||
<NgIf cond={props.message !== null} style={style}>
|
||||
<NgIf cond={props.message !== null} className="component_message" type="inline">
|
||||
- {props.message}
|
||||
</NgIf>
|
||||
);
|
||||
85
client/pages/filespage/thing-new.js
Normal file
85
client/pages/filespage/thing-new.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Card, NgIf, Icon, EventEmitter } from '../../components/';
|
||||
import { pathBuilder } from '../../helpers/';
|
||||
import "./thing.scss";
|
||||
|
||||
@EventEmitter
|
||||
export class NewThing extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
name: null,
|
||||
type: null,
|
||||
message: null,
|
||||
icon: null
|
||||
};
|
||||
}
|
||||
|
||||
onNew(type){
|
||||
if(this.state.type === type){
|
||||
this.onDelete();
|
||||
}else{
|
||||
this.setState({type: type, name: '', icon: type});
|
||||
}
|
||||
}
|
||||
|
||||
onDelete(){
|
||||
this.setState({type: null, name: null, icon: null});
|
||||
}
|
||||
|
||||
onSave(e){
|
||||
e.preventDefault();
|
||||
if(this.state.name !== null){
|
||||
this.props.emit('file.create', pathBuilder(this.props.path, this.state.name, this.state.type), this.state.type);
|
||||
}
|
||||
}
|
||||
|
||||
onSortUpdate(e){
|
||||
this.props.onSortUpdate(e.target.value);
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<div className="menubar no-select">
|
||||
<NgIf cond={this.props.accessRight.can_create_file === true} onClick={this.onNew.bind(this, 'file')} type="inline">New File</NgIf>
|
||||
<NgIf cond={this.props.accessRight.can_create_directory === true} onClick={this.onNew.bind(this, 'directory')} type="inline">New Directory</NgIf>
|
||||
<select value={this.props.sort} onChange={this.onSortUpdate.bind(this)}>
|
||||
<option value="type">Sort By Type</option>
|
||||
<option value="date">Sort By Date</option>
|
||||
<option value="name">Sort By Name</option>
|
||||
</select>
|
||||
</div>
|
||||
<NgIf cond={this.state.type !== null} className="component_thing">
|
||||
<Card className="mouse-is-hover">
|
||||
<Icon className="component_updater--icon" name={this.state.icon} />
|
||||
<span className="file-details">
|
||||
<form onSubmit={this.onSave.bind(this)}>
|
||||
<input onChange={(e) => this.setState({name: e.target.value})} value={this.state.name} type="text" autoFocus/>
|
||||
</form>
|
||||
</span>
|
||||
<NgIf className="component_message" cond={this.state.message !== null}>
|
||||
{this.state.message}
|
||||
</NgIf>
|
||||
<span className="component_updater">
|
||||
<div className="action">
|
||||
<div>
|
||||
<Icon className="component_updater--icon" name="delete" onClick={this.onDelete.bind(this)} />
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</Card>
|
||||
</NgIf>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
NewThing.PropTypes = {
|
||||
accessRight: PropTypes.obj,
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
onSortUpdate: PropTypes.func.isRequired,
|
||||
sort: PropTypes.string.isRequired
|
||||
}
|
||||
82
client/pages/filespage/thing.scss
Normal file
82
client/pages/filespage/thing.scss
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
.menubar{
|
||||
font-size: 15px;
|
||||
line-height: 15px;
|
||||
height: 15px;
|
||||
margin-top: 5px;
|
||||
color: var(--light);
|
||||
margin: 0 0 10px 0;
|
||||
|
||||
> span{cursor: pointer; margin-right: 15px;}
|
||||
select{
|
||||
float: right;
|
||||
color: var(--light);
|
||||
background: none;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
border: 1px solid var(--light);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.component_thing{
|
||||
.file-is-hover{
|
||||
background: var(--emphasis-primary);
|
||||
}
|
||||
.mouse-is-hover{
|
||||
background: var(--super-light);
|
||||
}
|
||||
.file-is-dragging{
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
|
||||
.component_icon{
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
form{
|
||||
display: inline;
|
||||
input{
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.component_updater{
|
||||
.action{
|
||||
float: right;
|
||||
color: #6f6f6f;
|
||||
line-height: 25px;
|
||||
margin: 0 -10px;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
.component_icon{
|
||||
padding: 1px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
.component_datetime{
|
||||
float: right;
|
||||
color: var(--light);
|
||||
line-height: 25px;
|
||||
margin: 0 -10px;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
}
|
||||
.component_filesize{
|
||||
span{
|
||||
color: var(--light);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
.component_message{
|
||||
color: var(--light);
|
||||
font-size: 0.9em;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.file-details{
|
||||
padding: 0 5px;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
|
||||
import { Session } from '../model/';
|
||||
import { Loader } from '../components/';
|
||||
import { invalidate } from '../helpers/';
|
||||
import { cache } from '../helpers/';
|
||||
|
||||
export class LogoutPage extends React.Component {
|
||||
constructor(props){
|
||||
|
|
@ -10,9 +10,9 @@ export class LogoutPage extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount(){
|
||||
invalidate();
|
||||
Session.logout()
|
||||
.then((res) => {
|
||||
cache.destroy();
|
||||
this.props.history.push('/');
|
||||
})
|
||||
.catch((res) => {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ export class ViewerPage extends React.Component {
|
|||
<IDE needSaving={this.needSaving.bind(this)}
|
||||
isSaving={this.state.isSaving}
|
||||
onSave={this.save.bind(this)}
|
||||
content={this.state.data}
|
||||
content={this.state.data || ''}
|
||||
filename={this.state.filename}/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.opener === 'image'} style={style}>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ export class Editor extends React.Component {
|
|||
minFoldSize: 1
|
||||
}
|
||||
});
|
||||
window.mirror = editor;
|
||||
|
||||
if(CodeMirror.afterInit){
|
||||
CodeMirror.afterInit(editor);
|
||||
|
|
|
|||
|
|
@ -61,3 +61,9 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
|||
color: #6f6f6f;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/* BUGFIX */
|
||||
// https://github.com/codemirror/CodeMirror/issues/5056
|
||||
.CodeMirror-cursor {
|
||||
width: 1px !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,18 +19,20 @@ import { Bundle, URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT } from
|
|||
export default class AppRouter extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<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>
|
||||
<div>
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
config_client.js
Normal file
3
config_client.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
fork_button: true
|
||||
}
|
||||
|
|
@ -22,7 +22,5 @@ module.exports = {
|
|||
clientID: "dropbox_client_id",
|
||||
redirectURI: "application_url/login"
|
||||
},
|
||||
server_secret: 'not_so_secret_key',
|
||||
// APPLICATION CONFIG
|
||||
fork_button: true
|
||||
secret_key: 'not_so_secret_key'
|
||||
}
|
||||
2687
package-lock.json
generated
2687
package-lock.json
generated
File diff suppressed because it is too large
Load diff
83
package.json
83
package.json
|
|
@ -18,14 +18,9 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.59.0",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"body-parser": "^1.17.2",
|
||||
"codemirror": "^5.26.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"cors": "^2.8.3",
|
||||
"crypto": "0.0.3",
|
||||
"dropbox": "^2.5.3",
|
||||
"ejs": "^2.5.6",
|
||||
"express": "^4.15.3",
|
||||
"express-winston": "^2.4.0",
|
||||
"ftp": "^0.3.10",
|
||||
|
|
@ -35,7 +30,50 @@
|
|||
"node-ssh": "^4.2.2",
|
||||
"nodegit": "^0.18.3",
|
||||
"path": "^0.12.7",
|
||||
"pdfjs-dist": "^1.8.426",
|
||||
"request": "^2.81.0",
|
||||
"request-promise": "^4.2.1",
|
||||
"scp2": "^0.5.0",
|
||||
"ssh2-sftp-client": "^1.1.0",
|
||||
"stream-to-string": "^1.1.0",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"webdav-fs": "^1.0.0",
|
||||
"winston": "^2.3.1",
|
||||
"winston-couchdb": "^0.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"assert": "^1.4.1",
|
||||
"babel-cli": "^6.11.4",
|
||||
"babel-core": "^6.13.2",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-es2015": "^6.13.2",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"babelify": "^8.0.0",
|
||||
"browserify": "^16.1.1",
|
||||
"chai": "^4.1.2",
|
||||
"codemirror": "^5.26.0",
|
||||
"cors": "^2.8.3",
|
||||
"css-loader": "^0.28.10",
|
||||
"dropbox": "^2.5.3",
|
||||
"ejs": "^2.5.6",
|
||||
"html-loader": "^0.4.5",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"http-server": "^0.9.0",
|
||||
"karma": "^2.0.0",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-browserify": "^5.2.0",
|
||||
"karma-chai": "^0.1.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-webpack": "^2.0.13",
|
||||
"less-loader": "^4.0.6",
|
||||
"mocha": "^5.0.4",
|
||||
"node-sass": "^4.7.2",
|
||||
"nodemon": "^1.17.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^15.3.2",
|
||||
"react-addons-css-transition-group": "^15.6.2",
|
||||
|
|
@ -46,40 +84,17 @@
|
|||
"react-draggable": "^2.2.6",
|
||||
"react-router": "^4.1.1",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"request": "^2.81.0",
|
||||
"request-promise": "^4.2.1",
|
||||
"requirejs": "^2.3.5",
|
||||
"rx-lite": "^4.0.8",
|
||||
"rxjs": "^5.4.0",
|
||||
"scp2": "^0.5.0",
|
||||
"ssh2-sftp-client": "^1.1.0",
|
||||
"stream-to-string": "^1.1.0",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"video.js": "^5.19.2",
|
||||
"wavesurfer.js": "^1.4.0",
|
||||
"webdav-fs": "^1.0.0",
|
||||
"winston": "^2.3.1",
|
||||
"winston-couchdb": "^0.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.11.4",
|
||||
"babel-core": "^6.13.2",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-preset-es2015": "^6.13.2",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"css-loader": "^0.28.10",
|
||||
"html-loader": "^0.4.5",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"http-server": "^0.9.0",
|
||||
"less-loader": "^4.0.6",
|
||||
"node-sass": "^4.7.2",
|
||||
"nodemon": "^1.17.1",
|
||||
"sass-loader": "^6.0.6",
|
||||
"sass-variable-loader": "^0.1.2",
|
||||
"style-loader": "^0.20.2",
|
||||
"url-loader": "^0.6.2",
|
||||
"video.js": "^5.19.2",
|
||||
"videojs-sublime-skin": "^1.0.3",
|
||||
"watchify": "^3.11.0",
|
||||
"wavesurfer.js": "^1.4.0",
|
||||
"webpack": "^2.7.0",
|
||||
"webpack-bundle-analyzer": "^2.8.2",
|
||||
"webpack-dev-server": "^3.1.0"
|
||||
|
|
|
|||
4
server/bootstrap.js
vendored
4
server/bootstrap.js
vendored
|
|
@ -1,11 +1,11 @@
|
|||
var bodyParser = require('body-parser'),
|
||||
cookieParser = require('cookie-parser'),
|
||||
cors = require('cors'),
|
||||
config = require('../config'),
|
||||
config = require('../config_server'),
|
||||
express = require('express'),
|
||||
winston = require('winston'),
|
||||
expressWinston = require('express-winston');
|
||||
|
||||
|
||||
require('winston-couchdb');
|
||||
|
||||
var app = express();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ app.get('/ls', function(req, res){
|
|||
})
|
||||
.catch(function(err){
|
||||
res.send({status: 'error', message: err.message || 'cannot fetch files', trace: err})
|
||||
})
|
||||
});
|
||||
}else{
|
||||
res.send({status: 'error', message: 'unknown path'})
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ app.get('/cat', function(req, res){
|
|||
|
||||
// create/update a file
|
||||
// https://github.com/pillarjs/multiparty
|
||||
app.post('/cat', function(req, res){
|
||||
app.post('/cat', function(req, res){
|
||||
var form = new multiparty.Form(),
|
||||
path = decodeURIComponent(req.query.path);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
var http = require('request-promise'),
|
||||
http_stream = require('request'),
|
||||
Path = require('path'),
|
||||
config = require('../../../config'),
|
||||
config = require('../../../config_server'),
|
||||
toString = require('stream-to-string'),
|
||||
Readable = require('stream').Readable;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// https://developers.google.com/apis-explorer/?hl=en_GB#p/drive/v3/
|
||||
var google = require('googleapis'),
|
||||
googleAuth = require('google-auth-library'),
|
||||
config = require('../../../config'),
|
||||
config = require('../../../config_server'),
|
||||
Stream = require('stream');
|
||||
|
||||
var client = google.drive('v3');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const CACHE_NAME = 'v1.0';
|
||||
const CACHE_NAME = 'v1.1';
|
||||
const DELAY_BEFORE_SENDING_CACHE = 2000;
|
||||
|
||||
/*
|
||||
|
|
@ -9,20 +9,10 @@ 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)){
|
||||
// TODO COOL FEATURE: https://github.com/mickael-kerjean/nuage/issues/11
|
||||
// basically, it's all about cache invalidation sniffing inside event.request
|
||||
if(event.request.method === "GET"){
|
||||
if(navigator.onLine === false){
|
||||
return event.respondWith(smartCacheStrategy(event.request));
|
||||
}else{
|
||||
return event.respondWith(networkFirstStrategy(event.request));
|
||||
}
|
||||
}
|
||||
return event;
|
||||
}else if(is_an_index(event.request)){
|
||||
return event.respondWith(smartCacheStrategy(event.request))
|
||||
}else{
|
||||
//console.log("WTF? ", event);
|
||||
return event;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const crypto = require('crypto'),
|
||||
algorithm = 'aes-256-cbc',
|
||||
password = require('../../config.js')['server_secret'];
|
||||
password = require('../../config_server')['secret_key'];
|
||||
|
||||
module.exports = {
|
||||
encrypt: function(obj){
|
||||
|
|
|
|||
5
test/client/test.js
Normal file
5
test/client/test.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
describe('Helpers::event', () => {
|
||||
it('test', () => {
|
||||
|
||||
});
|
||||
});
|
||||
20
test/helper_crypto.js
Normal file
20
test/helper_crypto.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// import assert from 'assert';
|
||||
// import { encrypt, decrypt } from '../client/helpers/';
|
||||
|
||||
// describe("Helper::crypto", function() {
|
||||
// it("can encrypt", function() {
|
||||
// const key = "SUPER_KEY";
|
||||
// const obj = {a: 3, b:4}
|
||||
// const encrypted_obj = encrypt(obj, key);
|
||||
// assert.ok(encrypted_obj);
|
||||
// assert.notEqual(obj, encrypted_obj);
|
||||
// });
|
||||
// it("can decrypt", function() {
|
||||
// const key = "SUPER_KEY";
|
||||
// const obj = {a: 3, b:4}
|
||||
// const encrypted_obj = encrypt(obj,key);
|
||||
// const decrypted_obj = decrypt(encrypted_obj, key);
|
||||
// assert.equal(JSON.stringify(obj), JSON.stringify(decrypted_obj));
|
||||
// });
|
||||
|
||||
// });
|
||||
44
test/helper_event.js
Normal file
44
test/helper_event.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// import assert from 'assert';
|
||||
// import { event } from '../client/helpers/';
|
||||
|
||||
// describe("Helper::event", function() {
|
||||
// afterEach(function(){
|
||||
// event.fns = [];
|
||||
// });
|
||||
|
||||
// it("can register", function() {
|
||||
// assert.equal(0, event.fns.length);
|
||||
// event.subscribe("event::test", function(){});
|
||||
// assert.equal(1, event.fns.length);
|
||||
// });
|
||||
|
||||
// it("can unregister", function() {
|
||||
// assert.equal(0, event.fns.length);
|
||||
// event.subscribe("event::test", function(){});
|
||||
// assert.equal(1, event.fns.length);
|
||||
// event.unsubscribe("event::test");
|
||||
// assert.equal(0, event.fns.length);
|
||||
// });
|
||||
|
||||
// it("can emit", function(done){
|
||||
// event.subscribe('event::test', function(){
|
||||
// done();
|
||||
// });
|
||||
// event.emit("event::test")
|
||||
// });
|
||||
|
||||
// it("can emit multiple times", function(done){
|
||||
// let count = 0;
|
||||
// event.subscribe('event::test', function(){
|
||||
// count += 1;
|
||||
// if(count === 3){
|
||||
// done();
|
||||
// }else{
|
||||
// assert.equal(true, count < 3);
|
||||
// }
|
||||
// });
|
||||
// event.emit("event::test");
|
||||
// event.emit("event::test");
|
||||
// event.emit("event::test");
|
||||
// });
|
||||
// });
|
||||
24
test/index.html
Normal file
24
test/index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha Tests</title>
|
||||
<link rel="stylesheet" href="../node_modules/mocha/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script>mocha.setup('bdd')</script>
|
||||
|
||||
<!-- Helpers -->
|
||||
<!--<script type="module" src="/client/helpers/events.js"></script>-->
|
||||
|
||||
<script type="module" src="../client/helpers/crypto.js"></script>
|
||||
|
||||
<!-- load your test files here -->
|
||||
|
||||
<script>
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
29
test/karma-init.js
Normal file
29
test/karma-init.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
var tests = [];
|
||||
for (var file in window.__karma__.files) {
|
||||
if (window.__karma__.files.hasOwnProperty(file)) {
|
||||
if (/Spec\.js$/.test(file)) {
|
||||
tests.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requirejs.config({
|
||||
baseUrl: '/base/src',
|
||||
|
||||
paths: {
|
||||
'jquery': '../lib/jquery',
|
||||
'underscore': '../lib/underscore',
|
||||
},
|
||||
|
||||
shim: {
|
||||
'underscore': {
|
||||
exports: '_'
|
||||
}
|
||||
},
|
||||
|
||||
// ask Require.js to load these files (all our tests)
|
||||
deps: tests,
|
||||
|
||||
// start test run, once Require.js is done
|
||||
callback: window.__karma__.start
|
||||
});
|
||||
Loading…
Reference in a new issue