Skip to content

Commit db70675

Browse files
authored
Fixed issue 279 Creating a new module causes errors. (#280)
App.tsx: Move fetchModules function from inside the useEffect callback for project to outside of it. Added onProjectChanged function that calls fetchModules and waits for it to complete. Pass onProjectChanged to Tabs and Menu components. Tabs.tsx: Added onProjectChanged to TabsProps. Removed setProject. Removed triggerProjectUpdate function. Call await props.onProjectChange() when project is changed (after renameModuleInProject, copyModuleInProject, and removeModuleFromProject). Pass onProjectChange to AddTabDialog component. AddTabDialog.tsx: Added onProjectChanged to AddTabDialogProps. Removed setProject. Call await props.onProjectChange() when project is changed (after addModuleToProject). Menu.tsx: Added onProjectChanged to MenuProps. Pass onProjectChange to FileManageModal component. FileManageModal.tsx: Added onProjectChanged to FileManageModalProps. Removed setProject. Removed triggerProjectUpdate function. Call await props.onProjectChange() when project is changed (after renameModuleInProject, copyModuleInProject, addModuleToProject, and removeModuleFromProject).
1 parent 06dfdb0 commit db70675

File tree

5 files changed

+86
-71
lines changed

5 files changed

+86
-71
lines changed

src/App.tsx

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -528,61 +528,77 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
528528
}
529529
}, [shownPythonToolboxCategories]);
530530

531-
// Fetch modules when project changes.
531+
// Fetch any unfetched modules when project changes.
532532
React.useEffect(() => {
533-
if (project && storage) {
534-
const fetchModules = async () => {
535-
const promises: {[modulePath: string]: Promise<string>} = {}; // value is promise of module content.
536-
promises[project.robot.modulePath] = storage.fetchFileContentText(project.robot.modulePath);
537-
project.mechanisms.forEach(mechanism => {
538-
promises[mechanism.modulePath] = storage.fetchFileContentText(mechanism.modulePath);
539-
});
540-
project.opModes.forEach(opmode => {
541-
promises[opmode.modulePath] = storage.fetchFileContentText(opmode.modulePath);
542-
});
543-
const updatedModulePathToContentText: {[modulePath: string]: string} = {}; // value is module content text
544-
await Promise.all(
545-
Object.entries(promises).map(async ([modulePath, promise]) => {
546-
updatedModulePathToContentText[modulePath] = await promise;
547-
})
548-
);
549-
const oldModulePathToContentText = modulePathToContentText;
550-
setModulePathToContentText(updatedModulePathToContentText);
551-
552-
// Remove any deleted modules from modulePaths, modulePathToBlocklyComponent, and
553-
// modulePathToEditor. Update currentModule if the current module was deleted.
554-
for (const modulePath in oldModulePathToContentText) {
555-
if (modulePath in updatedModulePathToContentText) {
556-
continue;
557-
}
558-
if (currentModule && currentModule.modulePath === modulePath) {
559-
setCurrentModule(project.robot);
560-
setActiveTab(project.robot.modulePath);
561-
}
562-
const indexToRemove: number = modulePaths.current.indexOf(modulePath);
563-
if (indexToRemove !== -1) {
564-
modulePaths.current.splice(indexToRemove, 1);
565-
}
566-
if (modulePath in modulePathToBlocklyComponent.current) {
567-
delete modulePathToBlocklyComponent.current[modulePath];
568-
}
569-
if (modulePath in modulePathToEditor.current) {
570-
const editor = modulePathToEditor.current[modulePath];
571-
editor.abandon();
572-
delete modulePathToEditor.current[modulePath];
573-
}
574-
}
575-
};
576-
fetchModules();
577-
}
533+
fetchModules();
578534
}, [project]);
579535

