Skip to content
Snippets Groups Projects
Commit 09592425 authored by Ray Schamp's avatar Ray Schamp
Browse files

Track "project changed" state in Redux

Listen to the new project changed event from the VM and keep track of the state in Redux.  Update it as appropriate.

Known issue is that the project is marked as "changed" immediately after it's loaded. This is due to asynchronous event emitting while the project data is loaded, and needs an update to the VM to fix.
parent a3431a99
No related branches found
No related tags found
No related merge requests found
...@@ -328,7 +328,10 @@ class Blocks extends React.Component { ...@@ -328,7 +328,10 @@ class Blocks extends React.Component {
error.message = `Workspace Update Error: ${error.message}`; error.message = `Workspace Update Error: ${error.message}`;
log.error(error); log.error(error);
} }
this.workspace.addChangeListener(this.props.vm.blockListener); // All of the changes that happened during the load above are queued with
// timeouts, so re-enable the listener in the next tick, so it happens after
// the events are already fired.
setTimeout(() => this.workspace.addChangeListener(this.props.vm.blockListener));
if (this.props.vm.editingTarget && this.state.workspaceMetrics[this.props.vm.editingTarget.id]) { if (this.props.vm.editingTarget && this.state.workspaceMetrics[this.props.vm.editingTarget.id]) {
const {scrollX, scrollY, scale} = this.state.workspaceMetrics[this.props.vm.editingTarget.id]; const {scrollX, scrollY, scale} = this.state.workspaceMetrics[this.props.vm.editingTarget.id];
......
...@@ -4,10 +4,12 @@ import {intlShape, injectIntl} from 'react-intl'; ...@@ -4,10 +4,12 @@ import {intlShape, injectIntl} from 'react-intl';
import bindAll from 'lodash.bindall'; import bindAll from 'lodash.bindall';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {setProjectUnchanged} from '../reducers/project-changed';
import { import {
LoadingStates, LoadingStates,
defaultProjectId, defaultProjectId,
getIsFetchingWithId, getIsFetchingWithId,
getIsShowingProject,
onFetchedProjectData, onFetchedProjectData,
projectError, projectError,
setProjectId setProjectId
...@@ -54,6 +56,9 @@ const ProjectFetcherHOC = function (WrappedComponent) { ...@@ -54,6 +56,9 @@ const ProjectFetcherHOC = function (WrappedComponent) {
if (this.props.isFetchingWithId && !prevProps.isFetchingWithId) { if (this.props.isFetchingWithId && !prevProps.isFetchingWithId) {
this.fetchProject(this.props.reduxProjectId, this.props.loadingState); this.fetchProject(this.props.reduxProjectId, this.props.loadingState);
} }
if (this.props.isShowingProject && !prevProps.isShowingProject) {
this.props.onProjectLoaded();
}
} }
fetchProject (projectId, loadingState) { fetchProject (projectId, loadingState) {
return storage return storage
...@@ -123,6 +128,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { ...@@ -123,6 +128,7 @@ const ProjectFetcherHOC = function (WrappedComponent) {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
isFetchingWithId: getIsFetchingWithId(state.scratchGui.projectState.loadingState), isFetchingWithId: getIsFetchingWithId(state.scratchGui.projectState.loadingState),
isShowingProject: getIsShowingProject(state.scratchGui.projectState.loadingState),
loadingState: state.scratchGui.projectState.loadingState, loadingState: state.scratchGui.projectState.loadingState,
reduxProjectId: state.scratchGui.projectState.projectId reduxProjectId: state.scratchGui.projectState.projectId
}); });
...@@ -130,7 +136,8 @@ const ProjectFetcherHOC = function (WrappedComponent) { ...@@ -130,7 +136,8 @@ const ProjectFetcherHOC = function (WrappedComponent) {
onError: error => dispatch(projectError(error)), onError: error => dispatch(projectError(error)),
onFetchedProjectData: (projectData, loadingState) => onFetchedProjectData: (projectData, loadingState) =>
dispatch(onFetchedProjectData(projectData, loadingState)), dispatch(onFetchedProjectData(projectData, loadingState)),
setProjectId: projectId => dispatch(setProjectId(projectId)) setProjectId: projectId => dispatch(setProjectId(projectId)),
onProjectLoaded: () => dispatch(setProjectUnchanged())
}); });
// Allow incoming props to override redux-provided props. Used to mock in tests. // Allow incoming props to override redux-provided props. Used to mock in tests.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign( const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
showAlertWithTimeout, showAlertWithTimeout,
showStandardAlert showStandardAlert
} from '../reducers/alerts'; } from '../reducers/alerts';
import {setProjectUnchanged} from '../reducers/project-changed';
import { import {
LoadingStates, LoadingStates,
autoUpdateProject, autoUpdateProject,
...@@ -167,7 +168,10 @@ const ProjectSaverHOC = function (WrappedComponent) { ...@@ -167,7 +168,10 @@ const ProjectSaverHOC = function (WrappedComponent) {
storage.DataFormat.JSON, storage.DataFormat.JSON,
body, body,
projectId projectId
); ).then(response => {
this.props.onSetProjectUnchanged();
return response;
});
}) })
.catch(err => { .catch(err => {
log.error(err); log.error(err);
...@@ -255,6 +259,7 @@ const ProjectSaverHOC = function (WrappedComponent) { ...@@ -255,6 +259,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
onCreatedProject: (projectId, loadingState) => dispatch(doneCreatingProject(projectId, loadingState)), onCreatedProject: (projectId, loadingState) => dispatch(doneCreatingProject(projectId, loadingState)),
onCreateProject: () => dispatch(createProject()), onCreateProject: () => dispatch(createProject()),
onProjectError: error => dispatch(projectError(error)), onProjectError: error => dispatch(projectError(error)),
onSetProjectUnchanged: () => dispatch(setProjectUnchanged()),
onShowAlert: alertType => dispatch(showStandardAlert(alertType)), onShowAlert: alertType => dispatch(showStandardAlert(alertType)),
onShowCreateSuccessAlert: () => showAlertWithTimeout(dispatch, 'createSuccess'), onShowCreateSuccessAlert: () => showAlertWithTimeout(dispatch, 'createSuccess'),
onShowCreatingAlert: () => showAlertWithTimeout(dispatch, 'creating'), onShowCreatingAlert: () => showAlertWithTimeout(dispatch, 'creating'),
......
...@@ -8,6 +8,7 @@ import {connect} from 'react-redux'; ...@@ -8,6 +8,7 @@ import {connect} from 'react-redux';
import {updateTargets} from '../reducers/targets'; import {updateTargets} from '../reducers/targets';
import {updateBlockDrag} from '../reducers/block-drag'; import {updateBlockDrag} from '../reducers/block-drag';
import {updateMonitors} from '../reducers/monitors'; import {updateMonitors} from '../reducers/monitors';
import {setProjectChanged, setProjectUnchanged} from '../reducers/project-changed';
import {setRunningState, setTurboState, setStartedState} from '../reducers/vm-status'; import {setRunningState, setTurboState, setStartedState} from '../reducers/vm-status';
import {showExtensionAlert} from '../reducers/alerts'; import {showExtensionAlert} from '../reducers/alerts';
import {updateMicIndicator} from '../reducers/mic-indicator'; import {updateMicIndicator} from '../reducers/mic-indicator';
...@@ -24,6 +25,7 @@ const vmListenerHOC = function (WrappedComponent) { ...@@ -24,6 +25,7 @@ const vmListenerHOC = function (WrappedComponent) {
bindAll(this, [ bindAll(this, [
'handleKeyDown', 'handleKeyDown',
'handleKeyUp', 'handleKeyUp',
'handleProjectChanged',
'handleTargetsUpdate' 'handleTargetsUpdate'
]); ]);
// We have to start listening to the vm here rather than in // We have to start listening to the vm here rather than in
...@@ -39,6 +41,7 @@ const vmListenerHOC = function (WrappedComponent) { ...@@ -39,6 +41,7 @@ const vmListenerHOC = function (WrappedComponent) {
this.props.vm.on('TURBO_MODE_OFF', this.props.onTurboModeOff); this.props.vm.on('TURBO_MODE_OFF', this.props.onTurboModeOff);
this.props.vm.on('PROJECT_RUN_START', this.props.onProjectRunStart); this.props.vm.on('PROJECT_RUN_START', this.props.onProjectRunStart);
this.props.vm.on('PROJECT_RUN_STOP', this.props.onProjectRunStop); this.props.vm.on('PROJECT_RUN_STOP', this.props.onProjectRunStop);
this.props.vm.on('PROJECT_CHANGED', this.handleProjectChanged);
this.props.vm.on('RUNTIME_STARTED', this.props.onRuntimeStarted); this.props.vm.on('RUNTIME_STARTED', this.props.onRuntimeStarted);
this.props.vm.on('PERIPHERAL_DISCONNECT_ERROR', this.props.onShowExtensionAlert); this.props.vm.on('PERIPHERAL_DISCONNECT_ERROR', this.props.onShowExtensionAlert);
this.props.vm.on('MIC_LISTENING', this.props.onMicListeningUpdate); this.props.vm.on('MIC_LISTENING', this.props.onMicListeningUpdate);
...@@ -69,6 +72,9 @@ const vmListenerHOC = function (WrappedComponent) { ...@@ -69,6 +72,9 @@ const vmListenerHOC = function (WrappedComponent) {
document.removeEventListener('keyup', this.handleKeyUp); document.removeEventListener('keyup', this.handleKeyUp);
} }
} }
handleProjectChanged () {
this.props.onProjectChanged();
}
handleTargetsUpdate (data) { handleTargetsUpdate (data) {
if (this.props.shouldEmitTargetsUpdate) { if (this.props.shouldEmitTargetsUpdate) {
this.props.onTargetsUpdate(data); this.props.onTargetsUpdate(data);
...@@ -167,6 +173,8 @@ const vmListenerHOC = function (WrappedComponent) { ...@@ -167,6 +173,8 @@ const vmListenerHOC = function (WrappedComponent) {
}, },
onProjectRunStart: () => dispatch(setRunningState(true)), onProjectRunStart: () => dispatch(setRunningState(true)),
onProjectRunStop: () => dispatch(setRunningState(false)), onProjectRunStop: () => dispatch(setRunningState(false)),
onProjectChanged: () => dispatch(setProjectChanged()),
onProjectSaved: () => dispatch(setProjectUnchanged()),
onRuntimeStarted: () => dispatch(setStartedState(true)), onRuntimeStarted: () => dispatch(setStartedState(true)),
onTurboModeOn: () => dispatch(setTurboState(true)), onTurboModeOn: () => dispatch(setTurboState(true)),
onTurboModeOff: () => dispatch(setTurboState(false)), onTurboModeOff: () => dispatch(setTurboState(false)),
......
...@@ -6,6 +6,7 @@ import {connect} from 'react-redux'; ...@@ -6,6 +6,7 @@ import {connect} from 'react-redux';
import VM from 'scratch-vm'; import VM from 'scratch-vm';
import AudioEngine from 'scratch-audio'; import AudioEngine from 'scratch-audio';
import {setProjectUnchanged} from '../reducers/project-changed';
import { import {
LoadingStates, LoadingStates,
getIsLoadingWithId, getIsLoadingWithId,
...@@ -53,6 +54,7 @@ const vmManagerHOC = function (WrappedComponent) { ...@@ -53,6 +54,7 @@ const vmManagerHOC = function (WrappedComponent) {
return this.props.vm.loadProject(this.props.projectData) return this.props.vm.loadProject(this.props.projectData)
.then(() => { .then(() => {
this.props.onLoadedProject(this.props.loadingState, this.props.canSave); this.props.onLoadedProject(this.props.loadingState, this.props.canSave);
this.props.onSetProjectUnchanged();
// If the vm is not running, call draw on the renderer manually // If the vm is not running, call draw on the renderer manually
// This draws the state of the loaded project with no blocks running // This draws the state of the loaded project with no blocks running
...@@ -123,7 +125,8 @@ const vmManagerHOC = function (WrappedComponent) { ...@@ -123,7 +125,8 @@ const vmManagerHOC = function (WrappedComponent) {
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onError: error => dispatch(projectError(error)), onError: error => dispatch(projectError(error)),
onLoadedProject: (loadingState, canSave) => onLoadedProject: (loadingState, canSave) =>
dispatch(onLoadedProject(loadingState, canSave)) dispatch(onLoadedProject(loadingState, canSave)),
onSetProjectUnchanged: () => dispatch(setProjectUnchanged())
}); });
// Allow incoming props to override redux-provided props. Used to mock in tests. // Allow incoming props to override redux-provided props. Used to mock in tests.
......
...@@ -14,6 +14,7 @@ import modalReducer, {modalsInitialState} from './modals'; ...@@ -14,6 +14,7 @@ import modalReducer, {modalsInitialState} from './modals';
import modeReducer, {modeInitialState} from './mode'; import modeReducer, {modeInitialState} from './mode';
import monitorReducer, {monitorsInitialState} from './monitors'; import monitorReducer, {monitorsInitialState} from './monitors';
import monitorLayoutReducer, {monitorLayoutInitialState} from './monitor-layout'; import monitorLayoutReducer, {monitorLayoutInitialState} from './monitor-layout';
import projectChangedReducer, {projectChangedInitialState} from './project-changed';
import projectStateReducer, {projectStateInitialState} from './project-state'; import projectStateReducer, {projectStateInitialState} from './project-state';
import projectTitleReducer, {projectTitleInitialState} from './project-title'; import projectTitleReducer, {projectTitleInitialState} from './project-title';
import restoreDeletionReducer, {restoreDeletionInitialState} from './restore-deletion'; import restoreDeletionReducer, {restoreDeletionInitialState} from './restore-deletion';
...@@ -45,6 +46,7 @@ const guiInitialState = { ...@@ -45,6 +46,7 @@ const guiInitialState = {
modals: modalsInitialState, modals: modalsInitialState,
monitors: monitorsInitialState, monitors: monitorsInitialState,
monitorLayout: monitorLayoutInitialState, monitorLayout: monitorLayoutInitialState,
projectChanged: projectChangedInitialState,
projectState: projectStateInitialState, projectState: projectStateInitialState,
projectTitle: projectTitleInitialState, projectTitle: projectTitleInitialState,
restoreDeletion: restoreDeletionInitialState, restoreDeletion: restoreDeletionInitialState,
...@@ -122,6 +124,7 @@ const guiReducer = combineReducers({ ...@@ -122,6 +124,7 @@ const guiReducer = combineReducers({
modals: modalReducer, modals: modalReducer,
monitors: monitorReducer, monitors: monitorReducer,
monitorLayout: monitorLayoutReducer, monitorLayout: monitorLayoutReducer,
projectChanged: projectChangedReducer,
projectState: projectStateReducer, projectState: projectStateReducer,
projectTitle: projectTitleReducer, projectTitle: projectTitleReducer,
restoreDeletion: restoreDeletionReducer, restoreDeletion: restoreDeletionReducer,
......
const SET_PROJECT_CHANGED = 'scratch-gui/project-changed/SET_PROJECT_CHANGED';
const initialState = false;
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
switch (action.type) {
case SET_PROJECT_CHANGED:
return action.changed;
default:
return state;
}
};
const setProjectChanged = () => ({
type: SET_PROJECT_CHANGED,
changed: true
});
const setProjectUnchanged = () => ({
type: SET_PROJECT_CHANGED,
changed: false
});
export {
reducer as default,
initialState as projectChangedInitialState,
setProjectChanged,
setProjectUnchanged
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment