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
119 changes: 70 additions & 49 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -528,61 +528,77 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
}
}, [shownPythonToolboxCategories]);

// Fetch modules when project changes.
// Fetch any unfetched modules when project changes.
React.useEffect(() => {
if (project && storage) {
const fetchModules = async () => {
const promises: {[modulePath: string]: Promise<string>} = {}; // value is promise of module content.
promises[project.robot.modulePath] = storage.fetchFileContentText(project.robot.modulePath);
project.mechanisms.forEach(mechanism => {
promises[mechanism.modulePath] = storage.fetchFileContentText(mechanism.modulePath);
});
project.opModes.forEach(opmode => {
promises[opmode.modulePath] = storage.fetchFileContentText(opmode.modulePath);
});
const updatedModulePathToContentText: {[modulePath: string]: string} = {}; // value is module content text
await Promise.all(
Object.entries(promises).map(async ([modulePath, promise]) => {
updatedModulePathToContentText[modulePath] = await promise;
})
);
const oldModulePathToContentText = modulePathToContentText;
setModulePathToContentText(updatedModulePathToContentText);

// Remove any deleted modules from modulePaths, modulePathToBlocklyComponent, and
// modulePathToEditor. Update currentModule if the current module was deleted.
for (const modulePath in oldModulePathToContentText) {
if (modulePath in updatedModulePathToContentText) {
continue;
}
if (currentModule && currentModule.modulePath === modulePath) {
setCurrentModule(project.robot);
setActiveTab(project.robot.modulePath);
}
const indexToRemove: number = modulePaths.current.indexOf(modulePath);
if (indexToRemove !== -1) {
modulePaths.current.splice(indexToRemove, 1);
}
if (modulePath in modulePathToBlocklyComponent.current) {
delete modulePathToBlocklyComponent.current[modulePath];
}
if (modulePath in modulePathToEditor.current) {
const editor = modulePathToEditor.current[modulePath];
editor.abandon();
delete modulePathToEditor.current[modulePath];
}
}
};
fetchModules();
}
fetchModules();
}, [project]);

const fetchModules = async () => {
if (!project || !storage) {
return;
}
const oldModulePathToContentText = modulePathToContentText;
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);
}

// Remove any deleted modules from modulePaths, modulePathToBlocklyComponent, and
// modulePathToEditor. Update currentModule if the current module was deleted.
for (const modulePath in oldModulePathToContentText) {
if (modulePath in updatedModulePathToContentText) {
continue;
}
if (currentModule && currentModule.modulePath === modulePath) {
setCurrentModule(project.robot);
setActiveTab(project.robot.modulePath);
}
const indexToRemove: number = modulePaths.current.indexOf(modulePath);
if (indexToRemove !== -1) {
modulePaths.current.splice(indexToRemove, 1);
}
if (modulePath in modulePathToBlocklyComponent.current) {
delete modulePathToBlocklyComponent.current[modulePath];
}
if (modulePath in modulePathToEditor.current) {
const editor = modulePathToEditor.current[modulePath];
editor.abandon();
delete modulePathToEditor.current[modulePath];
}
}
};

// Load saved tabs when project changes
React.useEffect(() => {
const loadSavedTabs = async () => {
if (project && !isLoading) {
setIsLoadingTabs(true);

// Add a small delay to ensure UserSettingsProvider context is updated
await new Promise(resolve => setTimeout(resolve, 0));

Expand Down Expand Up @@ -705,7 +721,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
}
return tab;
});

// Only update if something actually changed
const titlesChanged = updatedTabs.some((tab, index) => tab.title !== tabItems[index]?.title);
if (titlesChanged) {
Expand Down Expand Up @@ -734,6 +750,10 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
return () => clearTimeout(timeoutId);
}, [tabItems, project?.projectName, isLoadingTabs]);

const onProjectChanged = async (): Promise<void> => {
await fetchModules();
};

const { Sider, Content } = Antd.Layout;

