Merge branch 'feature/mobile-login' of https://github.com/ghalestrilo/p5.js-web-editor into feature/switch-mobile-desktop

This commit is contained in:
ghalestrilo 2020-08-21 15:25:43 -03:00
commit 90272b5f21
9 changed files with 221 additions and 82 deletions

View file

@ -1,15 +1,16 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { withTranslation } from 'react-i18next';
class ErrorModal extends React.Component { class ErrorModal extends React.Component {
forceAuthentication() { forceAuthentication() {
return ( return (
<p> <p>
In order to save sketches, you must be logged in. Please&nbsp; {this.props.t('ErrorModal.MessageLogin')}
<Link to="/login" onClick={this.props.closeModal}>Login</Link> <Link to="/login" onClick={this.props.closeModal}> {this.props.t('ErrorModal.Login')}</Link>
&nbsp;or&nbsp; {this.props.t('ErrorModal.LoginOr')}
<Link to="/signup" onClick={this.props.closeModal}>Sign Up</Link>. <Link to="/signup" onClick={this.props.closeModal}>{this.props.t('ErrorModal.SignUp')}</Link>.
</p> </p>
); );
} }
@ -17,8 +18,8 @@ class ErrorModal extends React.Component {
staleSession() { staleSession() {
return ( return (
<p> <p>
It looks like you&apos;ve been logged out. Please&nbsp; {this.props.t('ErrorModal.MessageLoggedOut')}
<Link to="/login" onClick={this.props.closeModal}>log in</Link>. <Link to="/login" onClick={this.props.closeModal}>{this.props.t('ErrorModal.LogIn')}</Link>.
</p> </p>
); );
} }
@ -26,8 +27,7 @@ class ErrorModal extends React.Component {
staleProject() { staleProject() {
return ( return (
<p> <p>
The project you have attempted to save has been saved from another window. {this.props.t('ErrorModal.SavedDifferentWindow')}
Please refresh the page to see the latest version.
</p> </p>
); );
} }
@ -51,7 +51,8 @@ class ErrorModal extends React.Component {
ErrorModal.propTypes = { ErrorModal.propTypes = {
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
closeModal: PropTypes.func.isRequired closeModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
}; };
export default ErrorModal; export default withTranslation()(ErrorModal);

View file

@ -3,6 +3,7 @@ import React from 'react';
import Dropzone from 'dropzone'; import Dropzone from 'dropzone';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import * as UploaderActions from '../actions/uploader'; import * as UploaderActions from '../actions/uploader';
import getConfig from '../../../utils/getConfig'; import getConfig from '../../../utils/getConfig';
import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils'; import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils';
@ -30,7 +31,7 @@ class FileUploader extends React.Component {
thumbnailWidth: 200, thumbnailWidth: 200,
thumbnailHeight: 200, thumbnailHeight: 200,
acceptedFiles: fileExtensionsAndMimeTypes, acceptedFiles: fileExtensionsAndMimeTypes,
dictDefaultMessage: 'Drop files here or click to use the file browser', dictDefaultMessage: this.props.t('FileUploader.DictDefaultMessage'),
accept: this.props.dropzoneAcceptCallback.bind(this, userId), accept: this.props.dropzoneAcceptCallback.bind(this, userId),
sending: this.props.dropzoneSendingCallback, sending: this.props.dropzoneSendingCallback,
complete: this.props.dropzoneCompleteCallback complete: this.props.dropzoneCompleteCallback
@ -59,7 +60,8 @@ FileUploader.propTypes = {
}), }),
user: PropTypes.shape({ user: PropTypes.shape({
id: PropTypes.string id: PropTypes.string
}) }),
t: PropTypes.func.isRequired
}; };
FileUploader.defaultProps = { FileUploader.defaultProps = {
@ -84,4 +86,4 @@ function mapDispatchToProps(dispatch) {
return bindActionCreators(UploaderActions, dispatch); return bindActionCreators(UploaderActions, dispatch);
} }
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader); export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(FileUploader));

View file

