diff --git a/.gitignore b/.gitignore
index 14cd44ed..4afc76b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
-server/public/js
-server/public/index.html
node_modules/
babel_cache/
+dist/
.DS_Store
\#*\#
.\#*
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f7ee9f0f..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-language: node_js
-node_js:
- - "7"
-
-services:
- - docker
-
-before_install:
- - echo $DOCKER_PASSWORD | docker login -u=$DOCKER_USERNAME --password-stdin
-
-script:
- - npm run image
- - npm run publish
-
-branches:
- only:
- - master
diff --git a/client/components/input.scss b/client/components/input.scss
index de8630c5..1ee3121d 100644
--- a/client/components/input.scss
+++ b/client/components/input.scss
@@ -4,7 +4,9 @@
border-radius: 0;
width: 100%;
display: inline-block;
- font-size: inherit;
+ font-family: "San Francisco","Roboto","Arial",sans-serif;
+ -webkit-text-size-adjust: 100%;
+ font-size: 16px;
padding: 5px 0px 5px 0px;
margin: 0 0 8px 0;
outline: none;
diff --git a/client/components/textarea.js b/client/components/textarea.js
index 8040df82..c8e7999c 100644
--- a/client/components/textarea.js
+++ b/client/components/textarea.js
@@ -9,11 +9,41 @@ export class Textarea extends React.Component {
}
render() {
+ let className = "component_textarea";
+ if(this.refs.el && this.refs.el.value.length > 0){
+ className += " hasText";
+ }
+
+ const disabledEnter = (e) => {
+ if(e.key === "Enter" && e.shiftKey === false){
+ e.preventDefault();
+ const $form = getForm(this.refs.el);
+ if($form){
+ $form.dispatchEvent(new Event('submit'));
+ }
+ }
+
+ function getForm($el){
+ if(!$el.parentElement) return $el;
+ if($el.parentElement.nodeName == "FORM"){
+ return $el.parentElement;
+ }
+ return getForm($el.parentElement);
+ }
+ };
+ const inputProps = (p) => {
+ return Object.keys(p).reduce((acc, key) => {
+ if(key === "disabledEnter") return acc;
+ acc[key] = p[key];
+ return acc;
+ }, {});
+ };
return (
);
}
@@ -21,5 +51,6 @@ export class Textarea extends React.Component {
Textarea.propTypes = {
type: PropTypes.string,
- placeholder: PropTypes.string
+ placeholder: PropTypes.string,
+ disabledEnter: PropTypes.bool
};
diff --git a/client/components/textarea.scss b/client/components/textarea.scss
index 15d59a22..af6ad58e 100644
--- a/client/components/textarea.scss
+++ b/client/components/textarea.scss
@@ -1,11 +1,17 @@
+@font-face {
+ font-family: password;
+ src: url('textarea.woff');
+}
+
.component_textarea{
background: inherit;
border: none;
border-radius: 0;
width: 100%;
display: inline-block;
- font-size: inherit;
- font-family: inherit;
+ font-family: "San Francisco","Roboto","Arial",sans-serif;
+ -webkit-text-size-adjust: 100%;
+ font-size: 16px;
padding: 5px 0px 5px 0px;
margin: 0 0 8px 0;
outline: none;
@@ -14,9 +20,13 @@
vertical-align: top;
min-width: 100%;
max-width: 100%;
+ max-height: 31px; /* Firefox was doing some weird things */
&[name="password"]{
- -webkit-text-security: disc;
+ &.hasText{
+ font-family: 'password';
+ }
+ -webkit-text-security:disc!important;
}
border-bottom: 2px solid rgba(70, 99, 114, 0.1);
diff --git a/client/helpers/settings.js b/client/helpers/settings.js
index e6373fea..efe0eee1 100644
--- a/client/helpers/settings.js
+++ b/client/helpers/settings.js
@@ -1,6 +1,9 @@
let settings = JSON.parse(window.localStorage.getItem("settings")) || {};
export function settings_get(key){
+ if(settings[key] === undefined){
+ return null;
+ }
return settings[key];
}
diff --git a/client/pages/connectpage.js b/client/pages/connectpage.js
index f89fe5cf..f2877bed 100644
--- a/client/pages/connectpage.js
+++ b/client/pages/connectpage.js
@@ -31,13 +31,11 @@ export class ConnectPage extends React.Component {
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
- // dropbox login
- if(getParam('state') === 'dropbox'){
+ const state = getParam('state');
+ if(state === "dropbox"){
this.setState({doing_a_third_party_login: true});
this.authenticate({bearer: getParam('access_token'), type: 'dropbox'});
- }
- // google drive login
- if(getParam('code')){
+ }else if(state === "googledrive"){
this.setState({doing_a_third_party_login: true});
this.authenticate({code: getParam('code'), type: 'gdrive'});
}
@@ -93,7 +91,7 @@ export class ConnectPage extends React.Component {
render() {
return (
-
+
diff --git a/client/pages/connectpage/credentials.js b/client/pages/connectpage/credentials.js
index 1b90f765..57baa32e 100644
--- a/client/pages/connectpage/credentials.js
+++ b/client/pages/connectpage/credentials.js
@@ -11,6 +11,7 @@ export class Credentials extends React.Component {
const key = memory.get('credentials_key') || ''; // we use a clojure for the "key" because we
// want to persist it in memory, not just in the
// state which is kill whenever react decide
+
this.state = {
key: key || '',
message: '',
diff --git a/client/pages/connectpage/form.js b/client/pages/connectpage/form.js
index 658290d7..cb28ab37 100644
--- a/client/pages/connectpage/form.js
+++ b/client/pages/connectpage/form.js
@@ -1,121 +1,73 @@
-import React from 'react';
-
-import { Container, Card, NgIf, Input, Button, Textarea, Loader, Notification, Prompt } from '../../components/';
-import { invalidate, encrypt, decrypt, gid } from '../../helpers/';
-import { Session } from '../../model/';
-import config from '../../../config_client';
-import './form.scss';
-import img_drive from '../../assets/img/google-drive.png';
-import img_dropbox from '../../assets/img/dropbox.png';
+import React from "react";
+import { Container, Card, NgIf, Input, Button, Textarea, Loader, Notification, Prompt } from "../../components/";
+import { invalidate, encrypt, decrypt, gid, settings_get, settings_put } from "../../helpers/";
+import { Session } from "../../model/";
+import "./form.scss";
+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){
super(props);
- const protocols = Object.keys(config.connections);
this.state = {
- refs: {},
- type: protocols.length > 2 ? protocols[1] : protocols[0] || null,
- advanced_ftp: false,
- advanced_sftp: false,
- advanced_webdav: false,
- advanced_s3: false,
- advanced_git: false,
- _dummy: true
+ select: CONFIG["connections"].length > 2 ? 2 : 0,
+ backends: JSON.parse(JSON.stringify(CONFIG["connections"])),
+ dummy: null
};
+
+ const select = settings_get("login_tab");
+ if(select !== null){ this.state.select = select; }
this.rerender = this.rerender.bind(this);
}
componentDidMount(){
- this.publishState(config.connections);
- this.publishState(this.props.credentials);
- window.addEventListener('resize', this.rerender);
+ window.addEventListener("resize", this.rerender);
+ this.publishState(this.props);
+ }
+
+ componentWillUnmount(){
+ settings_put("login_tab", this.state.select);
+ window.removeEventListener("resize", this.rerender);
}
componentWillReceiveProps(props){
if(JSON.stringify(props.credentials) !== JSON.stringify(this.props.credentials)){
- this.publishState(props.credentials);
+ this.publishState(props);
}
}
- componentWillUnmount(){
- window.removeEventListener('resize', this.rerender);
- }
-
- publishState(_credentials){
- const pushDOM = (credentials) => {
- for(let key in credentials){
- let names = credentials[key];
- for(let name in names){
- const ref_name = [key,name].join("_");
- if(this.state.refs[ref_name] && typeof credentials[key][name] !== 'boolean'){
- this.state.refs[ref_name].ref.value = credentials[key][name];
- }
+ publishState(props){
+ for(let key in props.credentials){
+ this.state.backends = this.state.backends.map((backend) => {
+ if(backend["type"] + "_" + backend["label"] === key){
+ backend = props.credentials[key];
}
- }
- };
-
- const setAdvancedCheckbox = (credentials) => {
- if(credentials['ftp'] && (credentials['ftp']['path'] || credentials['ftp']['port']) ){
- this.setState({advanced_ftp: true});
- }
- if(credentials['sftp'] && (
- credentials['sftp']['path'] || credentials['sftp']['port']
- || credentials['sftp']['private_key'])
- ){
- this.setState({advanced_sftp: true});
- }
- if(credentials['webdav'] && credentials['webdav']['path']){
- this.setState({advanced_webdav: true});
- }
- if(credentials['s3'] && (credentials['s3']['path'] || credentials['s3']['endpoint'])){
- this.setState({advanced_s3: true});
- }
- if(credentials['git'] && (
- credentials['git']['username'] || credentials['git']['commit']
- || credentials['git']['branch'] || credentials['git']['passphrase']
- || credentials['git']['author_name'] || credentials['git']['author_email']
- || credentials['git']['committer_name'] || credentials['git']['committer_email'])
- ){
- this.setState({advanced_git: true});
- }
- };
-
- setAdvancedCheckbox(_credentials);
- window.setTimeout(() => pushDOM(_credentials));
- // we made this async as DOM needs to be all set before we can insert the new state
+ return backend;
+ });
+ }
+ this.setState({backends: this.state.backends});
}
-
onSubmit(e){
e.preventDefault();
- // update the credentials object with data coming from the dom (aka "ref" in react language)
- let credentials = Object.assign({}, this.props.credentials);
- for(let key in this.state.refs){
- if(this.state.refs[key]){
- let [type, ...name] = key.split('_');
- name = name.join("_");
- if(!credentials[type]) credentials[type] = {};
- credentials[type][name] = this.state.refs[key].ref.value;
- }
- }
- // create the object we need to authenticate a user against a backend
- const auth_data = Object.assign({type: this.state.type}, credentials[this.state.type]);
- this.props.onSubmit(auth_data, credentials);
+ const authData = this.state.backends[this.state.select],
+ key = authData["type"]+"_"+authData["label"];
+
+ this.props.credentials[key] = authData;
+ this.props.onSubmit(authData, this.props.credentials);
}
- redirect_dropbox(e){
- this.props.onThirdPartyLogin('dropbox');
+ onFormUpdate(n, values){
+ this.state.backends[n] = values;
+ this.setState({backends: this.state.backends});
}
- redirect_google(e){
- this.props.onThirdPartyLogin('google');
+ redirect(service){
+ this.props.onThirdPartyLogin(service);
}
- onTypeChange(type){
- this.setState({type: type}, () => {
- this.publishState(config.connections);
- this.publishState(this.props.credentials);
- });
+ onTypeChange(n){
+ this.setState({select: n});
}
rerender(){
@@ -124,7 +76,7 @@ export class Form extends React.Component {
_marginTop(){
let size = 300;
- const $screen = document.querySelector('.login-form');
+ const $screen = document.querySelector(".login-form");
if($screen) size = $screen.offsetHeight;
size = Math.round((document.body.offsetHeight - size) / 2);
@@ -133,200 +85,380 @@ export class Form extends React.Component {
return size;
}
- should_appear(type, key){
- if(!config.connections[type]) return false;
- let value = config.connections[type][key];
- if(typeof value === 'string') return true;
- if(value === false) return false;
- return true;
- }
- input_type(type, key){
- if(!config.connections[type]) return 'hidden';
- let value = config.connections[type][key];
- if(typeof value === 'string') return 'hidden';
- else if(typeof value === 'number') return 'hidden';
- else if(value === false) return 'hidden';
- else if(key === 'password') return 'password';
- else if(key === 'secret_access_key') return 'password';
- else{
- return 'text';
- }
- }
-
render() {
- let className = (window.innerWidth < 600) ? 'scroll-x' : '';
+ let className = (window.innerWidth < 600) ? "scroll-x" : "";
return (
-
- 1 }>
+
+ 1 }>
{
- Object.keys(config.connections).map((type) => {
+ this.state.backends.map((backend, i) => {
return (
-
-
+
+
);
}
}
+
+const WebDavForm = formHelper(function(props){
+ const onAdvanced = (value) => {
+ if(value == true){
+ props.values.path = "";
+ }else{
+ delete props.values.path;
+ }
+ props.onChange(props.values);
+ };
+ const is_advanced = props.advanced(props.values.path);
+
+ return (
+
+
+ props.onChange("url", e.target.value)} type={props.input_type("url")} name="url" placeholder="Address*" autoComplete="new-password" />
+
+
+ props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
+
+
+ props.onChange("password", e.target.value)} type={props.input_type("password")} name="password" placeholder="Password" autoComplete="new-password" />
+
+
+
+
+
+
+ props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
+
+
+ CONNECT
+
+ );
+});
+
+const FtpForm = formHelper(function(props){
+ const onAdvanced = (value) => {
+ if(value == true){
+ props.values.path = "";
+ props.values.port = "";
+ }else{
+ delete props.values.path;
+ delete props.values.port;
+ }
+ props.onChange(props.values);
+ };
+ const is_advanced = props.advanced(
+ props.values.path,
+ props.values.port
+ );
+
+ return (
+
+
+ props.onChange("hostname", e.target.value)} type={props.input_type("hostname")} name="hostname" placeholder="Hostname*" autoComplete="new-password" />
+
+
+ props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
+
+
+ props.onChange("password", e.target.value)} type={props.input_type("password")} name="password" placeholder="Password" autoComplete="new-password" />
+
+
+
+
+
+
+ props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
+
+
+ props.onChange("port", e.target.value)} type={props.input_type("port")} name="port" placeholder="Port" autoComplete="new-password" />
+
+
+ CONNECT
+
+ );
+});
+
+const SftpForm = formHelper(function(props){
+ const onAdvanced = (value) => {
+ if(value == true){
+ props.values.path = "";
+ props.values.port = "";
+ props.values.passphrase = "";
+ }else{
+ delete props.values.path;
+ delete props.values.port;
+ delete props.values.passphrase;
+ }
+ props.onChange();
+ };
+ const is_advanced = props.advanced(
+ props.values.path,
+ props.values.port,
+ props.values.passphrase
+ );
+
+ return (
+
+
+ props.onChange("hostname", e.target.value)} value={props.values.hostname || ""} onChange={(e) => props.onChange("hostname", e.target.value)} type={props.input_type("hostname")} name="host" placeholder="Hostname*" autoComplete="new-password" />
+
+
+ props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
+
+
+
+
+
+
+
+
+ props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
+
+
+ props.onChange("port", e.target.value)} type={props.input_type("port")} name="port" placeholder="Port" autoComplete="new-password" />
+
+
+ props.onChange("passphrase", e.target.value)} type={props.input_type("passphrase")} name="port" placeholder="Passphrase" autoComplete="new-password" />
+
+
+ CONNECT
+
+ );
+});
+
+
+const GitForm = formHelper(function(props){
+ const onAdvanced = (value) => {
+ if(value == true){
+ props.values.path = "";
+ props.values.passphrase = "";
+ props.values.commit = "";
+ props.values.branch = "";
+ props.values.author_email = "";
+ props.values.author_name = "";
+ props.values.committer_email = "";
+ props.values.committer_name = "";
+ }else{
+ delete props.values.path;
+ delete props.values.passphrase;
+ delete props.values.commit;
+ delete props.values.branch;
+ delete props.values.author_email;
+ delete props.values.author_name;
+ delete props.values.committer_email;
+ delete props.values.committer_name;
+ }
+ props.onChange();
+ };
+ const is_advanced = props.advanced(
+ props.values.path,
+ props.values.passphrase,
+ props.values.commit,
+ props.values.branch,
+ props.values.author_email,
+ props.values.author_name,
+ props.values.committer_email,
+ props.values.committer_name
+ );
+
+ return (
+
+
+ props.onChange("repo", e.target.value)} type={props.input_type("repo")} name="repo" placeholder="Repository*" autoComplete="new-password" />
+
+
+ props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
+
+
+
+
+
+
+
+
+
+ props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
+
+
+ props.onChange("passphrase", e.target.value)} type={props.input_type("passphrase")} name="passphrase" placeholder="Passphrase" autoComplete="new-password" />
+
+
+ props.onChange("commit", e.target.value)} type={props.input_type("commit")} name="commit" placeholder='Commit Format: default to \"{action}({filename}): {path}\"' autoComplete="new-password" />
+
+
+ props.onChange("branch", e.target.value)} type={props.input_type("branch")} name="branch" placeholder='Branch: default to "master"' autoComplete="new-password" />
+
+
+ props.onChange("author_email", e.target.value)} type={props.input_type("author_email")} name="author_email" placeholder="Author email" autoComplete="new-password" />
+
+
+ props.onChange("author_name", e.target.value)} type={props.input_type("author_name")} name="author_name" placeholder="Author name" autoComplete="new-password" />
+
+
+ props.onChange("committer_email", e.target.value)} type={props.input_type("committer_email")} name="committer_email" placeholder="Committer email" autoComplete="new-password" />
+
+
+ props.onChange("committer_name", e.target.value)} type={props.input_type("committer_name")} name="committer_name" placeholder="Committer name" autoComplete="new-password" />
+
+
+ CONNECT
+
+ );
+});
+
+const S3Form = formHelper(function(props){
+ const onAdvanced = (value) => {
+ if(value == true){
+ props.values.path = "";
+ props.values.endpoint = "";
+ }else{
+ delete props.values.path;
+ delete props.values.endpoint;
+ }
+ props.onChange();
+ };
+ const is_advanced = props.advanced(
+ props.values.path,
+ props.values.endpoint
+ );
+
+ return (
+
+
+ props.onChange("access_key_id", e.target.value)} value={props.values.access_key_id || ""} onChange={(e) => props.onChange("access_key_id", e.target.value)} type={props.input_type("access_key_id")} name="access_key_id" placeholder="Access Key ID*" autoComplete="new-password" />
+
+
+ props.onChange("secret_access_key", e.target.value)} type={props.input_type("secret_access_key")} name="secret_access_key" placeholder="Secret Access Key*" autoComplete="new-password" />
+
+
+
+
+
+
+ props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
+
+
+ props.onChange("endpoint", e.target.value)} type={props.input_type("endpoint")} name="endpoint" placeholder="Endpoint" autoComplete="new-password" />
+
+
+ CONNECT
+
+ );
+});
+
+const DropboxForm = formHelper(function(props){
+ const redirect = () => {
+ return props.onThirdPartyLogin("dropbox");
+ };
+ return (
+
+
+

+
+
LOGIN WITH DROPBOX
+
+ );
+});
+
+const GDriveForm = formHelper(function(props){
+ const redirect = () => {
+ return props.onThirdPartyLogin("google");
+ };
+ return (
+
+
+

+
+
LOGIN WITH GOOGLE
+
+ );
+});
+
+function formHelper(WrappedComponent){
+ return (props) => {
+ const helpers = {
+ should_appear: function(key){
+ const val = props.config[key];
+ if(val === false) return false;
+ else if(val === null) return false;
+ else if(val === undefined) return true;
+ return false;
+ },
+ input_type: function(key){
+ if(["password", "passphrase", "secret_access_key"].indexOf(key) !== -1){
+ return "password";
+ }
+ return "text";
+ },
+ onChange: function(key, value){
+ let values = props.values;
+ if(typeof key === "string") values[key] = value;
+ props.onChange(values);
+ },
+ advanced: function(){
+ let res = false;
+ for (let i=0; i < arguments.length; i++){
+ if(arguments[i] !== undefined) {
+ return true;
+ }
+ }
+ return res;
+ }
+ };
+ return (
+
+ );
+ };
+}