Skip to content

Commit bfac33b

Browse files
committed
Add saving when tab changes
1 parent 5a2b206 commit bfac33b

File tree

3 files changed

+40
-33
lines changed

3 files changed

+40
-33
lines changed

src/App.tsx

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
139139
const { settings, updateLanguage, updateTheme, updateOpenTabs, getOpenTabs, storage, isLoading } = useUserSettings();
140140

141141
const [alertErrorMessage, setAlertErrorMessage] = React.useState('');
142-
const [currentModule, setCurrentModule] = React.useState<storageModule.Module | null>(null);
143142
const [messageApi, contextHolder] = Antd.message.useMessage();
144143
const [toolboxSettingsModalIsOpen, setToolboxSettingsModalIsOpen] = React.useState(false);
145144
const [modulePathToContentText, setModulePathToContentText] = React.useState<{[modulePath: string]: string}>({});
@@ -251,14 +250,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
251250
await storage.saveEntry(SHOWN_TOOLBOX_CATEGORIES_KEY, JSON.stringify(array));
252251
};
253252

254-
/** Changes current module. */
255-
const changeModule = async (module: storageModule.Module | null): Promise<void> => {
256-
setCurrentModule(module);
257-
if (module) {
258-
setActiveTab(module.modulePath);
259-
}
260-
};
261-
262253
/** Handles toolbox settings modal close. */
263254
const handleToolboxSettingsCancel = (): void => {
264255
setToolboxSettingsModalIsOpen(false);
@@ -306,7 +297,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
306297
if (!project || !storage) {
307298
return;
308299
}
309-
const oldModulePathToContentText = modulePathToContentText;
310300
const promises: {[modulePath: string]: Promise<string>} = {}; // value is promise of module content.
311301
const updatedModulePathToContentText: {[modulePath: string]: string} = {}; // value is module content text
312302
if (project.robot.modulePath in modulePathToContentText) {
@@ -336,17 +326,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
336326
);
337327
setModulePathToContentText(updatedModulePathToContentText);
338328
}
339-
340-
// Update currentModule if the current module was deleted.
341-
for (const modulePath in oldModulePathToContentText) {
342-
if (modulePath in updatedModulePathToContentText) {
343-
continue;
344-
}
345-
if (currentModule && currentModule.modulePath === modulePath) {
346-
setCurrentModule(project.robot);
347-
setActiveTab(project.robot.modulePath);
348-
}
349-
}
350329
};
351330

352331
// Load saved tabs when project changes
@@ -558,8 +537,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
558537
activeTab={activeTab}
559538
setTabList={setTabItems}
560539
setAlertErrorMessage={setAlertErrorMessage}
561-
currentModule={currentModule}
562-
setCurrentModule={changeModule}
563540
project={project}
564541
onProjectChanged={onProjectChanged}
565542
storage={storage}

src/reactComponents/TabContent.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ const CODE_PANEL_DEFAULT_SIZE = '25%';
3939
/** Minimum size for code panel. */
4040
const CODE_PANEL_MIN_SIZE = 100;
4141

