@@ -159,10 +159,26 @@ export interface State {
159159 projectLoadTimestamp : number ;
160160 // false if we're sure the user hasn't changed the project, otherwise true
161161 projectEdited : boolean ;
162- changedHeaderExpected : boolean ;
162+ /**
163+ * Set to true when we need to update MakeCode before we open it.
164+ */
163165 appEditNeedsFlushToEditor : boolean ;
164166 isEditorOpen : boolean ;
165167 isEditorReady : boolean ;
168+ /**
169+ * Whether we're expecting an editorChange call with a new header id
170+ * because we've
171+ *
172+ * In this case we need to update app state from the project.
173+ */
174+ isEditorLoadingFile : boolean ;
175+ /**
176+ * Whether we're expecting a editorChange call with a new header id
177+ * because we've imported an updated copy of the project.
178+ *
179+ * In this case app state (e.g. dataset) will already be in sync.
180+ */
181+ isEditorImportingState : boolean ;
166182 editorStartUp : EditorStartUp ;
167183 editorStartUpTimestamp : number ;
168184 editorPromises : {
@@ -253,7 +269,8 @@ export interface Actions {
253269 editorTimedOut ( ) : void ;
254270 getEditorStartUp ( ) : EditorStartUp ;
255271 setIsEditorTimedOutDialogOpen ( isOpen : boolean ) : void ;
256- setChangedHeaderExpected ( ) : void ;
272+ setEditorLoadingFile ( ) : void ;
273+ setEditorImportingState ( ) : void ;
257274 projectFlushedToEditor ( ) : void ;
258275
259276 setDownload ( state : DownloadState ) : void ;
@@ -316,6 +333,8 @@ const createMlStore = (logging: Logging) => {
316333 model : undefined ,
317334 isEditorOpen : false ,
318335 isEditorReady : false ,
336+ isEditorLoadingFile : false ,
337+ isEditorImportingState : false ,
319338 editorStartUp : "in-progress" ,
320339 editorStartUpTimestamp : Date . now ( ) ,
321340 editorPromises : {
@@ -325,7 +344,6 @@ const createMlStore = (logging: Logging) => {
325344 isEditorTimedOutDialogOpen : false ,
326345 langChanged : false ,
327346 appEditNeedsFlushToEditor : true ,
328- changedHeaderExpected : false ,
329347 // This dialog flow spans two pages
330348 trainModelDialogStage : TrainModelDialogStage . Closed ,
331349 trainModelProgress : 0 ,
@@ -669,7 +687,16 @@ const createMlStore = (logging: Logging) => {
669687 */
670688 loadProject ( project : MakeCodeProject , name : string ) {
671689 const newActions = getActionsFromProject ( project ) ;
672- set ( ( { settings } ) => {
690+ set ( ( { settings, project : prevProject } ) => {
691+ project = renameProject ( project , name ) ;
692+ project = {
693+ ...project ,
694+ header : {
695+ ...project . header ! ,
696+ // .org projects have a partial header with no id which causes MakeCode sadness
697+ id : project . header ?. id ?? prevProject . header ! . id ,
698+ } ,
699+ } ;
673700 const timestamp = Date . now ( ) ;
674701 return {
675702 settings : {
@@ -681,7 +708,7 @@ const createMlStore = (logging: Logging) => {
681708 actions : newActions ,
682709 dataWindow : getDataWindowFromActions ( newActions ) ,
683710 model : undefined ,
684- project : renameProject ( project , name ) ,
711+ project,
685712 projectEdited : true ,
686713 appEditNeedsFlushToEditor : true ,
687714 timestamp,
@@ -842,35 +869,60 @@ const createMlStore = (logging: Logging) => {
842869 } ,
843870
844871 editorChange ( newProject : MakeCodeProject ) {
872+ // Notes on past issues with the MakeCode integration:
873+ //
874+ // We update MakeCode only as needed. However, it loads in the
875+ // background because we need it to be ready. This means it will
876+ // have the initial project from the state open. So we must be sure
877+ // to update MakeCode before "Edit in MakeCode" and "Save" actions.
878+ //
879+ // MakeCode has a visibility listener that will cause it to re-run
880+ // its initialization when its tab becomes hidden/visible. We aim to
881+ // ignore this when MakeCode is closed. When MakeCode is open we'll
882+ // have up-to-date state to reinit MakeCode with. In the past this
883+ // has caused us to update app state with old data from MakeCode.
884+ //
885+ // It's too slow/async from a UI perspective to rely on only
886+ // understanding project contents via editorChange as they're
887+ // delayed by MakeCode load and then the async nature of loading a
888+ // project.
889+ //
890+ // We have no choice but to write to MakeCode and wait for the
891+ // project data in editorChange when loading a hex file.
892+
845893 const actionName = "editorChange" ;
846894 set (
847895 ( state ) => {
848896 const {
849897 project : prevProject ,
850898 isEditorOpen,
851- isEditorReady ,
852- changedHeaderExpected ,
899+ isEditorImportingState ,
900+ isEditorLoadingFile ,
853901 settings,
854902 } = state ;
855903 const newProjectHeader = newProject . header ! . id ;
856904 const previousProjectHeader = prevProject . header ! . id ;
857- if ( newProjectHeader !== previousProjectHeader ) {
858- if ( changedHeaderExpected ) {
905+ if (
906+ ( isEditorLoadingFile ||
907+ isEditorOpen ||
908+ isEditorImportingState ) &&
909+ newProjectHeader !== previousProjectHeader
910+ ) {
911+ if ( isEditorImportingState ) {
912+ // It's a change but we originated it so state is in sync.
859913 logging . log (
860- `[MakeCode] Detected new project, ignoring as expected due to import . ID change: ${ prevProject . header ?. id } -> ${ newProject . header ?. id } `
914+ `[MakeCode] Ignored header change due to us syncing state . ID change: ${ prevProject . header ?. id } -> ${ newProject . header ?. id } `
861915 ) ;
862916 return {
863- changedHeaderExpected : false ,
917+ isEditorImportingState : false ,
918+ // Still need to update this for the new header id.
864919 project : newProject ,
865920 } ;
866- }
867- if ( isEditorReady ) {
921+ } else {
922+ // It's a change that originated in MakeCode, e.g. a hex load, so update our state.
868923 logging . log (
869- `[MakeCode] Detected new project, loading actions . ID change: ${ prevProject . header ?. id } -> ${ newProject . header ?. id } `
924+ `[MakeCode] Updating state from MakeCode header change . ID change: ${ prevProject . header ?. id } -> ${ newProject . header ?. id } `
870925 ) ;
871- // It's a new project. Thanks user. We'll update our state.
872- // This will cause another write to MakeCode but that's OK as it gives us
873- // a chance to validate/update the project
874926 const timestamp = Date . now ( ) ;
875927 const newActions = getActionsFromProject ( newProject ) ;
876928 return {
@@ -892,12 +944,8 @@ const createMlStore = (logging: Logging) => {
892944 dataWindow : getDataWindowFromActions ( newActions ) ,
893945 model : undefined ,
894946 isEditorOpen : false ,
947+ isEditorLoadingFile : false ,
895948 } ;
896- } else {
897- // In particular, this happens if the MakeCode init completes after we've updated our project state from an import from .org
898- logging . log (
899- `[MakeCode] Ignoring changed ID before editor ready. ID change: ${ prevProject . header ?. id } -> ${ newProject . header ?. id } `
900- ) ;
901949 }
902950 } else if ( isEditorOpen ) {
903951 logging . log (
@@ -909,6 +957,7 @@ const createMlStore = (logging: Logging) => {
909957 projectEdited : true ,
910958 } ;
911959 } else {
960+ // This lets us skip more pointless init-time edits.
912961 logging . log (
913962 `[MakeCode] Edit ignored when closed. ID ${ newProject . header ?. id } `
914963 ) ;
@@ -932,11 +981,14 @@ const createMlStore = (logging: Logging) => {
932981 setSave ( save : SaveState ) {
933982 set ( { save } , false , "setSave" ) ;
934983 } ,
935- setChangedHeaderExpected ( ) {
984+ setEditorLoadingFile ( ) {
985+ set ( { isEditorLoadingFile : true } , false , "setEditorLoadingFile" ) ;
986+ } ,
987+ setEditorImportingState ( ) {
936988 set (
937- { changedHeaderExpected : true } ,
989+ { isEditorImportingState : true } ,
938990 false ,
939- "setChangedHeaderExpected "
991+ "setEditorImportingState "
940992 ) ;
941993 } ,
942994 langChangeFlushedToEditor ( ) {
0 commit comments