From af1fa944bf4165ba4a1abdba77d0aac9e18991e6 Mon Sep 17 00:00:00 2001 From: Mickael Kerjean Date: Wed, 18 Jul 2018 01:37:06 +1000 Subject: [PATCH] page (login): refactore the login page to enable multiple time the same backend --- .gitignore | 3 +- .travis.yml | 17 - client/components/input.scss | 4 +- client/components/textarea.js | 39 +- client/components/textarea.scss | 16 +- client/helpers/settings.js | 3 + client/pages/connectpage.js | 10 +- client/pages/connectpage/credentials.js | 1 + client/pages/connectpage/form.js | 674 ++++++++++++++---------- 9 files changed, 463 insertions(+), 304 deletions(-) delete mode 100644 .travis.yml 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 ( - ); }) } -
-
+
+
- - - { this.state.refs.webdav_url = input; }} autoComplete="new-password" /> - - - { this.state.refs.webdav_username = input; }} autoComplete="new-password" /> - - - { this.state.refs.webdav_password = input; }} autoComplete="new-password" /> - - - - - - - {this.state.refs.webdav_path = input; }} autoComplete="new-password" /> - - - - - - - {this.state.refs.ftp_hostname = input; }} autoComplete="new-password" /> - - - {this.state.refs.ftp_username = input; }} autoComplete="new-password" /> - - - {this.state.refs.ftp_password = input; }} autoComplete="new-password" /> - - - - - - - {this.state.refs.ftp_path = input; }} autoComplete="new-password" /> - - - {this.state.refs.ftp_port = input; }} autoComplete="new-password" /> - - - - - - - {this.state.refs.sftp_host = input; }} autoComplete="new-password" /> - - - {this.state.refs.sftp_username = input; }} autoComplete="new-password" /> - - - {this.state.refs.sftp_password = input; }} autoComplete="new-password" /> - - - - - - - {this.state.refs.sftp_path = input; }} autoComplete="new-password" /> - - - {this.state.refs.sftp_port = input; }} autoComplete="new-password" /> - - -