+
+
{project.name}
{selectedFile.name}
+
+
+ setOverlay('preferences')}
+ icon={PreferencesIcon}
+ aria-label="Open preferences menu"
+ />
+ { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" />
+
-
+
{
runtimeErrorWarningVisible={ide.runtimeErrorWarningVisible}
provideController={setTmController}
/>
-
+
);
@@ -205,6 +166,8 @@ IDEViewMobile.propTypes = {
updatedAt: PropTypes.string
}).isRequired,
+ startSketch: PropTypes.func.isRequired,
+
updateLintMessage: PropTypes.func.isRequired,
clearLintMessage: PropTypes.func.isRequired,
diff --git a/client/modules/Mobile/MobileSketchView.jsx b/client/modules/Mobile/MobileSketchView.jsx
new file mode 100644
index 00000000..37dc5ba9
--- /dev/null
+++ b/client/modules/Mobile/MobileSketchView.jsx
@@ -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 (
+
+
+
+
+
{projectName}
+
+
+
+
+ }
+
+ 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}
+ />
+
+ );
+};
+
+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);
diff --git a/client/routes.jsx b/client/routes.jsx
index e406e357..3a6a4b77 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -3,6 +3,7 @@ import React from 'react';
import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView';
import IDEViewMobile from './modules/IDE/pages/IDEViewMobile';
+import MobileSketchView from './modules/Mobile/MobileSketchView';
import FullView from './modules/IDE/pages/FullView';
import LoginView from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView';
@@ -21,7 +22,11 @@ const checkAuth = (store) => {
store.dispatch(getUser());
};
+// TODO: This short-circuit seems unnecessary - using the mobile
navigator (future) should prevent this from being called
const onRouteChange = (store) => {
+ const path = window.location.pathname;
+ if (path.includes('/mobile')) return;
+
store.dispatch(stopSketch());
};
@@ -50,8 +55,9 @@ const routes = store => (
-
+
+
);
diff --git a/client/theme.js b/client/theme.js
index 561fd835..5dba9b88 100644
--- a/client/theme.js
+++ b/client/theme.js
@@ -88,6 +88,13 @@ export default {
Icon: {
default: grays.middleGray,
hover: grays.darker
+ },
+ MobilePanel: {
+ default: {
+ foreground: colors.black,
+ background: grays.light,
+ border: grays.middleLight,
+ },
}
},
[Theme.dark]: {
@@ -120,6 +127,13 @@ export default {
Icon: {
default: grays.middleLight,
hover: grays.lightest
+ },
+ MobilePanel: {
+ default: {
+ foreground: grays.light,
+ background: grays.dark,
+ border: grays.middleDark,
+ },
}
},
[Theme.contrast]: {
@@ -152,6 +166,13 @@ export default {
Icon: {
default: grays.mediumLight,
hover: colors.yellow
+ },
+ MobilePanel: {
+ default: {
+ foreground: grays.light,
+ background: grays.dark,
+ border: grays.middleDark,
+ },
}
},
};
diff --git a/server/routes/server.routes.js b/server/routes/server.routes.js
index bec91b17..f7666f29 100644
--- a/server/routes/server.routes.js
+++ b/server/routes/server.routes.js
@@ -118,6 +118,14 @@ if (process.env.MOBILE_ENABLED) {
router.get('/mobile', (req, res) => {
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) => {