@@ -10,6 +10,9 @@ import { bindActionCreators } from 'redux';
1010
1111import * as IDEActions from '../actions/ide' ;
1212import * as ProjectActions from '../actions/project' ;
13+ import * as ConsoleActions from '../actions/console' ;
14+ import * as PreferencesActions from '../actions/preferences' ;
15+
1316
1417// Local Imports
1518import Editor from '../components/Editor' ;
@@ -26,11 +29,13 @@ import { remSize } from '../../../theme';
2629// import OverlayManager from '../../../components/OverlayManager';
2730import ActionStrip from '../../../components/mobile/ActionStrip' ;
2831import useAsModal from '../../../components/useAsModal' ;
29- import { PreferencesIcon } from '../../../common/icons' ;
32+ import { PreferencesIcon , TerminalIcon , SaveIcon } from '../../../common/icons' ;
3033import Dropdown from '../../../components/Dropdown' ;
3134
32- const isUserOwner = ( { project, user } ) =>
33- project . owner && project . owner . id === user . id ;
35+
36+ import { useEffectWithComparison , useEventListener } from '../../../utils/custom-hooks' ;
37+
38+ import * as device from '../../../utils/device' ;
3439
3540const withChangeDot = ( title , unsavedChanges = false ) => (
3641 < span >
@@ -65,13 +70,111 @@ const getNatOptions = (username = undefined) =>
6570 ]
6671 ) ;
6772
73+
74+ const isUserOwner = ( { project, user } ) =>
75+ project && project . owner && project . owner . id === user . id ;
76+
77+ const canSaveProject = ( project , user ) =>
78+ isUserOwner ( { project, user } ) || ( user . authenticated && ! project . owner ) ;
79+
80+ // TODO: This could go into <Editor />
81+ const handleGlobalKeydown = ( props , cmController ) => ( e ) => {
82+ const {
83+ user, project, ide,
84+ setAllAccessibleOutput,
85+ saveProject, cloneProject, showErrorModal, startSketch, stopSketch,
86+ expandSidebar, collapseSidebar, expandConsole, collapseConsole,
87+ closeNewFolderModal, closeUploadFileModal, closeNewFileModal
88+ } = props ;
89+
90+
91+ const isMac = device . isMac ( ) ;
92+
93+ // const ctrlDown = (e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac);
94+ const ctrlDown = ( isMac ? e . metaKey : e . ctrlKey ) ;
95+
96+ if ( ctrlDown ) {
97+ if ( e . shiftKey ) {
98+ if ( e . keyCode === 13 ) {
99+ e . preventDefault ( ) ;
100+ e . stopPropagation ( ) ;
101+ stopSketch ( ) ;
102+ } else if ( e . keyCode === 13 ) {
103+ e . preventDefault ( ) ;
104+ e . stopPropagation ( ) ;
105+ startSketch ( ) ;
106+ // 50 === 2
107+ } else if ( e . keyCode === 50
108+ ) {
109+ e . preventDefault ( ) ;
110+ setAllAccessibleOutput ( false ) ;
111+ // 49 === 1
112+ } else if ( e . keyCode === 49 ) {
113+ e . preventDefault ( ) ;
114+ setAllAccessibleOutput ( true ) ;
115+ }
116+ } else if ( e . keyCode === 83 ) {
117+ // 83 === s
118+ e . preventDefault ( ) ;
119+ e . stopPropagation ( ) ;
120+ if ( canSaveProject ( project , user ) ) saveProject ( cmController . getContent ( ) , false , true ) ;
121+ else if ( user . authenticated ) cloneProject ( ) ;
122+ else showErrorModal ( 'forceAuthentication' ) ;
123+
124+ // 13 === enter
125+ } else if ( e . keyCode === 66 ) {
126+ e . preventDefault ( ) ;
127+ if ( ! ide . sidebarIsExpanded ) expandSidebar ( ) ;
128+ else collapseSidebar ( ) ;
129+ }
130+ } else if ( e . keyCode === 192 && e . ctrlKey ) {
131+ e . preventDefault ( ) ;
132+ if ( ide . consoleIsExpanded ) collapseConsole ( ) ;
133+ else expandConsole ( ) ;
134+ } else if ( e . keyCode === 27 ) {
135+ if ( ide . newFolderModalVisible ) closeNewFolderModal ( ) ;
136+ else if ( ide . uploadFileModalVisible ) closeUploadFileModal ( ) ;
137+ else if ( ide . modalIsVisible ) closeNewFileModal ( ) ;
138+ }
139+ } ;
140+
141+
142+ const autosave = ( autosaveInterval , setAutosaveInterval ) => ( props , prevProps ) => {
143+ const {
144+ autosaveProject, preferences, ide, selectedFile : file , project, user
145+ } = props ;
146+
147+ const { selectedFile : oldFile } = prevProps ;
148+
149+ const doAutosave = ( ) => autosaveProject ( true ) ;
150+
151+ if ( isUserOwner ( props ) && project . id ) {
152+ if ( preferences . autosave && ide . unsavedChanges && ! ide . justOpenedProject ) {
153+ if ( file . name === oldFile . name && file . content !== oldFile . content ) {
154+ if ( autosaveInterval ) {
155+ clearTimeout ( autosaveInterval ) ;
156+ }
157+ console . log ( 'will save project in 20 seconds' ) ;
158+ setAutosaveInterval ( setTimeout ( doAutosave , 20000 ) ) ;
159+ }
160+ } else if ( autosaveInterval && ! preferences . autosave ) {
161+ clearTimeout ( autosaveInterval ) ;
162+ setAutosaveInterval ( null ) ;
163+ }
164+ } else if ( autosaveInterval ) {
165+ clearTimeout ( autosaveInterval ) ;
166+ setAutosaveInterval ( null ) ;
167+ }
168+ } ;
169+
68170const MobileIDEView = ( props ) => {
69171 const {
70- ide, project, selectedFile, user, params, unsavedChanges,
71- stopSketch, startSketch, getProject, clearPersistedState
172+ ide, preferences , project, selectedFile, user, params, unsavedChanges, expandConsole , collapseConsole ,
173+ stopSketch, startSketch, getProject, clearPersistedState, autosaveProject , saveProject
72174 } = props ;
73175
74- const [ tmController , setTmController ] = useState ( null ) ; // eslint-disable-line
176+
177+ const [ cmController , setCmController ] = useState ( null ) ; // eslint-disable-line
75178
76179 const { username } = user ;
77180 const { consoleIsExpanded } = ide ;
@@ -84,7 +187,10 @@ const MobileIDEView = (props) => {
84187
85188 // Force state reset
86189 useEffect ( clearPersistedState , [ ] ) ;
87- useEffect ( stopSketch , [ ] ) ;
190+ useEffect ( ( ) => {
191+ stopSketch ( ) ;
192+ collapseConsole ( ) ;
193+ } , [ ] ) ;
88194
89195 // Load Project
90196 const [ currentProjectID , setCurrentProjectID ] = useState ( null ) ;
@@ -99,6 +205,19 @@ const MobileIDEView = (props) => {
99205 } , [ params , project , username ] ) ;
100206
101207
208+ // TODO: This behavior could move to <Editor />
209+ const [ autosaveInterval , setAutosaveInterval ] = useState ( null ) ;
210+ useEffectWithComparison ( autosave ( autosaveInterval , setAutosaveInterval ) , {
211+ autosaveProject, preferences, ide, selectedFile, project, user
212+ } ) ;
213+
214+ useEventListener ( 'keydown' , handleGlobalKeydown ( props , cmController ) , false , [ props ] ) ;
215+
216+ const projectActions =
217+ [ { icon : TerminalIcon , aria : 'Toggle console open/closed' , action : consoleIsExpanded ? collapseConsole : expandConsole } ,
218+ { icon : SaveIcon , aria : 'Save project' , action : ( ) => saveProject ( cmController . getContent ( ) , false , true ) }
219+ ] ;
220+
102221 return (
103222 < Screen fullscreen >
104223 < Header
@@ -119,7 +238,7 @@ const MobileIDEView = (props) => {
119238 </ Header >
120239
121240 < IDEWrapper >
122- < Editor provideController = { setTmController } />
241+ < Editor provideController = { setCmController } />
123242 </ IDEWrapper >
124243
125244 < Footer >
@@ -128,17 +247,36 @@ const MobileIDEView = (props) => {
128247 < Console />
129248 </ Expander >
130249 ) }
131- < ActionStrip />
250+ < ActionStrip actions = { projectActions } />
132251 </ Footer >
133252 </ Screen >
134253 ) ;
135254} ;
136255
256+ const handleGlobalKeydownProps = {
257+ expandConsole : PropTypes . func . isRequired ,
258+ collapseConsole : PropTypes . func . isRequired ,
259+ expandSidebar : PropTypes . func . isRequired ,
260+ collapseSidebar : PropTypes . func . isRequired ,
261+
262+ setAllAccessibleOutput : PropTypes . func . isRequired ,
263+ saveProject : PropTypes . func . isRequired ,
264+ cloneProject : PropTypes . func . isRequired ,
265+ showErrorModal : PropTypes . func . isRequired ,
266+
267+ closeNewFolderModal : PropTypes . func . isRequired ,
268+ closeUploadFileModal : PropTypes . func . isRequired ,
269+ closeNewFileModal : PropTypes . func . isRequired ,
270+ } ;
271+
137272MobileIDEView . propTypes = {
138273 ide : PropTypes . shape ( {
139274 consoleIsExpanded : PropTypes . bool . isRequired ,
140275 } ) . isRequired ,
141276
277+ preferences : PropTypes . shape ( {
278+ } ) . isRequired ,
279+
142280 project : PropTypes . shape ( {
143281 id : PropTypes . string ,
144282 name : PropTypes . string . isRequired ,
@@ -172,6 +310,10 @@ MobileIDEView.propTypes = {
172310 stopSketch : PropTypes . func . isRequired ,
173311 getProject : PropTypes . func . isRequired ,
174312 clearPersistedState : PropTypes . func . isRequired ,
313+ autosaveProject : PropTypes . func . isRequired ,
314+
315+
316+ ...handleGlobalKeydownProps
175317} ;
176318
177319function mapStateToProps ( state ) {
@@ -192,7 +334,9 @@ function mapStateToProps(state) {
192334
193335const mapDispatchToProps = dispatch => bindActionCreators ( {
194336 ...ProjectActions ,
195- ...IDEActions
337+ ...IDEActions ,
338+ ...ConsoleActions ,
339+ ...PreferencesActions
196340} , dispatch ) ;
197341
198342export default withRouter ( connect ( mapStateToProps , mapDispatchToProps ) ( MobileIDEView ) ) ;
0 commit comments