return (
Expand Down Expand Up @@ -766,6 +786,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
gotoTab={setActiveTab}
project={project}
setProject={setProject}
onProjectChanged={onProjectChanged}
openWPIToolboxSettings={() => setToolboxSettingsModalIsOpen(true)}
theme={theme}
setTheme={setTheme}
Expand All @@ -784,7 +805,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
currentModule={currentModule}
setCurrentModule={changeModule}
project={project}
setProject={setProject}
onProjectChanged={onProjectChanged}
storage={storage}
/>
<div style={{ display: 'flex', height: FULL_HEIGHT }}>
Expand Down
3 changes: 2 additions & 1 deletion src/reactComponents/AddTabDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface AddTabDialogProps {
onOk: (newTab: TabItem) => void;
onCancel: () => void;
project: storageProject.Project | null;
setProject: (project: storageProject.Project | null) => void;
onProjectChanged: () => Promise<void>;
currentTabs: TabItem[];
storage: commonStorage.Storage | null;
}
Expand Down Expand Up @@ -111,6 +111,7 @@ export default function AddTabDialog(props: AddTabDialogProps) {

await storageProject.addModuleToProject(
props.storage, props.project, moduleType, newClassName);
await props.onProjectChanged();

const newModule = storageProject.findModuleByClassName(props.project, newClassName);
if (newModule) {
Expand Down
16 changes: 6 additions & 10 deletions src/reactComponents/FileManageModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface FileManageModalProps {
isOpen: boolean;
onClose: () => void;
project: storageProject.Project | null;
setProject: (project: storageProject.Project | null) => void;
onProjectChanged: () => Promise<void>;
gotoTab: (path: string) => void;
setAlertErrorMessage: (message: string) => void;
storage: commonStorage.Storage | null;
Expand All @@ -64,11 +64,6 @@ export default function FileManageModal(props: FileManageModalProps) {
const [name, setName] = React.useState('');
const [copyModalOpen, setCopyModalOpen] = React.useState(false);

const triggerProjectUpdate = (): void => {
if (props.project) {
props.setProject({...props.project});
}
}
React.useEffect(() => {
if (!props.project || props.tabType === null) {
setModules([]);
Expand Down Expand Up @@ -109,6 +104,7 @@ export default function FileManageModal(props: FileManageModalProps) {
newClassName,
origModule.path
);
await props.onProjectChanged();

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

setModules(newModules);
triggerProjectUpdate();

// Close the rename modal first
setRenameModalOpen(false);
Expand Down Expand Up @@ -147,6 +142,7 @@ export default function FileManageModal(props: FileManageModalProps) {
newClassName,
origModule.path
);
await props.onProjectChanged();

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

setModules(newModules);
triggerProjectUpdate();

// Close the copy modal first
setCopyModalOpen(false);
Expand Down Expand Up @@ -197,6 +192,7 @@ export default function FileManageModal(props: FileManageModalProps) {
moduleType,
newClassName
);
await props.onProjectChanged();

const newModule = storageProject.findModuleByClassName(props.project, newClassName);
if (newModule) {
Expand All @@ -212,7 +208,7 @@ export default function FileManageModal(props: FileManageModalProps) {
if(newModule){
props.gotoTab(newModule.modulePath);
}
triggerProjectUpdate();

props.onClose();
};

Expand All @@ -227,7 +223,7 @@ export default function FileManageModal(props: FileManageModalProps) {
props.project,
record.path
);
triggerProjectUpdate();
await props.onProjectChanged();
}
};

Expand Down
3 changes: 2 additions & 1 deletion src/reactComponents/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface MenuProps {
gotoTab: (tabKey: string) => void;
project: storageProject.Project | null;
setProject: (project: storageProject.Project | null) => void;
onProjectChanged: () => Promise<void>;
openWPIToolboxSettings: () => void;
theme: string;
setTheme: (theme: string) => void;
Expand Down Expand Up @@ -429,7 +430,7 @@ export function Component(props: MenuProps): React.JSX.Element {
project={props.project}
storage={props.storage}
tabType={tabType}
setProject={props.setProject}
onProjectChanged={props.onProjectChanged}
setAlertErrorMessage={props.setAlertErrorMessage}
gotoTab={props.gotoTab}
/>
Expand Down
16 changes: 6 additions & 10 deletions src/reactComponents/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface TabsProps {
setTabList: (items: TabItem[]) => void;
activeTab: string;
project: storageProject.Project | null;
setProject: (project: storageProject.Project | null) => void;
onProjectChanged: () => Promise<void>;
setAlertErrorMessage: (message: string) => void;
currentModule: storageModule.Module | null;
setCurrentModule: (module: storageModule.Module | null) => void;
Expand Down Expand Up @@ -76,10 +76,6 @@ export function Component(props: TabsProps): React.JSX.Element {
const [copyModalOpen, setCopyModalOpen] = React.useState(false);
const [currentTab, setCurrentTab] = React.useState<TabItem | null>(null);

const triggerProjectUpdate = (): void => {
props.setProject(structuredClone(props.project));
}

/** Handles tab change and updates current module. */
const handleTabChange = (key: string): void => {
if (props.project) {
Expand Down Expand Up @@ -154,7 +150,7 @@ export function Component(props: TabsProps): React.JSX.Element {
const handleAddTabOk = (newTab: TabItem): void => {
props.setTabList([...props.tabList, newTab]);

setActiveKey(newTab.key);
handleTabChange(newTab.key);
setAddTabDialogOpen(false);
};

Expand All @@ -173,6 +169,7 @@ export function Component(props: TabsProps): React.JSX.Element {
newClassName,
oldModulePath,
);
await props.onProjectChanged();

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

props.setTabList(newTabs);
setActiveKey(newModulePath);
triggerProjectUpdate();
} catch (error) {
console.error('Error renaming module:', error);
props.setAlertErrorMessage(t('FAILED_TO_RENAME_MODULE'));
Expand All @@ -207,6 +203,7 @@ export function Component(props: TabsProps): React.JSX.Element {
newClassName,
oldModulePath,
);
await props.onProjectChanged();

const newTabs = [...props.tabList];
const originalTab = props.tabList.find((tab) => tab.key === key);
Expand All @@ -220,7 +217,6 @@ export function Component(props: TabsProps): React.JSX.Element {
newTabs.push({ key: newModulePath, title: newClassName, type: originalTab.type });
props.setTabList(newTabs);
setActiveKey(newModulePath);
triggerProjectUpdate();
} catch (error) {
console.error('Error copying module:', error);
props.setAlertErrorMessage(t('FAILED_TO_COPY_MODULE'));
Expand Down Expand Up @@ -271,7 +267,7 @@ export function Component(props: TabsProps): React.JSX.Element {

if (props.storage && props.project) {
await storageProject.removeModuleFromProject(props.storage, props.project, tab.key);
triggerProjectUpdate();
await props.onProjectChanged();
}

if (newTabs.length > 0) {
Expand Down Expand Up @@ -360,7 +356,7 @@ export function Component(props: TabsProps): React.JSX.Element {
onCancel={() => setAddTabDialogOpen(false)}
onOk={handleAddTabOk}
project={props.project}
setProject={props.setProject}
onProjectChanged={props.onProjectChanged}
currentTabs={props.tabList}
storage={props.storage}
/>
Expand Down