🚨 ignore proptype errors

This commit is contained in:
ghalestrilo 2020-07-24 18:11:10 -03:00
parent 7dbcde452b
commit de0e32f6a3
14 changed files with 454 additions and 259 deletions

View File

@ -5,13 +5,20 @@ import EditIcon from '../../../images/pencil.svg';
// TODO I think this needs a description prop so that it's accessible // TODO I think this needs a description prop so that it's accessible
function EditableInput({ function EditableInput({
validate, value, emptyPlaceholder, InputComponent, inputProps, onChange validate,
value,
emptyPlaceholder,
InputComponent,
inputProps,
onChange,
}) { }) {
const [isEditing, setIsEditing] = React.useState(false); const [isEditing, setIsEditing] = React.useState(false);
const [currentValue, setCurrentValue] = React.useState(value || ''); const [currentValue, setCurrentValue] = React.useState(value || '');
const displayValue = currentValue || emptyPlaceholder; const displayValue = currentValue || emptyPlaceholder;
const hasValue = currentValue !== ''; const hasValue = currentValue !== '';
const classes = `editable-input editable-input--${isEditing ? 'is-editing' : 'is-not-editing'} editable-input--${hasValue ? 'has-value' : 'has-placeholder'}`; const classes = `editable-input editable-input--${
isEditing ? 'is-editing' : 'is-not-editing'
} editable-input--${hasValue ? 'has-value' : 'has-placeholder'}`;
const inputRef = React.createRef(); const inputRef = React.createRef();
React.useEffect(() => { React.useEffect(() => {
@ -54,7 +61,11 @@ function EditableInput({
aria-label={`Edit ${displayValue} value`} aria-label={`Edit ${displayValue} value`}
> >
<span>{displayValue}</span> <span>{displayValue}</span>
<EditIcon className="editable-input__icon" focusable="false" aria-hidden="true" /> <EditIcon
className="editable-input__icon"
focusable="false"
aria-hidden="true"
/>
</button> </button>
<InputComponent <InputComponent
@ -68,7 +79,7 @@ function EditableInput({
ref={inputRef} ref={inputRef}
value={currentValue} value={currentValue}
/> />
</span > </span>
); );
} }
@ -84,7 +95,7 @@ EditableInput.propTypes = {
emptyPlaceholder: PropTypes.string, emptyPlaceholder: PropTypes.string,
InputComponent: PropTypes.elementType, InputComponent: PropTypes.elementType,
// eslint-disable-next-line react/forbid-prop-types // eslint-disable-next-line react/forbid-prop-types
inputProps: PropTypes.object, inputProps: PropTypes.object, // eslint-disable-line
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
validate: PropTypes.func, validate: PropTypes.func,
value: PropTypes.string, value: PropTypes.string,

View File

@ -15,7 +15,10 @@ class NewFileForm extends React.Component {
} }
render() { render() {
const { fields: { name }, handleSubmit } = this.props; const {
fields: { name },
handleSubmit,
} = this.props;
return ( return (
<form <form
className="new-file-form" className="new-file-form"
@ -25,7 +28,9 @@ class NewFileForm extends React.Component {
}} }}
> >
<div className="new-file-form__input-wrapper"> <div className="new-file-form__input-wrapper">
<label className="new-file-form__name-label" htmlFor="name">Name:</label> <label className="new-file-form__name-label" htmlFor="name">
Name:
</label>
<input <input
className="new-file-form__name-input" className="new-file-form__name-input"
id="name" id="name"
@ -33,14 +38,15 @@ class NewFileForm extends React.Component {
placeholder="Name" placeholder="Name"
maxLength="128" maxLength="128"
{...domOnlyProps(name)} {...domOnlyProps(name)}
ref={(element) => { this.fileName = element; }} ref={(element) => {
this.fileName = element;
}}
/> />
<Button <Button type="submit">Add File</Button>
type="submit"
>Add File
</Button>
</div> </div>
{name.touched && name.error && <span className="form-error">{name.error}</span>} {name.touched && name.error && (
<span className="form-error">{name.error}</span>
)}
</form> </form>
); );
} }
@ -48,11 +54,11 @@ class NewFileForm extends React.Component {
NewFileForm.propTypes = { NewFileForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
name: PropTypes.object.isRequired name: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
createFile: PropTypes.func.isRequired, createFile: PropTypes.func.isRequired,
focusOnModal: PropTypes.func.isRequired focusOnModal: PropTypes.func.isRequired,
}; };
export default NewFileForm; export default NewFileForm;

View File

@ -16,7 +16,8 @@ class NewFolderForm extends React.Component {
render() { render() {
const { const {
fields: { name }, handleSubmit fields: { name },
handleSubmit,
} = this.props; } = this.props;
return ( return (
<form <form
@ -26,22 +27,25 @@ class NewFolderForm extends React.Component {
}} }}
> >
<div className="new-folder-form__input-wrapper"> <div className="new-folder-form__input-wrapper">
<label className="new-folder-form__name-label" htmlFor="name">Name:</label> <label className="new-folder-form__name-label" htmlFor="name">
Name:
</label>
<input <input
className="new-folder-form__name-input" className="new-folder-form__name-input"
id="name" id="name"
type="text" type="text"
maxLength="128" maxLength="128"
placeholder="Name" placeholder="Name"
ref={(element) => { this.fileName = element; }} ref={(element) => {
this.fileName = element;
}}
{...domOnlyProps(name)} {...domOnlyProps(name)}
/> />
<Button <Button type="submit">Add Folder</Button>
type="submit"
>Add Folder
</Button>
</div> </div>
{name.touched && name.error && <span className="form-error">{name.error}</span>} {name.touched && name.error && (
<span className="form-error">{name.error}</span>
)}
</form> </form>
); );
} }
@ -49,16 +53,16 @@ class NewFolderForm extends React.Component {
NewFolderForm.propTypes = { NewFolderForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
name: PropTypes.object.isRequired name: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
createFolder: PropTypes.func.isRequired, createFolder: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired, closeModal: PropTypes.func.isRequired,
submitting: PropTypes.bool, submitting: PropTypes.bool,
pristine: PropTypes.bool pristine: PropTypes.bool,
}; };
NewFolderForm.defaultProps = { NewFolderForm.defaultProps = {
submitting: false, submitting: false,
pristine: true pristine: true,
}; };
export default NewFolderForm; export default NewFolderForm;

View File

@ -44,13 +44,22 @@ function isUserOwner(props) {
return props.project.owner && props.project.owner.id === props.user.id; return props.project.owner && props.project.owner.id === props.user.id;
} }
function warnIfUnsavedChanges(props) { // eslint-disable-line function warnIfUnsavedChanges(props) {
// eslint-disable-line
const { route } = props.route; const { route } = props.route;
if (route && (route.action === 'PUSH' && (route.pathname === '/login' || route.pathname === '/signup'))) { if (
route &&
route.action === 'PUSH' &&
(route.pathname === '/login' || route.pathname === '/signup')
) {
// don't warn // don't warn
props.persistState(); props.persistState();
window.onbeforeunload = null; window.onbeforeunload = null;
} else if (route && (props.location.pathname === '/login' || props.location.pathname === '/signup')) { } else if (
route &&
(props.location.pathname === '/login' ||
props.location.pathname === '/signup')
) {
// don't warn // don't warn
props.persistState(); props.persistState();
window.onbeforeunload = null; window.onbeforeunload = null;
@ -61,6 +70,7 @@ function warnIfUnsavedChanges(props) { // eslint-disable-line
props.setUnsavedChanges(false); props.setUnsavedChanges(false);
return true; return true;
} }
return false;
} }
class IDEView extends React.Component { class IDEView extends React.Component {
@ -70,7 +80,7 @@ class IDEView extends React.Component {
this.state = { this.state = {
consoleSize: props.ide.consoleIsExpanded ? 150 : 29, consoleSize: props.ide.consoleIsExpanded ? 150 : 29,
sidebarSize: props.ide.sidebarIsExpanded ? 160 : 20 sidebarSize: props.ide.sidebarIsExpanded ? 160 : 20,
}; };
} }
@ -90,7 +100,10 @@ class IDEView extends React.Component {
this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1; this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1;
document.addEventListener('keydown', this.handleGlobalKeydown, false); document.addEventListener('keydown', this.handleGlobalKeydown, false);
this.props.router.setRouteLeaveHook(this.props.route, this.handleUnsavedChanges); this.props.router.setRouteLeaveHook(
this.props.route,
this.handleUnsavedChanges
);
window.onbeforeunload = this.handleUnsavedChanges; window.onbeforeunload = this.handleUnsavedChanges;
@ -103,11 +116,15 @@ class IDEView extends React.Component {
} }
if (this.props.ide.consoleIsExpanded !== nextProps.ide.consoleIsExpanded) { if (this.props.ide.consoleIsExpanded !== nextProps.ide.consoleIsExpanded) {
this.setState({ consoleSize: nextProps.ide.consoleIsExpanded ? 150 : 29 }); this.setState({
consoleSize: nextProps.ide.consoleIsExpanded ? 150 : 29,
});
} }
if (this.props.ide.sidebarIsExpanded !== nextProps.ide.sidebarIsExpanded) { if (this.props.ide.sidebarIsExpanded !== nextProps.ide.sidebarIsExpanded) {
this.setState({ sidebarSize: nextProps.ide.sidebarIsExpanded ? 160 : 20 }); this.setState({
sidebarSize: nextProps.ide.sidebarIsExpanded ? 160 : 20,
});
} }
} }
@ -121,10 +138,15 @@ class IDEView extends React.Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (isUserOwner(this.props) && this.props.project.id) { if (isUserOwner(this.props) && this.props.project.id) {
if (this.props.preferences.autosave && this.props.ide.unsavedChanges && !this.props.ide.justOpenedProject) { if (
this.props.preferences.autosave &&
this.props.ide.unsavedChanges &&
!this.props.ide.justOpenedProject
) {
if ( if (
this.props.selectedFile.name === prevProps.selectedFile.name && this.props.selectedFile.name === prevProps.selectedFile.name &&
this.props.selectedFile.content !== prevProps.selectedFile.content) { this.props.selectedFile.content !== prevProps.selectedFile.content
) {
if (this.autosaveInterval) { if (this.autosaveInterval) {
clearTimeout(this.autosaveInterval); clearTimeout(this.autosaveInterval);
} }
@ -141,7 +163,8 @@ class IDEView extends React.Component {
} }
if (this.props.route.path !== prevProps.route.path) { if (this.props.route.path !== prevProps.route.path) {
this.props.router.setRouteLeaveHook(this.props.route, () => warnIfUnsavedChanges(this.props)); this.props.router.setRouteLeaveHook(this.props.route, () =>
warnIfUnsavedChanges(this.props));
} }
} }
@ -153,10 +176,16 @@ class IDEView extends React.Component {
handleGlobalKeydown(e) { handleGlobalKeydown(e) {
// 83 === s // 83 === s
if (e.keyCode === 83 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) { if (
e.keyCode === 83 &&
((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))
) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (isUserOwner(this.props) || (this.props.user.authenticated && !this.props.project.owner)) { if (
isUserOwner(this.props) ||
(this.props.user.authenticated && !this.props.project.owner)
) {
this.props.saveProject(this.cmController.getContent()); this.props.saveProject(this.cmController.getContent());
} else if (this.props.user.authenticated) { } else if (this.props.user.authenticated) {
this.props.cloneProject(); this.props.cloneProject();
@ -164,23 +193,41 @@ class IDEView extends React.Component {
this.props.showErrorModal('forceAuthentication'); this.props.showErrorModal('forceAuthentication');
} }
// 13 === enter // 13 === enter
} else if (e.keyCode === 13 && e.shiftKey && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) { } else if (
e.keyCode === 13 &&
e.shiftKey &&
((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))
) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.props.stopSketch(); this.props.stopSketch();
} else if (e.keyCode === 13 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) { } else if (
e.keyCode === 13 &&
((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))
) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.props.startSketch(); this.props.startSketch();
// 50 === 2 // 50 === 2
} else if (e.keyCode === 50 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) { } else if (
e.keyCode === 50 &&
((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) &&
e.shiftKey
) {
e.preventDefault(); e.preventDefault();
this.props.setAllAccessibleOutput(false); this.props.setAllAccessibleOutput(false);
// 49 === 1 // 49 === 1
} else if (e.keyCode === 49 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) { } else if (
e.keyCode === 49 &&
((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) &&
e.shiftKey
) {
e.preventDefault(); e.preventDefault();
this.props.setAllAccessibleOutput(true); this.props.setAllAccessibleOutput(true);
} else if (e.keyCode === 66 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) { } else if (
e.keyCode === 66 &&
((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))
) {
e.preventDefault(); e.preventDefault();
if (!this.props.ide.sidebarIsExpanded) { if (!this.props.ide.sidebarIsExpanded) {
this.props.expandSidebar(); this.props.expandSidebar();
@ -219,7 +266,7 @@ class IDEView extends React.Component {
cmController={this.cmController} cmController={this.cmController}
/> />
<Toolbar key={this.props.project.id} /> <Toolbar key={this.props.project.id} />
{this.props.ide.preferencesIsVisible && {this.props.ide.preferencesIsVisible && (
<Overlay <Overlay
title={this.props.t('Settings')} title={this.props.t('Settings')}
ariaLabel="settings" ariaLabel="settings"
@ -246,7 +293,7 @@ class IDEView extends React.Component {
setTheme={this.props.setTheme} setTheme={this.props.setTheme}
/> />
</Overlay> </Overlay>
} )}
<main className="editor-preview-container"> <main className="editor-preview-container">
<SplitPane <SplitPane
split="vertical" split="vertical"
@ -275,10 +322,17 @@ class IDEView extends React.Component {
<SplitPane <SplitPane
split="vertical" split="vertical"
defaultSize="50%" defaultSize="50%"
onChange={() => { this.overlay.style.display = 'block'; }} onChange={() => {
onDragFinished={() => { this.overlay.style.display = 'none'; }} this.overlay.style.display = 'block';
}}
onDragFinished={() => {
this.overlay.style.display = 'none';
}}
resizerStyle={{ resizerStyle={{
borderLeftWidth: '2px', borderRightWidth: '2px', width: '2px', margin: '0px 0px' borderLeftWidth: '2px',
borderRightWidth: '2px',
width: '2px',
margin: '0px 0px',
}} }}
> >
<SplitPane <SplitPane
@ -304,7 +358,9 @@ class IDEView extends React.Component {
editorOptionsVisible={this.props.ide.editorOptionsVisible} editorOptionsVisible={this.props.ide.editorOptionsVisible}
showEditorOptions={this.props.showEditorOptions} showEditorOptions={this.props.showEditorOptions}
closeEditorOptions={this.props.closeEditorOptions} closeEditorOptions={this.props.closeEditorOptions}
showKeyboardShortcutModal={this.props.showKeyboardShortcutModal} showKeyboardShortcutModal={
this.props.showKeyboardShortcutModal
}
setUnsavedChanges={this.props.setUnsavedChanges} setUnsavedChanges={this.props.setUnsavedChanges}
isPlaying={this.props.ide.isPlaying} isPlaying={this.props.ide.isPlaying}
theme={this.props.preferences.theme} theme={this.props.preferences.theme}
@ -321,37 +377,44 @@ class IDEView extends React.Component {
consoleEvents={this.props.console} consoleEvents={this.props.console}
showRuntimeErrorWarning={this.props.showRuntimeErrorWarning} showRuntimeErrorWarning={this.props.showRuntimeErrorWarning}
hideRuntimeErrorWarning={this.props.hideRuntimeErrorWarning} hideRuntimeErrorWarning={this.props.hideRuntimeErrorWarning}
runtimeErrorWarningVisible={this.props.ide.runtimeErrorWarningVisible} runtimeErrorWarningVisible={
provideController={(ctl) => { this.cmController = ctl; }} this.props.ide.runtimeErrorWarningVisible
}
provideController={(ctl) => {
this.cmController = ctl;
}}
/> />
<Console /> <Console />
</SplitPane> </SplitPane>
<section className="preview-frame-holder"> <section className="preview-frame-holder">
<header className="preview-frame__header"> <header className="preview-frame__header">
<h2 className="preview-frame__title">{this.props.t('Preview')}</h2> <h2 className="preview-frame__title">
{this.props.t('Preview')}
</h2>
</header> </header>
<div className="preview-frame__content"> <div className="preview-frame__content">
<div className="preview-frame-overlay" ref={(element) => { this.overlay = element; }}> <div
className="preview-frame-overlay"
ref={(element) => {
this.overlay = element;
}}
>
</div> </div>
<div> <div>
{( {((this.props.preferences.textOutput ||
( this.props.preferences.gridOutput ||
(this.props.preferences.textOutput || this.props.preferences.soundOutput) &&
this.props.preferences.gridOutput || this.props.ide.isPlaying) ||
this.props.preferences.soundOutput this.props.ide.isAccessibleOutputPlaying}
) &&
this.props.ide.isPlaying
) ||
this.props.ide.isAccessibleOutputPlaying
)
}
</div> </div>
<PreviewFrame <PreviewFrame
htmlFile={this.props.htmlFile} htmlFile={this.props.htmlFile}
files={this.props.files} files={this.props.files}
content={this.props.selectedFile.content} content={this.props.selectedFile.content}
isPlaying={this.props.ide.isPlaying} isPlaying={this.props.ide.isPlaying}
isAccessibleOutputPlaying={this.props.ide.isAccessibleOutputPlaying} isAccessibleOutputPlaying={
this.props.ide.isAccessibleOutputPlaying
}
textOutput={this.props.preferences.textOutput} textOutput={this.props.preferences.textOutput}
gridOutput={this.props.preferences.gridOutput} gridOutput={this.props.preferences.gridOutput}
soundOutput={this.props.preferences.soundOutput} soundOutput={this.props.preferences.soundOutput}
@ -373,21 +436,17 @@ class IDEView extends React.Component {
</SplitPane> </SplitPane>
</SplitPane> </SplitPane>
</main> </main>
{ this.props.ide.modalIsVisible && {this.props.ide.modalIsVisible && <NewFileModal />}
<NewFileModal /> {this.props.ide.newFolderModalVisible && (
}
{this.props.ide.newFolderModalVisible &&
<NewFolderModal <NewFolderModal
closeModal={this.props.closeNewFolderModal} closeModal={this.props.closeNewFolderModal}
createFolder={this.props.createFolder} createFolder={this.props.createFolder}
/> />
} )}
{this.props.ide.uploadFileModalVisible && {this.props.ide.uploadFileModalVisible && (
<UploadFileModal <UploadFileModal closeModal={this.props.closeUploadFileModal} />
closeModal={this.props.closeUploadFileModal} )}
/> {this.props.location.pathname === '/about' && (
}
{ this.props.location.pathname === '/about' &&
<Overlay <Overlay
title={this.props.t('About')} title={this.props.t('About')}
previousPath={this.props.ide.previousPath} previousPath={this.props.ide.previousPath}
@ -395,8 +454,8 @@ class IDEView extends React.Component {
> >
<About previousPath={this.props.ide.previousPath} /> <About previousPath={this.props.ide.previousPath} />
</Overlay> </Overlay>
} )}
{this.props.location.pathname === '/feedback' && {this.props.location.pathname === '/feedback' && (
<Overlay <Overlay
title="Submit Feedback" title="Submit Feedback"
previousPath={this.props.ide.previousPath} previousPath={this.props.ide.previousPath}
@ -404,8 +463,8 @@ class IDEView extends React.Component {
> >
<Feedback previousPath={this.props.ide.previousPath} /> <Feedback previousPath={this.props.ide.previousPath} />
</Overlay> </Overlay>
} )}
{this.props.location.pathname.match(/add-to-collection$/) && {this.props.location.pathname.match(/add-to-collection$/) && (
<Overlay <Overlay
ariaLabel="add to collection" ariaLabel="add to collection"
title="Add to collection" title="Add to collection"
@ -419,8 +478,8 @@ class IDEView extends React.Component {
user={this.props.user} user={this.props.user}
/> />
</Overlay> </Overlay>
} )}
{this.props.ide.shareModalVisible && {this.props.ide.shareModalVisible && (
<Overlay <Overlay
title="Share" title="Share"
ariaLabel="share" ariaLabel="share"
@ -432,8 +491,8 @@ class IDEView extends React.Component {
ownerUsername={this.props.ide.shareModalProjectUsername} ownerUsername={this.props.ide.shareModalProjectUsername}
/> />
</Overlay> </Overlay>
} )}
{this.props.ide.keyboardShortcutVisible && {this.props.ide.keyboardShortcutVisible && (
<Overlay <Overlay
title={this.props.t('KeyboardShortcuts')} title={this.props.t('KeyboardShortcuts')}
ariaLabel="keyboard shortcuts" ariaLabel="keyboard shortcuts"
@ -441,8 +500,8 @@ class IDEView extends React.Component {
> >
<KeyboardShortcutModal /> <KeyboardShortcutModal />
</Overlay> </Overlay>
} )}
{this.props.ide.errorType && {this.props.ide.errorType && (
<Overlay <Overlay
title="Error" title="Error"
ariaLabel="error" ariaLabel="error"
@ -453,7 +512,7 @@ class IDEView extends React.Component {
closeModal={this.props.hideErrorModal} closeModal={this.props.hideErrorModal}
/> />
</Overlay> </Overlay>
} )}
</div> </div>
); );
} }
@ -466,19 +525,19 @@ IDEView.propTypes = {
reset_password_token: PropTypes.string, reset_password_token: PropTypes.string,
}).isRequired, }).isRequired,
location: PropTypes.shape({ location: PropTypes.shape({
pathname: PropTypes.string pathname: PropTypes.string,
}).isRequired, }).isRequired,
getProject: PropTypes.func.isRequired, getProject: PropTypes.func.isRequired,
user: PropTypes.shape({ user: PropTypes.shape({
authenticated: PropTypes.bool.isRequired, authenticated: PropTypes.bool.isRequired,
id: PropTypes.string, id: PropTypes.string,
username: PropTypes.string username: PropTypes.string,
}).isRequired, }).isRequired,
saveProject: PropTypes.func.isRequired, saveProject: PropTypes.func.isRequired,
ide: PropTypes.shape({ ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired, isPlaying: PropTypes.bool.isRequired,
isAccessibleOutputPlaying: PropTypes.bool.isRequired, isAccessibleOutputPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.array, consoleEvent: PropTypes.array, // eslint-disable-line
modalIsVisible: PropTypes.bool.isRequired, modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired, sidebarIsExpanded: PropTypes.bool.isRequired,
consoleIsExpanded: PropTypes.bool.isRequired, consoleIsExpanded: PropTypes.bool.isRequired,
@ -500,7 +559,7 @@ IDEView.propTypes = {
justOpenedProject: PropTypes.bool.isRequired, justOpenedProject: PropTypes.bool.isRequired,
errorType: PropTypes.string, errorType: PropTypes.string,
runtimeErrorWarningVisible: PropTypes.bool.isRequired, runtimeErrorWarningVisible: PropTypes.bool.isRequired,
uploadFileModalVisible: PropTypes.bool.isRequired uploadFileModalVisible: PropTypes.bool.isRequired,
}).isRequired, }).isRequired,
stopSketch: PropTypes.func.isRequired, stopSketch: PropTypes.func.isRequired,
project: PropTypes.shape({ project: PropTypes.shape({
@ -508,12 +567,12 @@ IDEView.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
owner: PropTypes.shape({ owner: PropTypes.shape({
username: PropTypes.string, username: PropTypes.string,
id: PropTypes.string id: PropTypes.string,
}), }),
updatedAt: PropTypes.string updatedAt: PropTypes.string,
}).isRequired, }).isRequired,
editorAccessibility: PropTypes.shape({ editorAccessibility: PropTypes.shape({
lintMessages: PropTypes.array.isRequired, lintMessages: PropTypes.array.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
updateLintMessage: PropTypes.func.isRequired, updateLintMessage: PropTypes.func.isRequired,
clearLintMessage: PropTypes.func.isRequired, clearLintMessage: PropTypes.func.isRequired,
@ -527,7 +586,7 @@ IDEView.propTypes = {
gridOutput: PropTypes.bool.isRequired, gridOutput: PropTypes.bool.isRequired,
soundOutput: PropTypes.bool.isRequired, soundOutput: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired, theme: PropTypes.string.isRequired,
autorefresh: PropTypes.bool.isRequired autorefresh: PropTypes.bool.isRequired,
}).isRequired, }).isRequired,
closePreferences: PropTypes.func.isRequired, closePreferences: PropTypes.func.isRequired,
setFontSize: PropTypes.func.isRequired, setFontSize: PropTypes.func.isRequired,
@ -542,19 +601,19 @@ IDEView.propTypes = {
files: PropTypes.arrayOf(PropTypes.shape({ files: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
content: PropTypes.string.isRequired content: PropTypes.string.isRequired,
})).isRequired, })).isRequired,
updateFileContent: PropTypes.func.isRequired, updateFileContent: PropTypes.func.isRequired,
selectedFile: PropTypes.shape({ selectedFile: PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired, content: PropTypes.string.isRequired,
name: PropTypes.string.isRequired name: PropTypes.string.isRequired,
}).isRequired, }).isRequired,
setSelectedFile: PropTypes.func.isRequired, setSelectedFile: PropTypes.func.isRequired,
htmlFile: PropTypes.shape({ htmlFile: PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
content: PropTypes.string.isRequired content: PropTypes.string.isRequired,
}).isRequired, }).isRequired,
dispatchConsoleEvent: PropTypes.func.isRequired, dispatchConsoleEvent: PropTypes.func.isRequired,
newFile: PropTypes.func.isRequired, newFile: PropTypes.func.isRequired,
@ -577,11 +636,11 @@ IDEView.propTypes = {
showKeyboardShortcutModal: PropTypes.func.isRequired, showKeyboardShortcutModal: PropTypes.func.isRequired,
closeKeyboardShortcutModal: PropTypes.func.isRequired, closeKeyboardShortcutModal: PropTypes.func.isRequired,
toast: PropTypes.shape({ toast: PropTypes.shape({
isVisible: PropTypes.bool.isRequired isVisible: PropTypes.bool.isRequired,
}).isRequired, }).isRequired,
autosaveProject: PropTypes.func.isRequired, autosaveProject: PropTypes.func.isRequired,
router: PropTypes.shape({ router: PropTypes.shape({
setRouteLeaveHook: PropTypes.func setRouteLeaveHook: PropTypes.func,
}).isRequired, }).isRequired,
route: PropTypes.oneOfType([PropTypes.object, PropTypes.element]).isRequired, route: PropTypes.oneOfType([PropTypes.object, PropTypes.element]).isRequired,
setUnsavedChanges: PropTypes.func.isRequired, setUnsavedChanges: PropTypes.func.isRequired,
@ -592,7 +651,7 @@ IDEView.propTypes = {
setPreviousPath: PropTypes.func.isRequired, setPreviousPath: PropTypes.func.isRequired,
console: PropTypes.arrayOf(PropTypes.shape({ console: PropTypes.arrayOf(PropTypes.shape({
method: PropTypes.string.isRequired, method: PropTypes.string.isRequired,
args: PropTypes.arrayOf(PropTypes.string) args: PropTypes.arrayOf(PropTypes.string),
})).isRequired, })).isRequired,
clearConsole: PropTypes.func.isRequired, clearConsole: PropTypes.func.isRequired,
showErrorModal: PropTypes.func.isRequired, showErrorModal: PropTypes.func.isRequired,
@ -603,13 +662,14 @@ IDEView.propTypes = {
startSketch: PropTypes.func.isRequired, startSketch: PropTypes.func.isRequired,
openUploadFileModal: PropTypes.func.isRequired, openUploadFileModal: PropTypes.func.isRequired,
closeUploadFileModal: PropTypes.func.isRequired, closeUploadFileModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired t: PropTypes.func.isRequired,
}; };
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
files: state.files, files: state.files,
selectedFile: state.files.find(file => file.isSelectedFile) || selectedFile:
state.files.find(file => file.isSelectedFile) ||
state.files.find(file => file.name === 'sketch.js') || state.files.find(file => file.name === 'sketch.js') ||
state.files.find(file => file.name !== 'root'), state.files.find(file => file.name !== 'root'),
htmlFile: getHTMLFile(state.files), htmlFile: getHTMLFile(state.files),
@ -619,7 +679,7 @@ function mapStateToProps(state) {
user: state.user, user: state.user,
project: state.project, project: state.project,
toast: state.toast, toast: state.toast,
console: state.console console: state.console,
}; };
} }

View File

@ -30,8 +30,8 @@ import Console from '../components/Console';
import { remSize } from '../../../theme'; import { remSize } from '../../../theme';
import ActionStrip from '../../../components/mobile/ActionStrip'; import ActionStrip from '../../../components/mobile/ActionStrip';
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id); const isUserOwner = ({ project, user }) =>
project.owner && project.owner.id === user.id;
const Expander = styled.div` const Expander = styled.div`
height: ${props => (props.expanded ? remSize(160) : remSize(27))}; height: ${props => (props.expanded ? remSize(160) : remSize(27))};
@ -39,11 +39,28 @@ const Expander = styled.div`
const MobileIDEView = (props) => { const MobileIDEView = (props) => {
const { const {
preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage, preferences,
selectedFile, updateFileContent, files, ide,
closeEditorOptions, showEditorOptions, showKeyboardShortcutModal, setUnsavedChanges, editorAccessibility,
startRefreshSketch, stopSketch, expandSidebar, collapseSidebar, clearConsole, console, project,
showRuntimeErrorWarning, hideRuntimeErrorWarning, startSketch updateLintMessage,
clearLintMessage,
selectedFile,
updateFileContent,
files,
closeEditorOptions,
showEditorOptions,
showKeyboardShortcutModal,
setUnsavedChanges,
startRefreshSketch,
stopSketch,
expandSidebar,
collapseSidebar,
clearConsole,
console,
showRuntimeErrorWarning,
hideRuntimeErrorWarning,
startSketch,
} = props; } = props;
const [tmController, setTmController] = useState(null); // eslint-disable-line const [tmController, setTmController] = useState(null); // eslint-disable-line
@ -55,7 +72,11 @@ const MobileIDEView = (props) => {
title={project.name} title={project.name}
subtitle={selectedFile.name} subtitle={selectedFile.name}
leftButton={ leftButton={
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" /> <IconButton
to="/mobile"
icon={ExitIcon}
aria-label="Return to original editor"
/>
} }
> >
<IconButton <IconButton
@ -64,7 +85,14 @@ const MobileIDEView = (props) => {
icon={PreferencesIcon} icon={PreferencesIcon}
aria-label="Open preferences menu" aria-label="Open preferences menu"
/> />
<IconButton to="/mobile/preview" onClick={() => { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" /> <IconButton
to="/mobile/preview"
onClick={() => {
startSketch();
}}
icon={PlayIcon}
aria-label="Run sketch"
/>
</Header> </Header>
<IDEWrapper> <IDEWrapper>
@ -104,16 +132,18 @@ const MobileIDEView = (props) => {
/> />
</IDEWrapper> </IDEWrapper>
<Footer> <Footer>
{ide.consoleIsExpanded && <Expander expanded><Console /></Expander>} {ide.consoleIsExpanded && (
<Expander expanded>
<Console />
</Expander>
)}
<ActionStrip /> <ActionStrip />
</Footer> </Footer>
</Screen> </Screen>
); );
}; };
MobileIDEView.propTypes = { MobileIDEView.propTypes = {
preferences: PropTypes.shape({ preferences: PropTypes.shape({
fontSize: PropTypes.number.isRequired, fontSize: PropTypes.number.isRequired,
autosave: PropTypes.bool.isRequired, autosave: PropTypes.bool.isRequired,
@ -124,13 +154,13 @@ MobileIDEView.propTypes = {
gridOutput: PropTypes.bool.isRequired, gridOutput: PropTypes.bool.isRequired,
soundOutput: PropTypes.bool.isRequired, soundOutput: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired, theme: PropTypes.string.isRequired,
autorefresh: PropTypes.bool.isRequired autorefresh: PropTypes.bool.isRequired,
}).isRequired, }).isRequired,
ide: PropTypes.shape({ ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired, isPlaying: PropTypes.bool.isRequired,
isAccessibleOutputPlaying: PropTypes.bool.isRequired, isAccessibleOutputPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.array, consoleEvent: PropTypes.array, // eslint-disable-line
modalIsVisible: PropTypes.bool.isRequired, modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired, sidebarIsExpanded: PropTypes.bool.isRequired,
consoleIsExpanded: PropTypes.bool.isRequired, consoleIsExpanded: PropTypes.bool.isRequired,
@ -152,11 +182,11 @@ MobileIDEView.propTypes = {
justOpenedProject: PropTypes.bool.isRequired, justOpenedProject: PropTypes.bool.isRequired,
errorType: PropTypes.string, errorType: PropTypes.string,
runtimeErrorWarningVisible: PropTypes.bool.isRequired, runtimeErrorWarningVisible: PropTypes.bool.isRequired,
uploadFileModalVisible: PropTypes.bool.isRequired uploadFileModalVisible: PropTypes.bool.isRequired,
}).isRequired, }).isRequired,
editorAccessibility: PropTypes.shape({ editorAccessibility: PropTypes.shape({
lintMessages: PropTypes.array.isRequired, lintMessages: PropTypes.array.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
project: PropTypes.shape({ project: PropTypes.shape({
@ -164,9 +194,9 @@ MobileIDEView.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
owner: PropTypes.shape({ owner: PropTypes.shape({
username: PropTypes.string, username: PropTypes.string,
id: PropTypes.string id: PropTypes.string,
}), }),
updatedAt: PropTypes.string updatedAt: PropTypes.string,
}).isRequired, }).isRequired,
startSketch: PropTypes.func.isRequired, startSketch: PropTypes.func.isRequired,
@ -178,7 +208,7 @@ MobileIDEView.propTypes = {
selectedFile: PropTypes.shape({ selectedFile: PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired, content: PropTypes.string.isRequired,
name: PropTypes.string.isRequired name: PropTypes.string.isRequired,
}).isRequired, }).isRequired,
updateFileContent: PropTypes.func.isRequired, updateFileContent: PropTypes.func.isRequired,
@ -186,7 +216,7 @@ MobileIDEView.propTypes = {
files: PropTypes.arrayOf(PropTypes.shape({ files: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
content: PropTypes.string.isRequired content: PropTypes.string.isRequired,
})).isRequired, })).isRequired,
closeEditorOptions: PropTypes.func.isRequired, closeEditorOptions: PropTypes.func.isRequired,
@ -209,7 +239,7 @@ MobileIDEView.propTypes = {
console: PropTypes.arrayOf(PropTypes.shape({ console: PropTypes.arrayOf(PropTypes.shape({
method: PropTypes.string.isRequired, method: PropTypes.string.isRequired,
args: PropTypes.arrayOf(PropTypes.string) args: PropTypes.arrayOf(PropTypes.string),
})).isRequired, })).isRequired,
showRuntimeErrorWarning: PropTypes.func.isRequired, showRuntimeErrorWarning: PropTypes.func.isRequired,
@ -219,15 +249,15 @@ MobileIDEView.propTypes = {
user: PropTypes.shape({ user: PropTypes.shape({
authenticated: PropTypes.bool.isRequired, authenticated: PropTypes.bool.isRequired,
id: PropTypes.string, id: PropTypes.string,
username: PropTypes.string username: PropTypes.string,
}).isRequired, }).isRequired,
}; };
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
files: state.files, files: state.files,
selectedFile: state.files.find(file => file.isSelectedFile) || selectedFile:
state.files.find(file => file.isSelectedFile) ||
state.files.find(file => file.name === 'sketch.js') || state.files.find(file => file.name === 'sketch.js') ||
state.files.find(file => file.name !== 'root'), state.files.find(file => file.name !== 'root'),
htmlFile: getHTMLFile(state.files), htmlFile: getHTMLFile(state.files),
@ -237,7 +267,7 @@ function mapStateToProps(state) {
user: state.user, user: state.user,
project: state.project, project: state.project,
toast: state.toast, toast: state.toast,
console: state.console console: state.console,
}; };
} }
@ -258,5 +288,4 @@ function mapDispatchToProps(dispatch) {
); );
} }
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileIDEView)); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileIDEView));

View File

@ -0,0 +1,16 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import Screen from '../../components/mobile/MobileScreen';
import Header from '../../components/mobile/Header';
import IconButton from '../../components/mobile/IconButton';
import { ExitIcon } from '../../common/icons';
const MobileExamples = () => (
<Screen fullscreen>
<Header transparent title="My Stuff">
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
</Header>
</Screen>
);
export default MobileExamples;

View File

@ -8,8 +8,8 @@ import CopyableInput from '../../IDE/components/CopyableInput';
import APIKeyList from './APIKeyList'; import APIKeyList from './APIKeyList';
export const APIKeyPropType = PropTypes.shape({ export const APIKeyPropType = PropTypes.shape({
id: PropTypes.object.isRequired, id: PropTypes.object.isRequired, // eslint-disable-line
token: PropTypes.object, token: PropTypes.object, // eslint-disable-line
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired,
lastUsedAt: PropTypes.string, lastUsedAt: PropTypes.string,
@ -30,7 +30,7 @@ class APIKeyForm extends React.Component {
const { keyLabel } = this.state; const { keyLabel } = this.state;
this.setState({ this.setState({
keyLabel: '' keyLabel: '',
}); });
this.props.createApiKey(keyLabel); this.props.createApiKey(keyLabel);
@ -64,18 +64,25 @@ class APIKeyForm extends React.Component {
<div className="api-key-form"> <div className="api-key-form">
<p className="api-key-form__summary"> <p className="api-key-form__summary">
Personal Access Tokens act like your password to allow automated Personal Access Tokens act like your password to allow automated
scripts to access the Editor API. Create a token for each script scripts to access the Editor API. Create a token for each script that
that needs access. needs access.
</p> </p>
<div className="api-key-form__section"> <div className="api-key-form__section">
<h3 className="api-key-form__title">Create new token</h3> <h3 className="api-key-form__title">Create new token</h3>
<form className="form form--inline" onSubmit={this.addKey}> <form className="form form--inline" onSubmit={this.addKey}>
<label htmlFor="keyLabel" className="form__label form__label--hidden ">What is this token for?</label> <label
htmlFor="keyLabel"
className="form__label form__label--hidden "
>
What is this token for?
</label>
<input <input
className="form__input" className="form__input"
id="keyLabel" id="keyLabel"
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }} onChange={(event) => {
this.setState({ keyLabel: event.target.value });
}}
placeholder="What is this token for? e.g. Example import script" placeholder="What is this token for? e.g. Example import script"
type="text" type="text"
value={this.state.keyLabel} value={this.state.keyLabel}
@ -90,18 +97,21 @@ class APIKeyForm extends React.Component {
</Button> </Button>
</form> </form>
{ {keyWithToken && (
keyWithToken && ( <div className="api-key-form__new-token">
<div className="api-key-form__new-token"> <h4 className="api-key-form__new-token__title">
<h4 className="api-key-form__new-token__title">Your new access token</h4> Your new access token
<p className="api-key-form__new-token__info"> </h4>
Make sure to copy your new personal access token now. <p className="api-key-form__new-token__info">
You wont be able to see it again! Make sure to copy your new personal access token now. You wont
</p> be able to see it again!
<CopyableInput label={keyWithToken.label} value={keyWithToken.token} /> </p>
</div> <CopyableInput
) label={keyWithToken.label}
} value={keyWithToken.token}
/>
</div>
)}
</div> </div>
<div className="api-key-form__section"> <div className="api-key-form__section">

View File

@ -13,7 +13,7 @@ function AccountForm(props) {
initiateVerification, initiateVerification,
submitting, submitting,
invalid, invalid,
pristine pristine,
} = props; } = props;
const handleInitiateVerification = (evt) => { const handleInitiateVerification = (evt) => {
@ -24,7 +24,9 @@ function AccountForm(props) {
return ( return (
<form className="form" onSubmit={handleSubmit(props.updateSettings)}> <form className="form" onSubmit={handleSubmit(props.updateSettings)}>
<p className="form__field"> <p className="form__field">
<label htmlFor="email" className="form__label">Email</label> <label htmlFor="email" className="form__label">
Email
</label>
<input <input
className="form__input" className="form__input"
aria-label="email" aria-label="email"
@ -32,30 +34,29 @@ function AccountForm(props) {
id="email" id="email"
{...domOnlyProps(email)} {...domOnlyProps(email)}
/> />
{email.touched && email.error && <span className="form-error">{email.error}</span>} {email.touched && email.error && (
<span className="form-error">{email.error}</span>
)}
</p> </p>
{ {user.verified !== 'verified' && (
user.verified !== 'verified' && <p className="form__context">
( <span className="form__status">Unconfirmed.</span>
<p className="form__context"> {user.emailVerificationInitiate === true ? (
<span className="form__status">Unconfirmed.</span> <span className="form__status">
{ {' '}
user.emailVerificationInitiate === true ? Confirmation sent, check your email.
( </span>
<span className="form__status"> Confirmation sent, check your email.</span> ) : (
) : <Button onClick={handleInitiateVerification}>
( Resend confirmation email
<Button </Button>
onClick={handleInitiateVerification} )}
>Resend confirmation email </p>
</Button> )}
)
}
</p>
)
}
<p className="form__field"> <p className="form__field">
<label htmlFor="username" className="form__label">User Name</label> <label htmlFor="username" className="form__label">
User Name
</label>
<input <input
className="form__input" className="form__input"
aria-label="username" aria-label="username"
@ -64,10 +65,14 @@ function AccountForm(props) {
defaultValue={username} defaultValue={username}
{...domOnlyProps(username)} {...domOnlyProps(username)}
/> />
{username.touched && username.error && <span className="form-error">{username.error}</span>} {username.touched && username.error && (
<span className="form-error">{username.error}</span>
)}
</p> </p>
<p className="form__field"> <p className="form__field">
<label htmlFor="current password" className="form__label">Current Password</label> <label htmlFor="current password" className="form__label">
Current Password
</label>
<input <input
className="form__input" className="form__input"
aria-label="currentPassword" aria-label="currentPassword"
@ -75,14 +80,14 @@ function AccountForm(props) {
id="currentPassword" id="currentPassword"
{...domOnlyProps(currentPassword)} {...domOnlyProps(currentPassword)}
/> />
{ {currentPassword.touched && currentPassword.error && (
currentPassword.touched &&
currentPassword.error &&
<span className="form-error">{currentPassword.error}</span> <span className="form-error">{currentPassword.error}</span>
} )}
</p> </p>
<p className="form__field"> <p className="form__field">
<label htmlFor="new password" className="form__label">New Password</label> <label htmlFor="new password" className="form__label">
New Password
</label>
<input <input
className="form__input" className="form__input"
aria-label="newPassword" aria-label="newPassword"
@ -90,12 +95,12 @@ function AccountForm(props) {
id="newPassword" id="newPassword"
{...domOnlyProps(newPassword)} {...domOnlyProps(newPassword)}
/> />
{newPassword.touched && newPassword.error && <span className="form-error">{newPassword.error}</span>} {newPassword.touched && newPassword.error && (
<span className="form-error">{newPassword.error}</span>
)}
</p> </p>
<Button <Button type="submit" disabled={submitting || invalid || pristine}>
type="submit" Save All Settings
disabled={submitting || invalid || pristine}
>Save All Settings
</Button> </Button>
</form> </form>
); );
@ -103,10 +108,10 @@ function AccountForm(props) {
AccountForm.propTypes = { AccountForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
username: PropTypes.object.isRequired, username: PropTypes.object.isRequired, // eslint-disable-line
email: PropTypes.object.isRequired, email: PropTypes.object.isRequired, // eslint-disable-line
currentPassword: PropTypes.object.isRequired, currentPassword: PropTypes.object.isRequired, // eslint-disable-line
newPassword: PropTypes.object.isRequired, newPassword: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
user: PropTypes.shape({ user: PropTypes.shape({
verified: PropTypes.number.isRequired, verified: PropTypes.number.isRequired,
@ -123,7 +128,7 @@ AccountForm.propTypes = {
AccountForm.defaultProps = { AccountForm.defaultProps = {
submitting: false, submitting: false,
pristine: true, pristine: true,
invalid: false invalid: false,
}; };
export default AccountForm; export default AccountForm;

View File

@ -7,12 +7,20 @@ import { domOnlyProps } from '../../../utils/reduxFormUtils';
function LoginForm(props) { function LoginForm(props) {
const { const {
fields: { email, password }, handleSubmit, submitting, pristine fields: { email, password },
handleSubmit,
submitting,
pristine,
} = props; } = props;
return ( return (
<form className="form" onSubmit={handleSubmit(props.validateAndLoginUser.bind(this, props.previousPath))}> <form
className="form"
onSubmit={handleSubmit(props.validateAndLoginUser.bind(this, props.previousPath))}
>
<p className="form__field"> <p className="form__field">
<label htmlFor="email" className="form__label">Email or Username</label> <label htmlFor="email" className="form__label">
Email or Username
</label>
<input <input
className="form__input" className="form__input"
aria-label="email or username" aria-label="email or username"
@ -20,10 +28,14 @@ function LoginForm(props) {
id="email" id="email"
{...domOnlyProps(email)} {...domOnlyProps(email)}
/> />
{email.touched && email.error && <span className="form-error">{email.error}</span>} {email.touched && email.error && (
<span className="form-error">{email.error}</span>
)}
</p> </p>
<p className="form__field"> <p className="form__field">
<label htmlFor="password" className="form__label">Password</label> <label htmlFor="password" className="form__label">
Password
</label>
<input <input
className="form__input" className="form__input"
aria-label="password" aria-label="password"
@ -31,12 +43,12 @@ function LoginForm(props) {
id="password" id="password"
{...domOnlyProps(password)} {...domOnlyProps(password)}
/> />
{password.touched && password.error && <span className="form-error">{password.error}</span>} {password.touched && password.error && (
<span className="form-error">{password.error}</span>
)}
</p> </p>
<Button <Button type="submit" disabled={submitting || pristine}>
type="submit" Log In
disabled={submitting || pristine}
>Log In
</Button> </Button>
</form> </form>
); );
@ -44,21 +56,21 @@ function LoginForm(props) {
LoginForm.propTypes = { LoginForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
email: PropTypes.object.isRequired, email: PropTypes.object.isRequired, // eslint-disable-line
password: PropTypes.object.isRequired password: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
validateAndLoginUser: PropTypes.func.isRequired, validateAndLoginUser: PropTypes.func.isRequired,
submitting: PropTypes.bool, submitting: PropTypes.bool,
pristine: PropTypes.bool, pristine: PropTypes.bool,
invalid: PropTypes.bool, invalid: PropTypes.bool,
previousPath: PropTypes.string.isRequired previousPath: PropTypes.string.isRequired,
}; };
LoginForm.defaultProps = { LoginForm.defaultProps = {
submitting: false, submitting: false,
pristine: true, pristine: true,
invalid: false invalid: false,
}; };
export default LoginForm; export default LoginForm;

View File

@ -6,12 +6,21 @@ import Button from '../../../common/Button';
function NewPasswordForm(props) { function NewPasswordForm(props) {
const { const {
fields: { password, confirmPassword }, handleSubmit, submitting, invalid, pristine fields: { password, confirmPassword },
handleSubmit,
submitting,
invalid,
pristine,
} = props; } = props;
return ( return (
<form className="form" onSubmit={handleSubmit(props.updatePassword.bind(this, props.params.reset_password_token))}> <form
className="form"
onSubmit={handleSubmit(props.updatePassword.bind(this, props.params.reset_password_token))}
>
<p className="form__field"> <p className="form__field">
<label htmlFor="password" className="form__label">Password</label> <label htmlFor="password" className="form__label">
Password
</label>
<input <input
className="form__input" className="form__input"
aria-label="password" aria-label="password"
@ -19,10 +28,14 @@ function NewPasswordForm(props) {
id="Password" id="Password"
{...domOnlyProps(password)} {...domOnlyProps(password)}
/> />
{password.touched && password.error && <span className="form-error">{password.error}</span>} {password.touched && password.error && (
<span className="form-error">{password.error}</span>
)}
</p> </p>
<p className="form__field"> <p className="form__field">
<label htmlFor="confirm password" className="form__label">Confirm Password</label> <label htmlFor="confirm password" className="form__label">
Confirm Password
</label>
<input <input
className="form__input" className="form__input"
type="password" type="password"
@ -30,21 +43,21 @@ function NewPasswordForm(props) {
id="confirm password" id="confirm password"
{...domOnlyProps(confirmPassword)} {...domOnlyProps(confirmPassword)}
/> />
{ {confirmPassword.touched && confirmPassword.error && (
confirmPassword.touched &&
confirmPassword.error &&
<span className="form-error">{confirmPassword.error}</span> <span className="form-error">{confirmPassword.error}</span>
} )}
</p> </p>
<Button type="submit" disabled={submitting || invalid || pristine}>Set New Password</Button> <Button type="submit" disabled={submitting || invalid || pristine}>
Set New Password
</Button>
</form> </form>
); );
} }
NewPasswordForm.propTypes = { NewPasswordForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
password: PropTypes.object.isRequired, password: PropTypes.object.isRequired, // eslint-disable-line
confirmPassword: PropTypes.object.isRequired confirmPassword: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
updatePassword: PropTypes.func.isRequired, updatePassword: PropTypes.func.isRequired,
@ -59,7 +72,7 @@ NewPasswordForm.propTypes = {
NewPasswordForm.defaultProps = { NewPasswordForm.defaultProps = {
invalid: false, invalid: false,
pristine: true, pristine: true,
submitting: false submitting: false,
}; };
export default NewPasswordForm; export default NewPasswordForm;

View File

@ -6,12 +6,21 @@ import Button from '../../../common/Button';
function ResetPasswordForm(props) { function ResetPasswordForm(props) {
const { const {
fields: { email }, handleSubmit, submitting, invalid, pristine fields: { email },
handleSubmit,
submitting,
invalid,
pristine,
} = props; } = props;
return ( return (
<form className="form" onSubmit={handleSubmit(props.initiateResetPassword.bind(this))}> <form
className="form"
onSubmit={handleSubmit(props.initiateResetPassword.bind(this))}
>
<p className="form__field"> <p className="form__field">
<label htmlFor="email" className="form__label">Email used for registration</label> <label htmlFor="email" className="form__label">
Email used for registration
</label>
<input <input
className="form__input" className="form__input"
aria-label="email" aria-label="email"
@ -19,12 +28,17 @@ function ResetPasswordForm(props) {
id="email" id="email"
{...domOnlyProps(email)} {...domOnlyProps(email)}
/> />
{email.touched && email.error && <span className="form-error">{email.error}</span>} {email.touched && email.error && (
<span className="form-error">{email.error}</span>
)}
</p> </p>
<Button <Button
type="submit" type="submit"
disabled={submitting || invalid || pristine || props.user.resetPasswordInitiate} disabled={
>Send Password Reset Email submitting || invalid || pristine || props.user.resetPasswordInitiate
}
>
Send Password Reset Email
</Button> </Button>
</form> </form>
); );
@ -32,7 +46,7 @@ function ResetPasswordForm(props) {
ResetPasswordForm.propTypes = { ResetPasswordForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
email: PropTypes.object.isRequired email: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
initiateResetPassword: PropTypes.func.isRequired, initiateResetPassword: PropTypes.func.isRequired,
@ -40,14 +54,14 @@ ResetPasswordForm.propTypes = {
invalid: PropTypes.bool, invalid: PropTypes.bool,
pristine: PropTypes.bool, pristine: PropTypes.bool,
user: PropTypes.shape({ user: PropTypes.shape({
resetPasswordInitiate: PropTypes.bool resetPasswordInitiate: PropTypes.bool,
}).isRequired }).isRequired,
}; };
ResetPasswordForm.defaultProps = { ResetPasswordForm.defaultProps = {
submitting: false, submitting: false,
pristine: true, pristine: true,
invalid: false invalid: false,
}; };
export default ResetPasswordForm; export default ResetPasswordForm;

View File

@ -8,12 +8,21 @@ function SignupForm(props) {
const { const {
fields: { fields: {
username, email, password, confirmPassword username, email, password, confirmPassword
}, handleSubmit, submitting, invalid, pristine },
handleSubmit,
submitting,
invalid,
pristine,
} = props; } = props;
return ( return (
<form className="form" onSubmit={handleSubmit(props.signUpUser.bind(this, props.previousPath))}> <form
className="form"
onSubmit={handleSubmit(props.signUpUser.bind(this, props.previousPath))}
>
<p className="form__field"> <p className="form__field">
<label htmlFor="username" className="form__label">User Name</label> <label htmlFor="username" className="form__label">
User Name
</label>
<input <input
className="form__input" className="form__input"
aria-label="username" aria-label="username"
@ -21,10 +30,14 @@ function SignupForm(props) {
id="username" id="username"
{...domOnlyProps(username)} {...domOnlyProps(username)}
/> />
{username.touched && username.error && <span className="form-error">{username.error}</span>} {username.touched && username.error && (
<span className="form-error">{username.error}</span>
)}
</p> </p>
<p className="form__field"> <p className="form__field">
<label htmlFor="email" className="form__label">Email</label> <label htmlFor="email" className="form__label">
Email
</label>
<input <input
className="form__input" className="form__input"
aria-label="email" aria-label="email"
@ -32,10 +45,14 @@ function SignupForm(props) {
id="email" id="email"
{...domOnlyProps(email)} {...domOnlyProps(email)}
/> />
{email.touched && email.error && <span className="form-error">{email.error}</span>} {email.touched && email.error && (
<span className="form-error">{email.error}</span>
)}
</p> </p>
<p className="form__field"> <p className="form__field">
<label htmlFor="password" className="form__label">Password</label> <label htmlFor="password" className="form__label">
Password
</label>
<input <input
className="form__input" className="form__input"
aria-label="password" aria-label="password"
@ -43,10 +60,14 @@ function SignupForm(props) {
id="password" id="password"
{...domOnlyProps(password)} {...domOnlyProps(password)}
/> />
{password.touched && password.error && <span className="form-error">{password.error}</span>} {password.touched && password.error && (
<span className="form-error">{password.error}</span>
)}
</p> </p>
<p className="form__field"> <p className="form__field">
<label htmlFor="confirm password" className="form__label">Confirm Password</label> <label htmlFor="confirm password" className="form__label">
Confirm Password
</label>
<input <input
className="form__input" className="form__input"
type="password" type="password"
@ -54,16 +75,12 @@ function SignupForm(props) {
id="confirm password" id="confirm password"
{...domOnlyProps(confirmPassword)} {...domOnlyProps(confirmPassword)}
/> />
{ {confirmPassword.touched && confirmPassword.error && (
confirmPassword.touched &&
confirmPassword.error &&
<span className="form-error">{confirmPassword.error}</span> <span className="form-error">{confirmPassword.error}</span>
} )}
</p> </p>
<Button <Button type="submit" disabled={submitting || invalid || pristine}>
type="submit" Sign Up
disabled={submitting || invalid || pristine}
>Sign Up
</Button> </Button>
</form> </form>
); );
@ -71,23 +88,23 @@ function SignupForm(props) {
SignupForm.propTypes = { SignupForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
username: PropTypes.object.isRequired, username: PropTypes.object.isRequired, // eslint-disable-line
email: PropTypes.object.isRequired, email: PropTypes.object.isRequired, // eslint-disable-line
password: PropTypes.object.isRequired, password: PropTypes.object.isRequired, // eslint-disable-line
confirmPassword: PropTypes.object.isRequired confirmPassword: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired, }).isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
signUpUser: PropTypes.func.isRequired, signUpUser: PropTypes.func.isRequired,
submitting: PropTypes.bool, submitting: PropTypes.bool,
invalid: PropTypes.bool, invalid: PropTypes.bool,
pristine: PropTypes.bool, pristine: PropTypes.bool,
previousPath: PropTypes.string.isRequired previousPath: PropTypes.string.isRequired,
}; };
SignupForm.defaultProps = { SignupForm.defaultProps = {
submitting: false, submitting: false,
pristine: true, pristine: true,
invalid: false invalid: false,
}; };
export default SignupForm; export default SignupForm;

View File

@ -18,6 +18,7 @@ import createRedirectWithUsername from './components/createRedirectWithUsername'
import { getUser } from './modules/User/actions'; import { getUser } from './modules/User/actions';
import { stopSketch } from './modules/IDE/actions/ide'; import { stopSketch } from './modules/IDE/actions/ide';
import { userIsAuthenticated, userIsNotAuthenticated, userIsAuthorized } from './utils/auth'; import { userIsAuthenticated, userIsNotAuthenticated, userIsAuthorized } from './utils/auth';
import MobileExamples from './modules/Mobile/MobileExamples';
const checkAuth = (store) => { const checkAuth = (store) => {
store.dispatch(getUser()); store.dispatch(getUser());
@ -60,6 +61,7 @@ const routes = store => (
<Route path="/mobile" component={MobileIDEView} /> <Route path="/mobile" component={MobileIDEView} />
<Route path="/mobile/preview" component={MobileSketchView} /> <Route path="/mobile/preview" component={MobileSketchView} />
<Route path="/mobile/preferences" component={MobilePreferences} /> <Route path="/mobile/preferences" component={MobilePreferences} />
<Route path="/mobile/examples" component={MobileExamples} />
</Route> </Route>
); );

View File

@ -115,17 +115,13 @@ router.get('/about', (req, res) => {
}); });
if (process.env.MOBILE_ENABLED) { if (process.env.MOBILE_ENABLED) {
router.get('/mobile', (req, res) => { router.get('/mobile', (req, res) => res.send(renderIndex()));
res.send(renderIndex());
});
router.get('/mobile/preview', (req, res) => { router.get('/mobile/preview', (req, res) => res.send(renderIndex()));
res.send(renderIndex());
});
router.get('/mobile/preferences', (req, res) => { router.get('/mobile/preferences', (req, res) => res.send(renderIndex()));
res.send(renderIndex());
}); router.get('/mobile/examples', (req, res) => res.send(renderIndex()));
} }
router.get('/:username/collections/create', (req, res) => { router.get('/:username/collections/create', (req, res) => {