fix: kebab menu rename jumps to wrong tab (#38346)

## Description
Fixes an issue when renaming from context menu in JS objects, cursor
always jumps to the first tab.

Fixes #38207 

## Automation

/ok-to-test tags="@tag.IDE"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12480124658>
> Commit: 221119c1487842f0e58340eb3ce2b1ecd740d56d
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12480124658&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.IDE`
> Spec:
> <hr>Tue, 24 Dec 2024 10:31:34 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
	- Improved context menu options with enhanced performance and clarity.
- **Bug Fixes**
- Added checks for the availability of the CodeMirror instance to
prevent errors.
- **Refactor**
- Simplified state management by replacing `useState` with `useBoolean`.
	- Optimized context menu option generation using `useMemo`.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Alex 2024-12-24 15:23:30 +03:00 committed by GitHub
parent ecf9934859
commit c0d393a8f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,4 +1,5 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useMemo } from "react";
import { useBoolean } from "usehooks-ts";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
moveJSCollectionRequest, moveJSCollectionRequest,
@ -34,6 +35,7 @@ import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import type { JSCollection } from "entities/JSCollection"; import type { JSCollection } from "entities/JSCollection";
import { setRenameEntity } from "actions/ideActions"; import { setRenameEntity } from "actions/ideActions";
import type CodeMirror from "codemirror";
interface AppJSEditorContextMenuProps { interface AppJSEditorContextMenuProps {
pageId: string; pageId: string;
@ -46,7 +48,11 @@ export function AppJSEditorContextMenu({
jsCollection, jsCollection,
pageId, pageId,
}: AppJSEditorContextMenuProps) { }: AppJSEditorContextMenuProps) {
const [confirmDelete, setConfirmDelete] = useState(false); const {
setFalse: cancelConfirmDelete,
setValue: setConfirmDelete,
value: confirmDelete,
} = useBoolean(false);
const dispatch = useDispatch(); const dispatch = useDispatch();
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const isDeletePermitted = getHasDeleteActionPermission( const isDeletePermitted = getHasDeleteActionPermission(
@ -63,7 +69,7 @@ export function AppJSEditorContextMenu({
setTimeout(() => { setTimeout(() => {
dispatch(setRenameEntity(jsCollection.id)); dispatch(setRenameEntity(jsCollection.id));
}, 100); }, 100);
}, []); }, [dispatch, jsCollection.id]);
const copyJSCollectionToPage = useCallback( const copyJSCollectionToPage = useCallback(
(actionId: string, actionName: string, pageId: string) => { (actionId: string, actionName: string, pageId: string) => {
@ -95,113 +101,130 @@ export function AppJSEditorContextMenu({
dispatch(deleteJSCollection({ id: actionId, name: actionName })); dispatch(deleteJSCollection({ id: actionId, name: actionName }));
setConfirmDelete(false); setConfirmDelete(false);
}, },
[dispatch], [dispatch, setConfirmDelete],
); );
const confirmDeletion = (value: boolean, event?: Event) => {
event?.preventDefault?.();
setConfirmDelete(value);
};
const menuPages = useSelector(getPageListAsOptions, equal); const menuPages = useSelector(getPageListAsOptions, equal);
const renameOption = { const options = useMemo(() => {
icon: "input-cursor-move" as IconName, const confirmDeletion = (value: boolean, event?: Event) => {
value: "rename", event?.preventDefault?.();
onSelect: renameJS, setConfirmDelete(value);
label: createMessage(CONTEXT_RENAME), };
disabled: !isChangePermitted,
};
const copyOption = { const renameOption = {
icon: "duplicate" as IconName, icon: "input-cursor-move" as IconName,
value: "copy", value: "rename",
onSelect: noop, onSelect: renameJS,
label: createMessage(CONTEXT_COPY), label: createMessage(CONTEXT_RENAME),
children: menuPages.map((page) => { disabled: !isChangePermitted,
return { };
...page,
onSelect: () =>
copyJSCollectionToPage(jsCollection.id, jsCollection.name, page.id),
};
}),
};
const moveOption = { const copyOption = {
icon: "swap-horizontal" as IconName, icon: "duplicate" as IconName,
value: "move", value: "copy",
onSelect: noop, onSelect: noop,
label: createMessage(CONTEXT_MOVE), label: createMessage(CONTEXT_COPY),
children: children: menuPages.map((page) => {
menuPages.length > 1 return {
? menuPages ...page,
.filter((page) => page.id !== pageId) // Remove current page from the list onSelect: () =>
.map((page) => { copyJSCollectionToPage(jsCollection.id, jsCollection.name, page.id),
return { };
...page, }),
onSelect: () => };
moveJSCollectionToPage(
jsCollection.id,
jsCollection.name,
page.id,
),
};
})
: [{ value: "No Pages", onSelect: noop, label: "No Pages" }],
};
const prettifyOptions = { const moveOption = {
value: "prettify", icon: "swap-horizontal" as IconName,
icon: "code" as IconName, value: "move",
subText: prettifyCodeKeyboardShortCut, onSelect: noop,
onSelect: () => { label: createMessage(CONTEXT_MOVE),
/* children:
PS: Please do not remove ts-ignore from here, TS keeps suggesting that menuPages.length > 1
the object is null, but that is not the case, and we need an ? menuPages
instance of the editor to pass to autoIndentCode function .filter((page) => page.id !== pageId) // Remove current page from the list
*/ .map((page) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment return {
// @ts-ignore ...page,
const editor = document.querySelector(".CodeMirror").CodeMirror; onSelect: () =>
moveJSCollectionToPage(
jsCollection.id,
jsCollection.name,
page.id,
),
};
})
: [{ value: "No Pages", onSelect: noop, label: "No Pages" }],
};
autoIndentCode(editor); const prettifyOptions = {
dispatch(updateJSCollectionBody(editor.getValue(), jsCollection.id)); value: "prettify",
AnalyticsUtil.logEvent("PRETTIFY_CODE_MANUAL_TRIGGER"); icon: "code" as IconName,
}, subText: prettifyCodeKeyboardShortCut,
label: "Prettify code", onSelect: () => {
}; const editorElement = document.querySelector(".CodeMirror");
const deleteOption = { if (
confirmDelete: confirmDelete, editorElement &&
icon: "delete-bin-line" as IconName, "CodeMirror" in editorElement &&
value: "delete", editorElement.CodeMirror
onSelect: (event?: Event): void => { ) {
confirmDelete const editor = editorElement.CodeMirror as CodeMirror.Editor;
? deleteJSCollectionFromPage(jsCollection.id, jsCollection.name)
: confirmDeletion(true, event);
},
label: confirmDelete
? createMessage(CONFIRM_CONTEXT_DELETE)
: createMessage(CONTEXT_DELETE),
className: "t--apiFormDeleteBtn error-menuitem",
};
const options: ContextMenuOption[] = [renameOption]; autoIndentCode(editor);
dispatch(updateJSCollectionBody(editor.getValue(), jsCollection.id));
AnalyticsUtil.logEvent("PRETTIFY_CODE_MANUAL_TRIGGER");
}
},
label: "Prettify code",
};
if (isChangePermitted) { const deleteOption = {
options.push(copyOption); confirmDelete: confirmDelete,
options.push(moveOption); icon: "delete-bin-line" as IconName,
options.push(prettifyOptions); value: "delete",
} onSelect: (event?: Event): void => {
confirmDelete
? deleteJSCollectionFromPage(jsCollection.id, jsCollection.name)
: confirmDeletion(true, event);
},
label: confirmDelete
? createMessage(CONFIRM_CONTEXT_DELETE)
: createMessage(CONTEXT_DELETE),
className: "t--apiFormDeleteBtn error-menuitem",
};
if (isDeletePermitted) options.push(deleteOption); const options: ContextMenuOption[] = [renameOption];
if (isChangePermitted) {
options.push(copyOption);
options.push(moveOption);
options.push(prettifyOptions);
}
if (isDeletePermitted) options.push(deleteOption);
return options;
}, [
confirmDelete,
copyJSCollectionToPage,
deleteJSCollectionFromPage,
dispatch,
isChangePermitted,
isDeletePermitted,
jsCollection.id,
jsCollection.name,
menuPages,
moveJSCollectionToPage,
pageId,
renameJS,
setConfirmDelete,
]);
return ( return (
<JSEditorContextMenu <JSEditorContextMenu
className="t--more-action-menu" className="t--more-action-menu"
onMenuClose={() => { onMenuClose={cancelConfirmDelete}
setConfirmDelete(false);
}}
options={options} options={options}
/> />
); );