42+
/** Interface for methods exposed by TabContent via ref. */
43+
export interface TabContentRef {
44+
saveModule: () => Promise<void>;
45+
}
46+
4247
export interface TabContentProps {
4348
modulePath: string;
4449
module: storageModule.Module;
@@ -56,7 +61,7 @@ export interface TabContentProps {
5661
* Component that manages a single tab's BlocklyComponent and CodeDisplay.
5762
* Each tab has its own workspace and code display.
5863
*/
59-
export const TabContent: React.FC<TabContentProps> = ({
64+
export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
6065
modulePath,
6166
module,
6267
project,
@@ -67,7 +72,7 @@ export const TabContent: React.FC<TabContentProps> = ({
6772
messageApi,
6873
setAlertErrorMessage,
6974
isActive,
70-
}) => {
75+
}, ref) => {
7176
const [blocklyComponent, setBlocklyComponent] = React.useState<BlocklyComponentType | null>(null);
7277
const [editorInstance, setEditorInstance] = React.useState<editor.Editor | null>(null);
7378
const [generatedCode, setGeneratedCode] = React.useState<string>('');
@@ -77,6 +82,15 @@ export const TabContent: React.FC<TabContentProps> = ({
7782
const [codePanelExpandedSize, setCodePanelExpandedSize] = React.useState<string | number>(CODE_PANEL_DEFAULT_SIZE);
7883
const [codePanelAnimating, setCodePanelAnimating] = React.useState(false);
7984

85+
/** Expose saveModule method via ref. */
86+
React.useImperativeHandle(ref, () => ({
87+
saveModule: async () => {
88+
if (editorInstance) {
89+
await editorInstance.saveModule();
90+
}
91+
},
92+
}), [editorInstance]);
93+
8094
/** Handles Blockly workspace changes and triggers code regeneration. */
8195
const handleBlocksChanged = React.useCallback((event: Blockly.Events.Abstract): void => {
8296
if (event.isUiEvent) {
@@ -261,4 +275,6 @@ export const TabContent: React.FC<TabContentProps> = ({
261275
</div>
262276
</div>
263277
);
264-
};
278+
});
279+
280+
TabContent.displayName = 'TabContent';

src/reactComponents/Tabs.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
import AddTabDialog from './AddTabDialog';
3636
import ClassNameComponent from './ClassNameComponent';
3737
import { TabType, TabTypeUtils } from '../types/TabType';
38-
import { TabContent } from './TabContent';
38+
import { TabContent, TabContentRef } from './TabContent';
3939

4040
/** Represents a tab item in the tab bar. */
4141
export interface TabItem {
@@ -52,8 +52,6 @@ export interface TabsProps {
5252
project: storageProject.Project | null;
5353
onProjectChanged: () => Promise<void>;
5454
setAlertErrorMessage: (message: string) => void;
55-
currentModule: storageModule.Module | null;
56-
setCurrentModule: (module: storageModule.Module | null) => void;
5755
storage: commonStorage.Storage | null;
5856
theme: string;
5957
shownPythonToolboxCategories: Set<string>;
@@ -81,14 +79,23 @@ export function Component(props: TabsProps): React.JSX.Element {
8179
const [renameModalOpen, setRenameModalOpen] = React.useState(false);
8280
const [copyModalOpen, setCopyModalOpen] = React.useState(false);
8381
const [currentTab, setCurrentTab] = React.useState<TabItem | null>(null);
82+
83+
// Store refs to TabContent components for each tab
84+
const tabContentRefs = React.useRef<Map<string, TabContentRef>>(new Map());
8485

8586
/** Handles tab change and updates current module. */
8687
const handleTabChange = (key: string): void => {
87-
if (props.project) {
88-
const modulePath = key;
89-
props.setCurrentModule(storageProject.findModuleByModulePath(props.project, modulePath));
90-
setActiveKey(key);
88+
if (key !== activeKey) {
89+
// Save the tab we are changing away from (async, but don't wait)
90+
const currentTabRef = tabContentRefs.current.get(activeKey);
91+
if (currentTabRef) {
92+
currentTabRef.saveModule().catch((error) => {
93+
console.error('Error saving module on tab switch:', error);
94+
props.setAlertErrorMessage(t('FAILED_TO_SAVE_MODULE'));
95+
});
96+
}
9197
}
98+
setActiveKey(key);
9299
};
93100

94101
/** Checks if a key exists in the current tab list. */
@@ -346,6 +353,13 @@ export function Component(props: TabsProps): React.JSX.Element {
346353
closable: tab.type !== TabType.ROBOT,
347354
children: module && props.project && props.storage ? (
348355
<TabContent
356+
ref={(ref) => {
357+
if (ref) {
358+
tabContentRefs.current.set(tab.key, ref);
359+
} else {
360+
tabContentRefs.current.delete(tab.key);
361+
}
362+
}}
349363
modulePath={tab.key}
350364
module={module}
351365
project={props.project}

0 commit comments

Comments
 (0)