(feat:tools) warn on unsaved changes, deep compare

This commit is contained in:
ManishMadan2882
2025-05-23 18:22:06 +05:30
parent 56b4b63749
commit 046f6c66ed
3 changed files with 116 additions and 2 deletions

View File

@@ -122,7 +122,10 @@
"manageTools": "Go to Tools",
"edit": "Edit",
"delete": "Delete",
"deleteWarning": "Are you sure you want to delete {toolName}?"
"deleteWarning": "Are you sure you want to delete {toolName}?",
"unsavedChanges": "You have unsaved changes that will be lost if you leave without saving.",
"leaveWithoutSaving": "Leave without Saving",
"saveAndLeave": "Save and Leave"
}
},
"modals": {

View File

@@ -15,6 +15,7 @@ import { ActiveState } from '../models/misc';
import { selectToken } from '../preferences/preferenceSlice';
import { APIActionType, APIToolType, UserToolType } from './types';
import { useTranslation } from 'react-i18next';
import { areObjectsEqual } from '../utils/objectUtils';
export default function ToolConfig({
tool,
@@ -34,7 +35,35 @@ export default function ToolConfig({
);
const [actionModalState, setActionModalState] =
React.useState<ActiveState>('INACTIVE');
const [initialState, setInitialState] = React.useState({
customName: tool.customName || '',
authKey: 'token' in tool.config ? tool.config.token : '',
config: tool.config,
actions: 'actions' in tool ? tool.actions : [],
});
const [hasUnsavedChanges, setHasUnsavedChanges] = React.useState(false);
const [showUnsavedModal, setShowUnsavedModal] = React.useState(false);
const { t } = useTranslation();
const handleBackClick = () => {
if (hasUnsavedChanges) {
setShowUnsavedModal(true);
} else {
handleGoBack();
}
};
React.useEffect(() => {
const currentState = {
customName,
authKey,
config: tool.config,
actions: 'actions' in tool ? tool.actions : [],
};
setHasUnsavedChanges(!areObjectsEqual(initialState, currentState));
}, [customName, authKey, tool]);
const handleCheckboxChange = (actionIndex: number, property: string) => {
setTool({
...tool,
@@ -79,6 +108,14 @@ export default function ToolConfig({
token,
)
.then(() => {
// Update initialState to match current state
setInitialState({
customName,
authKey,
config: tool.config,
actions: 'actions' in tool ? tool.actions : [],
});
setHasUnsavedChanges(false);
handleGoBack();
});
};
@@ -124,7 +161,7 @@ export default function ToolConfig({
<div className="flex items-center gap-3 text-sm text-eerie-black dark:text-bright-gray">
<button
className="rounded-full border p-3 text-sm text-gray-400 dark:border-0 dark:bg-[#28292D] dark:text-gray-500 dark:hover:bg-[#2E2F34]"
onClick={handleGoBack}
onClick={handleBackClick}
>
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
</button>
@@ -376,6 +413,51 @@ export default function ToolConfig({
setModalState={setActionModalState}
handleSubmit={handleAddNewAction}
/>
{showUnsavedModal && (
<ConfirmationModal
message={t('settings.tools.unsavedChanges', {
defaultValue:
'You have unsaved changes that will be lost if you leave without saving.',
})}
modalState="ACTIVE"
setModalState={(state) => setShowUnsavedModal(state === 'ACTIVE')}
submitLabel={t('settings.tools.leaveWithoutSaving', {
defaultValue: 'Leave without Saving',
})}
handleSubmit={() => {
setShowUnsavedModal(false);
handleGoBack();
}}
cancelLabel={t('settings.tools.saveAndLeave', {
defaultValue: 'Save and Leave',
})}
handleCancel={() => {
// First save changes, then go back
userService
.updateTool(
{
id: tool.id,
name: tool.name,
displayName: tool.displayName,
customName: customName,
description: tool.description,
config:
tool.name === 'api_tool'
? tool.config
: { token: authKey },
actions: 'actions' in tool ? tool.actions : [],
status: tool.status,
},
token,
)
.then(() => {
setShowUnsavedModal(false);
handleGoBack();
});
}}
variant="danger"
/>
)}
</div>
</div>
);

View File

@@ -0,0 +1,29 @@
/**
* Deeply compares two objects for equality
* @param obj1 First object to compare
* @param obj2 Second object to compare
* @returns boolean indicating if objects are equal
*/
export function areObjectsEqual(obj1: any, obj2: any): boolean {
if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return false;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length !== obj2.length) return false;
return obj1.every((val, idx) => areObjectsEqual(val, obj2[idx]));
}
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every((key) => {
return keys2.includes(key) && areObjectsEqual(obj1[key], obj2[key]);
});
}