Skip to content
Snippets Groups Projects
Unverified Commit 863f3138 authored by Ray Schamp's avatar Ray Schamp Committed by GitHub
Browse files

Merge pull request #3879 from rschamp/project-changed-state

Track "project changed" state in Redux
parents 75a1abd1 f6d419ae
No related branches found
No related tags found
No related merge requests found
......@@ -328,7 +328,10 @@ class Blocks extends React.Component {
error.message = `Workspace Update Error: ${error.message}`;
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]) {
const {scrollX, scrollY, scale} = this.state.workspaceMetrics[this.props.vm.editingTarget.id];
......
......@@ -4,10 +4,12 @@ import {intlShape, injectIntl} from 'react-intl';
import bindAll from 'lodash.bindall';
import {connect} from 'react-redux';
import {setProjectUnchanged} from '../reducers/project-changed';
import {
LoadingStates,
defaultProjectId,
getIsFetchingWithId,
getIsShowingProject,
onFetchedProjectData,
projectError,
setProjectId
......@@ -54,6 +56,9 @@ const ProjectFetcherHOC = function (WrappedComponent) {
if (this.props.isFetchingWithId && !prevProps.isFetchingWithId) {
this.fetchProject(this.props.reduxProjectId, this.props.loadingState);
}
if (this.props.isShowingProject && !prevProps.isShowingProject) {
this.props.onProjectLoaded();
}
}
fetchProject (projectId, loadingState) {
return storage
......@@ -123,6 +128,7 @@ const ProjectFetcherHOC = function (WrappedComponent) {
const mapStateToProps = state => ({
isFetchingWithId: getIsFetchingWithId(state.scratchGui.projectState.loadingState),
isShowingProject: getIsShowingProject(state.scratchGui.projectState.loadingState),
loadingState: state.scratchGui.projectState.loadingState,
reduxProjectId: state.scratchGui.projectState.projectId
});
......@@ -130,7 +136,8 @@ const ProjectFetcherHOC = function (WrappedComponent) {
onError: error => dispatch(projectError(error)),
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.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
......
......@@ -9,6 +9,7 @@ import {
showAlertWithTimeout,
showStandardAlert
} from '../reducers/alerts';
import {setProjectUnchanged} from '../reducers/project-changed';
import {
LoadingStates,
autoUpdateProject,
......@@ -167,7 +168,10 @@ const ProjectSaverHOC = function (WrappedComponent) {
storage.DataFormat.JSON,
body,
projectId
);
).then(response => {
this.props.onSetProjectUnchanged();
return response;
});
})
.catch(err => {
log.error(err);
......@@ -255,6 +259,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
onCreatedProject: (projectId, loadingState) => dispatch(doneCreatingProject(projectId, loadingState)),
onCreateProject: () => dispatch(createProject()),
onProjectError: error => dispatch(projectError(error)),
onSetProjectUnchanged: () => dispatch(setProjectUnchanged()),
onShowAlert: alertType => dispatch(showStandardAlert(alertType)),
onShowCreateSuccessAlert: () => showAlertWithTimeout(dispatch, 'createSuccess'),
onShowCreatingAlert: () => showAlertWithTimeout(dispatch, 'creating'),
......
......@@ -8,6 +8,7 @@ import {connect} from 'react-redux';
import {updateTargets} from '../reducers/targets';
import {updateBlockDrag} from '../reducers/block-drag';
import {updateMonitors} from '../reducers/monitors';
import {setProjectChanged, setProjectUnchanged} from '../reducers/project-changed';
import {setRunningState, setTurboState, setStartedState} from '../reducers/vm-status';
import {showExtensionAlert} from '../reducers/alerts';
import {updateMicIndicator} from '../reducers/mic-indicator';
......@@ -24,6 +25,7 @@ const vmListenerHOC = function (WrappedComponent) {
bindAll(this, [
'handleKeyDown',
'handleKeyUp',
'handleProjectChanged',
'handleTargetsUpdate'
]);
// We have to start listening to the vm here rather than in
......@@ -39,6 +41,7 @@ const vmListenerHOC = function (WrappedComponent) {
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_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('PERIPHERAL_DISCONNECT_ERROR', this.props.onShowExtensionAlert);
this.props.vm.on('MIC_LISTENING', this.props.onMicListeningUpdate);
......@@ -69,6 +72,9 @@ const vmListenerHOC = function (WrappedComponent) {
document.removeEventListener('keyup', this.handleKeyUp);
}
}
handleProjectChanged () {
this.props.onProjectChanged();
}
handleTargetsUpdate (data) {
if (this.props.shouldEmitTargetsUpdate) {
this.props.onTargetsUpdate(data);
......@@ -167,6 +173,8 @@ const vmListenerHOC = function (WrappedComponent) {
},
onProjectRunStart: () => dispatch(setRunningState(true)),
onProjectRunStop: () => dispatch(setRunningState(false)),
onProjectChanged: () => dispatch(setProjectChanged()),
onProjectSaved: () => dispatch(setProjectUnchanged()),
onRuntimeStarted: () => dispatch(setStartedState(true)),
onTurboModeOn: () => dispatch(setTurboState(true)),
onTurboModeOff: () => dispatch(setTurboState(false)),
......
......@@ -6,6 +6,7 @@ import {connect} from 'react-redux';
import VM from 'scratch-vm';
import AudioEngine from 'scratch-audio';
import {setProjectUnchanged} from '../reducers/project-changed';
import {
LoadingStates,
getIsLoadingWithId,
......@@ -53,6 +54,9 @@ const vmManagerHOC = function (WrappedComponent) {
return this.props.vm.loadProject(this.props.projectData)
.then(() => {
this.props.onLoadedProject(this.props.loadingState, this.props.canSave);
// Wrap in a setTimeout because skin loading in
// the renderer can be async.
setTimeout(() => this.props.onSetProjectUnchanged());
// If the vm is not running, call draw on the renderer manually
// This draws the state of the loaded project with no blocks running
......@@ -123,7 +127,8 @@ const vmManagerHOC = function (WrappedComponent) {
const mapDispatchToProps = dispatch => ({
onError: error => dispatch(projectError(error)),
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.
......
......@@ -14,6 +14,7 @@ import modalReducer, {modalsInitialState} from './modals';
import modeReducer, {modeInitialState} from './mode';
import monitorReducer, {monitorsInitialState} from './monitors';
import monitorLayoutReducer, {monitorLayoutInitialState} from './monitor-layout';
import projectChangedReducer, {projectChangedInitialState} from './project-changed';
import projectStateReducer, {projectStateInitialState} from './project-state';
import projectTitleReducer, {projectTitleInitialState} from './project-title';
import restoreDeletionReducer, {restoreDeletionInitialState} from './restore-deletion';
......@@ -45,6 +46,7 @@ const guiInitialState = {
modals: modalsInitialState,
monitors: monitorsInitialState,
monitorLayout: monitorLayoutInitialState,
projectChanged: projectChangedInitialState,
projectState: projectStateInitialState,
projectTitle: projectTitleInitialState,
restoreDeletion: restoreDeletionInitialState,
......@@ -122,6 +124,7 @@ const guiReducer = combineReducers({
modals: modalReducer,
monitors: monitorReducer,
monitorLayout: monitorLayoutReducer,
projectChanged: projectChangedReducer,
projectState: projectStateReducer,
projectTitle: projectTitleReducer,
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