Skip to content

Commit d77b85d

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 d77b85d

File tree

2 files changed

+84
-29
lines changed

2 files changed

+84
-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: 77 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,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

Comments
 (0)