536+
const fetchModules = async () => {
537+
if (!project || !storage) {
538+
return;
539+
}
540+
const oldModulePathToContentText = modulePathToContentText;
541+
const promises: {[modulePath: string]: Promise<string>} = {}; // value is promise of module content.
542+
const updatedModulePathToContentText: {[modulePath: string]: string} = {}; // value is module content text
543+
if (project.robot.modulePath in modulePathToContentText) {
544+
updatedModulePathToContentText[project.robot.modulePath] = modulePathToContentText[project.robot.modulePath];
545+
} else {
546+
promises[project.robot.modulePath] = storage.fetchFileContentText(project.robot.modulePath);
547+
}
548+
project.mechanisms.forEach(mechanism => {
549+
if (mechanism.modulePath in modulePathToContentText) {
550+
updatedModulePathToContentText[mechanism.modulePath] = modulePathToContentText[mechanism.modulePath];
551+
} else {
552+
promises[mechanism.modulePath] = storage.fetchFileContentText(mechanism.modulePath);
553+
}
554+
});
555+
project.opModes.forEach(opmode => {
556+
if (opmode.modulePath in modulePathToContentText) {
557+
updatedModulePathToContentText[opmode.modulePath] = modulePathToContentText[opmode.modulePath];
558+
} else {
559+
promises[opmode.modulePath] = storage.fetchFileContentText(opmode.modulePath);
560+
}
561+
});
562+
if (Object.keys(promises).length) {
563+
await Promise.all(
564+
Object.entries(promises).map(async ([modulePath, promise]) => {
565+
updatedModulePathToContentText[modulePath] = await promise;
566+
})
567+
);
568+
setModulePathToContentText(updatedModulePathToContentText);
569+
}
570+
571+
// Remove any deleted modules from modulePaths, modulePathToBlocklyComponent, and
572+
// modulePathToEditor. Update currentModule if the current module was deleted.
573+
for (const modulePath in oldModulePathToContentText) {
574+
if (modulePath in updatedModulePathToContentText) {
575+
continue;
576+
}
577+
if (currentModule && currentModule.modulePath === modulePath) {
578+
setCurrentModule(project.robot);
579+
setActiveTab(project.robot.modulePath);
580+
}
581+
const indexToRemove: number = modulePaths.current.indexOf(modulePath);
582+
if (indexToRemove !== -1) {
583+
modulePaths.current.splice(indexToRemove, 1);
584+
}
585+
if (modulePath in modulePathToBlocklyComponent.current) {
586+
delete modulePathToBlocklyComponent.current[modulePath];
587+
}
588+
if (modulePath in modulePathToEditor.current) {
589+
const editor = modulePathToEditor.current[modulePath];
590+
editor.abandon();
591+
delete modulePathToEditor.current[modulePath];
592+
}
593+
}
594+
};
595+
580596
// Load saved tabs when project changes
581597
React.useEffect(() => {
582598
const loadSavedTabs = async () => {
583599
if (project && !isLoading) {
584600
setIsLoadingTabs(true);
585-
601+
586602
// Add a small delay to ensure UserSettingsProvider context is updated
587603
await new Promise(resolve => setTimeout(resolve, 0));
588604

@@ -705,7 +721,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
705721
}
706722
return tab;
707723
});
708-
724+
709725
// Only update if something actually changed
710726
const titlesChanged = updatedTabs.some((tab, index) => tab.title !== tabItems[index]?.title);
711727
if (titlesChanged) {
@@ -734,6 +750,10 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
734750
return () => clearTimeout(timeoutId);
735751
}, [tabItems, project?.projectName, isLoadingTabs]);
736752

753+
const onProjectChanged = async (): Promise<void> => {
754+
await fetchModules();
755+
};
756+
737757
const { Sider, Content } = Antd.Layout;
738758

