Skip to content

Commit 036d97e

Browse files
Revised fix for cross-tab issues
The previous fix still had issues when creating a new project in a second tab and switching between them. I initially fixed this by taking the same approach for new project (via MakeCode) but it's slow and you see the old project render. The loading states that we'd need don't seen proportionate. So I've gone with: - updating MakeCode only as needed - ensuring import from .org has a header id by setting one explicitly - carefully tracking when we expect to get new content from MakeCode in editorChange Fixes #618
1 parent a1a13ff commit 036d97e

File tree

2 files changed

+85
-29
lines changed

2 files changed

+85
-29
lines changed

src/hooks/project-hooks.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ export const ProjectProvider = ({
130130
const openEditorTimedOutDialog = useStore(
131131
(s) => () => s.setIsEditorTimedOutDialogOpen(true)
132132
);
133-
const expectChangedHeader = useStore((s) => s.setChangedHeaderExpected);
133+
const setEditorLoadingFile = useStore((s) => s.setEditorLoadingFile);
134+
const setEditorImportingState = useStore((s) => s.setEditorImportingState);
134135
const projectFlushedToEditor = useStore((s) => s.projectFlushedToEditor);
135136
const checkIfProjectNeedsFlush = useStore((s) => s.checkIfProjectNeedsFlush);
136137
const getCurrentProject = useStore((s) => s.getCurrentProject);
@@ -218,8 +219,8 @@ export const ProjectProvider = ({
218219
logging.log("[MakeCode] Importing project");
219220
await editorReadyPromise.promise;
220221
const project = getCurrentProject();
221-
expectChangedHeader();
222222
try {
223+
setEditorImportingState();
223224
await driverRef.current.importProject({ project });
224225
logging.log("[MakeCode] Project import succeeded");
225226
projectFlushedToEditor();
@@ -260,7 +261,7 @@ export const ProjectProvider = ({
260261
logging,
261262
editorReadyPromise.promise,
262263
getCurrentProject,
263-
expectChangedHeader,
264+
setEditorImportingState,
264265
projectFlushedToEditor,
265266
langChangeFlushedToEditor,
266267
checkIfEditorStartUpTimedOut,
@@ -332,6 +333,7 @@ export const ProjectProvider = ({
332333
return;
333334
}
334335
// This triggers the code in editorChanged to update actions etc.
336+
setEditorLoadingFile();
335337
driverRef.current!.importFile({
336338
filename: file.name,
337339
parts: [hex],
@@ -346,12 +348,13 @@ export const ProjectProvider = ({
346348
[
347349
checkIfEditorStartUpTimedOut,
348350
driverRef,
349-
editorReadyPromise,
351+
editorReadyPromise.promise,
350352
loadDataset,
351353
logging,
352354
navigate,
353355
openEditorTimedOutDialog,
354356
setPostImportDialogState,
357+
setEditorLoadingFile,
355358
]
356359
);
357360

src/store.ts

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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,7 @@ const createMlStore = (logging: Logging) => {
325344
isEditorTimedOutDialogOpen: false,
326345
langChanged: false,
327346
appEditNeedsFlushToEditor: true,
328-
changedHeaderExpected: false,
347+
waitingForLoad: false,
329348
// This dialog flow spans two pages
330349
trainModelDialogStage: TrainModelDialogStage.Closed,
331350
trainModelProgress: 0,
@@ -669,7 +688,16 @@ const createMlStore = (logging: Logging) => {
669688
*/
670689
loadProject(project: MakeCodeProject, name: string) {
671690
const newActions = getActionsFromProject(project);
672-
set(({ settings }) => {
691+
set(({ settings, project: prevProject }) => {
692+
project = renameProject(project, name);
693+
project = {
694+
...project,
695+
header: {
696+
...project.header!,
697+
// .org projects have a partial header with no id which causes MakeCode sadness
698+
id: project.header?.id ?? prevProject.header!.id,
699+
},
700+
};
673701
const timestamp = Date.now();
674702
return {
675703
settings: {
@@ -681,7 +709,7 @@ const createMlStore = (logging: Logging) => {
681709
actions: newActions,
682710
dataWindow: getDataWindowFromActions(newActions),
683711
model: undefined,
684-
project: renameProject(project, name),
712+
project,
685713
projectEdited: true,
686714
appEditNeedsFlushToEditor: true,
687715
timestamp,
@@ -842,35 +870,60 @@ const createMlStore = (logging: Logging) => {
842870
},
843871

844872
editorChange(newProject: MakeCodeProject) {
873+
// Notes on past issues with the MakeCode integration:
874+
//
875+
// We update MakeCode only as needed. However, it loads the background
876+
// because we need it to be ready. This means it will have the initial
877+
// project from the state open. So we must be sure to update MakeCode
878+
// before "Edit in MakeCode" and "Save" actions.
879+
//
880+
// MakeCode has a visibility listener that will cause it to re-run
881+
// its initialization when its tab becomes hidden/visible. We aim to
882+
// ignore this when MakeCode is closed. When MakeCode is open we'll have
883+
// up-to-date state to reinit MakeCode with. In the past this has
884+
// caused us to update app state with old data from MakeCode.
885+
//
886+
// It's too slow/async from a UI perspective to rely on only
887+
// understanding project contents via editorChange as they're
888+
// delayed by MakeCode load and then the async nature of loading a
889+
// project.
890+
//
891+
// We have no choice but to write to MakeCode and wait for the project
892+
// data in editorChange when loading a hex file.
893+
845894
const actionName = "editorChange";
846895
set(
847896
(state) => {
848897
const {
849898
project: prevProject,
850899
isEditorOpen,
851-
isEditorReady,
852-
changedHeaderExpected,
900+
isEditorImportingState,
901+
isEditorLoadingFile,
853902
settings,
854903
} = state;
855904
const newProjectHeader = newProject.header!.id;
856905
const previousProjectHeader = prevProject.header!.id;
857-
if (newProjectHeader !== previousProjectHeader) {
858-
if (changedHeaderExpected) {
906+
if (
907+
(isEditorLoadingFile ||
908+
isEditorOpen ||
909+
isEditorImportingState) &&
910+
newProjectHeader !== previousProjectHeader
911+
) {
912+
if (isEditorImportingState) {
913+
// It's a change but we originated it so state is in sync.
859914
logging.log(
860-
`[MakeCode] Detected new project, ignoring as expected due to import. ID change: ${prevProject.header?.id} -> ${newProject.header?.id}`
915+
`[MakeCode] Ignored header change due to us syncing state. ID change: ${prevProject.header?.id} -> ${newProject.header?.id}`
861916
);
862917
return {
863-
changedHeaderExpected: false,
918+
isEditorImportingState: false,
919+
// Still need to update this for the new header id.
864920
project: newProject,
865921
};
866-
}
867-
if (isEditorReady) {
922+
} else {
923+
// It's a change that originated in MakeCode, e.g. a hex load, so update our state.
868924
logging.log(
869-
`[MakeCode] Detected new project, loading actions. ID change: ${prevProject.header?.id} -> ${newProject.header?.id}`
925+
`[MakeCode] Updating state from MakeCode header change. ID change: ${prevProject.header?.id} -> ${newProject.header?.id}`
870926
);
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
874927
const timestamp = Date.now();
875928
const newActions = getActionsFromProject(newProject);
876929
return {
@@ -892,12 +945,8 @@ const createMlStore = (logging: Logging) => {
892945
dataWindow: getDataWindowFromActions(newActions),
893946
model: undefined,
894947
isEditorOpen: false,
948+
isEditorLoadingFile: false,
895949
};
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-
);
901950
}
902951
} else if (isEditorOpen) {
903952
logging.log(
@@ -909,6 +958,7 @@ const createMlStore = (logging: Logging) => {
909958
projectEdited: true,
910959
};
911960
} else {
961+
// This lets us skip more pointless init-time edits.
912962
logging.log(
913963
`[MakeCode] Edit ignored when closed. ID ${newProject.header?.id}`
914964
);
@@ -932,11 +982,14 @@ const createMlStore = (logging: Logging) => {
932982
setSave(save: SaveState) {
933983
set({ save }, false, "setSave");
934984
},
935-
setChangedHeaderExpected() {
985+
setEditorLoadingFile() {
986+
set({ isEditorLoadingFile: true }, false, "setEditorLoadingFile");
987+
},
988+
setEditorImportingState() {
936989
set(
937-
{ changedHeaderExpected: true },
990+
{ isEditorImportingState: true },
938991
false,
939-
"setChangedHeaderExpected"
992+
"setEditorImportingState"
940993
);
941994
},
942995
langChangeFlushedToEditor() {

0 commit comments

Comments
 (0)