🚨 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.textOutput ||
this.props.preferences.gridOutput || this.props.preferences.gridOutput ||
this.props.preferences.soundOutput this.props.preferences.soundOutput) &&
) && this.props.ide.isPlaying) ||
this.props.ide.isPlaying this.props.ide.isAccessibleOutputPlaying}
) ||
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">Your new access token</h4> <h4 className="api-key-form__new-token__title">
Your new access token
</h4>
<p className="api-key-form__new-token__info"> <p className="api-key-form__new-token__info">
Make sure to copy your new personal access token now. Make sure to copy your new personal access token now. You wont
You wont be able to see it again! be able to see it again!
</p> </p>
<CopyableInput label={keyWithToken.label} value={keyWithToken.token} /> <CopyableInput
label={keyWithToken.label}
value={keyWithToken.token}
/>
</div> </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"> <p className="form__context">
<span className="form__status">Unconfirmed.</span> <span className="form__status">Unconfirmed.</span>
{ {user.emailVerificationInitiate === true ? (
user.emailVerificationInitiate === true ? <span className="form__status">
( {' '}
<span className="form__status"> Confirmation sent, check your email.</span> Confirmation sent, check your email.
) : </span>
( ) : (
<Button <Button onClick={handleInitiateVerification}>
onClick={handleInitiateVerification} Resend confirmation email
>Resend confirmation email
</Button> </Button>
) )}
}
</p> </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) => {