Merge pull request #1467 from ghalestrilo/feature/mobile-sketch-view
Mobile Sketch Preview Screen
This commit is contained in:
commit
ff658c75ff
12 changed files with 385 additions and 108 deletions
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { remSize, prop } from '../theme';
|
import { prop } from '../theme';
|
||||||
import SortArrowUp from '../images/sort-arrow-up.svg';
|
import SortArrowUp from '../images/sort-arrow-up.svg';
|
||||||
import SortArrowDown from '../images/sort-arrow-down.svg';
|
import SortArrowDown from '../images/sort-arrow-down.svg';
|
||||||
import Github from '../images/github.svg';
|
import Github from '../images/github.svg';
|
||||||
|
@ -10,6 +10,8 @@ import Plus from '../images/plus-icon.svg';
|
||||||
import Close from '../images/close.svg';
|
import Close from '../images/close.svg';
|
||||||
import Exit from '../images/exit.svg';
|
import Exit from '../images/exit.svg';
|
||||||
import DropdownArrow from '../images/down-filled-triangle.svg';
|
import DropdownArrow from '../images/down-filled-triangle.svg';
|
||||||
|
import Preferences from '../images/preferences.svg';
|
||||||
|
import Play from '../images/triangle-arrow-right.svg';
|
||||||
|
|
||||||
// HOC that adds the right web accessibility props
|
// HOC that adds the right web accessibility props
|
||||||
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
|
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
|
||||||
|
@ -70,3 +72,5 @@ export const PlusIcon = withLabel(Plus);
|
||||||
export const CloseIcon = withLabel(Close);
|
export const CloseIcon = withLabel(Close);
|
||||||
export const ExitIcon = withLabel(Exit);
|
export const ExitIcon = withLabel(Exit);
|
||||||
export const DropdownArrowIcon = withLabel(DropdownArrow);
|
export const DropdownArrowIcon = withLabel(DropdownArrow);
|
||||||
|
export const PreferencesIcon = withLabel(Preferences);
|
||||||
|
export const PlayIcon = withLabel(Play);
|
||||||
|
|
20
client/components/mobile/Footer.jsx
Normal file
20
client/components/mobile/Footer.jsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { prop, remSize } from '../../theme';
|
||||||
|
|
||||||
|
const background = prop('MobilePanel.default.background');
|
||||||
|
const textColor = prop('primaryTextColor');
|
||||||
|
|
||||||
|
const Footer = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
background: ${background};
|
||||||
|
color: ${textColor};
|
||||||
|
padding: ${remSize(12)};
|
||||||
|
padding-left: ${remSize(32)};
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
bottom: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Footer;
|
30
client/components/mobile/Header.jsx
Normal file
30
client/components/mobile/Header.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { prop, remSize } from '../../theme';
|
||||||
|
|
||||||
|
const background = prop('MobilePanel.default.background');
|
||||||
|
const textColor = prop('primaryTextColor');
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
background: ${background};
|
||||||
|
color: ${textColor};
|
||||||
|
padding: ${remSize(12)};
|
||||||
|
padding-left: ${remSize(16)};
|
||||||
|
padding-right: ${remSize(16)};
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
svg {
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Header;
|
8
client/components/mobile/IDEWrapper.jsx
Normal file
8
client/components/mobile/IDEWrapper.jsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { remSize } from '../../theme';
|
||||||
|
|
||||||
|
export default styled.div`
|
||||||
|
z-index: 0;
|
||||||
|
margin-top: ${remSize(16)};
|
||||||
|
`;
|
30
client/components/mobile/IconButton.jsx
Normal file
30
client/components/mobile/IconButton.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import Button from '../../common/Button';
|
||||||
|
|
||||||
|
const ButtonWrapper = styled(Button)`
|
||||||
|
width: 3rem;
|
||||||
|
> svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const IconButton = (props) => {
|
||||||
|
const { icon, ...otherProps } = props;
|
||||||
|
const Icon = icon;
|
||||||
|
|
||||||
|
return (<ButtonWrapper
|
||||||
|
iconBefore={<Icon />}
|
||||||
|
kind={Button.kinds.inline}
|
||||||
|
focusable="false"
|
||||||
|
{...otherProps}
|
||||||
|
/>);
|
||||||
|
};
|
||||||
|
|
||||||
|
IconButton.propTypes = {
|
||||||
|
icon: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IconButton;
|
13
client/components/mobile/MobileScreen.jsx
Normal file
13
client/components/mobile/MobileScreen.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const Screen = ({ children }) => (
|
||||||
|
<div className="fullscreen-preview">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
Screen.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Screen;
|
|
@ -22,6 +22,23 @@ import {
|
||||||
import { hijackConsoleErrorsScript, startTag, getAllScriptOffsets }
|
import { hijackConsoleErrorsScript, startTag, getAllScriptOffsets }
|
||||||
from '../../../utils/consoleUtils';
|
from '../../../utils/consoleUtils';
|
||||||
|
|
||||||
|
|
||||||
|
const shouldRenderSketch = (props, prevProps = undefined) => {
|
||||||
|
const { isPlaying, previewIsRefreshing, fullView } = props;
|
||||||
|
|
||||||
|
// if the user explicitly clicks on the play button
|
||||||
|
if (isPlaying && previewIsRefreshing) return true;
|
||||||
|
|
||||||
|
if (!prevProps) return false;
|
||||||
|
|
||||||
|
return (props.isPlaying !== prevProps.isPlaying // if sketch starts or stops playing, want to rerender
|
||||||
|
|| props.isAccessibleOutputPlaying !== prevProps.isAccessibleOutputPlaying // if user switches textoutput preferences
|
||||||
|
|| props.textOutput !== prevProps.textOutput
|
||||||
|
|| props.gridOutput !== prevProps.gridOutput
|
||||||
|
|| props.soundOutput !== prevProps.soundOutput
|
||||||
|
|| (fullView && props.files[0].id !== prevProps.files[0].id));
|
||||||
|
};
|
||||||
|
|
||||||
class PreviewFrame extends React.Component {
|
class PreviewFrame extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -30,46 +47,17 @@ class PreviewFrame extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener('message', this.handleConsoleEvent);
|
window.addEventListener('message', this.handleConsoleEvent);
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
...this.props,
|
||||||
|
previewIsRefreshing: this.props.previewIsRefreshing,
|
||||||
|
isAccessibleOutputPlaying: this.props.isAccessibleOutputPlaying
|
||||||
|
};
|
||||||
|
if (shouldRenderSketch(props)) this.renderSketch();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
// if sketch starts or stops playing, want to rerender
|
if (shouldRenderSketch(this.props, prevProps)) this.renderSketch();
|
||||||
if (this.props.isPlaying !== prevProps.isPlaying) {
|
|
||||||
this.renderSketch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the user explicitly clicks on the play button
|
|
||||||
if (this.props.isPlaying && this.props.previewIsRefreshing) {
|
|
||||||
this.renderSketch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if user switches textoutput preferences
|
|
||||||
if (this.props.isAccessibleOutputPlaying !== prevProps.isAccessibleOutputPlaying) {
|
|
||||||
this.renderSketch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.textOutput !== prevProps.textOutput) {
|
|
||||||
this.renderSketch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.gridOutput !== prevProps.gridOutput) {
|
|
||||||
this.renderSketch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.soundOutput !== prevProps.soundOutput) {
|
|
||||||
this.renderSketch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.fullView && this.props.files[0].id !== prevProps.files[0].id) {
|
|
||||||
this.renderSketch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// small bug - if autorefresh is on, and the usr changes files
|
// small bug - if autorefresh is on, and the usr changes files
|
||||||
// in the sketch, preview will reload
|
// in the sketch, preview will reload
|
||||||
}
|
}
|
||||||
|
@ -398,7 +386,7 @@ PreviewFrame.propTypes = {
|
||||||
clearConsole: PropTypes.func.isRequired,
|
clearConsole: PropTypes.func.isRequired,
|
||||||
cmController: PropTypes.shape({
|
cmController: PropTypes.shape({
|
||||||
getContent: PropTypes.func
|
getContent: PropTypes.func
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
PreviewFrame.defaultProps = {
|
PreviewFrame.defaultProps = {
|
||||||
|
|
|
@ -20,93 +20,54 @@ import { getHTMLFile } from '../reducers/files';
|
||||||
|
|
||||||
// Local Imports
|
// Local Imports
|
||||||
import Editor from '../components/Editor';
|
import Editor from '../components/Editor';
|
||||||
import { prop, remSize } from '../../../theme';
|
import { PreferencesIcon, PlayIcon, ExitIcon } from '../../../common/icons';
|
||||||
import { ExitIcon } from '../../../common/icons';
|
|
||||||
|
|
||||||
const background = prop('Button.default.background');
|
import IconButton from '../../../components/mobile/IconButton';
|
||||||
const textColor = prop('primaryTextColor');
|
import Header from '../../../components/mobile/Header';
|
||||||
|
import Screen from '../../../components/mobile/MobileScreen';
|
||||||
|
import Footer from '../../../components/mobile/Footer';
|
||||||
|
import IDEWrapper from '../../../components/mobile/IDEWrapper';
|
||||||
|
import { remSize } from '../../../theme';
|
||||||
|
|
||||||
|
const IconContainer = styled.div`
|
||||||
const Header = styled.div`
|
margin-left: ${remSize(32)};
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
background: ${background};
|
|
||||||
color: ${textColor};
|
|
||||||
padding: ${remSize(12)};
|
|
||||||
padding-left: ${remSize(32)};
|
|
||||||
padding-right: ${remSize(32)};
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Footer = styled.div`
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
background: ${background};
|
|
||||||
color: ${textColor};
|
|
||||||
padding: ${remSize(12)};
|
|
||||||
padding-left: ${remSize(32)};
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
bottom: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Content = styled.div`
|
|
||||||
z-index: 0;
|
|
||||||
margin-top: ${remSize(16)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Icon = styled.a`
|
|
||||||
> svg {
|
|
||||||
fill: ${textColor};
|
|
||||||
color: ${textColor};
|
|
||||||
margin-left: ${remSize(16)};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const IconLinkWrapper = styled(Link)`
|
|
||||||
width: 3rem;
|
|
||||||
margin-right: 1.25rem;
|
|
||||||
margin-left: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
const Screen = ({ children }) => (
|
|
||||||
<div className="fullscreen-preview">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
Screen.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id);
|
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id);
|
||||||
|
|
||||||
const IDEViewMobile = (props) => {
|
const IDEViewMobile = (props) => {
|
||||||
const {
|
const {
|
||||||
preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage, selectedFile, updateFileContent, files, closeEditorOptions, showEditorOptions, showKeyboardShortcutModal, setUnsavedChanges, startRefreshSketch, stopSketch, expandSidebar, collapseSidebar, clearConsole, console, showRuntimeErrorWarning, hideRuntimeErrorWarning
|
preferences, ide, editorAccessibility, project, 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);
|
const [tmController, setTmController] = useState(null); // eslint-disable-line
|
||||||
|
const [overlay, setOverlay] = useState(null); // eslint-disable-line
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen>
|
<Screen>
|
||||||
<Header>
|
<Header>
|
||||||
<IconLinkWrapper to="/" aria-label="Return to original editor">
|
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />
|
||||||
<ExitIcon />
|
<div style={{ marginLeft: '1rem' }}>
|
||||||
</IconLinkWrapper>
|
|
||||||
<div>
|
|
||||||
<h2>{project.name}</h2>
|
<h2>{project.name}</h2>
|
||||||
<h3>{selectedFile.name}</h3>
|
<h3>{selectedFile.name}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<IconContainer>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setOverlay('preferences')}
|
||||||
|
icon={PreferencesIcon}
|
||||||
|
aria-label="Open preferences menu"
|
||||||
|
/>
|
||||||
|
<IconButton to="/mobile/preview" onClick={() => { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" />
|
||||||
|
</IconContainer>
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<Content>
|
<IDEWrapper>
|
||||||
<Editor
|
<Editor
|
||||||
lintWarning={preferences.lintWarning}
|
lintWarning={preferences.lintWarning}
|
||||||
linewrap={preferences.linewrap}
|
linewrap={preferences.linewrap}
|
||||||
|
@ -141,7 +102,7 @@ const IDEViewMobile = (props) => {
|
||||||
runtimeErrorWarningVisible={ide.runtimeErrorWarningVisible}
|
runtimeErrorWarningVisible={ide.runtimeErrorWarningVisible}
|
||||||
provideController={setTmController}
|
provideController={setTmController}
|
||||||
/>
|
/>
|
||||||
</Content>
|
</IDEWrapper>
|
||||||
<Footer><h2>Bottom Bar</h2></Footer>
|
<Footer><h2>Bottom Bar</h2></Footer>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
|
@ -205,6 +166,8 @@ IDEViewMobile.propTypes = {
|
||||||
updatedAt: PropTypes.string
|
updatedAt: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
|
startSketch: PropTypes.func.isRequired,
|
||||||
|
|
||||||
updateLintMessage: PropTypes.func.isRequired,
|
updateLintMessage: PropTypes.func.isRequired,
|
||||||
|
|
||||||
clearLintMessage: PropTypes.func.isRequired,
|
clearLintMessage: PropTypes.func.isRequired,
|
||||||
|
|
186
client/modules/Mobile/MobileSketchView.jsx
Normal file
186
client/modules/Mobile/MobileSketchView.jsx
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import Header from '../../components/mobile/Header';
|
||||||
|
import IconButton from '../../components/mobile/IconButton';
|
||||||
|
import PreviewFrame from '../IDE/components/PreviewFrame';
|
||||||
|
import Screen from '../../components/mobile/MobileScreen';
|
||||||
|
import * as ProjectActions from '../IDE/actions/project';
|
||||||
|
import * as IDEActions from '../IDE/actions/ide';
|
||||||
|
import * as PreferencesActions from '../IDE/actions/preferences';
|
||||||
|
import * as ConsoleActions from '../IDE/actions/console';
|
||||||
|
import * as FilesActions from '../IDE/actions/files';
|
||||||
|
|
||||||
|
import { getHTMLFile } from '../IDE/reducers/files';
|
||||||
|
|
||||||
|
|
||||||
|
import { ExitIcon } from '../../common/icons';
|
||||||
|
import { remSize } from '../../theme';
|
||||||
|
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
z-index: 0;
|
||||||
|
margin-top: ${remSize(68)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MobileSketchView = (props) => {
|
||||||
|
// TODO: useSelector requires react-redux ^7.1.0
|
||||||
|
// const htmlFile = useSelector(state => getHTMLFile(state.files));
|
||||||
|
// const jsFiles = useSelector(state => getJSFiles(state.files));
|
||||||
|
// const cssFiles = useSelector(state => getCSSFiles(state.files));
|
||||||
|
// const files = useSelector(state => state.files);
|
||||||
|
|
||||||
|
const {
|
||||||
|
htmlFile, files, selectedFile, projectName
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
const {
|
||||||
|
setTextOutput, setGridOutput, setSoundOutput,
|
||||||
|
endSketchRefresh, stopSketch,
|
||||||
|
dispatchConsoleEvent, expandConsole, clearConsole,
|
||||||
|
setBlobUrl,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { preferences, ide } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Screen>
|
||||||
|
<Header>
|
||||||
|
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />
|
||||||
|
<div style={{ marginLeft: '1rem' }}>
|
||||||
|
<h2>{projectName}</h2>
|
||||||
|
<h3><br /></h3>
|
||||||
|
</div>
|
||||||
|
</Header>
|
||||||
|
<Content>
|
||||||
|
<PreviewFrame
|
||||||
|
htmlFile={htmlFile}
|
||||||
|
files={files}
|
||||||
|
head={<link type="text/css" rel="stylesheet" href="/preview-styles.css" />}
|
||||||
|
|
||||||
|
content={selectedFile.content}
|
||||||
|
|
||||||
|
isPlaying
|
||||||
|
isAccessibleOutputPlaying={ide.isAccessibleOutputPlaying}
|
||||||
|
previewIsRefreshing={ide.previewIsRefreshing}
|
||||||
|
|
||||||
|
textOutput={preferences.textOutput}
|
||||||
|
gridOutput={preferences.gridOutput}
|
||||||
|
soundOutput={preferences.soundOutput}
|
||||||
|
autorefresh={preferences.autorefresh}
|
||||||
|
|
||||||
|
setTextOutput={setTextOutput}
|
||||||
|
setGridOutput={setGridOutput}
|
||||||
|
setSoundOutput={setSoundOutput}
|
||||||
|
dispatchConsoleEvent={dispatchConsoleEvent}
|
||||||
|
endSketchRefresh={endSketchRefresh}
|
||||||
|
stopSketch={stopSketch}
|
||||||
|
setBlobUrl={setBlobUrl}
|
||||||
|
expandConsole={expandConsole}
|
||||||
|
clearConsole={clearConsole}
|
||||||
|
/>
|
||||||
|
</Content>
|
||||||
|
</Screen>);
|
||||||
|
};
|
||||||
|
|
||||||
|
MobileSketchView.propTypes = {
|
||||||
|
params: PropTypes.shape({
|
||||||
|
project_id: PropTypes.string,
|
||||||
|
username: PropTypes.string
|
||||||
|
}).isRequired,
|
||||||
|
|
||||||
|
htmlFile: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
}).isRequired,
|
||||||
|
files: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
})).isRequired,
|
||||||
|
|
||||||
|
selectedFile: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
}).isRequired,
|
||||||
|
|
||||||
|
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({
|
||||||
|
isPlaying: PropTypes.bool.isRequired,
|
||||||
|
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
|
||||||
|
consoleEvent: PropTypes.array,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
|
||||||
|
projectName: PropTypes.string.isRequired,
|
||||||
|
|
||||||
|
setTextOutput: PropTypes.func.isRequired,
|
||||||
|
setGridOutput: PropTypes.func.isRequired,
|
||||||
|
setSoundOutput: PropTypes.func.isRequired,
|
||||||
|
dispatchConsoleEvent: PropTypes.func.isRequired,
|
||||||
|
endSketchRefresh: PropTypes.func.isRequired,
|
||||||
|
stopSketch: PropTypes.func.isRequired,
|
||||||
|
setBlobUrl: PropTypes.func.isRequired,
|
||||||
|
expandConsole: PropTypes.func.isRequired,
|
||||||
|
clearConsole: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
htmlFile: getHTMLFile(state.files),
|
||||||
|
projectName: state.project.name,
|
||||||
|
files: state.files,
|
||||||
|
ide: state.ide,
|
||||||
|
preferences: state.preferences,
|
||||||
|
selectedFile: state.files.find(file => file.isSelectedFile) ||
|
||||||
|
state.files.find(file => file.name === 'sketch.js') ||
|
||||||
|
state.files.find(file => file.name !== 'root'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
...ProjectActions, ...IDEActions, ...PreferencesActions, ...ConsoleActions, ...FilesActions
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(MobileSketchView);
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import App from './modules/App/App';
|
import App from './modules/App/App';
|
||||||
import IDEView from './modules/IDE/pages/IDEView';
|
import IDEView from './modules/IDE/pages/IDEView';
|
||||||
import IDEViewMobile from './modules/IDE/pages/IDEViewMobile';
|
import IDEViewMobile from './modules/IDE/pages/IDEViewMobile';
|
||||||
|
import MobileSketchView from './modules/Mobile/MobileSketchView';
|
||||||
import FullView from './modules/IDE/pages/FullView';
|
import FullView from './modules/IDE/pages/FullView';
|
||||||
import LoginView from './modules/User/pages/LoginView';
|
import LoginView from './modules/User/pages/LoginView';
|
||||||
import SignupView from './modules/User/pages/SignupView';
|
import SignupView from './modules/User/pages/SignupView';
|
||||||
|
@ -21,7 +22,11 @@ const checkAuth = (store) => {
|
||||||
store.dispatch(getUser());
|
store.dispatch(getUser());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: This short-circuit seems unnecessary - using the mobile <Switch /> navigator (future) should prevent this from being called
|
||||||
const onRouteChange = (store) => {
|
const onRouteChange = (store) => {
|
||||||
|
const path = window.location.pathname;
|
||||||
|
if (path.includes('/mobile')) return;
|
||||||
|
|
||||||
store.dispatch(stopSketch());
|
store.dispatch(stopSketch());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,8 +55,9 @@ const routes = store => (
|
||||||
<Route path="/:username/collections/create" component={DashboardView} />
|
<Route path="/:username/collections/create" component={DashboardView} />
|
||||||
<Route path="/:username/collections/:collection_id" component={CollectionView} />
|
<Route path="/:username/collections/:collection_id" component={CollectionView} />
|
||||||
<Route path="/about" component={IDEView} />
|
<Route path="/about" component={IDEView} />
|
||||||
<Route path="/mobile" component={IDEViewMobile} />
|
|
||||||
|
|
||||||
|
<Route path="/mobile" component={IDEViewMobile} />
|
||||||
|
<Route path="/mobile/preview" component={MobileSketchView} />
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,13 @@ export default {
|
||||||
Icon: {
|
Icon: {
|
||||||
default: grays.middleGray,
|
default: grays.middleGray,
|
||||||
hover: grays.darker
|
hover: grays.darker
|
||||||
|
},
|
||||||
|
MobilePanel: {
|
||||||
|
default: {
|
||||||
|
foreground: colors.black,
|
||||||
|
background: grays.light,
|
||||||
|
border: grays.middleLight,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[Theme.dark]: {
|
[Theme.dark]: {
|
||||||
|
@ -120,6 +127,13 @@ export default {
|
||||||
Icon: {
|
Icon: {
|
||||||
default: grays.middleLight,
|
default: grays.middleLight,
|
||||||
hover: grays.lightest
|
hover: grays.lightest
|
||||||
|
},
|
||||||
|
MobilePanel: {
|
||||||
|
default: {
|
||||||
|
foreground: grays.light,
|
||||||
|
background: grays.dark,
|
||||||
|
border: grays.middleDark,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[Theme.contrast]: {
|
[Theme.contrast]: {
|
||||||
|
@ -152,6 +166,13 @@ export default {
|
||||||
Icon: {
|
Icon: {
|
||||||
default: grays.mediumLight,
|
default: grays.mediumLight,
|
||||||
hover: colors.yellow
|
hover: colors.yellow
|
||||||
|
},
|
||||||
|
MobilePanel: {
|
||||||
|
default: {
|
||||||
|
foreground: grays.light,
|
||||||
|
background: grays.dark,
|
||||||
|
border: grays.middleDark,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -118,6 +118,14 @@ 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) => {
|
||||||
|
res.send(renderIndex());
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/mobile/*', (req, res) => {
|
||||||
|
res.send(renderIndex());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/:username/collections/create', (req, res) => {
|
router.get('/:username/collections/create', (req, res) => {
|
||||||
|
|
Loading…
Reference in a new issue