@@ -10,6 +10,9 @@ import { bindActionCreators } from 'redux';
10
10
11
11
import * as IDEActions from '../actions/ide' ;
12
12
import * as ProjectActions from '../actions/project' ;
13
+ import * as ConsoleActions from '../actions/console' ;
14
+ import * as PreferencesActions from '../actions/preferences' ;
15
+
13
16
14
17
// Local Imports
15
18
import Editor from '../components/Editor' ;
@@ -26,11 +29,13 @@ import { remSize } from '../../../theme';
26
29
// import OverlayManager from '../../../components/OverlayManager';
27
30
import ActionStrip from '../../../components/mobile/ActionStrip' ;
28
31
import useAsModal from '../../../components/useAsModal' ;
29
- import { PreferencesIcon } from '../../../common/icons' ;
32
+ import { PreferencesIcon , TerminalIcon , SaveIcon } from '../../../common/icons' ;
30
33
import Dropdown from '../../../components/Dropdown' ;
31
34
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' ;
34
39
35
40
const withChangeDot = ( title , unsavedChanges = false ) => (
36
41
< span >
@@ -65,13 +70,111 @@ const getNatOptions = (username = undefined) =>
65
70
]
66
71
) ;
67
72
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
+
68
170
const MobileIDEView = ( props ) => {
69
171
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
72
174
} = props ;
73
175
74
- const [ tmController , setTmController ] = useState ( null ) ; // eslint-disable-line
176
+
177
+ const [ cmController , setCmController ] = useState ( null ) ; // eslint-disable-line
75
178
76
179
const { username } = user ;
77
180
const { consoleIsExpanded } = ide ;
@@ -84,7 +187,10 @@ const MobileIDEView = (props) => {
84
187
85
188
// Force state reset
86
189
useEffect ( clearPersistedState , [ ] ) ;
87
- useEffect ( stopSketch , [ ] ) ;
190
+ useEffect ( ( ) => {
191
+ stopSketch ( ) ;
192
+ collapseConsole ( ) ;
193
+ } , [ ] ) ;
88
194
89
195
// Load Project
90
196
const [ currentProjectID , setCurrentProjectID ] = useState ( null ) ;
@@ -99,6 +205,19 @@ const MobileIDEView = (props) => {
99
205
} , [ params , project , username ] ) ;
100
206
101
207
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
+
102
221
return (
103
222
< Screen fullscreen >
104
223
< Header
@@ -119,7 +238,7 @@ const MobileIDEView = (props) => {
119
238
</ Header >
120
239
121
240
< IDEWrapper >
122
- < Editor provideController = { setTmController } />
241
+ < Editor provideController = { setCmController } />
123
242
</ IDEWrapper >
124
243
125
244
< Footer >
@@ -128,17 +247,36 @@ const MobileIDEView = (props) => {
128
247
< Console />
129
248
</ Expander >
130
249
) }
131
- < ActionStrip />
250
+ < ActionStrip actions = { projectActions } />
132
251
</ Footer >
133
252
</ Screen >
134
253
) ;
135
254
} ;
136
255
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
+
137
272
MobileIDEView . propTypes = {
138
273
ide : PropTypes . shape ( {
139
274
consoleIsExpanded : PropTypes . bool . isRequired ,
140
275
} ) . isRequired ,
141
276
277
+ preferences : PropTypes . shape ( {
278
+ } ) . isRequired ,
279
+
142
280
project : PropTypes . shape ( {
143
281
id : PropTypes . string ,
144
282
name : PropTypes . string . isRequired ,
@@ -172,6 +310,10 @@ MobileIDEView.propTypes = {
172
310
stopSketch : PropTypes . func . isRequired ,
173
311
getProject : PropTypes . func . isRequired ,
174
312
clearPersistedState : PropTypes . func . isRequired ,
313
+ autosaveProject : PropTypes . func . isRequired ,
314
+
315
+
316
+ ...handleGlobalKeydownProps
175
317
} ;
176
318
177
319
function mapStateToProps ( state ) {
@@ -192,7 +334,9 @@ function mapStateToProps(state) {
192
334
193
335
const mapDispatchToProps = dispatch => bindActionCreators ( {
194
336
...ProjectActions ,
195
- ...IDEActions
337
+ ...IDEActions ,
338
+ ...ConsoleActions ,
339
+ ...PreferencesActions
196
340
} , dispatch ) ;
197
341
198
342
export default withRouter ( connect ( mapStateToProps , mapDispatchToProps ) ( MobileIDEView ) ) ;
0 commit comments