Merge branch 'develop' into fix/mobile-sidebar-color
This commit is contained in:
commit
b412ade583
10 changed files with 329 additions and 287 deletions
|
@ -14,6 +14,7 @@ import Preferences from '../images/preferences.svg';
|
||||||
import Play from '../images/triangle-arrow-right.svg';
|
import Play from '../images/triangle-arrow-right.svg';
|
||||||
import More from '../images/more.svg';
|
import More from '../images/more.svg';
|
||||||
import Code from '../images/code.svg';
|
import Code from '../images/code.svg';
|
||||||
|
import Save from '../images/save.svg';
|
||||||
import Terminal from '../images/terminal.svg';
|
import Terminal from '../images/terminal.svg';
|
||||||
|
|
||||||
import Folder from '../images/folder-padded.svg';
|
import Folder from '../images/folder-padded.svg';
|
||||||
|
@ -87,6 +88,7 @@ export const PlayIcon = withLabel(Play);
|
||||||
export const MoreIcon = withLabel(More);
|
export const MoreIcon = withLabel(More);
|
||||||
export const TerminalIcon = withLabel(Terminal);
|
export const TerminalIcon = withLabel(Terminal);
|
||||||
export const CodeIcon = withLabel(Code);
|
export const CodeIcon = withLabel(Code);
|
||||||
|
export const SaveIcon = withLabel(Save);
|
||||||
|
|
||||||
export const FolderIcon = withLabel(Folder);
|
export const FolderIcon = withLabel(Folder);
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { bindActionCreators } from 'redux';
|
import styled from 'styled-components';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { remSize, prop } from '../../theme';
|
import { remSize, prop } from '../../theme';
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
import { TerminalIcon, FolderIcon } from '../../common/icons';
|
|
||||||
import * as IDEActions from '../../modules/IDE/actions/ide';
|
|
||||||
|
|
||||||
const BottomBarContent = styled.div`
|
const BottomBarContent = styled.div`
|
||||||
padding: ${remSize(8)};
|
padding: ${remSize(8)};
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8,1fr);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
max-height: ${remSize(32)};
|
max-height: ${remSize(32)};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path { fill: ${prop('primaryTextColor')} !important }
|
path { fill: ${prop('primaryTextColor')} !important }
|
||||||
|
@ -25,42 +21,28 @@ const BottomBarContent = styled.div`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Maybe this component shouldn't be connected, and instead just receive the `actions` prop
|
const ActionStrip = ({ actions }) => (
|
||||||
const ActionStrip = ({ toggleExplorer }) => {
|
|
||||||
const { expandConsole, collapseConsole } = bindActionCreators(IDEActions, useDispatch());
|
|
||||||
const { consoleIsExpanded } = useSelector(state => state.ide);
|
|
||||||
|
|
||||||
const actions = [
|
|
||||||
{
|
|
||||||
icon: TerminalIcon, inverted: true, aria: 'Open terminal console', action: consoleIsExpanded ? collapseConsole : expandConsole
|
|
||||||
},
|
|
||||||
{ icon: FolderIcon, aria: 'Open files explorer', action: toggleExplorer }
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BottomBarContent>
|
<BottomBarContent>
|
||||||
{actions.map(({
|
{actions.map(({
|
||||||
icon, aria, action, inverted
|
icon, aria, action, inverted
|
||||||
}) =>
|
}) =>
|
||||||
(
|
(<IconButton
|
||||||
<IconButton
|
|
||||||
inverted={inverted}
|
inverted={inverted}
|
||||||
className={inverted && 'inverted'}
|
className={inverted && 'inverted'}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
aria-label={aria}
|
aria-label={aria}
|
||||||
key={`bottom-bar-${aria}`}
|
key={`bottom-bar-${aria}`}
|
||||||
onClick={() => action()}
|
onClick={action}
|
||||||
/>))}
|
/>))}
|
||||||
</BottomBarContent>
|
</BottomBarContent>);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionStrip.propTypes = {
|
ActionStrip.propTypes = {
|
||||||
toggleExplorer: PropTypes.func
|
actions: PropTypes.arrayOf(PropTypes.shape({
|
||||||
};
|
icon: PropTypes.any,
|
||||||
|
aria: PropTypes.string.isRequired,
|
||||||
ActionStrip.defaultProps = {
|
action: PropTypes.func.isRequired,
|
||||||
toggleExplorer: () => {}
|
inverted: PropTypes.bool
|
||||||
|
})).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ActionStrip;
|
export default ActionStrip;
|
||||||
|
|
|
@ -35,6 +35,12 @@ const HeaderDiv = styled.div`
|
||||||
}
|
}
|
||||||
|
|
||||||
& svg path { fill: ${textColor} !important; }
|
& svg path { fill: ${textColor} !important; }
|
||||||
|
|
||||||
|
.editor__unsaved-changes svg {
|
||||||
|
width: ${remSize(16)};
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: top
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const IconContainer = styled.div`
|
const IconContainer = styled.div`
|
||||||
|
@ -71,8 +77,9 @@ const Header = ({
|
||||||
</HeaderDiv>
|
</HeaderDiv>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
Header.propTypes = {
|
Header.propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
subtitle: PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
leftButton: PropTypes.element,
|
leftButton: PropTypes.element,
|
||||||
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
|
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
|
||||||
|
|
3
client/images/save.svg
Normal file
3
client/images/save.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="-4 -4 32 32" width="24">
|
||||||
|
<path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 249 B |
|
@ -126,7 +126,7 @@ function getSynchedProject(currentState, responseProject) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveProject(selectedFile = null, autosave = false) {
|
export function saveProject(selectedFile = null, autosave = false, mobile = false) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
if (state.project.isSaving) {
|
if (state.project.isSaving) {
|
||||||
|
@ -185,16 +185,15 @@ export function saveProject(selectedFile = null, autosave = false) {
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch(endSavingProject());
|
dispatch(endSavingProject());
|
||||||
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
|
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
|
||||||
|
|
||||||
|
dispatch(setNewProject(synchedProject));
|
||||||
|
dispatch(setUnsavedChanges(false));
|
||||||
|
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
|
||||||
|
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
dispatch(setNewProject(synchedProject));
|
|
||||||
dispatch(setUnsavedChanges(false));
|
|
||||||
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
|
|
||||||
dispatch(setUnsavedChanges(true));
|
dispatch(setUnsavedChanges(true));
|
||||||
} else {
|
|
||||||
dispatch(setNewProject(synchedProject));
|
|
||||||
dispatch(setUnsavedChanges(false));
|
|
||||||
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(projectSaveSuccess());
|
dispatch(projectSaveSuccess());
|
||||||
if (!autosave) {
|
if (!autosave) {
|
||||||
if (state.preferences.autosave) {
|
if (state.preferences.autosave) {
|
||||||
|
@ -222,9 +221,9 @@ export function saveProject(selectedFile = null, autosave = false) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function autosaveProject() {
|
export function autosaveProject(mobile = false) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
saveProject(null, true)(dispatch, getState);
|
saveProject(null, true, mobile)(dispatch, getState);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ import { CSSLint } from 'csslint';
|
||||||
import { HTMLHint } from 'htmlhint';
|
import { HTMLHint } from 'htmlhint';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
import '../../../utils/htmlmixed';
|
import '../../../utils/htmlmixed';
|
||||||
import '../../../utils/p5-javascript';
|
import '../../../utils/p5-javascript';
|
||||||
import '../../../utils/webGL-clike';
|
import '../../../utils/webGL-clike';
|
||||||
|
@ -40,6 +42,16 @@ import beepUrl from '../../../sounds/audioAlert.mp3';
|
||||||
import UnsavedChangesDotIcon from '../../../images/unsaved-changes-dot.svg';
|
import UnsavedChangesDotIcon from '../../../images/unsaved-changes-dot.svg';
|
||||||
import RightArrowIcon from '../../../images/right-arrow.svg';
|
import RightArrowIcon from '../../../images/right-arrow.svg';
|
||||||
import LeftArrowIcon from '../../../images/left-arrow.svg';
|
import LeftArrowIcon from '../../../images/left-arrow.svg';
|
||||||
|
import { getHTMLFile } from '../reducers/files';
|
||||||
|
|
||||||
|
import * as FileActions from '../actions/files';
|
||||||
|
import * as IDEActions from '../actions/ide';
|
||||||
|
import * as ProjectActions from '../actions/project';
|
||||||
|
import * as EditorAccessibilityActions from '../actions/editorAccessibility';
|
||||||
|
import * as PreferencesActions from '../actions/preferences';
|
||||||
|
import * as UserActions from '../../User/actions';
|
||||||
|
import * as ToastActions from '../actions/toast';
|
||||||
|
import * as ConsoleActions from '../actions/console';
|
||||||
|
|
||||||
search(CodeMirror);
|
search(CodeMirror);
|
||||||
|
|
||||||
|
@ -413,4 +425,47 @@ Editor.defaultProps = {
|
||||||
consoleEvents: [],
|
consoleEvents: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(Editor);
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
files: state.files,
|
||||||
|
file:
|
||||||
|
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),
|
||||||
|
ide: state.ide,
|
||||||
|
preferences: state.preferences,
|
||||||
|
editorAccessibility: state.editorAccessibility,
|
||||||
|
user: state.user,
|
||||||
|
project: state.project,
|
||||||
|
toast: state.toast,
|
||||||
|
console: state.console,
|
||||||
|
|
||||||
|
...state.preferences,
|
||||||
|
...state.ide,
|
||||||
|
...state.project,
|
||||||
|
...state.editorAccessibility,
|
||||||
|
isExpanded: state.ide.consoleIsExpanded,
|
||||||
|
projectSavedTime: state.project.updatedAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators(
|
||||||
|
Object.assign(
|
||||||
|
{},
|
||||||
|
EditorAccessibilityActions,
|
||||||
|
FileActions,
|
||||||
|
ProjectActions,
|
||||||
|
IDEActions,
|
||||||
|
PreferencesActions,
|
||||||
|
UserActions,
|
||||||
|
ToastActions,
|
||||||
|
ConsoleActions
|
||||||
|
),
|
||||||
|
dispatch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(Editor));
|
||||||
|
|
|
@ -343,46 +343,7 @@ class IDEView extends React.Component {
|
||||||
allowResize={this.props.ide.consoleIsExpanded}
|
allowResize={this.props.ide.consoleIsExpanded}
|
||||||
className="editor-preview-subpanel"
|
className="editor-preview-subpanel"
|
||||||
>
|
>
|
||||||
<Editor
|
<Editor provideController={(ctl) => { this.cmController = ctl; }} />
|
||||||
lintWarning={this.props.preferences.lintWarning}
|
|
||||||
linewrap={this.props.preferences.linewrap}
|
|
||||||
lintMessages={this.props.editorAccessibility.lintMessages}
|
|
||||||
updateLintMessage={this.props.updateLintMessage}
|
|
||||||
clearLintMessage={this.props.clearLintMessage}
|
|
||||||
file={this.props.selectedFile}
|
|
||||||
updateFileContent={this.props.updateFileContent}
|
|
||||||
fontSize={this.props.preferences.fontSize}
|
|
||||||
lineNumbers={this.props.preferences.lineNumbers}
|
|
||||||
files={this.props.files}
|
|
||||||
editorOptionsVisible={this.props.ide.editorOptionsVisible}
|
|
||||||
showEditorOptions={this.props.showEditorOptions}
|
|
||||||
closeEditorOptions={this.props.closeEditorOptions}
|
|
||||||
showKeyboardShortcutModal={
|
|
||||||
this.props.showKeyboardShortcutModal
|
|
||||||
}
|
|
||||||
setUnsavedChanges={this.props.setUnsavedChanges}
|
|
||||||
isPlaying={this.props.ide.isPlaying}
|
|
||||||
theme={this.props.preferences.theme}
|
|
||||||
startRefreshSketch={this.props.startRefreshSketch}
|
|
||||||
stopSketch={this.props.stopSketch}
|
|
||||||
autorefresh={this.props.preferences.autorefresh}
|
|
||||||
unsavedChanges={this.props.ide.unsavedChanges}
|
|
||||||
projectSavedTime={this.props.project.updatedAt}
|
|
||||||
isExpanded={this.props.ide.sidebarIsExpanded}
|
|
||||||
expandSidebar={this.props.expandSidebar}
|
|
||||||
collapseSidebar={this.props.collapseSidebar}
|
|
||||||
isUserOwner={isUserOwner(this.props)}
|
|
||||||
clearConsole={this.props.clearConsole}
|
|
||||||
consoleEvents={this.props.console}
|
|
||||||
showRuntimeErrorWarning={this.props.showRuntimeErrorWarning}
|
|
||||||
hideRuntimeErrorWarning={this.props.hideRuntimeErrorWarning}
|
|
||||||
runtimeErrorWarningVisible={
|
|
||||||
this.props.ide.runtimeErrorWarningVisible
|
|
||||||
}
|
|
||||||
provideController={(ctl) => {
|
|
||||||
this.cmController = ctl;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Console />
|
<Console />
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
<section className="preview-frame-holder">
|
<section className="preview-frame-holder">
|
||||||
|
@ -533,31 +494,25 @@ IDEView.propTypes = {
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
saveProject: PropTypes.func.isRequired,
|
saveProject: PropTypes.func.isRequired,
|
||||||
ide: PropTypes.shape({
|
ide: PropTypes.shape({
|
||||||
isPlaying: PropTypes.bool.isRequired,
|
errorType: PropTypes.string,
|
||||||
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
|
keyboardShortcutVisible: PropTypes.bool.isRequired,
|
||||||
consoleEvent: PropTypes.array, // eslint-disable-line
|
|
||||||
modalIsVisible: PropTypes.bool.isRequired,
|
|
||||||
sidebarIsExpanded: PropTypes.bool.isRequired,
|
|
||||||
consoleIsExpanded: PropTypes.bool.isRequired,
|
|
||||||
preferencesIsVisible: PropTypes.bool.isRequired,
|
|
||||||
projectOptionsVisible: PropTypes.bool.isRequired,
|
|
||||||
newFolderModalVisible: PropTypes.bool.isRequired,
|
|
||||||
shareModalVisible: PropTypes.bool.isRequired,
|
shareModalVisible: PropTypes.bool.isRequired,
|
||||||
shareModalProjectId: PropTypes.string.isRequired,
|
shareModalProjectId: PropTypes.string.isRequired,
|
||||||
shareModalProjectName: PropTypes.string.isRequired,
|
shareModalProjectName: PropTypes.string.isRequired,
|
||||||
shareModalProjectUsername: PropTypes.string.isRequired,
|
shareModalProjectUsername: PropTypes.string.isRequired,
|
||||||
editorOptionsVisible: PropTypes.bool.isRequired,
|
|
||||||
keyboardShortcutVisible: PropTypes.bool.isRequired,
|
|
||||||
unsavedChanges: PropTypes.bool.isRequired,
|
|
||||||
infiniteLoop: PropTypes.bool.isRequired,
|
|
||||||
previewIsRefreshing: PropTypes.bool.isRequired,
|
|
||||||
infiniteLoopMessage: PropTypes.string.isRequired,
|
|
||||||
projectSavedTime: PropTypes.string,
|
|
||||||
previousPath: PropTypes.string.isRequired,
|
previousPath: PropTypes.string.isRequired,
|
||||||
justOpenedProject: PropTypes.bool.isRequired,
|
previewIsRefreshing: PropTypes.bool.isRequired,
|
||||||
errorType: PropTypes.string,
|
isPlaying: PropTypes.bool.isRequired,
|
||||||
runtimeErrorWarningVisible: PropTypes.bool.isRequired,
|
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
|
||||||
|
projectOptionsVisible: PropTypes.bool.isRequired,
|
||||||
|
preferencesIsVisible: PropTypes.bool.isRequired,
|
||||||
|
modalIsVisible: PropTypes.bool.isRequired,
|
||||||
uploadFileModalVisible: PropTypes.bool.isRequired,
|
uploadFileModalVisible: PropTypes.bool.isRequired,
|
||||||
|
newFolderModalVisible: PropTypes.bool.isRequired,
|
||||||
|
justOpenedProject: PropTypes.bool.isRequired,
|
||||||
|
sidebarIsExpanded: PropTypes.bool.isRequired,
|
||||||
|
consoleIsExpanded: PropTypes.bool.isRequired,
|
||||||
|
unsavedChanges: PropTypes.bool.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
project: PropTypes.shape({
|
project: PropTypes.shape({
|
||||||
|
@ -572,11 +527,9 @@ IDEView.propTypes = {
|
||||||
editorAccessibility: PropTypes.shape({
|
editorAccessibility: PropTypes.shape({
|
||||||
lintMessages: PropTypes.array.isRequired, // eslint-disable-line
|
lintMessages: PropTypes.array.isRequired, // eslint-disable-line
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
updateLintMessage: PropTypes.func.isRequired,
|
|
||||||
clearLintMessage: PropTypes.func.isRequired,
|
|
||||||
preferences: PropTypes.shape({
|
preferences: PropTypes.shape({
|
||||||
fontSize: PropTypes.number.isRequired,
|
|
||||||
autosave: PropTypes.bool.isRequired,
|
autosave: PropTypes.bool.isRequired,
|
||||||
|
fontSize: PropTypes.number.isRequired,
|
||||||
linewrap: PropTypes.bool.isRequired,
|
linewrap: PropTypes.bool.isRequired,
|
||||||
lineNumbers: PropTypes.bool.isRequired,
|
lineNumbers: PropTypes.bool.isRequired,
|
||||||
lintWarning: PropTypes.bool.isRequired,
|
lintWarning: PropTypes.bool.isRequired,
|
||||||
|
@ -602,7 +555,6 @@ IDEView.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
content: PropTypes.string.isRequired,
|
content: PropTypes.string.isRequired,
|
||||||
})).isRequired,
|
})).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,
|
||||||
|
@ -630,9 +582,6 @@ IDEView.propTypes = {
|
||||||
closeNewFileModal: PropTypes.func.isRequired,
|
closeNewFileModal: PropTypes.func.isRequired,
|
||||||
createFolder: PropTypes.func.isRequired,
|
createFolder: PropTypes.func.isRequired,
|
||||||
closeShareModal: PropTypes.func.isRequired,
|
closeShareModal: PropTypes.func.isRequired,
|
||||||
showEditorOptions: PropTypes.func.isRequired,
|
|
||||||
closeEditorOptions: 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,
|
||||||
|
@ -642,22 +591,14 @@ IDEView.propTypes = {
|
||||||
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,
|
|
||||||
setTheme: PropTypes.func.isRequired,
|
setTheme: PropTypes.func.isRequired,
|
||||||
endSketchRefresh: PropTypes.func.isRequired,
|
endSketchRefresh: PropTypes.func.isRequired,
|
||||||
startRefreshSketch: PropTypes.func.isRequired,
|
|
||||||
setBlobUrl: PropTypes.func.isRequired,
|
setBlobUrl: PropTypes.func.isRequired,
|
||||||
setPreviousPath: PropTypes.func.isRequired,
|
setPreviousPath: PropTypes.func.isRequired,
|
||||||
console: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
method: PropTypes.string.isRequired,
|
|
||||||
args: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
})).isRequired,
|
|
||||||
clearConsole: PropTypes.func.isRequired,
|
clearConsole: PropTypes.func.isRequired,
|
||||||
showErrorModal: PropTypes.func.isRequired,
|
showErrorModal: PropTypes.func.isRequired,
|
||||||
hideErrorModal: PropTypes.func.isRequired,
|
hideErrorModal: PropTypes.func.isRequired,
|
||||||
clearPersistedState: PropTypes.func.isRequired,
|
clearPersistedState: PropTypes.func.isRequired,
|
||||||
showRuntimeErrorWarning: PropTypes.func.isRequired,
|
|
||||||
hideRuntimeErrorWarning: PropTypes.func.isRequired,
|
|
||||||
startSketch: PropTypes.func.isRequired,
|
startSketch: PropTypes.func.isRequired,
|
||||||
openUploadFileModal: PropTypes.func.isRequired,
|
openUploadFileModal: PropTypes.func.isRequired,
|
||||||
closeUploadFileModal: PropTypes.func.isRequired,
|
closeUploadFileModal: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -8,19 +8,17 @@ import styled from 'styled-components';
|
||||||
// Imports to be Refactored
|
// Imports to be Refactored
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import * as FileActions from '../actions/files';
|
|
||||||
import * as IDEActions from '../actions/ide';
|
import * as IDEActions from '../actions/ide';
|
||||||
import * as ProjectActions from '../actions/project';
|
import * as ProjectActions from '../actions/project';
|
||||||
import * as EditorAccessibilityActions from '../actions/editorAccessibility';
|
|
||||||
import * as PreferencesActions from '../actions/preferences';
|
|
||||||
import * as UserActions from '../../User/actions';
|
|
||||||
import * as ToastActions from '../actions/toast';
|
|
||||||
import * as ConsoleActions from '../actions/console';
|
import * as ConsoleActions from '../actions/console';
|
||||||
import { getHTMLFile } from '../reducers/files';
|
import * as PreferencesActions from '../actions/preferences';
|
||||||
|
import * as EditorAccessibilityActions from '../actions/editorAccessibility';
|
||||||
|
|
||||||
// Local Imports
|
// Local Imports
|
||||||
import Editor from '../components/Editor';
|
import Editor from '../components/Editor';
|
||||||
import { PlayIcon, MoreIcon } from '../../../common/icons';
|
|
||||||
|
import { PlayIcon, MoreIcon, FolderIcon, PreferencesIcon, TerminalIcon, SaveIcon } from '../../../common/icons';
|
||||||
|
import UnsavedChangesDotIcon from '../../../images/unsaved-changes-dot.svg';
|
||||||
|
|
||||||
import IconButton from '../../../components/mobile/IconButton';
|
import IconButton from '../../../components/mobile/IconButton';
|
||||||
import Header from '../../../components/mobile/Header';
|
import Header from '../../../components/mobile/Header';
|
||||||
|
@ -33,28 +31,25 @@ import { remSize } from '../../../theme';
|
||||||
|
|
||||||
import ActionStrip from '../../../components/mobile/ActionStrip';
|
import ActionStrip from '../../../components/mobile/ActionStrip';
|
||||||
import useAsModal from '../../../components/useAsModal';
|
import useAsModal from '../../../components/useAsModal';
|
||||||
import { PreferencesIcon } from '../../../common/icons';
|
|
||||||
import Dropdown from '../../../components/Dropdown';
|
import Dropdown from '../../../components/Dropdown';
|
||||||
|
|
||||||
|
|
||||||
|
import { useEffectWithComparison, useEventListener } from '../../../utils/custom-hooks';
|
||||||
|
|
||||||
|
import * as device from '../../../utils/device';
|
||||||
|
|
||||||
|
const withChangeDot = (title, unsavedChanges = false) => (
|
||||||
|
<span>
|
||||||
|
{title}
|
||||||
|
<span className="editor__unsaved-changes">
|
||||||
|
{unsavedChanges &&
|
||||||
|
<UnsavedChangesDotIcon role="img" aria-label="Sketch has unsaved changes" focusable="false" />}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
const getRootFile = files => files && files.filter(file => file.name === 'root')[0];
|
const getRootFile = files => files && files.filter(file => file.name === 'root')[0];
|
||||||
const getRootFileID = files => (root => root && root.id)(getRootFile(files));
|
const getRootFileID = files => (root => root && root.id)(getRootFile(files));
|
||||||
|
|
||||||
const isUserOwner = ({ project, user }) =>
|
|
||||||
project.owner && project.owner.id === user.id;
|
|
||||||
|
|
||||||
|
|
||||||
// const userCanEditProject = (props) => {
|
|
||||||
// let canEdit;
|
|
||||||
// if (!props.owner) {
|
|
||||||
// canEdit = true;
|
|
||||||
// } else if (props.user.authenticated && props.owner.id === props.user.id) {
|
|
||||||
// canEdit = true;
|
|
||||||
// } else {
|
|
||||||
// canEdit = false;
|
|
||||||
// }
|
|
||||||
// return canEdit;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const Expander = styled.div`
|
const Expander = styled.div`
|
||||||
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
|
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
|
||||||
`;
|
`;
|
||||||
|
@ -80,24 +75,135 @@ const getNavOptions = (username = undefined, logoutUser = () => {}, toggleForceD
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const MobileIDEView = (props) => {
|
|
||||||
|
const isUserOwner = ({ project, user }) =>
|
||||||
|
project && project.owner && project.owner.id === user.id;
|
||||||
|
|
||||||
|
const canSaveProject = (project, user) =>
|
||||||
|
isUserOwner({ project, user }) || (user.authenticated && !project.owner);
|
||||||
|
|
||||||
|
// TODO: This could go into <Editor />
|
||||||
|
const handleGlobalKeydown = (props, cmController) => (e) => {
|
||||||
const {
|
const {
|
||||||
preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage,
|
user, project, ide,
|
||||||
selectedFile, updateFileContent, files, user, params,
|
setAllAccessibleOutput,
|
||||||
closeEditorOptions, showEditorOptions, logoutUser,
|
saveProject, cloneProject, showErrorModal, startSketch, stopSketch,
|
||||||
startRefreshSketch, stopSketch, expandSidebar, collapseSidebar, clearConsole, console,
|
expandSidebar, collapseSidebar, expandConsole, collapseConsole,
|
||||||
showRuntimeErrorWarning, hideRuntimeErrorWarning, startSketch, getProject, clearPersistedState, setUnsavedChanges,
|
closeNewFolderModal, closeUploadFileModal, closeNewFileModal
|
||||||
toggleForceDesktop
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [tmController, setTmController] = useState(null); // eslint-disable-line
|
|
||||||
|
const isMac = device.isMac();
|
||||||
|
|
||||||
|
// const ctrlDown = (e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac);
|
||||||
|
const ctrlDown = (isMac ? e.metaKey : e.ctrlKey);
|
||||||
|
|
||||||
|
if (ctrlDown) {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
stopSketch();
|
||||||
|
} else if (e.keyCode === 13) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
startSketch();
|
||||||
|
// 50 === 2
|
||||||
|
} else if (e.keyCode === 50
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
setAllAccessibleOutput(false);
|
||||||
|
// 49 === 1
|
||||||
|
} else if (e.keyCode === 49) {
|
||||||
|
e.preventDefault();
|
||||||
|
setAllAccessibleOutput(true);
|
||||||
|
}
|
||||||
|
} else if (e.keyCode === 83) {
|
||||||
|
// 83 === s
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (canSaveProject(project, user)) saveProject(cmController.getContent(), false, true);
|
||||||
|
else if (user.authenticated) cloneProject();
|
||||||
|
else showErrorModal('forceAuthentication');
|
||||||
|
|
||||||
|
// 13 === enter
|
||||||
|
} else if (e.keyCode === 66) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!ide.sidebarIsExpanded) expandSidebar();
|
||||||
|
else collapseSidebar();
|
||||||
|
}
|
||||||
|
} else if (e.keyCode === 192 && e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (ide.consoleIsExpanded) collapseConsole();
|
||||||
|
else expandConsole();
|
||||||
|
} else if (e.keyCode === 27) {
|
||||||
|
if (ide.newFolderModalVisible) closeNewFolderModal();
|
||||||
|
else if (ide.uploadFileModalVisible) closeUploadFileModal();
|
||||||
|
else if (ide.modalIsVisible) closeNewFileModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const autosave = (autosaveInterval, setAutosaveInterval) => (props, prevProps) => {
|
||||||
|
const {
|
||||||
|
autosaveProject, preferences, ide, selectedFile: file, project
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { selectedFile: oldFile } = prevProps;
|
||||||
|
|
||||||
|
const doAutosave = () => autosaveProject(true);
|
||||||
|
|
||||||
|
if (isUserOwner(props) && project.id) {
|
||||||
|
if (preferences.autosave && ide.unsavedChanges && !ide.justOpenedProject) {
|
||||||
|
if (file.name === oldFile.name && file.content !== oldFile.content) {
|
||||||
|
if (autosaveInterval) {
|
||||||
|
clearTimeout(autosaveInterval);
|
||||||
|
}
|
||||||
|
console.log('will save project in 20 seconds');
|
||||||
|
setAutosaveInterval(setTimeout(doAutosave, 20000));
|
||||||
|
}
|
||||||
|
} else if (autosaveInterval && !preferences.autosave) {
|
||||||
|
clearTimeout(autosaveInterval);
|
||||||
|
setAutosaveInterval(null);
|
||||||
|
}
|
||||||
|
} else if (autosaveInterval) {
|
||||||
|
clearTimeout(autosaveInterval);
|
||||||
|
setAutosaveInterval(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ide, preferences, project, selectedFile, user, params, unsavedChanges, expandConsole, collapseConsole,
|
||||||
|
// stopSketch, startSketch, getProject, clearPersistedState, autosaveProject, saveProject, files
|
||||||
|
|
||||||
|
const MobileIDEView = (props) => {
|
||||||
|
// const {
|
||||||
|
// preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage,
|
||||||
|
// selectedFile, updateFileContent, files, user, params,
|
||||||
|
// closeEditorOptions, showEditorOptions, logoutUser,
|
||||||
|
// startRefreshSketch, stopSketch, expandSidebar, collapseSidebar, clearConsole, console,
|
||||||
|
// showRuntimeErrorWarning, hideRuntimeErrorWarning, startSketch, getProject, clearPersistedState, setUnsavedChanges,
|
||||||
|
// toggleForceDesktop
|
||||||
|
// } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
ide, preferences, project, selectedFile, user, params, unsavedChanges, expandConsole, collapseConsole,
|
||||||
|
stopSketch, startSketch, getProject, clearPersistedState, autosaveProject, saveProject, files,
|
||||||
|
toggleForceDesktop, logoutUser
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
|
||||||
|
const [cmController, setCmController] = useState(null); // eslint-disable-line
|
||||||
|
|
||||||
const { username } = user;
|
const { username } = user;
|
||||||
|
const { consoleIsExpanded } = ide;
|
||||||
|
const { name: filename } = selectedFile;
|
||||||
|
|
||||||
// Force state reset
|
// Force state reset
|
||||||
useEffect(clearPersistedState, []);
|
useEffect(clearPersistedState, []);
|
||||||
useEffect(stopSketch, []);
|
useEffect(() => {
|
||||||
|
stopSketch();
|
||||||
|
collapseConsole();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Load Project
|
// Load Project
|
||||||
const [currentProjectID, setCurrentProjectID] = useState(null);
|
const [currentProjectID, setCurrentProjectID] = useState(null);
|
||||||
|
@ -124,12 +230,28 @@ const MobileIDEView = (props) => {
|
||||||
onPressClose={toggle}
|
onPressClose={toggle}
|
||||||
/>), true);
|
/>), true);
|
||||||
|
|
||||||
|
// TODO: This behavior could move to <Editor />
|
||||||
|
const [autosaveInterval, setAutosaveInterval] = useState(null);
|
||||||
|
useEffectWithComparison(autosave(autosaveInterval, setAutosaveInterval), {
|
||||||
|
autosaveProject, preferences, ide, selectedFile, project, user
|
||||||
|
});
|
||||||
|
|
||||||
|
useEventListener('keydown', handleGlobalKeydown(props, cmController), false, [props]);
|
||||||
|
|
||||||
|
const projectActions =
|
||||||
|
[{
|
||||||
|
icon: TerminalIcon, aria: 'Toggle console open/closed', action: consoleIsExpanded ? collapseConsole : expandConsole, inverted: true
|
||||||
|
},
|
||||||
|
{ icon: SaveIcon, aria: 'Save project', action: () => saveProject(cmController.getContent(), false, true) },
|
||||||
|
{ icon: FolderIcon, aria: 'Open files explorer', action: toggleExplorer }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen fullscreen>
|
<Screen fullscreen>
|
||||||
<Explorer />
|
<Explorer />
|
||||||
<Header
|
<Header
|
||||||
title={project.name}
|
title={withChangeDot(project.name, unsavedChanges)}
|
||||||
subtitle={selectedFile.name}
|
subtitle={filename}
|
||||||
>
|
>
|
||||||
<NavItem>
|
<NavItem>
|
||||||
|
|
||||||
|
@ -146,97 +268,43 @@ const MobileIDEView = (props) => {
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<IDEWrapper>
|
<IDEWrapper>
|
||||||
<Editor
|
<Editor provideController={setCmController} />
|
||||||
lintWarning={preferences.lintWarning}
|
|
||||||
linewrap={preferences.linewrap}
|
|
||||||
lintMessages={editorAccessibility.lintMessages}
|
|
||||||
updateLintMessage={updateLintMessage}
|
|
||||||
clearLintMessage={clearLintMessage}
|
|
||||||
file={selectedFile}
|
|
||||||
updateFileContent={updateFileContent}
|
|
||||||
fontSize={preferences.fontSize}
|
|
||||||
lineNumbers={preferences.lineNumbers}
|
|
||||||
files={files}
|
|
||||||
editorOptionsVisible={ide.editorOptionsVisible}
|
|
||||||
showEditorOptions={showEditorOptions}
|
|
||||||
closeEditorOptions={closeEditorOptions}
|
|
||||||
showKeyboard={ide.isPlaying}
|
|
||||||
theme={preferences.theme}
|
|
||||||
startRefreshSketch={startRefreshSketch}
|
|
||||||
stopSketch={stopSketch}
|
|
||||||
autorefresh={preferences.autorefresh}
|
|
||||||
unsavedChanges={ide.unsavedChanges}
|
|
||||||
projectSavedTime={project.updatedAt}
|
|
||||||
isExpanded={ide.sidebarIsExpanded}
|
|
||||||
expandSidebar={expandSidebar}
|
|
||||||
collapseSidebar={collapseSidebar}
|
|
||||||
isUserOwner={isUserOwner(props)}
|
|
||||||
clearConsole={clearConsole}
|
|
||||||
consoleEvents={console}
|
|
||||||
showRuntimeErrorWarning={showRuntimeErrorWarning}
|
|
||||||
hideRuntimeErrorWarning={hideRuntimeErrorWarning}
|
|
||||||
runtimeErrorWarningVisible={ide.runtimeErrorWarningVisible}
|
|
||||||
provideController={setTmController}
|
|
||||||
setUnsavedChanges={setUnsavedChanges}
|
|
||||||
/>
|
|
||||||
</IDEWrapper>
|
</IDEWrapper>
|
||||||
|
|
||||||
<Footer>
|
<Footer>
|
||||||
{ide.consoleIsExpanded && (
|
{consoleIsExpanded && (
|
||||||
<Expander expanded>
|
<Expander expanded>
|
||||||
<Console />
|
<Console />
|
||||||
</Expander>
|
</Expander>
|
||||||
)}
|
)}
|
||||||
<ActionStrip toggleExplorer={toggleExplorer} />
|
<ActionStrip actions={projectActions} />
|
||||||
</Footer>
|
</Footer>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGlobalKeydownProps = {
|
||||||
|
expandConsole: PropTypes.func.isRequired,
|
||||||
|
collapseConsole: PropTypes.func.isRequired,
|
||||||
|
expandSidebar: PropTypes.func.isRequired,
|
||||||
|
collapseSidebar: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
setAllAccessibleOutput: PropTypes.func.isRequired,
|
||||||
|
saveProject: PropTypes.func.isRequired,
|
||||||
|
cloneProject: PropTypes.func.isRequired,
|
||||||
|
showErrorModal: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
closeNewFolderModal: PropTypes.func.isRequired,
|
||||||
|
closeUploadFileModal: PropTypes.func.isRequired,
|
||||||
|
closeNewFileModal: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
MobileIDEView.propTypes = {
|
MobileIDEView.propTypes = {
|
||||||
preferences: PropTypes.shape({
|
|
||||||
fontSize: PropTypes.number.isRequired,
|
|
||||||
autosave: PropTypes.bool.isRequired,
|
|
||||||
linewrap: PropTypes.bool.isRequired,
|
|
||||||
lineNumbers: PropTypes.bool.isRequired,
|
|
||||||
lintWarning: PropTypes.bool.isRequired,
|
|
||||||
textOutput: PropTypes.bool.isRequired,
|
|
||||||
gridOutput: PropTypes.bool.isRequired,
|
|
||||||
soundOutput: PropTypes.bool.isRequired,
|
|
||||||
theme: PropTypes.string.isRequired,
|
|
||||||
autorefresh: PropTypes.bool.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
|
|
||||||
ide: PropTypes.shape({
|
ide: PropTypes.shape({
|
||||||
isPlaying: PropTypes.bool.isRequired,
|
|
||||||
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
|
|
||||||
consoleEvent: PropTypes.array,
|
|
||||||
modalIsVisible: PropTypes.bool.isRequired,
|
|
||||||
sidebarIsExpanded: PropTypes.bool.isRequired,
|
|
||||||
consoleIsExpanded: PropTypes.bool.isRequired,
|
consoleIsExpanded: PropTypes.bool.isRequired,
|
||||||
preferencesIsVisible: PropTypes.bool.isRequired,
|
|
||||||
projectOptionsVisible: PropTypes.bool.isRequired,
|
|
||||||
newFolderModalVisible: PropTypes.bool.isRequired,
|
|
||||||
shareModalVisible: PropTypes.bool.isRequired,
|
|
||||||
shareModalProjectId: PropTypes.string.isRequired,
|
|
||||||
shareModalProjectName: PropTypes.string.isRequired,
|
|
||||||
shareModalProjectUsername: PropTypes.string.isRequired,
|
|
||||||
editorOptionsVisible: PropTypes.bool.isRequired,
|
|
||||||
keyboardShortcutVisible: PropTypes.bool.isRequired,
|
|
||||||
unsavedChanges: PropTypes.bool.isRequired,
|
|
||||||
infiniteLoop: PropTypes.bool.isRequired,
|
|
||||||
previewIsRefreshing: PropTypes.bool.isRequired,
|
|
||||||
infiniteLoopMessage: PropTypes.string.isRequired,
|
|
||||||
projectSavedTime: PropTypes.string,
|
|
||||||
previousPath: PropTypes.string.isRequired,
|
|
||||||
justOpenedProject: PropTypes.bool.isRequired,
|
|
||||||
errorType: PropTypes.string,
|
|
||||||
runtimeErrorWarningVisible: PropTypes.bool.isRequired,
|
|
||||||
uploadFileModalVisible: PropTypes.bool.isRequired,
|
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
editorAccessibility: PropTypes.shape({
|
preferences: PropTypes.shape({
|
||||||
lintMessages: PropTypes.array.isRequired,
|
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
project: PropTypes.shape({
|
project: PropTypes.shape({
|
||||||
|
@ -246,14 +314,8 @@ MobileIDEView.propTypes = {
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
updatedAt: PropTypes.string,
|
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
startSketch: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
updateLintMessage: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
clearLintMessage: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
selectedFile: PropTypes.shape({
|
selectedFile: PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
|
@ -261,36 +323,12 @@ MobileIDEView.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
updateFileContent: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
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,
|
|
||||||
|
|
||||||
showEditorOptions: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
startRefreshSketch: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
stopSketch: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
expandSidebar: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
collapseSidebar: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
clearConsole: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
console: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
method: PropTypes.string.isRequired,
|
|
||||||
args: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
})).isRequired,
|
|
||||||
|
|
||||||
showRuntimeErrorWarning: PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
hideRuntimeErrorWarning: PropTypes.func.isRequired,
|
|
||||||
toggleForceDesktop: PropTypes.func.isRequired,
|
toggleForceDesktop: PropTypes.func.isRequired,
|
||||||
|
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
|
@ -301,26 +339,32 @@ MobileIDEView.propTypes = {
|
||||||
|
|
||||||
logoutUser: PropTypes.func.isRequired,
|
logoutUser: PropTypes.func.isRequired,
|
||||||
|
|
||||||
setUnsavedChanges: PropTypes.func.isRequired,
|
|
||||||
getProject: PropTypes.func.isRequired,
|
getProject: PropTypes.func.isRequired,
|
||||||
clearPersistedState: PropTypes.func.isRequired,
|
clearPersistedState: PropTypes.func.isRequired,
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
project_id: PropTypes.string,
|
project_id: PropTypes.string,
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
|
startSketch: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
unsavedChanges: PropTypes.bool.isRequired,
|
||||||
|
autosaveProject: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
|
||||||
|
...handleGlobalKeydownProps
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
files: state.files,
|
|
||||||
selectedFile:
|
selectedFile:
|
||||||
state.files.find(file => file.isSelectedFile) ||
|
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),
|
|
||||||
ide: state.ide,
|
ide: state.ide,
|
||||||
|
files: state.files,
|
||||||
|
unsavedChanges: state.ide.unsavedChanges,
|
||||||
preferences: state.preferences,
|
preferences: state.preferences,
|
||||||
editorAccessibility: state.editorAccessibility,
|
|
||||||
user: state.user,
|
user: state.user,
|
||||||
project: state.project,
|
project: state.project,
|
||||||
toast: state.toast,
|
toast: state.toast,
|
||||||
|
@ -328,21 +372,12 @@ function mapStateToProps(state) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
const mapDispatchToProps = dispatch => bindActionCreators({
|
||||||
return bindActionCreators(
|
...ProjectActions,
|
||||||
Object.assign(
|
...IDEActions,
|
||||||
{},
|
...ConsoleActions,
|
||||||
EditorAccessibilityActions,
|
...PreferencesActions,
|
||||||
FileActions,
|
...EditorAccessibilityActions
|
||||||
ProjectActions,
|
}, dispatch);
|
||||||
IDEActions,
|
|
||||||
PreferencesActions,
|
|
||||||
UserActions,
|
|
||||||
ToastActions,
|
|
||||||
ConsoleActions
|
|
||||||
),
|
|
||||||
dispatch
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileIDEView));
|
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileIDEView));
|
||||||
|
|
|
@ -40,3 +40,20 @@ export const useModalBehavior = (hideOverlay) => {
|
||||||
|
|
||||||
return [visible, trigger, setRef];
|
return [visible, trigger, setRef];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Usage: useEffectWithComparison((props, prevProps) => { ... }, { prop1, prop2 })
|
||||||
|
// This hook basically applies useEffect but keeping track of the last value of relevant props
|
||||||
|
// So you can passa a 2-param function to capture new and old values and do whatever with them.
|
||||||
|
export const useEffectWithComparison = (fn, props) => {
|
||||||
|
const [prevProps, update] = useState({});
|
||||||
|
|
||||||
|
return useEffect(() => {
|
||||||
|
fn(props, prevProps);
|
||||||
|
update(props);
|
||||||
|
}, Object.values(props));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useEventListener = (event, callback, useCapture = false, list = []) => useEffect(() => {
|
||||||
|
document.addEventListener(event, callback, useCapture);
|
||||||
|
return () => document.removeEventListener(event, callback, useCapture);
|
||||||
|
}, list);
|
||||||
|
|
1
client/utils/device.js
Normal file
1
client/utils/device.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const isMac = () => navigator.userAgent.toLowerCase().indexOf('mac') !== -1; // eslint-disable-line
|
Loading…
Reference in a new issue