Skip to content

Commit 87d15ec

Browse files
authored
Merge branch 'master' into wzy/990/fixPreview
2 parents 2fc613b + 76e4363 commit 87d15ec

File tree

15 files changed

+170
-70
lines changed

15 files changed

+170
-70
lines changed

client/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,6 @@ export const HIDE_HELP_MODAL = 'HIDE_HELP_MODAL';
117117
export const HIDE_RUNTIME_ERROR_WARNING = 'HIDE_RUNTIME_ERROR_WARNING';
118118
export const SHOW_RUNTIME_ERROR_WARNING = 'SHOW_RUNTIME_ERROR_WARNING';
119119
export const SET_ASSETS = 'SET_ASSETS';
120+
121+
export const START_SAVING_PROJECT = 'START_SAVING_PROJECT';
122+
export const END_SAVING_PROJECT = 'END_SAVING_PROJECT';

client/modules/IDE/actions/files.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import blobUtil from 'blob-util';
44
import { reset } from 'redux-form';
55
import * as ActionTypes from '../../../constants';
66
import { setUnsavedChanges } from './ide';
7+
import { setProjectSavedTime } from './project';
78

89
const __process = (typeof global !== 'undefined' ? global : window).process;
910
const ROOT_URL = __process.env.API_URL;
@@ -60,9 +61,10 @@ export function createFile(formProps) {
6061
.then((response) => {
6162
dispatch({
6263
type: ActionTypes.CREATE_FILE,
63-
...response.data,
64+
...response.data.updatedFile,
6465
parentId
6566
});
67+
dispatch(setProjectSavedTime(response.data.project.updatedAt));
6668
dispatch(reset('new-file'));
6769
// dispatch({
6870
// type: ActionTypes.HIDE_MODAL
@@ -117,9 +119,10 @@ export function createFolder(formProps) {
117119
.then((response) => {
118120
dispatch({
119121
type: ActionTypes.CREATE_FILE,
120-
...response.data,
122+
...response.data.updatedFile,
121123
parentId
122124
});
125+
dispatch(setProjectSavedTime(response.data.project.updatedAt));
123126
dispatch({
124127
type: ActionTypes.CLOSE_NEW_FOLDER_MODAL
125128
});

client/modules/IDE/actions/project.js

Lines changed: 108 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { browserHistory } from 'react-router';
22
import axios from 'axios';
33
import objectID from 'bson-objectid';
44
import each from 'async/each';
5-
import { isEqual, pick } from 'lodash';
5+
import isEqual from 'lodash/isEqual';
66
import * as ActionTypes from '../../../constants';
77
import { showToast, setToastText } from './toast';
88
import {
@@ -32,6 +32,22 @@ export function setProjectName(name) {
3232
};
3333
}
3434

35+
export function projectSaveFail(error) {
36+
return {
37+
type: ActionTypes.PROJECT_SAVE_FAIL,
38+
error
39+
};
40+
}
41+
42+
export function setNewProject(project) {
43+
return {
44+
type: ActionTypes.NEW_PROJECT,
45+
project,
46+
owner: project.user,
47+
files: project.files
48+
};
49+
}
50+
3551
export function getProject(id) {
3652
return (dispatch, getState) => {
3753
dispatch(justOpenedProject());
@@ -66,37 +82,71 @@ export function clearPersistedState() {
6682
};
6783
}
6884

85+
export function startSavingProject() {
86+
return {
87+
type: ActionTypes.START_SAVING_PROJECT
88+
};
89+
}
90+
91+
export function endSavingProject() {
92+
return {
93+
type: ActionTypes.END_SAVING_PROJECT
94+
};
95+
}
96+
97+
export function projectSaveSuccess() {
98+
return {
99+
type: ActionTypes.PROJECT_SAVE_SUCCESS
100+
};
101+
}
102+
103+
// want a function that will check for changes on the front end
104+
function getSynchedProject(currentState, responseProject) {
105+
let hasChanges = false;
106+
const synchedProject = Object.assign({}, responseProject);
107+
const currentFiles = currentState.files.map(({ name, children, content }) => ({ name, children, content }));
108+
const responseFiles = responseProject.files.map(({ name, children, content }) => ({ name, children, content }));
109+
if (!isEqual(currentFiles, responseFiles)) {
110+
synchedProject.files = currentState.files;
111+
hasChanges = true;
112+
}
113+
if (currentState.project.name !== responseProject.name) {
114+
synchedProject.name = currentState.project.name;
115+
hasChanges = true;
116+
}
117+
return {
118+
synchedProject,
119+
hasChanges
120+
};
121+
}
122+
69123
export function saveProject(selectedFile = null, autosave = false) {
70124
return (dispatch, getState) => {
71125
const state = getState();
126+
if (state.project.isSaving) {
127+
return Promise.resolve();
128+
}
129+
dispatch(startSavingProject());
72130
if (state.user.id && state.project.owner && state.project.owner.id !== state.user.id) {
73131
return Promise.reject();
74132
}
75133
const formParams = Object.assign({}, state.project);
76134
formParams.files = [...state.files];
77135
if (selectedFile) {
78-
console.log('selected file being updated');
79136
const fileToUpdate = formParams.files.find(file => file.id === selectedFile.id);
80137
fileToUpdate.content = selectedFile.content;
81138
}
82139
if (state.project.id) {
83140
return axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
84141
.then((response) => {
85-
const currentState = getState();
86-
const savedProject = Object.assign({}, response.data);
87-
if (!isEqual(
88-
pick(currentState.files, ['name', 'children', 'content']),
89-
pick(response.data.files, ['name', 'children', 'content'])
90-
)) {
91-
savedProject.files = currentState.files;
142+
dispatch(endSavingProject());
143+
dispatch(setUnsavedChanges(false));
144+
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
145+
if (hasChanges) {
92146
dispatch(setUnsavedChanges(true));
93-
} else {
94-
dispatch(setUnsavedChanges(false));
95147
}
96-
dispatch(setProject(savedProject));
97-
dispatch({
98-
type: ActionTypes.PROJECT_SAVE_SUCCESS
99-
});
148+
dispatch(setProject(synchedProject));
149+
dispatch(projectSaveSuccess());
100150
if (!autosave) {
101151
if (state.ide.justOpenedProject && state.preferences.autosave) {
102152
dispatch(showToast(5500));
@@ -110,30 +160,32 @@ export function saveProject(selectedFile = null, autosave = false) {
110160
}
111161
})
112162
.catch((response) => {
163+
dispatch(endSavingProject());
113164
if (response.status === 403) {
114165
dispatch(showErrorModal('staleSession'));
115166
} else if (response.status === 409) {
116167
dispatch(showErrorModal('staleProject'));
117168
} else {
118-
dispatch({
119-
type: ActionTypes.PROJECT_SAVE_FAIL,
120-
error: response.data
121-
});
169+
dispatch(projectSaveFail(response.data));
122170
}
123171
});
124172
}
125173

126174
return axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
127175
.then((response) => {
128-
dispatch(setUnsavedChanges(false));
129-
dispatch(setProject(response.data));
130-
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
131-
dispatch({
132-
type: ActionTypes.NEW_PROJECT,
133-
project: response.data,
134-
owner: response.data.user,
135-
files: response.data.files
136-
});
176+
dispatch(endSavingProject());
177+
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
178+
if (hasChanges) {
179+
dispatch(setNewProject(synchedProject));
180+
dispatch(setUnsavedChanges(false));
181+
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
182+
dispatch(setUnsavedChanges(true));
183+
} else {
184+
dispatch(setNewProject(synchedProject));
185+
dispatch(setUnsavedChanges(false));
186+
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
187+
}
188+
dispatch(projectSaveSuccess());
137189
if (!autosave) {
138190
if (state.preferences.autosave) {
139191
dispatch(showToast(5500));
@@ -147,13 +199,11 @@ export function saveProject(selectedFile = null, autosave = false) {
147199
}
148200
})
149201
.catch((response) => {
202+
dispatch(endSavingProject());
150203
if (response.status === 403) {
151204
dispatch(showErrorModal('staleSession'));
152205
} else {
153-
dispatch({
154-
type: ActionTypes.PROJECT_SAVE_FAIL,
155-
error: response.data
156-
});
206+
dispatch(projectSaveFail(response.data));
157207
}
158208
});
159209
};
@@ -166,22 +216,28 @@ export function autosaveProject() {
166216
}
167217

168218
export function createProject() {
169-
return (dispatch) => {
219+
return (dispatch, getState) => {
220+
const state = getState();
221+
if (state.project.isSaving) {
222+
Promise.resolve();
223+
return;
224+
}
225+
dispatch(startSavingProject());
170226
axios.post(`${ROOT_URL}/projects`, {}, { withCredentials: true })
171227
.then((response) => {
172-
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
173-
dispatch({
174-
type: ActionTypes.NEW_PROJECT,
175-
project: response.data,
176-
owner: response.data.user,
177-
files: response.data.files
178-
});
228+
dispatch(endSavingProject());
179229
dispatch(setUnsavedChanges(false));
230+
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
231+
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
232+
if (hasChanges) {
233+
dispatch(setUnsavedChanges(true));
234+
}
235+
dispatch(setNewProject(synchedProject));
180236
})
181-
.catch(response => dispatch({
182-
type: ActionTypes.PROJECT_SAVE_FAIL,
183-
error: response.data
184-
}));
237+
.catch((response) => {
238+
dispatch(endSavingProject());
239+
dispatch(projectSaveFail(response.data));
240+
});
185241
};
186242
}
187243

@@ -251,12 +307,7 @@ export function cloneProject() {
251307
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
252308
.then((response) => {
253309
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
254-
dispatch({
255-
type: ActionTypes.NEW_PROJECT,
256-
project: response.data,
257-
owner: response.data.user,
258-
files: response.data.files
259-
});
310+
dispatch(setNewProject(response.data));
260311
})
261312
.catch(response => dispatch({
262313
type: ActionTypes.PROJECT_SAVE_FAIL,
@@ -277,3 +328,10 @@ export function hideEditProjectName() {
277328
type: ActionTypes.HIDE_EDIT_PROJECT_NAME
278329
};
279330
}
331+
332+
export function setProjectSavedTime(updatedAt) {
333+
return {
334+
type: ActionTypes.SET_PROJECT_SAVED_TIME,
335+
value: updatedAt
336+
};
337+
}

client/modules/IDE/components/ErrorModal.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ErrorModal extends React.Component {
2626
staleProject() {
2727
return (
2828
<p>
29-
The project you have attempted to save is out of date. Please refresh the page.
29+
The project you have attempted to save has been saved from another window. Please refresh the page to see the latest version.
3030
</p>
3131
);
3232
}

client/modules/IDE/components/PreviewFrame.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ class PreviewFrame extends React.Component {
332332
}
333333

334334
renderSketch() {
335+
this.props.clearConsole();
335336
const doc = this.iframeElement;
336337
const localFiles = this.injectLocalFiles();
337338
if (this.props.isPlaying) {
@@ -387,6 +388,7 @@ PreviewFrame.propTypes = {
387388
setBlobUrl: PropTypes.func.isRequired,
388389
stopSketch: PropTypes.func.isRequired,
389390
expandConsole: PropTypes.func.isRequired,
391+
clearConsole: PropTypes.func.isRequired,
390392
cmController: PropTypes.shape({
391393
getContent: PropTypes.func
392394
}).isRequired

client/modules/IDE/pages/IDEView.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ class IDEView extends React.Component {
344344
stopSketch={this.props.stopSketch}
345345
setBlobUrl={this.props.setBlobUrl}
346346
expandConsole={this.props.expandConsole}
347+
clearConsole={this.props.clearConsole}
347348
cmController={this.cmController}
348349
/>
349350
</div>

client/modules/IDE/reducers/project.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ const initialState = () => {
1212
const generatedName = generatedString.charAt(0).toUpperCase() + generatedString.slice(1);
1313
return {
1414
name: generatedName,
15-
updatedAt: ''
15+
updatedAt: '',
16+
isSaving: false
1617
};
1718
};
1819

@@ -28,14 +29,16 @@ const project = (state, action) => {
2829
id: action.project.id,
2930
name: action.project.name,
3031
updatedAt: action.project.updatedAt,
31-
owner: action.owner
32+
owner: action.owner,
33+
isSaving: false
3234
};
3335
case ActionTypes.SET_PROJECT:
3436
return {
3537
id: action.project.id,
3638
name: action.project.name,
3739
updatedAt: action.project.updatedAt,
38-
owner: action.owner
40+
owner: action.owner,
41+
isSaving: false
3942
};
4043
case ActionTypes.RESET_PROJECT:
4144
return initialState();
@@ -45,6 +48,10 @@ const project = (state, action) => {
4548
return Object.assign({}, state, { isEditingName: false });
4649
case ActionTypes.SET_PROJECT_SAVED_TIME:
4750
return Object.assign({}, state, { updatedAt: action.value });
51+
case ActionTypes.START_SAVING_PROJECT:
52+
return Object.assign({}, state, { isSaving: true });
53+
case ActionTypes.START_STOP_PROJECT:
54+
return Object.assign({}, state, { isSaving: false });
4855
default:
4956
return state;
5057
}

client/modules/User/actions.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { browserHistory } from 'react-router';
22
import axios from 'axios';
33
import * as ActionTypes from '../../constants';
44
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
5+
import { showToast, setToastText } from '../IDE/actions/toast';
6+
57

68
const __process = (typeof global !== 'undefined' ? global : window).process;
79
const ROOT_URL = __process.env.API_URL;
@@ -211,6 +213,8 @@ export function updateSettings(formValues) {
211213
.then((response) => {
212214
dispatch(updateSettingsSuccess(response.data));
213215
browserHistory.push('/');
216+
dispatch(showToast(5500));
217+
dispatch(setToastText('Settings saved.'));
214218
})
215219
.catch(response => Promise.reject(new Error(response.data.error)));
216220
}

0 commit comments

Comments
 (0)