Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 2 additions & 44 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
const [alertErrorMessage, setAlertErrorMessage] = React.useState('');
const [messageApi, contextHolder] = Antd.message.useMessage();
const [toolboxSettingsModalIsOpen, setToolboxSettingsModalIsOpen] = React.useState(false);
const [modulePathToContentText, setModulePathToContentText] = React.useState<{[modulePath: string]: string}>({});
const [tabItems, setTabItems] = React.useState<Tabs.TabItem[]>([]);
const [isLoadingTabs, setIsLoadingTabs] = React.useState(false);
const [shownPythonToolboxCategories, setShownPythonToolboxCategories] = React.useState<Set<string>>(new Set());
Expand Down Expand Up @@ -269,46 +268,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
initializeShownPythonToolboxCategories();
}, [storage]);

// Fetch any unfetched modules when project changes.
React.useEffect(() => {
fetchModules();
}, [project]);

const fetchModules = async () => {
if (!project || !storage) {
return;
}
const promises: {[modulePath: string]: Promise<string>} = {}; // value is promise of module content.
const updatedModulePathToContentText: {[modulePath: string]: string} = {}; // value is module content text
if (project.robot.modulePath in modulePathToContentText) {
updatedModulePathToContentText[project.robot.modulePath] = modulePathToContentText[project.robot.modulePath];
} else {
promises[project.robot.modulePath] = storage.fetchFileContentText(project.robot.modulePath);
}
project.mechanisms.forEach(mechanism => {
if (mechanism.modulePath in modulePathToContentText) {
updatedModulePathToContentText[mechanism.modulePath] = modulePathToContentText[mechanism.modulePath];
} else {
promises[mechanism.modulePath] = storage.fetchFileContentText(mechanism.modulePath);
}
});
project.opModes.forEach(opmode => {
if (opmode.modulePath in modulePathToContentText) {
updatedModulePathToContentText[opmode.modulePath] = modulePathToContentText[opmode.modulePath];
} else {
promises[opmode.modulePath] = storage.fetchFileContentText(opmode.modulePath);
}
});
if (Object.keys(promises).length) {
await Promise.all(
Object.entries(promises).map(async ([modulePath, promise]) => {
updatedModulePathToContentText[modulePath] = await promise;
})
);
setModulePathToContentText(updatedModulePathToContentText);
}
};

// Load saved tabs when project changes
React.useEffect(() => {
const loadSavedTabs = async () => {
Expand Down Expand Up @@ -438,7 +397,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
setTabItems(updatedTabs);
}
}
}, [modulePathToContentText]);
}, [project, tabItems]);

// Save tabs when tab list changes (but not during initial loading)
React.useEffect(() => {
Expand All @@ -461,7 +420,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
}, [tabItems, project?.projectName, isLoadingTabs]);

const onProjectChanged = async (): Promise<void> => {
await fetchModules();
// No need to fetch modules anymore - each editor fetches what it needs
};

