add unsavedChanges to redux, handle unsaved changes
This commit is contained in:
parent
bd71041794
commit
f48b872500
6 changed files with 59 additions and 13 deletions
|
@ -82,5 +82,7 @@ export const SHOW_TOAST = 'SHOW_TOAST';
|
|||
export const HIDE_TOAST = 'HIDE_TOAST';
|
||||
export const SET_TOAST_TEXT = 'SET_TOAST_TEXT';
|
||||
|
||||
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
|
||||
|
||||
// eventually, handle errors more specifically and better
|
||||
export const ERROR = 'ERROR';
|
||||
|
|
|
@ -163,3 +163,10 @@ export function closeKeyboardShortcutModal() {
|
|||
};
|
||||
}
|
||||
|
||||
export function setUnsavedChanges(value) {
|
||||
return {
|
||||
type: ActionTypes.SET_UNSAVED_CHANGES,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import JSZipUtils from 'jszip-utils';
|
|||
import { saveAs } from 'file-saver';
|
||||
import { getBlobUrl } from './files';
|
||||
import { showToast, setToastText } from './toast';
|
||||
import { setUnsavedChanges } from './ide';
|
||||
|
||||
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||
|
||||
|
@ -32,6 +33,7 @@ export function getProject(id) {
|
|||
owner: response.data.user
|
||||
});
|
||||
getProjectBlobUrls()(dispatch, getState);
|
||||
dispatch(setUnsavedChanges(false));
|
||||
})
|
||||
.catch(response => dispatch({
|
||||
type: ActionTypes.ERROR,
|
||||
|
@ -58,6 +60,7 @@ export function saveProject(autosave) {
|
|||
if (state.project.id) {
|
||||
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
|
||||
.then(() => {
|
||||
dispatch(setUnsavedChanges(false));
|
||||
dispatch({
|
||||
type: ActionTypes.PROJECT_SAVE_SUCCESS
|
||||
});
|
||||
|
@ -73,6 +76,7 @@ export function saveProject(autosave) {
|
|||
} else {
|
||||
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
||||
.then(response => {
|
||||
dispatch(setUnsavedChanges(false));
|
||||
browserHistory.push(`/projects/${response.data.id}`);
|
||||
dispatch({
|
||||
type: ActionTypes.NEW_PROJECT,
|
||||
|
@ -112,6 +116,7 @@ export function createProject() {
|
|||
owner: response.data.user,
|
||||
files: response.data.files
|
||||
});
|
||||
dispatch(setUnsavedChanges(false));
|
||||
})
|
||||
.catch(response => dispatch({
|
||||
type: ActionTypes.PROJECT_SAVE_FAIL,
|
||||
|
|
|
@ -60,33 +60,33 @@ class Editor extends React.Component {
|
|||
})
|
||||
}
|
||||
});
|
||||
|
||||
this._cm.on('change', debounce(200, () => {
|
||||
this.props.setUnsavedChanges(true);
|
||||
this.props.updateFileContent(this.props.file.name, this._cm.getValue());
|
||||
}));
|
||||
|
||||
this._cm.on('keyup', () => {
|
||||
const temp = `line ${parseInt((this._cm.getCursor().line) + 1, 10)}`;
|
||||
document.getElementById('current-line').innerHTML = temp;
|
||||
});
|
||||
// this._cm.on('change', () => { // eslint-disable-line
|
||||
// // this.props.updateFileContent('sketch.js', this._cm.getValue());
|
||||
// throttle(1000, () => console.log('debounce is working!'));
|
||||
// this.props.updateFileContent(this.props.file.name, this._cm.getValue());
|
||||
// });
|
||||
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
||||
this._cm.setOption('indentWithTabs', this.props.isTabIndent);
|
||||
this._cm.setOption('tabSize', this.props.indentationAmount);
|
||||
|
||||
this._cm.on('keydown', (_cm, e) => {
|
||||
if (e.key === 'Tab' && e.shiftKey) {
|
||||
this.tidyCode();
|
||||
}
|
||||
});
|
||||
|
||||
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
||||
this._cm.setOption('indentWithTabs', this.props.isTabIndent);
|
||||
this._cm.setOption('tabSize', this.props.indentationAmount);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.file.content !== prevProps.file.content &&
|
||||
this.props.file.content !== this._cm.getValue()) {
|
||||
this._cm.setValue(this.props.file.content); // eslint-disable-line no-underscore-dangle
|
||||
setTimeout(() => this.props.setUnsavedChanges(false), 500);
|
||||
}
|
||||
if (this.props.fontSize !== prevProps.fontSize) {
|
||||
this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`;
|
||||
|
@ -190,7 +190,8 @@ Editor.propTypes = {
|
|||
editorOptionsVisible: PropTypes.bool.isRequired,
|
||||
showEditorOptions: PropTypes.func.isRequired,
|
||||
closeEditorOptions: PropTypes.func.isRequired,
|
||||
showKeyboardShortcutModal: PropTypes.func.isRequired
|
||||
showKeyboardShortcutModal: PropTypes.func.isRequired,
|
||||
setUnsavedChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
|
|
|
@ -14,6 +14,7 @@ import Console from '../components/Console';
|
|||
import Toast from '../components/Toast';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import * as FileActions from '../actions/files';
|
||||
import * as IDEActions from '../actions/ide';
|
||||
import * as ProjectActions from '../actions/project';
|
||||
|
@ -33,6 +34,7 @@ class IDEView extends React.Component {
|
|||
this._handleConsolePaneOnDragFinished = this._handleConsolePaneOnDragFinished.bind(this);
|
||||
this._handleSidebarPaneOnDragFinished = this._handleSidebarPaneOnDragFinished.bind(this);
|
||||
this.handleGlobalKeydown = this.handleGlobalKeydown.bind(this);
|
||||
this.warnIfUnsavedChanges = this.warnIfUnsavedChanges.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -55,6 +57,10 @@ class IDEView extends React.Component {
|
|||
|
||||
this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1;
|
||||
document.addEventListener('keydown', this.handleGlobalKeydown, false);
|
||||
|
||||
this.props.router.setRouteLeaveHook(this.props.route, () => this.warnIfUnsavedChanges());
|
||||
|
||||
window.onbeforeunload = () => this.warnIfUnsavedChanges();
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
|
@ -91,6 +97,10 @@ class IDEView extends React.Component {
|
|||
clearInterval(this.autosaveInterval);
|
||||
this.autosaveInterval = null;
|
||||
}
|
||||
|
||||
if (this.props.route.path !== prevProps.route.path) {
|
||||
this.props.router.setRouteLeaveHook(this.props.route, () => this.warnIfUnsavedChanges());
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -134,6 +144,17 @@ class IDEView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
warnIfUnsavedChanges() { // eslint-disable-line
|
||||
if (this.props.ide.unsavedChanges
|
||||
&& ((this.props.project.owner && this.props.project.owner.id === this.props.user.id)
|
||||
|| (!this.props.project.owner))) {
|
||||
if (!window.confirm('Are you sure you want to leave this page? You have unsaved changes.')) {
|
||||
return false;
|
||||
}
|
||||
this.props.setUnsavedChanges(false);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="ide">
|
||||
|
@ -243,6 +264,7 @@ class IDEView extends React.Component {
|
|||
showEditorOptions={this.props.showEditorOptions}
|
||||
closeEditorOptions={this.props.closeEditorOptions}
|
||||
showKeyboardShortcutModal={this.props.showKeyboardShortcutModal}
|
||||
setUnsavedChanges={this.props.setUnsavedChanges}
|
||||
/>
|
||||
<Console
|
||||
consoleEvent={this.props.ide.consoleEvent}
|
||||
|
@ -380,7 +402,8 @@ IDEView.propTypes = {
|
|||
newFolderModalVisible: PropTypes.bool.isRequired,
|
||||
shareModalVisible: PropTypes.bool.isRequired,
|
||||
editorOptionsVisible: PropTypes.bool.isRequired,
|
||||
keyboardShortcutVisible: PropTypes.bool.isRequired
|
||||
keyboardShortcutVisible: PropTypes.bool.isRequired,
|
||||
unsavedChanges: PropTypes.bool.isRequired
|
||||
}).isRequired,
|
||||
startSketch: PropTypes.func.isRequired,
|
||||
stopSketch: PropTypes.func.isRequired,
|
||||
|
@ -464,7 +487,12 @@ IDEView.propTypes = {
|
|||
}).isRequired,
|
||||
showToast: PropTypes.func.isRequired,
|
||||
setToastText: PropTypes.func.isRequired,
|
||||
autosaveProject: PropTypes.func.isRequired
|
||||
autosaveProject: PropTypes.func.isRequired,
|
||||
router: PropTypes.shape({
|
||||
setRouteLeaveHook: PropTypes.func
|
||||
}).isRequired,
|
||||
route: PropTypes.object.isRequired,
|
||||
setUnsavedChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
@ -495,4 +523,4 @@ function mapDispatchToProps(dispatch) {
|
|||
dispatch);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(IDEView);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(IDEView));
|
||||
|
|
|
@ -15,7 +15,8 @@ const initialState = {
|
|||
newFolderModalVisible: false,
|
||||
shareModalVisible: false,
|
||||
editorOptionsVisible: false,
|
||||
keyboardShortcutVisible: false
|
||||
keyboardShortcutVisible: false,
|
||||
unsavedChanges: false
|
||||
};
|
||||
|
||||
const ide = (state = initialState, action) => {
|
||||
|
@ -70,6 +71,8 @@ const ide = (state = initialState, action) => {
|
|||
return Object.assign({}, state, { keyboardShortcutVisible: true });
|
||||
case ActionTypes.CLOSE_KEYBOARD_SHORTCUT_MODAL:
|
||||
return Object.assign({}, state, { keyboardShortcutVisible: false });
|
||||
case ActionTypes.SET_UNSAVED_CHANGES:
|
||||
return Object.assign({}, state, { unsavedChanges: action.value });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue