🚨 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
function EditableInput({
validate, value, emptyPlaceholder, InputComponent, inputProps, onChange
validate,
value,
emptyPlaceholder,
InputComponent,
inputProps,
onChange,
}) {
const [isEditing, setIsEditing] = React.useState(false);
const [currentValue, setCurrentValue] = React.useState(value || '');
const displayValue = currentValue || emptyPlaceholder;
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();
React.useEffect(() => {
@ -54,7 +61,11 @@ function EditableInput({
aria-label={`Edit ${displayValue} value`}
>
<span>{displayValue}</span>
<EditIcon className="editable-input__icon" focusable="false" aria-hidden="true" />
<EditIcon
className="editable-input__icon"
focusable="false"
aria-hidden="true"
/>
</button>
<InputComponent
@ -84,7 +95,7 @@ EditableInput.propTypes = {
emptyPlaceholder: PropTypes.string,
InputComponent: PropTypes.elementType,
// eslint-disable-next-line react/forbid-prop-types
inputProps: PropTypes.object,
inputProps: PropTypes.object, // eslint-disable-line
onChange: PropTypes.func.isRequired,
validate: PropTypes.func,
value: PropTypes.string,

View file

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

View file

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

View file

@ -44,13 +44,22 @@ function isUserOwner(props) {
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;
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
props.persistState();
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
props.persistState();
window.onbeforeunload = null;
@ -61,6 +70,7 @@ function warnIfUnsavedChanges(props) { // eslint-disable-line
props.setUnsavedChanges(false);
return true;
}
return false;
}
class IDEView extends React.Component {
@ -70,7 +80,7 @@ class IDEView extends React.Component {
this.state = {
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;
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;
@ -103,11 +116,15 @@ class IDEView extends React.Component {
}
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) {
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) {
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 (
this.props.selectedFile.name === prevProps.selectedFile.name &&
this.props.selectedFile.content !== prevProps.selectedFile.content) {
this.props.selectedFile.content !== prevProps.selectedFile.content
) {
if (this.autosaveInterval) {
clearTimeout(this.autosaveInterval);
}
@ -141,7 +163,8 @@ class IDEView extends React.Component {
}
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) {
// 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.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());
} else if (this.props.user.authenticated) {
this.props.cloneProject();
@ -164,23 +193,41 @@ class IDEView extends React.Component {
this.props.showErrorModal('forceAuthentication');
}
// 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.stopPropagation();
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.stopPropagation();
this.props.startSketch();
// 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();
this.props.setAllAccessibleOutput(false);
// 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();
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();
if (!this.props.ide.sidebarIsExpanded) {
this.props.expandSidebar();
@ -219,7 +266,7 @@ class IDEView extends React.Component {
cmController={this.cmController}
/>
<Toolbar key={this.props.project.id} />
{this.props.ide.preferencesIsVisible &&
{this.props.ide.preferencesIsVisible && (
<Overlay
title={this.props.t('Settings')}
ariaLabel="settings"
@ -246,7 +293,7 @@ class IDEView extends React.Component {
setTheme={this.props.setTheme}
/>
</Overlay>
}
)}
<main className="editor-preview-container">
<SplitPane
split="vertical"
@ -275,10 +322,17 @@ class IDEView extends React.Component {
<SplitPane
split="vertical"
defaultSize="50%"
onChange={() => { this.overlay.style.display = 'block'; }}
onDragFinished={() => { this.overlay.style.display = 'none'; }}
onChange={() => {
this.overlay.style.display = 'block';
}}
onDragFinished={() => {
this.overlay.style.display = 'none';
}}
resizerStyle={{
borderLeftWidth: '2px', borderRightWidth: '2px', width: '2px', margin: '0px 0px'
borderLeftWidth: '2px',
borderRightWidth: '2px',
width: '2px',
margin: '0px 0px',
}}
>
<SplitPane
@ -304,7 +358,9 @@ class IDEView extends React.Component {
editorOptionsVisible={this.props.ide.editorOptionsVisible}
showEditorOptions={this.props.showEditorOptions}
closeEditorOptions={this.props.closeEditorOptions}
showKeyboardShortcutModal={this.props.showKeyboardShortcutModal}
showKeyboardShortcutModal={
this.props.showKeyboardShortcutModal
}
setUnsavedChanges={this.props.setUnsavedChanges}
isPlaying={this.props.ide.isPlaying}
theme={this.props.preferences.theme}
@ -321,37 +377,44 @@ class IDEView extends React.Component {
consoleEvents={this.props.console}
showRuntimeErrorWarning={this.props.showRuntimeErrorWarning}
hideRuntimeErrorWarning={this.props.hideRuntimeErrorWarning}
runtimeErrorWarningVisible={this.props.ide.runtimeErrorWarningVisible}
provideController={(ctl) => { this.cmController = ctl; }}
runtimeErrorWarningVisible={
this.props.ide.runtimeErrorWarningVisible
}
provideController={(ctl) => {
this.cmController = ctl;
}}
/>
<Console />
</SplitPane>
<section className="preview-frame-holder">
<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>
<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>
{(
(
(this.props.preferences.textOutput ||
{((this.props.preferences.textOutput ||
this.props.preferences.gridOutput ||
this.props.preferences.soundOutput
) &&
this.props.ide.isPlaying
) ||
this.props.ide.isAccessibleOutputPlaying
)
}
this.props.preferences.soundOutput) &&
this.props.ide.isPlaying) ||
this.props.ide.isAccessibleOutputPlaying}
</div>
<PreviewFrame
htmlFile={this.props.htmlFile}
files={this.props.files}
content={this.props.selectedFile.content}
isPlaying={this.props.ide.isPlaying}
isAccessibleOutputPlaying={this.props.ide.isAccessibleOutputPlaying}
isAccessibleOutputPlaying={
this.props.ide.isAccessibleOutputPlaying
}
textOutput={this.props.preferences.textOutput}
gridOutput={this.props.preferences.gridOutput}
soundOutput={this.props.preferences.soundOutput}
@ -373,21 +436,17 @@ class IDEView extends React.Component {
</SplitPane>
</SplitPane>
</main>
{ this.props.ide.modalIsVisible &&
<NewFileModal />
}
{this.props.ide.newFolderModalVisible &&
{this.props.ide.modalIsVisible && <NewFileModal />}
{this.props.ide.newFolderModalVisible && (
<NewFolderModal
closeModal={this.props.closeNewFolderModal}
createFolder={this.props.createFolder}
/>
}
{this.props.ide.uploadFileModalVisible &&
<UploadFileModal
closeModal={this.props.closeUploadFileModal}
/>
}
{ this.props.location.pathname === '/about' &&
)}
{this.props.ide.uploadFileModalVisible && (
<UploadFileModal closeModal={this.props.closeUploadFileModal} />
)}
{this.props.location.pathname === '/about' && (
<Overlay
title={this.props.t('About')}
previousPath={this.props.ide.previousPath}
@ -395,8 +454,8 @@ class IDEView extends React.Component {
>
<About previousPath={this.props.ide.previousPath} />
</Overlay>
}
{this.props.location.pathname === '/feedback' &&
)}
{this.props.location.pathname === '/feedback' && (
<Overlay
title="Submit Feedback"
previousPath={this.props.ide.previousPath}
@ -404,8 +463,8 @@ class IDEView extends React.Component {
>
<Feedback previousPath={this.props.ide.previousPath} />
</Overlay>
}
{this.props.location.pathname.match(/add-to-collection$/) &&
)}
{this.props.location.pathname.match(/add-to-collection$/) && (
<Overlay
ariaLabel="add to collection"
title="Add to collection"
@ -419,8 +478,8 @@ class IDEView extends React.Component {
user={this.props.user}
/>
</Overlay>
}
{this.props.ide.shareModalVisible &&
)}
{this.props.ide.shareModalVisible && (
<Overlay
title="Share"
ariaLabel="share"
@ -432,8 +491,8 @@ class IDEView extends React.Component {
ownerUsername={this.props.ide.shareModalProjectUsername}
/>
</Overlay>
}
{this.props.ide.keyboardShortcutVisible &&
)}
{this.props.ide.keyboardShortcutVisible && (
<Overlay
title={this.props.t('KeyboardShortcuts')}
ariaLabel="keyboard shortcuts"
@ -441,8 +500,8 @@ class IDEView extends React.Component {
>
<KeyboardShortcutModal />
</Overlay>
}
{this.props.ide.errorType &&
)}
{this.props.ide.errorType && (
<Overlay
title="Error"
ariaLabel="error"
@ -453,7 +512,7 @@ class IDEView extends React.Component {
closeModal={this.props.hideErrorModal}
/>
</Overlay>
}
)}
</div>
);
}
@ -466,19 +525,19 @@ IDEView.propTypes = {
reset_password_token: PropTypes.string,
}).isRequired,
location: PropTypes.shape({
pathname: PropTypes.string
pathname: PropTypes.string,
}).isRequired,
getProject: PropTypes.func.isRequired,
user: PropTypes.shape({
authenticated: PropTypes.bool.isRequired,
id: PropTypes.string,
username: PropTypes.string
username: PropTypes.string,
}).isRequired,
saveProject: PropTypes.func.isRequired,
ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired,
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.array,
consoleEvent: PropTypes.array, // eslint-disable-line
modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired,
consoleIsExpanded: PropTypes.bool.isRequired,
@ -500,7 +559,7 @@ IDEView.propTypes = {
justOpenedProject: PropTypes.bool.isRequired,
errorType: PropTypes.string,
runtimeErrorWarningVisible: PropTypes.bool.isRequired,
uploadFileModalVisible: PropTypes.bool.isRequired
uploadFileModalVisible: PropTypes.bool.isRequired,
}).isRequired,
stopSketch: PropTypes.func.isRequired,
project: PropTypes.shape({
@ -508,12 +567,12 @@ IDEView.propTypes = {
name: PropTypes.string.isRequired,
owner: PropTypes.shape({
username: PropTypes.string,
id: PropTypes.string
id: PropTypes.string,
}),
updatedAt: PropTypes.string
updatedAt: PropTypes.string,
}).isRequired,
editorAccessibility: PropTypes.shape({
lintMessages: PropTypes.array.isRequired,
lintMessages: PropTypes.array.isRequired, // eslint-disable-line
}).isRequired,
updateLintMessage: PropTypes.func.isRequired,
clearLintMessage: PropTypes.func.isRequired,
@ -527,7 +586,7 @@ IDEView.propTypes = {
gridOutput: PropTypes.bool.isRequired,
soundOutput: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired,
autorefresh: PropTypes.bool.isRequired
autorefresh: PropTypes.bool.isRequired,
}).isRequired,
closePreferences: PropTypes.func.isRequired,
setFontSize: PropTypes.func.isRequired,
@ -542,19 +601,19 @@ IDEView.propTypes = {
files: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
content: PropTypes.string.isRequired
content: PropTypes.string.isRequired,
})).isRequired,
updateFileContent: PropTypes.func.isRequired,
selectedFile: PropTypes.shape({
id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
name: PropTypes.string.isRequired,
}).isRequired,
setSelectedFile: PropTypes.func.isRequired,
htmlFile: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
content: PropTypes.string.isRequired
content: PropTypes.string.isRequired,
}).isRequired,
dispatchConsoleEvent: PropTypes.func.isRequired,
newFile: PropTypes.func.isRequired,
@ -577,11 +636,11 @@ IDEView.propTypes = {
showKeyboardShortcutModal: PropTypes.func.isRequired,
closeKeyboardShortcutModal: PropTypes.func.isRequired,
toast: PropTypes.shape({
isVisible: PropTypes.bool.isRequired
isVisible: PropTypes.bool.isRequired,
}).isRequired,
autosaveProject: PropTypes.func.isRequired,
router: PropTypes.shape({
setRouteLeaveHook: PropTypes.func
setRouteLeaveHook: PropTypes.func,
}).isRequired,
route: PropTypes.oneOfType([PropTypes.object, PropTypes.element]).isRequired,
setUnsavedChanges: PropTypes.func.isRequired,
@ -592,7 +651,7 @@ IDEView.propTypes = {
setPreviousPath: PropTypes.func.isRequired,
console: PropTypes.arrayOf(PropTypes.shape({
method: PropTypes.string.isRequired,
args: PropTypes.arrayOf(PropTypes.string)
args: PropTypes.arrayOf(PropTypes.string),
})).isRequired,
clearConsole: PropTypes.func.isRequired,
showErrorModal: PropTypes.func.isRequired,
@ -603,13 +662,14 @@ IDEView.propTypes = {
startSketch: PropTypes.func.isRequired,
openUploadFileModal: PropTypes.func.isRequired,
closeUploadFileModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
t: PropTypes.func.isRequired,
};
function mapStateToProps(state) {
return {
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 !== 'root'),
htmlFile: getHTMLFile(state.files),
@ -619,7 +679,7 @@ function mapStateToProps(state) {
user: state.user,
project: state.project,
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 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`
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
@ -39,11 +39,28 @@ const Expander = styled.div`
const MobileIDEView = (props) => {
const {
preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage,
selectedFile, updateFileContent, files,
closeEditorOptions, showEditorOptions, showKeyboardShortcutModal, setUnsavedChanges,
startRefreshSketch, stopSketch, expandSidebar, collapseSidebar, clearConsole, console,
showRuntimeErrorWarning, hideRuntimeErrorWarning, startSketch
preferences,
ide,
editorAccessibility,
project,
updateLintMessage,
clearLintMessage,
selectedFile,
updateFileContent,
files,
closeEditorOptions,
showEditorOptions,
showKeyboardShortcutModal,
setUnsavedChanges,
startRefreshSketch,
stopSketch,
expandSidebar,
collapseSidebar,
clearConsole,
console,
showRuntimeErrorWarning,
hideRuntimeErrorWarning,
startSketch,
} = props;
const [tmController, setTmController] = useState(null); // eslint-disable-line
@ -55,7 +72,11 @@ const MobileIDEView = (props) => {
title={project.name}
subtitle={selectedFile.name}
leftButton={
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />
<IconButton
to="/mobile"
icon={ExitIcon}
aria-label="Return to original editor"
/>
}
>
<IconButton
@ -64,7 +85,14 @@ const MobileIDEView = (props) => {
icon={PreferencesIcon}
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>
<IDEWrapper>
@ -104,16 +132,18 @@ const MobileIDEView = (props) => {
/>
</IDEWrapper>
<Footer>
{ide.consoleIsExpanded && <Expander expanded><Console /></Expander>}
{ide.consoleIsExpanded && (
<Expander expanded>
<Console />
</Expander>
)}
<ActionStrip />
</Footer>
</Screen>
);
};
MobileIDEView.propTypes = {
preferences: PropTypes.shape({
fontSize: PropTypes.number.isRequired,
autosave: PropTypes.bool.isRequired,
@ -124,13 +154,13 @@ MobileIDEView.propTypes = {
gridOutput: PropTypes.bool.isRequired,
soundOutput: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired,
autorefresh: PropTypes.bool.isRequired
autorefresh: PropTypes.bool.isRequired,
}).isRequired,
ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired,
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.array,
consoleEvent: PropTypes.array, // eslint-disable-line
modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired,
consoleIsExpanded: PropTypes.bool.isRequired,
@ -152,11 +182,11 @@ MobileIDEView.propTypes = {
justOpenedProject: PropTypes.bool.isRequired,
errorType: PropTypes.string,
runtimeErrorWarningVisible: PropTypes.bool.isRequired,
uploadFileModalVisible: PropTypes.bool.isRequired
uploadFileModalVisible: PropTypes.bool.isRequired,
}).isRequired,
editorAccessibility: PropTypes.shape({
lintMessages: PropTypes.array.isRequired,
lintMessages: PropTypes.array.isRequired, // eslint-disable-line
}).isRequired,
project: PropTypes.shape({
@ -164,9 +194,9 @@ MobileIDEView.propTypes = {
name: PropTypes.string.isRequired,
owner: PropTypes.shape({
username: PropTypes.string,
id: PropTypes.string
id: PropTypes.string,
}),
updatedAt: PropTypes.string
updatedAt: PropTypes.string,
}).isRequired,
startSketch: PropTypes.func.isRequired,
@ -178,7 +208,7 @@ MobileIDEView.propTypes = {
selectedFile: PropTypes.shape({
id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
name: PropTypes.string.isRequired,
}).isRequired,
updateFileContent: PropTypes.func.isRequired,
@ -186,7 +216,7 @@ MobileIDEView.propTypes = {
files: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
content: PropTypes.string.isRequired
content: PropTypes.string.isRequired,
})).isRequired,
closeEditorOptions: PropTypes.func.isRequired,
@ -209,7 +239,7 @@ MobileIDEView.propTypes = {
console: PropTypes.arrayOf(PropTypes.shape({
method: PropTypes.string.isRequired,
args: PropTypes.arrayOf(PropTypes.string)
args: PropTypes.arrayOf(PropTypes.string),
})).isRequired,
showRuntimeErrorWarning: PropTypes.func.isRequired,
@ -219,15 +249,15 @@ MobileIDEView.propTypes = {
user: PropTypes.shape({
authenticated: PropTypes.bool.isRequired,
id: PropTypes.string,
username: PropTypes.string
username: PropTypes.string,
}).isRequired,
};
function mapStateToProps(state) {
return {
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 !== 'root'),
htmlFile: getHTMLFile(state.files),
@ -237,7 +267,7 @@ function mapStateToProps(state) {
user: state.user,
project: state.project,
toast: state.toast,
console: state.console
console: state.console,
};
}
@ -258,5 +288,4 @@ function mapDispatchToProps(dispatch) {
);
}
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';
export const APIKeyPropType = PropTypes.shape({
id: PropTypes.object.isRequired,
token: PropTypes.object,
id: PropTypes.object.isRequired, // eslint-disable-line
token: PropTypes.object, // eslint-disable-line
label: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
lastUsedAt: PropTypes.string,
@ -30,7 +30,7 @@ class APIKeyForm extends React.Component {
const { keyLabel } = this.state;
this.setState({
keyLabel: ''
keyLabel: '',
});
this.props.createApiKey(keyLabel);
@ -64,18 +64,25 @@ class APIKeyForm extends React.Component {
<div className="api-key-form">
<p className="api-key-form__summary">
Personal Access Tokens act like your password to allow automated
scripts to access the Editor API. Create a token for each script
that needs access.
scripts to access the Editor API. Create a token for each script that
needs access.
</p>
<div className="api-key-form__section">
<h3 className="api-key-form__title">Create new token</h3>
<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
className="form__input"
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"
type="text"
value={this.state.keyLabel}
@ -90,18 +97,21 @@ class APIKeyForm extends React.Component {
</Button>
</form>
{
keyWithToken && (
{keyWithToken && (
<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">
Make sure to copy your new personal access token now.
You wont be able to see it again!
Make sure to copy your new personal access token now. You wont
be able to see it again!
</p>
<CopyableInput label={keyWithToken.label} value={keyWithToken.token} />
<CopyableInput
label={keyWithToken.label}
value={keyWithToken.token}
/>
</div>
)
}
)}
</div>
<div className="api-key-form__section">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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