@@ -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. */
4854export 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