From ac9e101eaf3e0e1b58f2294a000c55652947b834 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Thu, 31 Oct 2024 17:25:04 +0530 Subject: [PATCH] chore: Add Rename context menu (#37116) --- .../ads/src/Icon/Icon.provider.tsx | 5 + .../icons/ads/input-cursor-move.svg | 7 ++ .../EditableName/EditableName.test.tsx | 14 +-- .../Components/EditableName/EditableName.tsx | 91 +++++++++++++------ .../EditableName/RenameMenuItem.tsx | 30 ++++++ .../src/IDE/Components/EditableName/index.ts | 2 + .../Components/EditableName/useIsRenaming.ts | 35 +++++++ .../Components/EditableName/useNameEditor.ts | 21 +++-- app/client/src/IDE/index.ts | 6 +- .../components/PluginActionNameEditor.tsx | 25 ++--- app/client/src/actions/ideActions.ts | 7 ++ .../src/ce/constants/ReduxActionConstants.tsx | 1 + .../components/ToolbarMenu/ToolbarMenu.tsx | 5 +- .../editorComponents/EditableText.tsx | 5 + .../Editor/IDE/EditorTabs/EditableTab.tsx | 16 ++-- .../JSEditor/AppJSEditorContextMenu.tsx | 19 +++- .../JSObjectNameEditor/JSObjectNameEditor.tsx | 11 +-- .../src/reducers/uiReducers/ideReducer.ts | 10 ++ app/client/src/selectors/ideSelectors.tsx | 7 ++ 19 files changed, 234 insertions(+), 83 deletions(-) create mode 100644 app/client/packages/design-system/ads/src/__assets__/icons/ads/input-cursor-move.svg create mode 100644 app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx create mode 100644 app/client/src/IDE/Components/EditableName/useIsRenaming.ts diff --git a/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx b/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx index 62662323becd..b4154a27d2d5 100644 --- a/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx +++ b/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx @@ -1068,6 +1068,10 @@ const ExternalLinkIcon = importRemixIcon( async () => import("remixicon-react/ExternalLinkLineIcon"), ); +const InputCursorMoveIcon = importSvg( + async () => import("../__assets__/icons/ads/input-cursor-move.svg"), +); + import PlayIconPNG from "../__assets__/icons/control/play-icon.png"; function PlayIconPNGWrapper() { @@ -1363,6 +1367,7 @@ const ICON_LOOKUP = { "minimize-v3": MinimizeV3Icon, "maximize-v3": MaximizeV3Icon, "workflows-mono": WorkflowsMonochromeIcon, + "input-cursor-move": InputCursorMoveIcon, billing: BillingIcon, binding: Binding, book: BookIcon, diff --git a/app/client/packages/design-system/ads/src/__assets__/icons/ads/input-cursor-move.svg b/app/client/packages/design-system/ads/src/__assets__/icons/ads/input-cursor-move.svg new file mode 100644 index 000000000000..e05a3c4b656a --- /dev/null +++ b/app/client/packages/design-system/ads/src/__assets__/icons/ads/input-cursor-move.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/app/client/src/IDE/Components/EditableName/EditableName.test.tsx b/app/client/src/IDE/Components/EditableName/EditableName.test.tsx index 77a4f756555b..6dfadcebe2f6 100644 --- a/app/client/src/IDE/Components/EditableName/EditableName.test.tsx +++ b/app/client/src/IDE/Components/EditableName/EditableName.test.tsx @@ -145,15 +145,13 @@ describe("EditableName", () => { target: { value: invalidTitle }, }); - fireEvent.keyUp(inputElement, KEY_CONFIG.ENTER); - expect(getByRole("tooltip")).toBeInTheDocument(); expect(getByRole("tooltip").textContent).toEqual(validationError); await userEvent.click(document.body); - expect(getByRole("tooltip").textContent).toEqual(""); + expect(getByRole("tooltip").textContent).toEqual(validationError); expect(exitEditing).toHaveBeenCalled(); expect(onNameSave).not.toHaveBeenCalledWith(invalidTitle); @@ -169,7 +167,6 @@ describe("EditableName", () => { target: { value: invalidTitle }, }); - fireEvent.keyUp(inputElement, KEY_CONFIG.ENTER); fireEvent.keyUp(inputElement, KEY_CONFIG.ESC); expect(getByRole("tooltip")).toBeInTheDocument(); @@ -189,9 +186,8 @@ describe("EditableName", () => { target: { value: invalidTitle }, }); - fireEvent.keyUp(inputElement, KEY_CONFIG.ENTER); fireEvent.focusOut(inputElement); - expect(getByRole("tooltip").textContent).toEqual(""); + expect(getByRole("tooltip").textContent).toEqual(validationError); expect(exitEditing).toHaveBeenCalled(); expect(onNameSave).not.toHaveBeenCalledWith(invalidTitle); }); @@ -201,12 +197,12 @@ describe("EditableName", () => { const input = getByRole("textbox"); fireEvent.change(input, { target: { value: "" } }); - fireEvent.keyUp(input, KEY_CONFIG.ENTER); - - expect(onNameSave).not.toHaveBeenCalledWith(""); expect(getByRole("tooltip")).toHaveTextContent( "Please enter a valid name", ); + fireEvent.keyUp(input, KEY_CONFIG.ENTER); + + expect(onNameSave).not.toHaveBeenCalledWith(""); }); }); }); diff --git a/app/client/src/IDE/Components/EditableName/EditableName.tsx b/app/client/src/IDE/Components/EditableName/EditableName.tsx index 074c7ca6a829..1f91d827b801 100644 --- a/app/client/src/IDE/Components/EditableName/EditableName.tsx +++ b/app/client/src/IDE/Components/EditableName/EditableName.tsx @@ -1,4 +1,10 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { Spinner, Text, Tooltip } from "@appsmith/ads"; import { useEventCallback, useEventListener } from "usehooks-ts"; import { usePrevious } from "@mantine/hooks"; @@ -6,10 +12,19 @@ import { useNameEditor } from "./useNameEditor"; interface EditableTextProps { name: string; + /** isLoading true will show a spinner **/ isLoading?: boolean; + /** if a valid name is entered, the onNameSave + * will be called with the new name */ onNameSave: (name: string) => void; + /** Used in conjunction with exit editing to control + * this component input editable state */ isEditing: boolean; + /** Used in conjunction with exit editing to control this component + * input editable state This function will be called when the + * user is trying to exit the editing mode **/ exitEditing: () => void; + /** Icon is replaced by spinner when isLoading is shown */ icon: React.ReactNode; inputTestId?: string; } @@ -32,30 +47,61 @@ export const EditableName = ({ entityName: name, }); + const exitWithoutSaving = useCallback(() => { + exitEditing(); + setEditableName(name); + setValidationError(null); + }, [exitEditing, name]); + + const validate = useCallback( + (name: string) => { + const nameError = validateName(name); + + if (nameError === null) { + setValidationError(null); + } else { + setValidationError(nameError); + } + + return nameError; + }, + [validateName], + ); + + const attemptSave = useCallback(() => { + const nameError = validate(editableName); + + if (editableName === name) { + exitWithoutSaving(); + } else if (nameError === null) { + exitEditing(); + onNameSave(editableName); + } + }, [ + editableName, + exitEditing, + exitWithoutSaving, + name, + onNameSave, + validate, + ]); + const handleKeyUp = useEventCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter") { - const nameError = validateName(editableName); - - if (nameError === null) { - exitEditing(); - onNameSave(editableName); - } else { - setValidationError(nameError); - } + attemptSave(); } else if (e.key === "Escape") { - exitEditing(); - setEditableName(name); - setValidationError(null); - } else { - setValidationError(null); + exitWithoutSaving(); } }, ); const handleTitleChange = useEventCallback( (e: React.ChangeEvent) => { - setEditableName(normalizeName(e.target.value)); + const value = normalizeName(e.target.value); + + setEditableName(value); + validate(value); }, ); @@ -67,23 +113,14 @@ export const EditableName = ({ autoFocus: true, style: { paddingTop: 0, paddingBottom: 0, left: -1, top: -1 }, }), - [handleKeyUp, handleTitleChange], + [handleKeyUp, handleTitleChange, inputTestId], ); useEventListener( "focusout", function handleFocusOut() { if (isEditing) { - const nameError = validateName(editableName); - - exitEditing(); - - if (nameError === null) { - onNameSave(editableName); - } else { - setEditableName(name); - setValidationError(null); - } + attemptSave(); } }, inputRef, @@ -120,9 +157,9 @@ export const EditableName = ({ {editableName} diff --git a/app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx b/app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx new file mode 100644 index 000000000000..3c7d6049841a --- /dev/null +++ b/app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx @@ -0,0 +1,30 @@ +import React, { useCallback } from "react"; +import { MenuItem } from "@appsmith/ads"; +import { useDispatch } from "react-redux"; +import { setRenameEntity } from "actions/ideActions"; + +interface Props { + disabled?: boolean; + entityId: string; +} + +export const RenameMenuItem = ({ disabled, entityId }: Props) => { + const dispatch = useDispatch(); + + const setRename = useCallback(() => { + // We add a delay to avoid having the focus stuck in the menu trigger + setTimeout(() => { + dispatch(setRenameEntity(entityId)); + }, 100); + }, [dispatch, entityId]); + + return ( + + Rename + + ); +}; diff --git a/app/client/src/IDE/Components/EditableName/index.ts b/app/client/src/IDE/Components/EditableName/index.ts index 875359b04b0d..f714786de369 100644 --- a/app/client/src/IDE/Components/EditableName/index.ts +++ b/app/client/src/IDE/Components/EditableName/index.ts @@ -1 +1,3 @@ export { EditableName } from "./EditableName"; +export { RenameMenuItem } from "./RenameMenuItem"; +export { useIsRenaming } from "./useIsRenaming"; diff --git a/app/client/src/IDE/Components/EditableName/useIsRenaming.ts b/app/client/src/IDE/Components/EditableName/useIsRenaming.ts new file mode 100644 index 000000000000..85bac3e91193 --- /dev/null +++ b/app/client/src/IDE/Components/EditableName/useIsRenaming.ts @@ -0,0 +1,35 @@ +import { useDispatch, useSelector } from "react-redux"; +import { getIsRenaming } from "selectors/ideSelectors"; +import { useCallback, useEffect, useState } from "react"; +import { setRenameEntity } from "actions/ideActions"; + +export const useIsRenaming = (id: string) => { + const dispatch = useDispatch(); + const [isEditing, setIsEditing] = useState(false); + + const isEditingViaExternal = useSelector(getIsRenaming(id)); + + useEffect( + function onExternalEditEvent() { + if (isEditingViaExternal) { + setIsEditing(true); + } + + return () => { + setIsEditing(false); + }; + }, + [isEditingViaExternal], + ); + + const enterEditMode = useCallback(() => { + setIsEditing(true); + }, []); + + const exitEditMode = useCallback(() => { + dispatch(setRenameEntity("")); + setIsEditing(false); + }, [dispatch]); + + return { isEditing, enterEditMode, exitEditMode }; +}; diff --git a/app/client/src/IDE/Components/EditableName/useNameEditor.ts b/app/client/src/IDE/Components/EditableName/useNameEditor.ts index 40e7bf1d18d0..cae166cab481 100644 --- a/app/client/src/IDE/Components/EditableName/useNameEditor.ts +++ b/app/client/src/IDE/Components/EditableName/useNameEditor.ts @@ -6,8 +6,8 @@ import { import { shallowEqual, useSelector } from "react-redux"; import type { AppState } from "ee/reducers"; import { getUsedActionNames } from "selectors/actionSelectors"; -import { useEventCallback } from "usehooks-ts"; import { isNameValid, removeSpecialChars } from "utils/helpers"; +import { useCallback } from "react"; interface UseNameEditorProps { entityName: string; @@ -25,15 +25,18 @@ export function useNameEditor(props: UseNameEditorProps) { shallowEqual, ); - const validateName = useEventCallback((name: string): string | null => { - if (!name || name.trim().length === 0) { - return createMessage(ACTION_INVALID_NAME_ERROR); - } else if (name !== entityName && !isNameValid(name, usedEntityNames)) { - return createMessage(nameErrorMessage, name); - } + const validateName = useCallback( + (name: string): string | null => { + if (!name || name.trim().length === 0) { + return createMessage(ACTION_INVALID_NAME_ERROR); + } else if (name !== entityName && !isNameValid(name, usedEntityNames)) { + return createMessage(nameErrorMessage, name); + } - return null; - }); + return null; + }, + [entityName, nameErrorMessage, usedEntityNames], + ); return { validateName, diff --git a/app/client/src/IDE/index.ts b/app/client/src/IDE/index.ts index c3e0efa7be72..cc69383fab62 100644 --- a/app/client/src/IDE/index.ts +++ b/app/client/src/IDE/index.ts @@ -69,7 +69,11 @@ export { ToolbarSettingsPopover } from "./Components/ToolbarSettingsPopover"; * EditableName is a component that allows the user to edit the name of an entity * It is used in the IDE for renaming pages, actions, queries, etc. */ -export { EditableName } from "./Components/EditableName"; +export { + EditableName, + RenameMenuItem, + useIsRenaming, +} from "./Components/EditableName"; /* ==================================================== **** Interfaces **** diff --git a/app/client/src/PluginActionEditor/components/PluginActionNameEditor.tsx b/app/client/src/PluginActionEditor/components/PluginActionNameEditor.tsx index 576ab8106fe2..4fa5020b8da1 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionNameEditor.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionNameEditor.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from "react"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { usePluginActionContext } from "../PluginActionContext"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; @@ -8,11 +8,10 @@ import type { ReduxAction } from "ee/constants/ReduxActionConstants"; import { getSavingStatusForActionName } from "selectors/actionSelectors"; import { getAssetUrl } from "ee/utils/airgapHelpers"; import { ActionUrlIcon } from "pages/Editor/Explorer/ExplorerIcons"; -import { Text as ADSText, Flex } from "@appsmith/ads"; +import { Flex } from "@appsmith/ads"; import styled from "styled-components"; -import { useBoolean } from "usehooks-ts"; import { noop } from "lodash"; -import { EditableName } from "IDE"; +import { EditableName, useIsRenaming } from "IDE"; export interface SaveActionNameParams { id: string; @@ -49,26 +48,16 @@ export const IconContainer = styled.div` } `; -export const Text = styled(ADSText)` - min-width: 3ch; - padding: 0 var(--ads-v2-spaces-1); - font-weight: 500; -`; - const PluginActionNameEditor = ({ saveActionName, }: PluginActionNameEditorProps) => { const { action, plugin } = usePluginActionContext(); const isLoading = useSelector( - (state) => getSavingStatusForActionName(state, action?.id || "").isSaving, + (state) => getSavingStatusForActionName(state, action.id).isSaving, ); - const { - setFalse: exitEditMode, - setTrue: enterEditMode, - value: isEditing, - } = useBoolean(false); + const { enterEditMode, exitEditMode, isEditing } = useIsRenaming(action.id); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); const isChangePermitted = getHasManageActionPermission( @@ -80,9 +69,11 @@ const PluginActionNameEditor = ({ const handleDoubleClick = isChangePermitted ? enterEditMode : noop; + const dispatch = useDispatch(); + const handleNameSave = useCallback( (name: string) => { - saveActionName({ id: action.id, name }); + dispatch(saveActionName({ id: action.id, name })); }, [action.id, saveActionName], ); diff --git a/app/client/src/actions/ideActions.ts b/app/client/src/actions/ideActions.ts index a2d1809b5abc..fbdf13b00c37 100644 --- a/app/client/src/actions/ideActions.ts +++ b/app/client/src/actions/ideActions.ts @@ -61,3 +61,10 @@ export const setListViewActiveState = (payload: boolean) => { payload, }; }; + +export const setRenameEntity = (id: string) => { + return { + type: ReduxActionTypes.SET_RENAME_ENTITY, + payload: id, + }; +}; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index c225e29be85d..7e7375ede276 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -500,6 +500,7 @@ const IDEActionTypes = { CLOSE_QUERY_ACTION_TAB_SUCCESS: "CLOSE_QUERY_ACTION_TAB_SUCCESS", SET_IS_LIST_VIEW_ACTIVE: "SET_IS_LIST_VIEW_ACTIVE", OPEN_PLUGIN_ACTION_SETTINGS: "OPEN_PLUGIN_ACTION_SETTINGS", + SET_RENAME_ENTITY: "SET_RENAME_ENTITY", }; const IDEActionErrorTypes = { diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx index e321611af6a5..27121fbacdd3 100644 --- a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx +++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx @@ -14,7 +14,7 @@ import { ConvertToModuleCTA } from "../ConvertToModule"; import { Move } from "./Move"; import { Copy } from "./Copy"; import { Delete } from "./Delete"; -import { Rename } from "./Rename"; +import { RenameMenuItem } from "IDE"; export const ToolbarMenu = () => { const { action } = usePluginActionContext(); @@ -31,11 +31,10 @@ export const ToolbarMenu = () => { return ( <> - + - diff --git a/app/client/src/components/editorComponents/EditableText.tsx b/app/client/src/components/editorComponents/EditableText.tsx index 29b4439871af..a4516918b0c5 100644 --- a/app/client/src/components/editorComponents/EditableText.tsx +++ b/app/client/src/components/editorComponents/EditableText.tsx @@ -61,6 +61,7 @@ const EditableTextWrapper = styled.div<{ justify-content: flex-start; align-items: flex-start; width: 100%; + & .${Classes.EDITABLE_TEXT} { background: ${(props) => props.isEditing && !props.minimal @@ -73,11 +74,13 @@ const EditableTextWrapper = styled.div<{ max-width: 100%; overflow: hidden; display: flex; + &:before, &:after { display: none; } } + & div.${Classes.EDITABLE_TEXT_INPUT} { text-transform: none; width: 100%; @@ -100,6 +103,7 @@ const TextContainer = styled.div<{ color: var(--ads-v2-color-fg-emphasis-plus); display: flex; align-items: center; + &&&& .${Classes.EDITABLE_TEXT} { & .${Classes.EDITABLE_TEXT_CONTENT} { &:hover { @@ -108,6 +112,7 @@ const TextContainer = styled.div<{ } } } + &&& .${Classes.EDITABLE_TEXT_CONTENT}:hover { ${(props) => props.underline diff --git a/app/client/src/pages/Editor/IDE/EditorTabs/EditableTab.tsx b/app/client/src/pages/Editor/IDE/EditorTabs/EditableTab.tsx index f44f5bbfb117..672c1bf366c5 100644 --- a/app/client/src/pages/Editor/IDE/EditorTabs/EditableTab.tsx +++ b/app/client/src/pages/Editor/IDE/EditorTabs/EditableTab.tsx @@ -4,8 +4,8 @@ import { FileTab } from "IDE/Components/FileTab"; import { type EntityItem } from "ee/entities/IDE/constants"; import { useCurrentEditorState } from "../hooks"; -import { useSelector } from "react-redux"; -import { useBoolean, useEventCallback } from "usehooks-ts"; +import { useDispatch, useSelector } from "react-redux"; +import { useEventCallback } from "usehooks-ts"; import { getIsSavingEntityName } from "ee/selectors/entitiesSelector"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; @@ -14,7 +14,7 @@ import { saveEntityName, } from "ee/entities/IDE/utils"; import { noop } from "lodash"; -import { EditableName } from "IDE"; +import { EditableName, useIsRenaming } from "IDE"; import { IconContainer } from "IDE/Components/FileTab/styles"; interface EditableTabProps { @@ -37,11 +37,7 @@ export function EditableTab(props: EditableTabProps) { entity, }); - const { - setFalse: exitEditMode, - setTrue: enterEditMode, - value: isEditing, - } = useBoolean(false); + const { enterEditMode, exitEditMode, isEditing } = useIsRenaming(id); const isLoading = useSelector((state) => getIsSavingEntityName(state, { id, segment, entity }), @@ -54,9 +50,11 @@ export function EditableTab(props: EditableTabProps) { const handleDoubleClick = isChangePermitted ? enterEditMode : noop; + const dispatch = useDispatch(); + const handleNameSave = useCallback( (name: string) => { - saveEntityName({ params: { id, name }, segment, entity }); + dispatch(saveEntityName({ params: { id, name }, segment, entity })); exitEditMode(); }, [entity, exitEditMode, id, segment], diff --git a/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx b/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx index 9ec5c9811001..6538da16c979 100644 --- a/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx +++ b/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx @@ -12,6 +12,7 @@ import { CONFIRM_CONTEXT_DELETE, CONTEXT_MOVE, createMessage, + CONTEXT_RENAME, } from "ee/constants/messages"; import { getPageListAsOptions } from "ee/selectors/entitiesSelector"; import { @@ -32,6 +33,7 @@ import { import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; import type { JSCollection } from "entities/JSCollection"; +import { setRenameEntity } from "actions/ideActions"; interface AppJSEditorContextMenuProps { pageId: string; @@ -56,6 +58,13 @@ export function AppJSEditorContextMenu({ jsCollection?.userPermissions || [], ); + const renameJS = useCallback(() => { + // We add a delay to avoid having the focus stuck in the menu trigger + setTimeout(() => { + dispatch(setRenameEntity(jsCollection.id)); + }, 100); + }, []); + const copyJSCollectionToPage = useCallback( (actionId: string, actionName: string, pageId: string) => { dispatch( @@ -96,6 +105,14 @@ export function AppJSEditorContextMenu({ const menuPages = useSelector(getPageListAsOptions, equal); + const renameOption = { + icon: "input-cursor-move" as IconName, + value: "rename", + onSelect: renameJS, + label: createMessage(CONTEXT_RENAME), + disabled: !isChangePermitted, + }; + const copyOption = { icon: "duplicate" as IconName, value: "copy", @@ -169,7 +186,7 @@ export function AppJSEditorContextMenu({ className: "t--apiFormDeleteBtn error-menuitem", }; - const options: ContextMenuOption[] = []; + const options: ContextMenuOption[] = [renameOption]; if (isChangePermitted) { options.push(copyOption); diff --git a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/JSObjectNameEditor.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/JSObjectNameEditor.tsx index 35771dfa77bd..224f77384916 100644 --- a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/JSObjectNameEditor.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/JSObjectNameEditor.tsx @@ -7,7 +7,6 @@ import { getSavingStatusForJSObjectName } from "selectors/actionSelectors"; import { getAssetUrl } from "ee/utils/airgapHelpers"; import { Text as ADSText, Flex } from "@appsmith/ads"; import styled from "styled-components"; -import { useBoolean } from "usehooks-ts"; import { noop } from "lodash"; import { useParams } from "react-router"; import type { AppState } from "ee/reducers"; @@ -16,7 +15,7 @@ import { getPlugin, } from "ee/selectors/entitiesSelector"; import { JSObjectNameEditor as OldJSObjectNameEditor } from "./old/JSObjectNameEditor"; -import { EditableName } from "IDE"; +import { EditableName, useIsRenaming } from "IDE"; export interface SaveActionNameParams { id: string; @@ -86,11 +85,9 @@ export const JSObjectNameEditor = ({ const name = currentJSObjectConfig?.name || ""; - const { - setFalse: exitEditMode, - setTrue: enterEditMode, - value: isEditing, - } = useBoolean(false); + const { enterEditMode, exitEditMode, isEditing } = useIsRenaming( + currentJSObjectConfig?.id || "", + ); const handleDoubleClick = disabled ? noop : enterEditMode; diff --git a/app/client/src/reducers/uiReducers/ideReducer.ts b/app/client/src/reducers/uiReducers/ideReducer.ts index 86384fe393d5..5b6d03c3d1f6 100644 --- a/app/client/src/reducers/uiReducers/ideReducer.ts +++ b/app/client/src/reducers/uiReducers/ideReducer.ts @@ -15,6 +15,7 @@ const initialState: IDEState = { tabs: {}, isListViewActive: false, showCreateModal: false, + renameEntity: "", ideCanvasSideBySideHover: { navigated: false, widgetTypes: [], @@ -110,6 +111,14 @@ const ideReducer = createImmerReducer(initialState, { ) => { state.isListViewActive = action.payload; }, + [ReduxActionTypes.SET_RENAME_ENTITY]: ( + state: IDEState, + action: { + payload: string; + }, + ) => { + state.renameEntity = action.payload; + }, }); export interface IDEState { @@ -117,6 +126,7 @@ export interface IDEState { isListViewActive: boolean; tabs: ParentEntityIDETabs; showCreateModal: boolean; + renameEntity: string; ideCanvasSideBySideHover: IDECanvasSideBySideHover; } diff --git a/app/client/src/selectors/ideSelectors.tsx b/app/client/src/selectors/ideSelectors.tsx index 65fb4e68a90d..6abbd7255a54 100644 --- a/app/client/src/selectors/ideSelectors.tsx +++ b/app/client/src/selectors/ideSelectors.tsx @@ -64,3 +64,10 @@ export const getIdeCanvasSideBySideHoverState = (state: AppState) => export const getListViewActiveState = (state: AppState) => state.ui.ide.isListViewActive; + +export const getRenameEntity = (state: AppState) => state.ui.ide.renameEntity; + +export const getIsRenaming = (id: string) => + createSelector(getRenameEntity, (entityId) => { + return entityId === id; + });