739759
return (
@@ -766,6 +786,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
766786
gotoTab={setActiveTab}
767787
project={project}
768788
setProject={setProject}
789+
onProjectChanged={onProjectChanged}
769790
openWPIToolboxSettings={() => setToolboxSettingsModalIsOpen(true)}
770791
theme={theme}
771792
setTheme={setTheme}
@@ -784,7 +805,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
784805
currentModule={currentModule}
785806
setCurrentModule={changeModule}
786807
project={project}
787-
setProject={setProject}
808+
onProjectChanged={onProjectChanged}
788809
storage={storage}
789810
/>
790811
<div style={{ display: 'flex', height: FULL_HEIGHT }}>

src/reactComponents/AddTabDialog.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ interface AddTabDialogProps {
4141
onOk: (newTab: TabItem) => void;
4242
onCancel: () => void;
4343
project: storageProject.Project | null;
44-
setProject: (project: storageProject.Project | null) => void;
44+
onProjectChanged: () => Promise<void>;
4545
currentTabs: TabItem[];
4646
storage: commonStorage.Storage | null;
4747
}
@@ -111,6 +111,7 @@ export default function AddTabDialog(props: AddTabDialogProps) {
111111

112112
await storageProject.addModuleToProject(
113113
props.storage, props.project, moduleType, newClassName);
114+
await props.onProjectChanged();
114115

115116
const newModule = storageProject.findModuleByClassName(props.project, newClassName);
116117
if (newModule) {

src/reactComponents/FileManageModal.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface FileManageModalProps {
4040
isOpen: boolean;
4141
onClose: () => void;
4242
project: storageProject.Project | null;
43-
setProject: (project: storageProject.Project | null) => void;
43+
onProjectChanged: () => Promise<void>;
4444
gotoTab: (path: string) => void;
4545
setAlertErrorMessage: (message: string) => void;
4646
storage: commonStorage.Storage | null;
@@ -64,11 +64,6 @@ export default function FileManageModal(props: FileManageModalProps) {
6464
const [name, setName] = React.useState('');
6565
const [copyModalOpen, setCopyModalOpen] = React.useState(false);
6666

67-
const triggerProjectUpdate = (): void => {
68-
if (props.project) {
69-
props.setProject({...props.project});
70-
}
71-
}
7267
React.useEffect(() => {
7368
if (!props.project || props.tabType === null) {
7469
setModules([]);
@@ -109,6 +104,7 @@ export default function FileManageModal(props: FileManageModalProps) {
109104
newClassName,
110105
origModule.path
111106
);
107+
await props.onProjectChanged();
112108

113109
const newModules = modules.map((module) => {
114110
if (module.path === origModule.path) {
@@ -118,7 +114,6 @@ export default function FileManageModal(props: FileManageModalProps) {
118114
});
119115

120116
setModules(newModules);
121-
triggerProjectUpdate();
122117

123118
// Close the rename modal first
124119
setRenameModalOpen(false);
@@ -147,6 +142,7 @@ export default function FileManageModal(props: FileManageModalProps) {
147142
newClassName,
148143
origModule.path
149144
);
145+
await props.onProjectChanged();
150146

151147
const originalModule = modules.find((module) => module.path === origModule.path);
152148
if (!originalModule) {
@@ -165,7 +161,6 @@ export default function FileManageModal(props: FileManageModalProps) {
165161
newModules.push(newModule);
166162

167163
setModules(newModules);
168-
triggerProjectUpdate();
169164

170165
// Close the copy modal first
171166
setCopyModalOpen(false);
@@ -197,6 +192,7 @@ export default function FileManageModal(props: FileManageModalProps) {
197192
moduleType,
198193
newClassName
199194
);
195+
await props.onProjectChanged();
200196

201197
const newModule = storageProject.findModuleByClassName(props.project, newClassName);
202198
if (newModule) {
@@ -212,7 +208,7 @@ export default function FileManageModal(props: FileManageModalProps) {
212208
if(newModule){
213209
props.gotoTab(newModule.modulePath);
214210
}
215-
triggerProjectUpdate();
211+
216212
props.onClose();
217213
};
218214

@@ -227,7 +223,7 @@ export default function FileManageModal(props: FileManageModalProps) {
227223
props.project,
228224
record.path
229225
);
230-
triggerProjectUpdate();
226+
await props.onProjectChanged();
231227
}
232228
};
233229

src/reactComponents/Menu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface MenuProps {
6060
gotoTab: (tabKey: string) => void;
6161
project: storageProject.Project | null;
6262
setProject: (project: storageProject.Project | null) => void;
63+
onProjectChanged: () => Promise<void>;
6364
openWPIToolboxSettings: () => void;
6465
theme: string;
6566
setTheme: (theme: string) => void;
@@ -429,7 +430,7 @@ export function Component(props: MenuProps): React.JSX.Element {
429430
project={props.project}
430431
storage={props.storage}
431432
tabType={tabType}
432-
setProject={props.setProject}
433+
onProjectChanged={props.onProjectChanged}
433434
setAlertErrorMessage={props.setAlertErrorMessage}
434435
gotoTab={props.gotoTab}
435436
/>

src/reactComponents/Tabs.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export interface TabsProps {
4848
setTabList: (items: TabItem[]) => void;
4949
activeTab: string;
5050
project: storageProject.Project | null;
51-
setProject: (project: storageProject.Project | null) => void;
51+
onProjectChanged: () => Promise<void>;
5252
setAlertErrorMessage: (message: string) => void;
5353
currentModule: storageModule.Module | null;
5454
setCurrentModule: (module: storageModule.Module | null) => void;
@@ -76,10 +76,6 @@ export function Component(props: TabsProps): React.JSX.Element {
7676
const [copyModalOpen, setCopyModalOpen] = React.useState(false);
7777
const [currentTab, setCurrentTab] = React.useState<TabItem | null>(null);
7878

79-
const triggerProjectUpdate = (): void => {
80-
props.setProject(structuredClone(props.project));
81-
}
82-
8379
/** Handles tab change and updates current module. */
8480
const handleTabChange = (key: string): void => {
8581
if (props.project) {
@@ -154,7 +150,7 @@ export function Component(props: TabsProps): React.JSX.Element {
154150
const handleAddTabOk = (newTab: TabItem): void => {
155151
props.setTabList([...props.tabList, newTab]);
156152

157-
setActiveKey(newTab.key);
153+
handleTabChange(newTab.key);
158154
setAddTabDialogOpen(false);
159155
};
160156

@@ -173,6 +169,7 @@ export function Component(props: TabsProps): React.JSX.Element {
173169
newClassName,
174170
oldModulePath,
175171
);
172+
await props.onProjectChanged();
176173

177174
const newTabs = props.tabList.map((tab) => {
178175
if (tab.key === key) {
@@ -183,7 +180,6 @@ export function Component(props: TabsProps): React.JSX.Element {
183180

184181
props.setTabList(newTabs);
185182
setActiveKey(newModulePath);
186-
triggerProjectUpdate();
187183
} catch (error) {
188184
console.error('Error renaming module:', error);
189185
props.setAlertErrorMessage(t('FAILED_TO_RENAME_MODULE'));
@@ -207,6 +203,7 @@ export function Component(props: TabsProps): React.JSX.Element {
207203
newClassName,
208204
oldModulePath,
209205
);
206+
await props.onProjectChanged();
210207

211208
const newTabs = [...props.tabList];
212209
const originalTab = props.tabList.find((tab) => tab.key === key);
@@ -220,7 +217,6 @@ export function Component(props: TabsProps): React.JSX.Element {
220217
newTabs.push({ key: newModulePath, title: newClassName, type: originalTab.type });
221218
props.setTabList(newTabs);
222219
setActiveKey(newModulePath);
223-
triggerProjectUpdate();
224220
} catch (error) {
225221
console.error('Error copying module:', error);
226222
props.setAlertErrorMessage(t('FAILED_TO_COPY_MODULE'));
@@ -271,7 +267,7 @@ export function Component(props: TabsProps): React.JSX.Element {
271267

272268
if (props.storage && props.project) {
273269
await storageProject.removeModuleFromProject(props.storage, props.project, tab.key);
274-
triggerProjectUpdate();
270+
await props.onProjectChanged();
275271
}
276272

277273
if (newTabs.length > 0) {
@@ -360,7 +356,7 @@ export function Component(props: TabsProps): React.JSX.Element {
360356
onCancel={() => setAddTabDialogOpen(false)}
361357
onOk={handleAddTabOk}
362358
project={props.project}
363-
setProject={props.setProject}
359+
onProjectChanged={props.onProjectChanged}
364360
currentTabs={props.tabList}
365361
storage={props.storage}
366362
/>

0 commit comments

Comments
 (0)