From 1ead9ae2ecafcc594443f2ca4dfee24b9126416a Mon Sep 17 00:00:00 2001 From: Ankita Kinger Date: Thu, 24 Oct 2024 13:06:09 +0530 Subject: [PATCH 1/2] adding new name editor for JS object in toolbar --- app/client/src/pages/Editor/JSEditor/Form.tsx | 3 + .../JSEditorToolbar/JSEditorToolbar.tsx | 11 +- .../JSEditor/JSEditorToolbar/JSHeader.tsx | 2 +- .../JSObjectNameEditor/JSObjectNameEditor.tsx | 232 ++++++++++++++++++ .../JSEditor/JSObjectNameEditor/index.tsx | 4 + .../old}/JSObjectNameEditor.tsx | 7 - 6 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/JSObjectNameEditor.tsx create mode 100644 app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/index.tsx rename app/client/src/pages/Editor/JSEditor/{ => JSObjectNameEditor/old}/JSObjectNameEditor.tsx (90%) diff --git a/app/client/src/pages/Editor/JSEditor/Form.tsx b/app/client/src/pages/Editor/JSEditor/Form.tsx index 6eabdd909634..92b56ac9cf2e 100644 --- a/app/client/src/pages/Editor/JSEditor/Form.tsx +++ b/app/client/src/pages/Editor/JSEditor/Form.tsx @@ -68,6 +68,7 @@ interface JSFormProps { hideContextMenuOnEditor?: boolean; hideEditIconOnEditor?: boolean; notification?: React.ReactNode; + showNameEditor?: boolean; } type Props = JSFormProps; @@ -108,6 +109,7 @@ function JSEditorForm({ notification, onUpdateSettings, saveJSObjectName, + showNameEditor = false, showSettings = true, }: Props) { const theme = EditorTheme.LIGHT; @@ -353,6 +355,7 @@ function JSEditorForm({ onUpdateSettings={onUpdateSettings} saveJSObjectName={saveJSObjectName} selected={selectedJSActionOption} + showNameEditor={showNameEditor} showSettings={showSettings} /> {notification && ( diff --git a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx index 06f3df58cd2f..29876c531e6d 100644 --- a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx @@ -13,6 +13,7 @@ import { JSHeader } from "./JSHeader"; import { JSFunctionSettings } from "./components/JSFunctionSettings"; import type { JSFunctionSettingsProps } from "./components/old/JSFunctionSettings"; import { convertJSActionsToDropdownOptions } from "./utils"; +import { JSObjectNameEditor } from "../JSObjectNameEditor"; interface Props { changePermitted: boolean; @@ -33,6 +34,7 @@ interface Props { jsActions: JSAction[]; selected: JSActionDropdownOption; onUpdateSettings: JSFunctionSettingsProps["onUpdateSettings"]; + showNameEditor?: boolean; showSettings: boolean; } @@ -59,7 +61,14 @@ export const JSEditorToolbar = (props: Props) => { // Render the IDEToolbar with JSFunctionRun and JSFunctionSettings components return ( - + + {props.showNameEditor && ( + + )} +
ReduxAction; +} + +export const NameWrapper = styled(Flex)` + height: 100%; + position: relative; + font-size: 12px; + color: var(--ads-v2-colors-text-default); + cursor: pointer; + gap: var(--ads-v2-spaces-2); + align-items: center; + justify-content: center; + padding: var(--ads-v2-spaces-3); +`; + +export const IconContainer = styled.div` + height: 12px; + width: 12px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + img { + width: 12px; + } +`; + +export const Text = styled(ADSText)` + min-width: 3ch; + padding: 0 var(--ads-v2-spaces-1); + font-weight: 500; +`; + +export const JSObjectNameEditor = (props: JSObjectNameEditorProps) => { + const params = useParams<{ + baseCollectionId?: string; + baseQueryId?: string; + }>(); + + const currentJSObjectConfig = useSelector((state: AppState) => + getJsCollectionByBaseId(state, params.baseCollectionId || ""), + ); + + const currentPlugin = useSelector((state: AppState) => + getPlugin(state, currentJSObjectConfig?.pluginId || ""), + ); + + const isLoading = useSelector( + (state) => + getSavingStatusForJSObjectName(state, currentJSObjectConfig?.id || "") + .isSaving, + ); + + const title = currentJSObjectConfig?.name || ""; + const previousTitle = usePrevious(title); + const [editableTitle, setEditableTitle] = useState(title); + const [validationError, setValidationError] = useState(null); + const inputRef = useRef(null); + + const { handleNameSave, normalizeName, validateName } = useNameEditor({ + entityId: params?.baseCollectionId || "", + entityName: title, + nameSaveAction: props.saveJSObjectName, + }); + + const { + setFalse: exitEditMode, + setTrue: enterEditMode, + value: isEditing, + } = useBoolean(false); + + const currentTitle = + isEditing || isLoading || title !== editableTitle ? editableTitle : title; + + const handleKeyUp = useEventCallback( + (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + const nameError = validateName(editableTitle); + + if (nameError === null) { + exitEditMode(); + handleNameSave(editableTitle); + } else { + setValidationError(nameError); + } + } else if (e.key === "Escape") { + exitEditMode(); + setEditableTitle(title); + setValidationError(null); + } else { + setValidationError(null); + } + }, + ); + + const handleTitleChange = useEventCallback( + (e: React.ChangeEvent) => { + setEditableTitle(normalizeName(e.target.value)); + }, + ); + + const handleEnterEditMode = useEventCallback(() => { + setEditableTitle(title); + enterEditMode(); + }); + + const handleDoubleClick = props.disabled ? noop : handleEnterEditMode; + + const inputProps = useMemo( + () => ({ + onKeyUp: handleKeyUp, + onChange: handleTitleChange, + autoFocus: true, + style: { + paddingTop: 0, + paddingBottom: 0, + left: -1, + top: -1, + }, + }), + [handleKeyUp, handleTitleChange], + ); + + useEventListener( + "focusout", + function handleFocusOut() { + if (isEditing) { + const nameError = validateName(editableTitle); + + exitEditMode(); + + if (nameError === null) { + handleNameSave(editableTitle); + } else { + setEditableTitle(title); + setValidationError(null); + } + } + }, + inputRef, + ); + + useEffect( + function syncEditableTitle() { + if (!isEditing && previousTitle !== title) { + setEditableTitle(title); + } + }, + [title, previousTitle, isEditing], + ); + + useEffect( + function recaptureFocusInEventOfFocusRetention() { + const input = inputRef.current; + + if (isEditing && input) { + setTimeout(() => { + input.focus(); + }, 200); + } + }, + [isEditing], + ); + + const isActionRedesignEnabled = useFeatureFlag( + FEATURE_FLAG.release_actions_redesign_enabled, + ); + + if (!isActionRedesignEnabled) { + return ( + + ); + } + + return ( + + {currentPlugin && !isLoading ? ( + + {currentPlugin.name} + + ) : null} + {isLoading && } + + + + {currentTitle} + + + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/index.tsx b/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/index.tsx new file mode 100644 index 000000000000..6dd2a6a8ae90 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/index.tsx @@ -0,0 +1,4 @@ +export { + JSObjectNameEditor, + type JSObjectNameEditorProps, +} from "./JSObjectNameEditor"; diff --git a/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor.tsx b/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/old/JSObjectNameEditor.tsx similarity index 90% rename from app/client/src/pages/Editor/JSEditor/JSObjectNameEditor.tsx rename to app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/old/JSObjectNameEditor.tsx index 0ce1112c3c1f..53e1e920850a 100644 --- a/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/old/JSObjectNameEditor.tsx @@ -28,13 +28,6 @@ import type { ReduxAction } from "ee/constants/ReduxActionConstants"; import type { SaveActionNameParams } from "PluginActionEditor"; export interface JSObjectNameEditorProps { - /* - This prop checks if page is API Pane or Query Pane or Curl Pane - So, that we can toggle between ads editable-text component and existing editable-text component - Right now, it's optional so that it doesn't impact any other pages other than API Pane. - In future, when default component will be ads editable-text, then we can remove this prop. - */ - page?: string; disabled?: boolean; saveJSObjectName: ( params: SaveActionNameParams, From 7ae7ec74dbe45be237b20a07b56b1c32aa2dbba5 Mon Sep 17 00:00:00 2001 From: Ankita Kinger Date: Thu, 24 Oct 2024 15:14:13 +0530 Subject: [PATCH 2/2] addressing review comment --- app/client/src/pages/Editor/JSEditor/Form.tsx | 2 +- .../pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx | 2 +- .../src/pages/Editor/JSEditor/JSEditorToolbar/JSHeader.tsx | 2 +- .../JSObjectNameEditor/JSObjectNameEditor.tsx | 0 .../JSEditor/{ => JSEditorToolbar}/JSObjectNameEditor/index.tsx | 0 .../JSObjectNameEditor/old/JSObjectNameEditor.tsx | 0 6 files changed, 3 insertions(+), 3 deletions(-) rename app/client/src/pages/Editor/JSEditor/{ => JSEditorToolbar}/JSObjectNameEditor/JSObjectNameEditor.tsx (100%) rename app/client/src/pages/Editor/JSEditor/{ => JSEditorToolbar}/JSObjectNameEditor/index.tsx (100%) rename app/client/src/pages/Editor/JSEditor/{ => JSEditorToolbar}/JSObjectNameEditor/old/JSObjectNameEditor.tsx (100%) diff --git a/app/client/src/pages/Editor/JSEditor/Form.tsx b/app/client/src/pages/Editor/JSEditor/Form.tsx index 92b56ac9cf2e..68028eb4ebaf 100644 --- a/app/client/src/pages/Editor/JSEditor/Form.tsx +++ b/app/client/src/pages/Editor/JSEditor/Form.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import type { JSAction } from "entities/JSCollection"; import type { DropdownOnSelect } from "@appsmith/ads-old"; import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; -import type { JSObjectNameEditorProps } from "./JSObjectNameEditor"; +import type { JSObjectNameEditorProps } from "./JSEditorToolbar/JSObjectNameEditor"; import { setActiveJSAction, setJsPaneConfigSelectedTab, diff --git a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx index 29876c531e6d..d2c6237ebf3c 100644 --- a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar.tsx @@ -13,7 +13,7 @@ import { JSHeader } from "./JSHeader"; import { JSFunctionSettings } from "./components/JSFunctionSettings"; import type { JSFunctionSettingsProps } from "./components/old/JSFunctionSettings"; import { convertJSActionsToDropdownOptions } from "./utils"; -import { JSObjectNameEditor } from "../JSObjectNameEditor"; +import { JSObjectNameEditor } from "./JSObjectNameEditor"; interface Props { changePermitted: boolean; diff --git a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSHeader.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSHeader.tsx index 40c87732b748..35465e7e8e0f 100644 --- a/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSHeader.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSHeader.tsx @@ -6,7 +6,7 @@ import type { SaveActionNameParams } from "PluginActionEditor"; import type { ReduxAction } from "ee/constants/ReduxActionConstants"; import type { JSAction, JSCollection } from "entities/JSCollection"; import type { DropdownOnSelect } from "@appsmith/ads-old"; -import { JSObjectNameEditor } from "../JSObjectNameEditor"; +import { JSObjectNameEditor } from "./JSObjectNameEditor"; import { Flex } from "@appsmith/ads"; import { convertJSActionsToDropdownOptions } from "./utils"; diff --git a/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/JSObjectNameEditor.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/JSObjectNameEditor.tsx similarity index 100% rename from app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/JSObjectNameEditor.tsx rename to app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/JSObjectNameEditor.tsx diff --git a/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/index.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/index.tsx similarity index 100% rename from app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/index.tsx rename to app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/index.tsx diff --git a/app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/old/JSObjectNameEditor.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/old/JSObjectNameEditor.tsx similarity index 100% rename from app/client/src/pages/Editor/JSEditor/JSObjectNameEditor/old/JSObjectNameEditor.tsx rename to app/client/src/pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor/old/JSObjectNameEditor.tsx