@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { withTranslation } from 'react-i18next';
import CopyableInput from './CopyableInput'; import CopyableInput from './CopyableInput';
class ShareModal extends React.PureComponent { class ShareModal extends React.PureComponent {
@ -16,21 +17,21 @@ class ShareModal extends React.PureComponent {
{projectName} {projectName}
</h3> </h3>
<CopyableInput <CopyableInput
label="Embed" label={this.props.t('ShareModal.Embed')}
value={`<iframe src="${hostname}/${ownerUsername}/embed/${projectId}"></iframe>`} value={`<iframe src="${hostname}/${ownerUsername}/embed/${projectId}"></iframe>`}
/> />
<CopyableInput <CopyableInput
label="Present" label={this.props.t('ShareModal.Present')}
hasPreviewLink hasPreviewLink
value={`${hostname}/${ownerUsername}/present/${projectId}`} value={`${hostname}/${ownerUsername}/present/${projectId}`}
/> />
<CopyableInput <CopyableInput
label="Fullscreen" label={this.props.t('ShareModal.Fullscreen')}
hasPreviewLink hasPreviewLink
value={`${hostname}/${ownerUsername}/full/${projectId}`} value={`${hostname}/${ownerUsername}/full/${projectId}`}
/> />
<CopyableInput <CopyableInput
label="Edit" label={this.props.t('ShareModal.Edit')}
hasPreviewLink hasPreviewLink
value={`${hostname}/${ownerUsername}/sketches/${projectId}`} value={`${hostname}/${ownerUsername}/sketches/${projectId}`}
/> />
@ -42,7 +43,8 @@ class ShareModal extends React.PureComponent {
ShareModal.propTypes = { ShareModal.propTypes = {
projectId: PropTypes.string.isRequired, projectId: PropTypes.string.isRequired,
ownerUsername: PropTypes.string.isRequired, ownerUsername: PropTypes.string.isRequired,
projectName: PropTypes.string.isRequired projectName: PropTypes.string.isRequired,
t: PropTypes.func.isRequired
}; };
export default ShareModal; export default withTranslation()(ShareModal);

View file

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { withTranslation } from 'react-i18next';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
import getConfig from '../../../utils/getConfig'; import getConfig from '../../../utils/getConfig';
import FileUploader from './FileUploader'; import FileUploader from './FileUploader';
@ -14,7 +15,8 @@ const limitText = prettyBytes(limit);
class UploadFileModal extends React.Component { class UploadFileModal extends React.Component {
propTypes = { propTypes = {
reachedTotalSizeLimit: PropTypes.bool.isRequired, reachedTotalSizeLimit: PropTypes.bool.isRequired,
closeModal: PropTypes.func.isRequired closeModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
} }
componentDidMount() { componentDidMount() {
@ -31,22 +33,18 @@ class UploadFileModal extends React.Component {
<section className="modal" ref={(element) => { this.modal = element; }}> <section className="modal" ref={(element) => { this.modal = element; }}>
<div className="modal-content"> <div className="modal-content">
<div className="modal__header"> <div className="modal__header">
<h2 className="modal__title">Upload File</h2> <h2 className="modal__title">{this.props.t('UploadFileModal.Title')}</h2>
<button <button
className="modal__exit-button" className="modal__exit-button"
onClick={this.props.closeModal} onClick={this.props.closeModal}
aria-label="Close upload file modal" aria-label={this.props.t('UploadFileModal.CloseButtonARIA')}
> >
<ExitIcon focusable="false" aria-hidden="true" /> <ExitIcon focusable="false" aria-hidden="true" />
</button> </button>
</div> </div>
{ this.props.reachedTotalSizeLimit && { this.props.reachedTotalSizeLimit &&
<p> <p>
{ {this.props.t('UploadFileModal.SizeLimitError', { sizeLimit: limitText })}
`Error: You cannot upload any more files. You have reached the total size limit of ${limitText}.
If you would like to upload more, please remove the ones you aren't using anymore by
in your `
}
<Link to="/assets" onClick={this.props.closeModal}>assets</Link> <Link to="/assets" onClick={this.props.closeModal}>assets</Link>
. .
</p> </p>
@ -68,4 +66,4 @@ function mapStateToProps(state) {
}; };
} }
export default connect(mapStateToProps)(UploadFileModal); export default withTranslation()(connect(mapStateToProps)(UploadFileModal));

View file

@ -0,0 +1,59 @@
import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { remSize } from '../../../theme';
const ResponsiveFormWrapper = styled.div`
.form-container__content {
width: unset !important;
padding-top: ${remSize(16)};
padding-bottom: ${remSize(64)};
}
.form__input {
min-width: unset;
padding: 0px ${remSize(12)};
height: ${remSize(28)};
}
.form-container__title { margin-bottom: ${remSize(14)}}
p.form__field { margin-top: 0px !important; }
label.form__label { margin-top: ${remSize(8)} !important; }
.form-error { width: 100% }
.nav__items-right:last-child { display: none }
.form-container {
height: 100%
}
.form-container__stack {
svg {
width: ${remSize(12)};
height: ${remSize(12)}
}
a { padding: 0px }
}
`;
const ResponsiveForm = props =>
(props.mobile === true
? (
<ResponsiveFormWrapper>
{props.children}
</ResponsiveFormWrapper>
)
: props.children);
ResponsiveForm.propTypes = {
mobile: PropTypes.bool,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.array]),
};
ResponsiveForm.defaultProps = {
mobile: false,
children: []
};
export default ResponsiveForm;

View file

@ -10,6 +10,7 @@ import LoginForm from '../components/LoginForm';
import { validateLogin } from '../../../utils/reduxFormUtils'; import { validateLogin } from '../../../utils/reduxFormUtils';
import SocialAuthButton from '../components/SocialAuthButton'; import SocialAuthButton from '../components/SocialAuthButton';
import Nav from '../../../components/Nav'; import Nav from '../../../components/Nav';
import ResponsiveForm from '../components/ResponsiveForm';
class LoginView extends React.Component { class LoginView extends React.Component {
constructor(props) { constructor(props) {
@ -27,36 +28,40 @@ class LoginView extends React.Component {
} }
render() { render() {
const isMobile = () => (window.innerWidth < 770);
if (this.props.user.authenticated) { if (this.props.user.authenticated) {
this.gotoHomePage(); this.gotoHomePage();
return null; return null;
} }
// TODO: mobile currently forced to true
return ( return (
<div className="login"> <ResponsiveForm mobile={isMobile() || this.props.mobile}>
<Nav layout="dashboard" /> <div className="login">
<main className="form-container"> <Nav layout="dashboard" />
<Helmet> <main className="form-container">
<title>{this.props.t('LoginView.Title')}</title> <Helmet>
</Helmet> <title>{this.props.t('LoginView.Title')}</title>
<div className="form-container__content"> </Helmet>
<h2 className="form-container__title">{this.props.t('LoginView.Login')}</h2> <div className="form-container__content">
<LoginForm {...this.props} /> <h2 className="form-container__title">{this.props.t('LoginView.Login')}</h2>
<h2 className="form-container__divider">{this.props.t('LoginView.LoginOr')}</h2> <LoginForm {...this.props} />
<div className="form-container__stack"> <h2 className="form-container__divider">{this.props.t('LoginView.LoginOr')}</h2>
<SocialAuthButton service={SocialAuthButton.services.github} /> <div className="form-container__stack">
<SocialAuthButton service={SocialAuthButton.services.google} /> <SocialAuthButton service={SocialAuthButton.services.github} />
<SocialAuthButton service={SocialAuthButton.services.google} />
</div>
<p className="form__navigation-options">
{this.props.t('LoginView.DontHaveAccount')}
<Link className="form__signup-button" to="/signup">{this.props.t('LoginView.SignUp')}</Link>
</p>
<p className="form__navigation-options">
{this.props.t('LoginView.ForgotPassword')}
<Link className="form__reset-password-button" to="/reset-password"> {this.props.t('LoginView.ResetPassword')}</Link>
</p>
</div> </div>
<p className="form__navigation-options"> </main>
{this.props.t('LoginView.DontHaveAccount')} </div>
<Link className="form__signup-button" to="/signup">{this.props.t('LoginView.SignUp')}</Link> </ResponsiveForm>
</p>
<p className="form__navigation-options">
{this.props.t('LoginView.ForgotPassword')}
<Link className="form__reset-password-button" to="/reset-password"> {this.props.t('LoginView.ResetPassword')}</Link>
</p>
</div>
</main>
</div>
); );
} }
} }
@ -79,13 +84,15 @@ LoginView.propTypes = {
user: PropTypes.shape({ user: PropTypes.shape({
authenticated: PropTypes.bool authenticated: PropTypes.bool
}), }),
t: PropTypes.func.isRequired t: PropTypes.func.isRequired,
mobile: PropTypes.bool
}; };
LoginView.defaultProps = { LoginView.defaultProps = {
user: { user: {
authenticated: false authenticated: false
} },
mobile: false
}; };
export default withTranslation()(reduxForm({ export default withTranslation()(reduxForm({

View file

@ -11,6 +11,9 @@ import apiClient from '../../../utils/apiClient';
import { validateSignup } from '../../../utils/reduxFormUtils'; import { validateSignup } from '../../../utils/reduxFormUtils';
import SocialAuthButton from '../components/SocialAuthButton'; import SocialAuthButton from '../components/SocialAuthButton';
import Nav from '../../../components/Nav'; import Nav from '../../../components/Nav';
import ResponsiveForm from '../components/ResponsiveForm';
const isMobile = () => (window.innerWidth < 770);
class SignupView extends React.Component { class SignupView extends React.Component {
gotoHomePage = () => { gotoHomePage = () => {
@ -23,27 +26,29 @@ class SignupView extends React.Component {
return null; return null;
} }
return ( return (
<div className="signup"> <ResponsiveForm mobile={isMobile() || this.props.mobile}>
<Nav layout="dashboard" /> <div className="signup">
<main className="form-container"> <Nav layout="dashboard" />
<Helmet> <main className="form-container">
<title>{this.props.t('SignupView.Title')}</title> <Helmet>
</Helmet> <title>{this.props.t('SignupView.Title')}</title>
<div className="form-container__content"> </Helmet>
<h2 className="form-container__title">{this.props.t('SignupView.Description')}</h2> <div className="form-container__content">
<SignupForm {...this.props} /> <h2 className="form-container__title">{this.props.t('SignupView.Description')}</h2>
<h2 className="form-container__divider">{this.props.t('SignupView.Or')}</h2> <SignupForm {...this.props} />
<div className="form-container__stack"> <h2 className="form-container__divider">{this.props.t('SignupView.Or')}</h2>
<SocialAuthButton service={SocialAuthButton.services.github} /> <div className="form-container__stack">
<SocialAuthButton service={SocialAuthButton.services.google} /> <SocialAuthButton service={SocialAuthButton.services.github} />
<SocialAuthButton service={SocialAuthButton.services.google} />
</div>
<p className="form__navigation-options">
{this.props.t('SignupView.AlreadyHave')}
<Link className="form__login-button" to="/login">{this.props.t('SignupView.Login')}</Link>
</p>
</div> </div>
<p className="form__navigation-options"> </main>
{this.props.t('SignupView.AlreadyHave')} </div>
<Link className="form__login-button" to="/login">{this.props.t('SignupView.Login')}</Link> </ResponsiveForm>
</p>
</div>
</main>
</div>
); );
} }
} }
@ -110,13 +115,15 @@ SignupView.propTypes = {
user: PropTypes.shape({ user: PropTypes.shape({
authenticated: PropTypes.bool authenticated: PropTypes.bool
}), }),
t: PropTypes.func.isRequired t: PropTypes.func.isRequired,
mobile: PropTypes.bool
}; };
SignupView.defaultProps = { SignupView.defaultProps = {
user: { user: {
authenticated: false authenticated: false
} },
mobile: false
}; };
export default withTranslation()(reduxForm({ export default withTranslation()(reduxForm({

View file

@ -326,7 +326,6 @@
"AlreadyHave": "Already have an account?", "AlreadyHave": "Already have an account?",
"Login": "Log In" "Login": "Log In"
}, },
"EmailVerificationView": { "EmailVerificationView": {
"Title": "p5.js Web Editor | Email Verification", "Title": "p5.js Web Editor | Email Verification",
"Verify": "Verify your email", "Verify": "Verify your email",
@ -334,5 +333,28 @@
"Checking": "Validating token, please wait...", "Checking": "Validating token, please wait...",
"Verified": "All done, your email address has been verified.", "Verified": "All done, your email address has been verified.",
"InvalidState": "Something went wrong." "InvalidState": "Something went wrong."
},
"UploadFileModal": {
"Title": "Upload File",
"CloseButtonARIA": "Close upload file modal",
"SizeLimitError": "Error: You cannot upload any more files. You have reached the total size limit of {{sizeLimit}}.\n If you would like to upload more, please remove the ones you aren't using anymore by\n in your "
},
"FileUploader": {
"DictDefaultMessage": "Drop files here or click to use the file browser"
},
"ErrorModal": {
"MessageLogin": "In order to save sketches, you must be logged in. Please ",
"Login": "Login",
"LoginOr": " or ",
"SignUp": "Sign Up",
"MessageLoggedOut": "It looks like you've been logged out. Please ",
"LogIn": "log in",
"SavedDifferentWindow": "The project you have attempted to save has been saved from another window.\n Please refresh the page to see the latest version."
},
"ShareModal": {
"Embed": "Embed",
"Present": "Present",
"Fullscreen": "Fullscreen",
"Edit": "Edit"
} }
} }

View file

@ -174,15 +174,33 @@
} }
}, },
"Sidebar": { "Sidebar": {
"Create": "Crear", "Title": "Archivos de Bosquejo",
"EnterName": "Introduce un nombre", "ToggleARIA": "Alternar abrir/cerrar opciones de archivo de bosquejo",
"Add": "Agregar", "AddFolder": "Crear directorio",
"Folder": "Directorio" "AddFolderARIA": "Agregar directorio",
"AddFile": "Crear archivo",
"AddFileARIA": "agregar archivo",
"UploadFile": "Subir archivo",
"UploadFileARIA": "Subir archivo"
},
"FileNode": {
"OpenFolderARIA": "Abrir contenidos del directorio",
"CloseFolderARIA": "Cerrar contenidos del directorio",
"ToggleFileOptionsARIA": "Alternar abrir/cerrar opciones de archivo",
"AddFolder": "Crear directorio",
"AddFolderARIA": "Agregar directorio",
"AddFile": "Crear archivo",
"AddFileARIA": "agregar archivo",
"UploadFile": "Subir archivo",
"UploadFileARIA": "Subir archivo",
"Rename": "Renombrar",
"Delete": "Borrar"
}, },
"Common": { "Common": {
"Error": "Error", "Error": "Error",
"Save": "Guardar", "Save": "Guardar",
"p5logoARIA": "Logo p5.js " "p5logoARIA": "Logo p5.js ",
"DeleteConfirmation": "¿Estás que quieres borrar {{name}}?"
}, },
"IDEView": { "IDEView": {
"SubmitFeedback": "Enviar retroalimentación" "SubmitFeedback": "Enviar retroalimentación"
@ -315,5 +333,28 @@
"Checking": "Validando token, por favor espera...", "Checking": "Validando token, por favor espera...",
"Verified": "Concluido, tu correo electrónico ha sido verificado.", "Verified": "Concluido, tu correo electrónico ha sido verificado.",
"InvalidState": "Algo salió mal." "InvalidState": "Algo salió mal."
},
"UploadFileModal": {
"Title": "Subir Archivo",
"CloseButtonARIA": "Cerrar diálogo para subir archivo",
"SizeLimitError": "Error: No puedes subir archivos. Has alcanzado el limite de tamaño total de {{sizeLimit}}.\n Si quieres agregar más,por favor remueve alugnos que no estes usando en tus "
},
"FileUploader": {
"DictDefaultMessage": "Deposita los archivos aquí o haz click para usar el navegador de archivos"
},
"ErrorModal": {
"MessageLogin": "Para poder guardar bosquejos, debes ingresar a tu cuenta. Por favor ",
"Login": "Ingresa",
"LoginOr": " o ",
"SignUp": "Registráte",
"MessageLoggedOut": "Parece que has salido de tu cuenta. Por favor ",
"LogIn": "ingresa",
"SavedDifferentWindow": " El proyecto que has intentado guardar ha sido guardado desde otra ventana.\n Por favor refresca la página para ver la versión más actual."
},
"ShareModal": {
"Embed": "Incrustar",
"Present": "Presentar",
"Fullscreen": "Pantalla Completa",
"Edit": "Editar"
} }
} }