@@ -153,7 +153,7 @@ interface AppContentProps {
153153
154154const AppContent : React . FC < AppContentProps > = ( { project, setProject } ) : React . JSX . Element => {
155155 const { t, i18n } = useTranslation ( ) ;
156- const { settings, updateLanguage, updateTheme, storage, isLoading } = useUserSettings ( ) ;
156+ const { settings, updateLanguage, updateTheme, updateOpenTabs , getOpenTabs , storage, isLoading } = useUserSettings ( ) ;
157157
158158 const [ alertErrorMessage , setAlertErrorMessage ] = React . useState ( '' ) ;
159159 const [ currentModule , setCurrentModule ] = React . useState < storageModule . Module | null > ( null ) ;
@@ -163,6 +163,7 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
163163 const [ modulePathToContentText , setModulePathToContentText ] = React . useState < { [ modulePath : string ] : string } > ( { } ) ;
164164 const [ tabItems , setTabItems ] = React . useState < Tabs . TabItem [ ] > ( [ ] ) ;
165165 const [ activeTab , setActiveTab ] = React . useState ( '' ) ;
166+ const [ isLoadingTabs , setIsLoadingTabs ] = React . useState ( false ) ;
166167 const [ shownPythonToolboxCategories , setShownPythonToolboxCategories ] = React . useState < Set < string > > ( new Set ( ) ) ;
167168 const [ triggerPythonRegeneration , setTriggerPythonRegeneration ] = React . useState ( 0 ) ;
168169 const [ leftCollapsed , setLeftCollapsed ] = React . useState ( false ) ;
@@ -384,35 +385,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
384385 handleToolboxSettingsOk ( updatedShownCategories ) ;
385386 } ;
386387
387- /** Creates tab items from project data. */
388- const createTabItemsFromProject = ( projectData : storageProject . Project ) : Tabs . TabItem [ ] => {
389- const tabs : Tabs . TabItem [ ] = [
390- {
391- key : projectData . robot . modulePath ,
392- title : t ( 'ROBOT' ) ,
393- type : TabType . ROBOT ,
394- } ,
395- ] ;
396-
397- projectData . mechanisms . forEach ( ( mechanism ) => {
398- tabs . push ( {
399- key : mechanism . modulePath ,
400- title : mechanism . className ,
401- type : TabType . MECHANISM ,
402- } ) ;
403- } ) ;
404-
405- projectData . opModes . forEach ( ( opmode ) => {
406- tabs . push ( {
407- key : opmode . modulePath ,
408- title : opmode . className ,
409- type : TabType . OPMODE ,
410- } ) ;
411- } ) ;
412-
413- return tabs ;
414- } ;
415-
416388 /** Handles toolbox update requests from blocks */
417389 const handleToolboxUpdateRequest = React . useCallback ( ( e : Event ) => {
418390 const workspaceId = ( e as CustomEvent ) . detail . workspaceId ;
@@ -581,20 +553,163 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
581553 }
582554 } , [ project ] ) ;
583555
584- // Update tab items when ever the modules in the project change.
556+ // Load saved tabs when project changes
585557 React . useEffect ( ( ) => {
586- if ( project ) {
587- const tabs = createTabItemsFromProject ( project ) ;
588- setTabItems ( tabs ) ;
558+ const loadSavedTabs = async ( ) => {
559+ if ( project && ! isLoading ) {
560+ setIsLoadingTabs ( true ) ;
561+
562+ // Add a small delay to ensure UserSettingsProvider context is updated
563+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
564+
565+ let tabsToSet : Tabs . TabItem [ ] = [ ] ;
566+ let usedSavedTabs = false ;
567+
568+ // Try to load saved tabs first
569+ try {
570+ const savedTabPaths = await getOpenTabs ( project . projectName ) ;
571+
572+ if ( savedTabPaths . length > 0 ) {
573+ // Filter saved tabs to only include those that still exist in the project
574+ const validSavedTabs = savedTabPaths . filter ( ( tabPath : string ) => {
575+ const module = storageProject . findModuleByModulePath ( project ! , tabPath ) ;
576+ return module !== null ;
577+ } ) ;
578+
579+ if ( validSavedTabs . length > 0 ) {
580+ usedSavedTabs = true ;
581+ // Convert paths back to TabItem objects
582+ tabsToSet = validSavedTabs . map ( ( path : string ) => {
583+ const module = storageProject . findModuleByModulePath ( project ! , path ) ;
584+ if ( ! module ) return null ;
585+
586+ let type : TabType ;
587+ let title : string ;
588+
589+ switch ( module . moduleType ) {
590+ case storageModule . ModuleType . ROBOT :
591+ type = TabType . ROBOT ;
592+ title = t ( 'ROBOT' ) ;
593+ break ;
594+ case storageModule . ModuleType . MECHANISM :
595+ type = TabType . MECHANISM ;
596+ title = module . className ;
597+ break ;
598+ case storageModule . ModuleType . OPMODE :
599+ type = TabType . OPMODE ;
600+ title = module . className ;
601+ break ;
602+ default :
603+ return null ;
604+ }
605+
606+ return {
607+ key : path ,
608+ title,
609+ type,
610+ } ;
611+ } ) . filter ( ( item ) : item is Tabs . TabItem => item !== null ) ;
612+ }
613+ }
614+ } catch ( error ) {
615+ console . error ( 'Failed to load saved tabs:' , error ) ;
616+ }
617+
618+ // If no saved tabs or loading failed, create default tabs (all project files)
619+ if ( tabsToSet . length === 0 ) {
620+ tabsToSet = [
621+ {
622+ key : project . robot . modulePath ,
623+ title : t ( 'ROBOT' ) ,
624+ type : TabType . ROBOT ,
625+ }
626+ ] ;
627+
628+ // Add all mechanisms
629+ project . mechanisms . forEach ( ( mechanism ) => {
630+ tabsToSet . push ( {
631+ key : mechanism . modulePath ,
632+ title : mechanism . className ,
633+ type : TabType . MECHANISM ,
634+ } ) ;
635+ } ) ;
636+
637+ // Add all opmodes
638+ project . opModes . forEach ( ( opmode ) => {
639+ tabsToSet . push ( {
640+ key : opmode . modulePath ,
641+ title : opmode . className ,
642+ type : TabType . OPMODE ,
643+ } ) ;
644+ } ) ;
645+ }
646+
647+ // Set the tabs
648+ setTabItems ( tabsToSet ) ;
649+
650+ // Only set active tab to robot if no active tab is set or if the current active tab no longer exists
651+ const currentActiveTabExists = tabsToSet . some ( tab => tab . key === activeTab ) ;
652+ if ( ! activeTab || ! currentActiveTabExists ) {
653+ setActiveTab ( project . robot . modulePath ) ;
654+ }
655+
656+ // Only auto-save if we didn't use saved tabs (i.e., this is a new project or the first time)
657+ if ( ! usedSavedTabs ) {
658+ try {
659+ const tabPaths = tabsToSet . map ( tab => tab . key ) ;
660+ await updateOpenTabs ( project . projectName , tabPaths ) ;
661+ } catch ( error ) {
662+ console . error ( 'Failed to auto-save default tabs:' , error ) ;
663+ }
664+ }
665+
666+ setIsLoadingTabs ( false ) ;
667+ }
668+ } ;
669+
670+ loadSavedTabs ( ) ;
671+ } , [ project ?. projectName , isLoading , getOpenTabs ] ) ;
672+
673+ // Update tab items when modules in project change (for title updates, etc)
674+ React . useEffect ( ( ) => {
675+ if ( project && tabItems . length > 0 ) {
676+ // Update existing tab titles in case they changed
677+ const updatedTabs = tabItems . map ( tab => {
678+ const module = storageProject . findModuleByModulePath ( project , tab . key ) ;
679+ if ( module && module . moduleType !== storageModule . ModuleType . ROBOT ) {
680+ return { ...tab , title : module . className } ;
681+ }
682+ return tab ;
683+ } ) ;
589684
590- // Only set active tab to robot if no active tab is set or if the current active tab no longer exists
591- const currentActiveTabExists = tabs . some ( tab => tab . key === activeTab ) ;
592- if ( ! activeTab || ! currentActiveTabExists ) {
593- setActiveTab ( project . robot . modulePath ) ;
685+ // Only update if something actually changed
686+ const titlesChanged = updatedTabs . some ( ( tab , index ) => tab . title !== tabItems [ index ] ?. title ) ;
687+ if ( titlesChanged ) {
688+ setTabItems ( updatedTabs ) ;
594689 }
595690 }
596691 } , [ modulePathToContentText ] ) ;
597692
693+ // Save tabs when tab list changes (but not during initial loading)
694+ React . useEffect ( ( ) => {
695+ const saveTabs = async ( ) => {
696+ // Don't save tabs while we're in the process of loading them
697+ if ( project ?. projectName && tabItems . length > 0 && ! isLoadingTabs ) {
698+ try {
699+ const tabPaths = tabItems . map ( tab => tab . key ) ;
700+ await updateOpenTabs ( project . projectName , tabPaths ) ;
701+ } catch ( error ) {
702+ console . error ( 'Failed to save open tabs:' , error ) ;
703+ // Don't show alert for save failures as they're not critical to user workflow
704+ }
705+ }
706+ } ;
707+
708+ // Use a small delay to debounce rapid tab changes
709+ const timeoutId = setTimeout ( saveTabs , 100 ) ;
710+ return ( ) => clearTimeout ( timeoutId ) ;
711+ } , [ tabItems , project ?. projectName , isLoadingTabs ] ) ;
712+
598713 const { Sider, Content } = Antd . Layout ;
599714
600715 return (
0 commit comments