const gotoTab = (tabKey: string): void => {
Expand Down Expand Up @@ -543,7 +502,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
storage={storage}
theme={theme}
shownPythonToolboxCategories={shownPythonToolboxCategories}
modulePathToContentText={modulePathToContentText}
messageApi={messageApi}
/>
</Antd.Layout>
Expand Down
40 changes: 20 additions & 20 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ const MRC_ON_MUTATOR_OPEN = 'mrcOnMutatorOpen';

export class Editor {
private static workspaceIdToEditor: { [workspaceId: string]: Editor } = {};
private static currentEditor: Editor | null = null;

private readonly blocklyWorkspace: Blockly.WorkspaceSvg;
private readonly module: storageModule.Module;
Expand All @@ -74,16 +73,16 @@ export class Editor {
module: storageModule.Module,
project: storageProject.Project,
storage: commonStorage.Storage,
modulePathToContentText: {[modulePath: string]: string}) {
moduleContentText: string) {
workspaces.addWorkspace(blocklyWorkspace, module.moduleType);
this.blocklyWorkspace = blocklyWorkspace;
this.module = module;
this.projectName = project.projectName;
this.storage = storage;
this.modulePath = module.modulePath;
this.robotPath = project.robot.modulePath;
this.moduleContentText = modulePathToContentText[module.modulePath];
this.parseModules(project, modulePathToContentText);
this.moduleContentText = moduleContentText;
// parseModules will be called async after construction
Editor.workspaceIdToEditor[blocklyWorkspace.id] = this;
}

Expand Down Expand Up @@ -205,13 +204,9 @@ export class Editor {
}
}

public makeCurrent(
project: storageProject.Project,
modulePathToContentText: {[modulePath: string]: string}): void {
Editor.currentEditor = this;

public async makeCurrent(project: storageProject.Project): Promise<void> {
// Parse modules since they might have changed.
this.parseModules(project, modulePathToContentText);
await this.parseModules(project);
this.updateToolboxImpl();

// Go through all the blocks in the workspace and call their mrcOnModuleCurrent method.
Expand All @@ -234,17 +229,26 @@ export class Editor {

public abandon(): void {
workspaces.removeWorkspace(this.blocklyWorkspace);
if (Editor.currentEditor === this) {
Editor.currentEditor = null;
}
if (this.blocklyWorkspace.id in Editor.workspaceIdToEditor) {
delete Editor.workspaceIdToEditor[this.blocklyWorkspace.id];
}
}

private parseModules(
project: storageProject.Project,
modulePathToContentText: {[modulePath: string]: string}): void {
private async parseModules(project: storageProject.Project): Promise<void> {
// Fetch all module content from storage
const modulePaths: string[] = [
project.robot.modulePath,
...project.mechanisms.map(m => m.modulePath),
...project.opModes.map(o => o.modulePath),
];

const modulePathToContentText: {[modulePath: string]: string} = {};
await Promise.all(
modulePaths.map(async (modulePath) => {
modulePathToContentText[modulePath] = await this.storage.fetchFileContentText(modulePath);
})
);

// Parse the modules.
this.modulePathToModuleContent = {}
for (const modulePath in modulePathToContentText) {
Expand Down Expand Up @@ -645,10 +649,6 @@ export class Editor {
return workspace ? Editor.getEditorForBlocklyWorkspace(workspace) : null;
}

public static getCurrentEditor(): Editor | null {
return Editor.currentEditor;
}

private setPasteLocation(): void {
const copyData = Blockly.clipboard.getLastCopiedData();
if (copyData && copyData.paster === 'block') {
Expand Down
33 changes: 17 additions & 16 deletions src/reactComponents/TabContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export interface TabContentProps {
storage: commonStorage.Storage;
theme: string;
shownPythonToolboxCategories: Set<string>;
modulePathToContentText: {[modulePath: string]: string};
messageApi: MessageInstance;
setAlertErrorMessage: (message: string) => void;
isActive: boolean;
Expand All @@ -70,7 +69,6 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
storage,
theme,
shownPythonToolboxCategories,
modulePathToContentText,
messageApi,
setAlertErrorMessage,
isActive,
Expand All @@ -90,11 +88,7 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
React.useImperativeHandle(ref, () => ({
saveModule: async () => {
if (editorInstance) {
const moduleContentText = await editorInstance.saveModule();
// Update modulePathToContentText.
// modulePathToContentText is passed to Editor.makeCurrent so the active editor will know
// about changes to other modules.
modulePathToContentText[modulePath] = moduleContentText;
await editorInstance.saveModule();
// Mark as saved after successful save
autosave.markAsSaved();
}
Expand Down Expand Up @@ -138,23 +132,29 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
}, [isActive]);

/** Called when workspace is created. */
const setupWorkspace = React.useCallback((_modulePath: string, newWorkspace: Blockly.WorkspaceSvg) => {
const setupWorkspace = React.useCallback(async (_modulePath: string, newWorkspace: Blockly.WorkspaceSvg) => {
newWorkspace.addChangeListener(handleBlocksChanged);
classMethodDef.registerToolboxButton(newWorkspace, messageApi);
eventHandler.registerToolboxButton(newWorkspace, messageApi);

// Fetch the module content from storage
const moduleContentText = await storage.fetchFileContentText(modulePath);

const newEditor = new editor.Editor(
newWorkspace,
module,
project,
storage,
modulePathToContentText
moduleContentText
);

// Parse modules after editor is created
await newEditor.makeCurrent(project);

setEditorInstance(newEditor);
newEditor.loadModuleBlocks();
newEditor.updateToolbox(shownPythonToolboxCategories);
}, [module, project, storage, modulePathToContentText, shownPythonToolboxCategories, messageApi, handleBlocksChanged]);
}, [module, project, storage, modulePath, shownPythonToolboxCategories, messageApi, handleBlocksChanged]);

/** Update editor toolbox when categories change. */
React.useEffect(() => {
Expand All @@ -171,13 +171,14 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
if (editorInstance && isActive) {
// Set flag to ignore changes during activation
isInitialActivation.current = true;
editorInstance.makeCurrent(project, modulePathToContentText);
// Clear the flag after a brief delay to allow workspace to settle
setTimeout(() => {
isInitialActivation.current = false;
}, 100);
editorInstance.makeCurrent(project).then(() => {
// Clear the flag after a brief delay to allow workspace to settle
setTimeout(() => {
isInitialActivation.current = false;
}, 100);
});
}
}, [isActive, blocklyComponent, editorInstance, project, modulePathToContentText]);
}, [isActive, blocklyComponent, editorInstance, project]);

/** Generate code when regeneration is triggered. */
React.useEffect(() => {
Expand Down
2 changes: 0 additions & 2 deletions src/reactComponents/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export interface TabsProps {
storage: commonStorage.Storage | null;
theme: string;
shownPythonToolboxCategories: Set<string>;
modulePathToContentText: {[modulePath: string]: string};
messageApi: MessageInstance;
}

Expand Down Expand Up @@ -429,7 +428,6 @@ export const Component = React.forwardRef<TabsRef, TabsProps>((props, ref): Reac
storage={props.storage}
theme={props.theme}
shownPythonToolboxCategories={props.shownPythonToolboxCategories}
modulePathToContentText={props.modulePathToContentText}
messageApi={props.messageApi}
setAlertErrorMessage={props.setAlertErrorMessage}
isActive={activeKey === tab.key}
Expand Down