Skip to content

Commit b9d4802

Browse files
authored
Changes to fix issues with tabs and manage dialogs (#345)
* Changes to fix rename problem * Fix deleting from FileManageModal
1 parent 08f544a commit b9d4802

File tree

4 files changed

+102
-58
lines changed

4 files changed

+102
-58
lines changed

src/App.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,14 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
141141
const [toolboxSettingsModalIsOpen, setToolboxSettingsModalIsOpen] = React.useState(false);
142142
const [modulePathToContentText, setModulePathToContentText] = React.useState<{[modulePath: string]: string}>({});
143143
const [tabItems, setTabItems] = React.useState<Tabs.TabItem[]>([]);
144-
const [activeTab, setActiveTab] = React.useState('');
145144
const [isLoadingTabs, setIsLoadingTabs] = React.useState(false);
146145
const [shownPythonToolboxCategories, setShownPythonToolboxCategories] = React.useState<Set<string>>(new Set());
147146
const [leftCollapsed, setLeftCollapsed] = React.useState(false);
148147
const [theme, setTheme] = React.useState('dark');
149148
const [languageInitialized, setLanguageInitialized] = React.useState(false);
150149
const [themeInitialized, setThemeInitialized] = React.useState(false);
150+
151+
const tabsRef = React.useRef<Tabs.TabsRef>(null);
151152

152153
/** Initialize language from UserSettings when app first starts. */
153154
React.useEffect(() => {
@@ -402,12 +403,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
402403
// Set the tabs
403404
setTabItems(tabsToSet);
404405

405-
// Only set active tab to robot if no active tab is set or if the current active tab no longer exists
406-
const currentActiveTabExists = tabsToSet.some(tab => tab.key === activeTab);
407-
if (!activeTab || !currentActiveTabExists) {
408-
setActiveTab(project.robot.modulePath);
409-
}
410-
411406
// Only auto-save if we didn't use saved tabs (i.e., this is a new project or the first time)
412407
if (!usedSavedTabs) {
413408
try {
@@ -469,6 +464,14 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
469464
await fetchModules();
470465
};
471466

467+
const gotoTab = (tabKey: string): void => {
468+
tabsRef.current?.gotoTab(tabKey);
469+
};
470+
471+
const closeTab = (tabKey: string): void => {
472+
tabsRef.current?.closeTab(tabKey);
473+
};
474+
472475
const { Sider } = Antd.Layout;
473476

474477
return (
@@ -498,7 +501,8 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
498501
<Menu.Component
499502
storage={storage}
500503
setAlertErrorMessage={setAlertErrorMessage}
501-
gotoTab={setActiveTab}
504+
gotoTab={gotoTab}
505+
closeTab={closeTab}
502506
currentProject={project}
503507
setCurrentProject={setProject}
504508
onProjectChanged={onProjectChanged}
@@ -513,8 +517,8 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
513517
</Sider>
514518
<Antd.Layout>
515519
<Tabs.Component
520+
ref={tabsRef}
516521
tabList={tabItems}
517-
activeTab={activeTab}
518522
setTabList={setTabItems}
519523
setAlertErrorMessage={setAlertErrorMessage}
520524
project={project}

src/reactComponents/FileManageModal.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ interface FileManageModalProps {
4242
project: storageProject.Project | null;
4343
onProjectChanged: () => Promise<void>;
4444
gotoTab: (path: string) => void;
45+
closeTab: (path: string) => void;
4546
setAlertErrorMessage: (message: string) => void;
4647
storage: commonStorage.Storage | null;
4748
tabType: TabType;
@@ -65,7 +66,7 @@ export default function FileManageModal(props: FileManageModalProps) {
6566
const [copyModalOpen, setCopyModalOpen] = React.useState(false);
6667

6768
React.useEffect(() => {
68-
if (!props.project || props.tabType === null) {
69+
if (!props.project || props.tabType === null || !props.isOpen) {
6970
setModules([]);
7071
return;
7172
}
@@ -89,7 +90,7 @@ export default function FileManageModal(props: FileManageModalProps) {
8990
// Sort modules alphabetically by name
9091
moduleList.sort((a, b) => a.name.localeCompare(b.name));
9192
setModules(moduleList);
92-
}, [props.project, props.tabType]);
93+
}, [props.project, props.tabType, props.isOpen]);
9394

9495
/** Handles renaming a module. */
9596
const handleRename = async (origModule: Module, newClassName: string): Promise<void> => {
@@ -106,19 +107,10 @@ export default function FileManageModal(props: FileManageModalProps) {
106107
);
107108
await props.onProjectChanged();
108109

109-
const newModules = modules.map((module) => {
110-
if (module.path === origModule.path) {
111-
return {...module, title: newClassName, path: newModulePath};
112-
}
113-
return module;
114-
});
115-
116-
setModules(newModules);
117-
118110
// Close the rename modal first
119111
setRenameModalOpen(false);
120112

121-
// Automatically select and open the newly created module
113+
// Automatically select and open the renamed module
122114
props.gotoTab(newModulePath);
123115
props.onClose();
124116

@@ -218,6 +210,9 @@ export default function FileManageModal(props: FileManageModalProps) {
218210
setModules(newModules);
219211

220212
if (props.storage && props.project) {
213+
// Close the tab before removing the module
214+
props.closeTab(record.path);
215+
221216
await storageProject.removeModuleFromProject(
222217
props.storage,
223218
props.project,

src/reactComponents/Menu.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export interface MenuProps {
5959
storage: commonStorage.Storage | null;
6060
setAlertErrorMessage: (message: string) => void;
6161
gotoTab: (tabKey: string) => void;
62+
closeTab: (tabKey: string) => void;
6263
currentProject: storageProject.Project | null;
6364
setCurrentProject: (project: storageProject.Project | null) => void;
6465
onProjectChanged: () => Promise<void>;
@@ -462,6 +463,7 @@ export function Component(props: MenuProps): React.JSX.Element {
462463
onProjectChanged={props.onProjectChanged}
463464
setAlertErrorMessage={props.setAlertErrorMessage}
464465
gotoTab={props.gotoTab}
466+
closeTab={props.closeTab}
465467
/>
466468
<ProjectManageModal
467469
noProjects={noProjects}

src/reactComponents/Tabs.tsx

Lines changed: 80 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,16 @@ export interface TabItem {
4444
type: TabType;
4545
}
4646

47+
/** Imperative methods exposed by Tabs component. */
48+
export interface TabsRef {
49+
gotoTab: (tabKey: string) => void;
50+
closeTab: (tabKey: string) => void;
51+
}
52+
4753
/** Props for the Tabs component. */
4854
export interface TabsProps {
4955
tabList: TabItem[];
5056
setTabList: (items: TabItem[]) => void;
51-
activeTab: string;
5257
project: storageProject.Project | null;
5358
onProjectChanged: () => Promise<void>;
5459
setAlertErrorMessage: (message: string) => void;
@@ -69,11 +74,11 @@ const MIN_TABS_FOR_CLOSE_OTHERS = 2;
6974
* Tab component that manages project module tabs with add, edit, delete, and rename functionality.
7075
* Provides context menus for tab operations and modal dialogs for user input.
7176
*/
72-
export function Component(props: TabsProps): React.JSX.Element {
77+
export const Component = React.forwardRef<TabsRef, TabsProps>((props, ref): React.JSX.Element => {
7378
const { t } = I18Next.useTranslation();
7479
const [modal, contextHolder] = Antd.Modal.useModal();
7580

76-
const [activeKey, setActiveKey] = React.useState(props.activeTab);
81+
const [activeKey, setActiveKey] = React.useState(props.tabList.length > 0 ? props.tabList[0].key : '');
7782
const [addTabDialogOpen, setAddTabDialogOpen] = React.useState(false);
7883
const [name, setName] = React.useState('');
7984
const [renameModalOpen, setRenameModalOpen] = React.useState(false);
@@ -100,41 +105,75 @@ export function Component(props: TabsProps): React.JSX.Element {
100105
setActiveKey(key);
101106
};
102107

103-
/** Checks if a key exists in the current tab list. */
104-
const isTabOpen = (key: string): boolean => {
105-
return props.tabList.some((tab) => tab.key === key);
106-
};
107-
108-
/** Adds a new tab for the given module key. */
109-
const addTab = (key: string): void => {
110-
const newTabs = [...props.tabList];
111-
if (!props.project) {
108+
/** Goes to a specific tab, adding it if needed or updating if renamed. */
109+
const gotoTab = (tabKey: string): void => {
110+
// Check if tab already exists
111+
const existingTab = props.tabList.find(tab => tab.key === tabKey);
112+
if (existingTab) {
113+
// Tab exists, just activate it
114+
setActiveKey(tabKey);
112115
return;
113116
}
114117

115-
const modulePath = key;
116-
const module = storageProject.findModuleByModulePath(props.project, modulePath);
117-
if (!module) {
118-
return;
119-
}
118+
if (!props.project) return;
119+
120+
// Check if this is a renamed module - look for a tab whose module no longer exists
121+
const targetModule = storageProject.findModuleByModulePath(props.project, tabKey);
122+
if (targetModule) {
123+
// Look for a tab with the same type but whose path no longer exists (indicating a rename)
124+
const staleTab = props.tabList.find(tab => {
125+
const tabModule = storageProject.findModuleByModulePath(props.project!, tab.key);
126+
// If tab's module doesn't exist and it matches the target type
127+
return !tabModule &&
128+
((tab.type === TabType.MECHANISM && targetModule.moduleType === storageModule.ModuleType.MECHANISM) ||
129+
(tab.type === TabType.OPMODE && targetModule.moduleType === storageModule.ModuleType.OPMODE));
130+
});
120131

121-
switch (module.moduleType) {
122-
case storageModule.ModuleType.MECHANISM:
123-
newTabs.push({ key, title: module.className, type: TabType.MECHANISM });
124-
break;
125-
case storageModule.ModuleType.OPMODE:
126-
newTabs.push({ key, title: module.className, type: TabType.OPMODE });
127-
break;
128-
case storageModule.ModuleType.ROBOT:
129-
break; // Robot tab is always first and cannot be added again.
130-
default:
131-
console.warn('Unknown module type:', module.moduleType);
132-
break;
132+
if (staleTab) {
133+
// This is a rename - update the existing tab
134+
const updatedTabs = props.tabList.map(tab =>
135+
tab.key === staleTab.key
136+
? { ...tab, key: tabKey, title: targetModule.className }
137+
: tab
138+
);
139+
props.setTabList(updatedTabs);
140+
setActiveKey(tabKey);
141+
return;
142+
}
143+
144+
// Not a rename - add new tab
145+
let newTab: TabItem;
146+
switch (targetModule.moduleType) {
147+
case storageModule.ModuleType.MECHANISM:
148+
newTab = { key: tabKey, title: targetModule.className, type: TabType.MECHANISM };
149+
break;
150+
case storageModule.ModuleType.OPMODE:
151+
newTab = { key: tabKey, title: targetModule.className, type: TabType.OPMODE };
152+
break;
153+
case storageModule.ModuleType.ROBOT:
154+
newTab = { key: tabKey, title: targetModule.className, type: TabType.ROBOT };
155+
break;
156+
default:
157+
return;
158+
}
159+
props.setTabList([...props.tabList, newTab]);
160+
setActiveKey(tabKey);
133161
}
162+
};
134163

164+
/** Closes a specific tab. */
165+
const closeTabMethod = (tabKey: string): void => {
166+
const newTabs = props.tabList.filter((tab) => tab.key !== tabKey);
135167
props.setTabList(newTabs);
168+
// The useEffect will handle switching to another tab if needed
136169
};
137170

171+
// Expose imperative methods via ref
172+
React.useImperativeHandle(ref, () => ({
173+
gotoTab,
174+
closeTab: closeTabMethod,
175+
}));
176+
138177
/** Handles tab edit actions (add/remove). */
139178
const handleTabEdit = (
140179
targetKey: React.MouseEvent | React.KeyboardEvent | string,
@@ -378,15 +417,19 @@ export function Component(props: TabsProps): React.JSX.Element {
378417
});
379418
};
380419

381-
// Effect to handle active tab changes
420+
// Effect to ensure activeKey is valid when tab list changes
382421
React.useEffect(() => {
383-
if (activeKey !== props.activeTab) {
384-
if (!isTabOpen(props.activeTab)) {
385-
addTab(props.activeTab);
386-
}
387-
handleTabChange(props.activeTab);
422+
// Check if current activeKey is still in the tab list
423+
const isActiveKeyValid = props.tabList.some(tab => tab.key === activeKey);
424+
425+
if (!isActiveKeyValid && props.tabList.length > 0) {
426+
// Active tab was removed, switch to first available tab
427+
const newActiveKey = props.tabList[0].key;
428+
setActiveKey(newActiveKey);
429+
} else if (props.tabList.length === 0) {
430+
setActiveKey('');
388431
}
389-
}, [props.activeTab]);
432+
}, [props.tabList.length]);
390433

391434
return (
392435
<>
@@ -480,4 +523,4 @@ export function Component(props: TabsProps): React.JSX.Element {
480523
/>
481524
</>
482525
);
483-
}
526+
});

0 commit comments

Comments
 (0)