diff --git a/app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx b/app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx index 3c7d6049841a..b290128b5e13 100644 --- a/app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx +++ b/app/client/src/IDE/Components/EditableName/RenameMenuItem.tsx @@ -2,6 +2,7 @@ import React, { useCallback } from "react"; import { MenuItem } from "@appsmith/ads"; import { useDispatch } from "react-redux"; import { setRenameEntity } from "actions/ideActions"; +import { CONTEXT_RENAME, createMessage } from "ee/constants/messages"; interface Props { disabled?: boolean; @@ -24,7 +25,7 @@ export const RenameMenuItem = ({ disabled, entityId }: Props) => { onSelect={setRename} startIcon="input-cursor-move" > - Rename + {createMessage(CONTEXT_RENAME)} ); }; diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx index 65db9a369bd6..0aadd95aa31f 100644 --- a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx @@ -1,27 +1,7 @@ import React from "react"; -import ExplorerJSCollectionEntity from "pages/Editor/Explorer/JSActions/JSActionEntity"; -import { Flex } from "@appsmith/ads"; import type { EntityItem } from "ee/entities/IDE/constants"; +import { JSEntityItem } from "pages/Editor/IDE/EditorPane/JS/EntityItem/JSEntityItem"; -export interface JSListItemProps { - item: EntityItem; - isActive: boolean; - parentEntityId: string; -} - -export const JSListItem = (props: JSListItemProps) => { - const { isActive, item, parentEntityId } = props; - - return ( - - - - ); +export const JSEntity = (props: { item: EntityItem }) => { + return ; }; diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/old/ListItem.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/old/ListItem.tsx new file mode 100644 index 000000000000..65db9a369bd6 --- /dev/null +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/old/ListItem.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import ExplorerJSCollectionEntity from "pages/Editor/Explorer/JSActions/JSActionEntity"; +import { Flex } from "@appsmith/ads"; +import type { EntityItem } from "ee/entities/IDE/constants"; + +export interface JSListItemProps { + item: EntityItem; + isActive: boolean; + parentEntityId: string; +} + +export const JSListItem = (props: JSListItemProps) => { + const { isActive, item, parentEntityId } = props; + + return ( + + + + ); +}; diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType.tsx new file mode 100644 index 000000000000..d56db7010543 --- /dev/null +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { IDE_TYPE, type IDEType } from "ee/entities/IDE/constants"; +import EntityContextMenu from "pages/Editor/IDE/EditorPane/components/EntityContextMenu"; +import { AppJSContextMenuItems } from "pages/Editor/IDE/EditorPane/JS/EntityItem/AppJSContextMenuItems"; +import type { JSCollection } from "entities/JSCollection"; + +export const getJSContextMenuByIdeType = ( + ideType: IDEType, + jsAction: JSCollection, +) => { + switch (ideType) { + case IDE_TYPE.App: { + return ( + + + + ); + } + } +}; diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/ListItem.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/ListItem.tsx index 90db39393a2a..201d728199f3 100644 --- a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/ListItem.tsx +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/ListItem.tsx @@ -1,25 +1,7 @@ import React from "react"; -import ExplorerActionEntity from "pages/Editor/Explorer/Actions/ActionEntity"; +import { QueryEntityItem } from "pages/Editor/IDE/EditorPane/Query/EntityItem/QueryEntityItem"; import type { EntityItem } from "ee/entities/IDE/constants"; -export interface QueryListItemProps { - item: EntityItem; - isActive: boolean; - parentEntityId: string; -} - -export const QueryListItem = (props: QueryListItemProps) => { - const { isActive, item, parentEntityId } = props; - - return ( - - ); +export const ActionEntityItem = (props: { item: EntityItem }) => { + return ; }; diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/old/ListItem.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/old/ListItem.tsx new file mode 100644 index 000000000000..90db39393a2a --- /dev/null +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/old/ListItem.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import ExplorerActionEntity from "pages/Editor/Explorer/Actions/ActionEntity"; +import type { EntityItem } from "ee/entities/IDE/constants"; + +export interface QueryListItemProps { + item: EntityItem; + isActive: boolean; + parentEntityId: string; +} + +export const QueryListItem = (props: QueryListItemProps) => { + const { isActive, item, parentEntityId } = props; + + return ( + + ); +}; diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType.tsx new file mode 100644 index 000000000000..052d6825d145 --- /dev/null +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { IDE_TYPE, type IDEType } from "ee/entities/IDE/constants"; +import type { Action } from "entities/Action"; +import { AppQueryContextMenuItems } from "pages/Editor/IDE/EditorPane/Query/EntityItem/AppQueryContextMenuItems"; +import EntityContextMenu from "pages/Editor/IDE/EditorPane/components/EntityContextMenu"; + +export const getQueryContextMenuByIdeType = ( + ideType: IDEType, + action: Action, +) => { + switch (ideType) { + case IDE_TYPE.App: { + return ( + + + + ); + } + } +}; diff --git a/app/client/src/ee/pages/Editor/IDE/EditorPane/JS/old/ListItem.tsx b/app/client/src/ee/pages/Editor/IDE/EditorPane/JS/old/ListItem.tsx new file mode 100644 index 000000000000..f105e9a1ae28 --- /dev/null +++ b/app/client/src/ee/pages/Editor/IDE/EditorPane/JS/old/ListItem.tsx @@ -0,0 +1 @@ +export * from "ce/pages/Editor/IDE/EditorPane/JS/old/ListItem"; diff --git a/app/client/src/ee/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType.tsx b/app/client/src/ee/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType.tsx new file mode 100644 index 000000000000..99126fb72c65 --- /dev/null +++ b/app/client/src/ee/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType.tsx @@ -0,0 +1 @@ +export * from "ce/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType"; diff --git a/app/client/src/ee/pages/Editor/IDE/EditorPane/Query/old/ListItem.tsx b/app/client/src/ee/pages/Editor/IDE/EditorPane/Query/old/ListItem.tsx new file mode 100644 index 000000000000..4157586a5aaa --- /dev/null +++ b/app/client/src/ee/pages/Editor/IDE/EditorPane/Query/old/ListItem.tsx @@ -0,0 +1 @@ +export * from "ce/pages/Editor/IDE/EditorPane/Query/old/ListItem"; diff --git a/app/client/src/ee/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType.ts b/app/client/src/ee/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType.ts new file mode 100644 index 000000000000..efb2008a1132 --- /dev/null +++ b/app/client/src/ee/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType.ts @@ -0,0 +1 @@ +export * from "ce/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType"; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCTA.tsx b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/ConvertToModule.tsx similarity index 84% rename from app/client/src/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCTA.tsx rename to app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/ConvertToModule.tsx index bf76c35a3a2d..3d04b8625d3e 100644 --- a/app/client/src/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCTA.tsx +++ b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/ConvertToModule.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { usePluginActionContext } from "PluginActionEditor"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; import { useSelector } from "react-redux"; @@ -11,21 +10,27 @@ import { import { MODULE_TYPE } from "ee/constants/ModuleConstants"; import ConvertToModuleInstanceCTA from "ee/pages/Editor/EntityEditor/ConvertToModuleInstanceCTA"; import { PluginType } from "entities/Plugin"; +import type { Action } from "entities/Action"; -const ConvertToModuleCTA = () => { - const { action, plugin } = usePluginActionContext(); - const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); +interface Props { + action: Action; +} + +export const ConvertToModule = ({ action }: Props) => { const pagePermissions = useSelector(getPagePermissions); + const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const isCreatePermitted = getHasCreateActionPermission( isFeatureEnabled, pagePermissions, ); + const isDeletePermitted = getHasDeleteActionPermission( isFeatureEnabled, action.userPermissions, ); - if (plugin.type === PluginType.INTERNAL) { + if (action.pluginType === PluginType.INTERNAL) { // Workflow queries cannot be converted to modules return null; } @@ -39,5 +44,3 @@ const ConvertToModuleCTA = () => { return ; }; - -export default ConvertToModuleCTA; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/Copy.tsx b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/Copy.tsx similarity index 73% rename from app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/Copy.tsx rename to app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/Copy.tsx index 2847f3481bf9..faaf175b72b8 100644 --- a/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/Copy.tsx +++ b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/Copy.tsx @@ -1,19 +1,20 @@ +import React from "react"; +import { MenuSub, MenuSubContent, MenuSubTrigger } from "@appsmith/ads"; import { useDispatch, useSelector } from "react-redux"; -import { getPageList } from "ee/selectors/entitiesSelector"; -import { usePluginActionContext } from "PluginActionEditor"; -import React, { useCallback } from "react"; +import { getPageList } from "selectors/editorSelectors"; +import { PageMenuItem } from "./PageMenuItem"; +import { useCallback } from "react"; +import type { Action } from "entities/Action"; import { copyActionRequest } from "actions/pluginActionActions"; -import { MenuSub, MenuSubContent, MenuSubTrigger } from "@appsmith/ads"; import { CONTEXT_COPY, createMessage } from "ee/constants/messages"; -import { PageMenuItem } from "./PageMenuItem"; interface Props { + action: Action; disabled?: boolean; } -export const Copy = ({ disabled }: Props) => { +export const Copy = ({ action, disabled }: Props) => { const menuPages = useSelector(getPageList); - const { action } = usePluginActionContext(); const dispatch = useDispatch(); const copyActionToPage = useCallback( @@ -30,13 +31,14 @@ export const Copy = ({ disabled }: Props) => { return ( - + {createMessage(CONTEXT_COPY)} - + {menuPages.map((page) => { return ( { - const { handleDeleteClick } = useHandleDeleteClick(); +export const Delete = ({ action, disabled }: Props) => { + const dispatch = useDispatch(); const [confirmDelete, setConfirmDelete] = useState(false); + const handleDeleteClick = useCallback( + ({ onSuccess }: { onSuccess?: () => void }) => { + dispatch( + deleteAction({ + id: action?.id ?? "", + name: action?.name ?? "", + onSuccess, + }), + ); + }, + [action.id, action.name, dispatch], + ); + const handleSelect = useCallback( (e?: Event) => { e?.preventDefault(); confirmDelete ? handleDeleteClick({}) : setConfirmDelete(true); + e?.stopPropagation(); }, [confirmDelete, handleDeleteClick], ); @@ -29,7 +46,7 @@ export const Delete = ({ disabled }: Props) => { return ( { +export const Move = ({ action, disabled }: Props) => { const dispatch = useDispatch(); - const { action } = usePluginActionContext(); const currentPageId = useSelector(getCurrentPageId); const allPages = useSelector(getPageList); @@ -42,14 +42,15 @@ export const Move = ({ disabled }: Props) => { return ( - + {createMessage(CONTEXT_MOVE)} - + {menuPages.length ? ( menuPages.map((page) => { return ( void; + disabled?: boolean; }) => { const handleOnSelect = useCallback(() => { props.onSelect(props.page.pageId); }, [props]); - return {props.page.pageName}; + return ( + + {props.page.pageName} + + ); }; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/Rename.tsx b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/Rename.tsx new file mode 100644 index 000000000000..d41f88578c87 --- /dev/null +++ b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/Rename.tsx @@ -0,0 +1,32 @@ +import React, { useCallback } from "react"; +import { MenuItem } from "@appsmith/ads"; +import type { Action } from "entities/Action"; +import { useDispatch } from "react-redux"; +import { initExplorerEntityNameEdit } from "actions/explorerActions"; +import { CONTEXT_RENAME, createMessage } from "ee/constants/messages"; + +interface Props { + action: Action; + disabled?: boolean; +} + +export const Rename = ({ action, disabled }: Props) => { + const dispatch = useDispatch(); + + const setRename = useCallback(() => { + // We add a delay to avoid having the focus stuck in the menu trigger + setTimeout(() => { + dispatch(initExplorerEntityNameEdit(action.id)); + }, 100); + }, [dispatch, action.id]); + + return ( + + {createMessage(CONTEXT_RENAME)} + + ); +}; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/ShowBindings.tsx b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/ShowBindings.tsx new file mode 100644 index 000000000000..9520bd595991 --- /dev/null +++ b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/ShowBindings.tsx @@ -0,0 +1,38 @@ +import React, { useCallback } from "react"; +import { CONTEXT_SHOW_BINDING, createMessage } from "ee/constants/messages"; +import { MenuItem } from "@appsmith/ads"; +import type { Action } from "entities/Action"; +import { useDispatch } from "react-redux"; +import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; +import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils"; + +interface Props { + action: Action; + disabled?: boolean; +} + +export const ShowBindings = ({ action, disabled }: Props) => { + const dispatch = useDispatch(); + + const handleSelect = useCallback(() => { + dispatch({ + type: ReduxActionTypes.SET_ENTITY_INFO, + payload: { + entityId: action.id, + entityName: action.name, + entityType: ENTITY_TYPE.ACTION, + show: true, + }, + }); + }, [action.id, action.name]); + + return ( + + {createMessage(CONTEXT_SHOW_BINDING)} + + ); +}; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/index.ts b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/index.ts new file mode 100644 index 000000000000..dba139b4290d --- /dev/null +++ b/app/client/src/pages/Editor/AppPluginActionEditor/components/ContextMenuItems/index.ts @@ -0,0 +1,6 @@ +export { Copy } from "./Copy"; +export { Move } from "./Move"; +export { Delete } from "./Delete"; +export { Rename } from "./Rename"; +export { ShowBindings } from "./ShowBindings"; +export { ConvertToModule } from "./ConvertToModule"; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ConvertToModule/index.tsx b/app/client/src/pages/Editor/AppPluginActionEditor/components/ConvertToModule/index.tsx index c390c4a5dd63..e0070cdd22a1 100644 --- a/app/client/src/pages/Editor/AppPluginActionEditor/components/ConvertToModule/index.tsx +++ b/app/client/src/pages/Editor/AppPluginActionEditor/components/ConvertToModule/index.tsx @@ -1,3 +1,2 @@ -export { default as ConvertToModuleCTA } from "./ConvertToModuleCTA"; export { default as ConvertToModuleDisabler } from "./ConvertToModuleDisabler"; export { default as ConvertToModuleCallout } from "./ConvertToModuleCallout"; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/Rename.tsx b/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/Rename.tsx deleted file mode 100644 index 471b6041b26e..000000000000 --- a/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/Rename.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import { MenuItem } from "@appsmith/ads"; - -interface Props { - disabled?: boolean; -} - -export const Rename = ({ disabled }: Props) => { - return ( - - Rename - - ); -}; diff --git a/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx b/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx index 27121fbacdd3..306e445dd7ee 100644 --- a/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx +++ b/app/client/src/pages/Editor/AppPluginActionEditor/components/ToolbarMenu/ToolbarMenu.tsx @@ -10,10 +10,7 @@ import { usePluginActionContext, DocsMenuItem as Docs, } from "PluginActionEditor"; -import { ConvertToModuleCTA } from "../ConvertToModule"; -import { Move } from "./Move"; -import { Copy } from "./Copy"; -import { Delete } from "./Delete"; +import { ConvertToModule, Copy, Delete, Move } from "../ContextMenuItems"; import { RenameMenuItem } from "IDE"; export const ToolbarMenu = () => { @@ -32,12 +29,12 @@ export const ToolbarMenu = () => { return ( <> - - - + + + - + > ); }; diff --git a/app/client/src/pages/Editor/Explorer/Files/index.tsx b/app/client/src/pages/Editor/Explorer/Files/index.tsx index daca88a00059..24758c651386 100644 --- a/app/client/src/pages/Editor/Explorer/Files/index.tsx +++ b/app/client/src/pages/Editor/Explorer/Files/index.tsx @@ -140,7 +140,7 @@ function Files() { ); } }), - [files, activeActionBaseId, parentEntityId, parentEntityType], + [files, activeActionBaseId, parentEntityId], ); const handleClick = useCallback( diff --git a/app/client/src/pages/Editor/IDE/EditorPane/JS/EntityItem/AppJSContextMenuItems.tsx b/app/client/src/pages/Editor/IDE/EditorPane/JS/EntityItem/AppJSContextMenuItems.tsx new file mode 100644 index 000000000000..849144058dee --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/JS/EntityItem/AppJSContextMenuItems.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; +import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; +import { + getHasDeleteActionPermission, + getHasManageActionPermission, +} from "ee/utils/BusinessFeatures/permissionPageHelpers"; +import type { JSCollection } from "entities/JSCollection"; +import { + Copy, + Delete, + Move, + Rename, + ShowBindings, +} from "pages/Editor/JSEditor/ContextMenuItems"; +import { MenuSeparator } from "@appsmith/ads"; + +export interface Props { + jsAction: JSCollection; +} + +export function AppJSContextMenuItems(props: Props) { + const { jsAction } = props; + const jsActionPermissions = jsAction.userPermissions || []; + + const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + + const canDeleteJSAction = getHasDeleteActionPermission( + isFeatureEnabled, + jsActionPermissions, + ); + + const canManageJSAction = getHasManageActionPermission( + isFeatureEnabled, + jsActionPermissions, + ); + + return ( + <> + + + + + + + > + ); +} diff --git a/app/client/src/pages/Editor/IDE/EditorPane/JS/EntityItem/JSEntityItem.tsx b/app/client/src/pages/Editor/IDE/EditorPane/JS/EntityItem/JSEntityItem.tsx new file mode 100644 index 000000000000..9a341d91324a --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/JS/EntityItem/JSEntityItem.tsx @@ -0,0 +1,109 @@ +import React, { useCallback, useMemo } from "react"; +import { EntityItem } from "@appsmith/ads"; +import type { EntityItem as EntityItemProps } from "ee/entities/IDE/constants"; +import type { AppState } from "ee/reducers"; +import { getJsCollectionByBaseId } from "ee/selectors/entitiesSelector"; +import { useDispatch, useSelector } from "react-redux"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; +import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; +import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; +import AnalyticsUtil from "ee/utils/AnalyticsUtil"; +import history, { NavigationMethod } from "utils/history"; +import { saveJSObjectNameBasedOnIdeType } from "ee/actions/helpers"; +import { useNameEditorState } from "pages/Editor/IDE/EditorPane/hooks/useNameEditorState"; +import { useValidateEntityName } from "IDE"; +import { useLocation } from "react-router"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; +import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks"; +import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo"; +import type { JSCollection } from "entities/JSCollection"; +import { jsCollectionIdURL } from "ee/RouteBuilder"; +import { JsFileIconV2 } from "pages/Editor/Explorer/ExplorerIcons"; +import { getJSContextMenuByIdeType } from "ee/pages/Editor/IDE/EditorPane/JS/utils/getJSContextMenuByIdeType"; + +export const JSEntityItem = ({ item }: { item: EntityItemProps }) => { + const jsAction = useSelector((state: AppState) => + getJsCollectionByBaseId(state, item.key), + ) as JSCollection; + const location = useLocation(); + const ideType = getIDETypeByUrl(location.pathname); + const activeActionBaseId = useActiveActionBaseId(); + const { parentEntityId } = useParentEntityInfo(ideType); + + const { editingEntity, enterEditMode, exitEditMode, updatingEntity } = + useNameEditorState(); + + const validateName = useValidateEntityName({ + entityName: item.title, + }); + const dispatch = useDispatch(); + const contextMenu = getJSContextMenuByIdeType(ideType, jsAction); + + const jsActionPermissions = jsAction.userPermissions || []; + + const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + + const canManageJSAction = getHasManageActionPermission( + isFeatureEnabled, + jsActionPermissions, + ); + + const navigateToUrl = jsCollectionIdURL({ + baseParentEntityId: parentEntityId, + baseCollectionId: jsAction.baseId, + params: {}, + }); + + const navigateToJSCollection = useCallback(() => { + if (jsAction.baseId) { + AnalyticsUtil.logEvent("ENTITY_EXPLORER_CLICK", { + type: "JSOBJECT", + fromUrl: location.pathname, + toUrl: navigateToUrl, + name: jsAction.name, + }); + history.push(navigateToUrl, { + invokedBy: NavigationMethod.EntityExplorer, + }); + } + }, [parentEntityId, jsAction.baseId, jsAction.name, location.pathname]); + + const nameEditorConfig = useMemo(() => { + return { + canEdit: canManageJSAction && !Boolean(jsAction.isMainJSCollection), + isEditing: editingEntity === jsAction.id, + isLoading: updatingEntity === jsAction.id, + onEditComplete: exitEditMode, + onNameSave: (newName: string) => + dispatch(saveJSObjectNameBasedOnIdeType(jsAction.id, newName, ideType)), + validateName: (newName: string) => validateName(newName, item.title), + }; + }, [ + canManageJSAction, + editingEntity, + exitEditMode, + ideType, + item.title, + jsAction.id, + jsAction.isMainJSCollection, + dispatch, + updatingEntity, + validateName, + ]); + + return ( + enterEditMode(jsAction.id)} + rightControl={contextMenu} + rightControlVisibility="hover" + startIcon={JsFileIconV2(16, 16)} + title={item.title} + /> + ); +}; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx b/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx index f1df474f1654..992afea302bd 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx @@ -1,25 +1,31 @@ import React, { useState } from "react"; import { useSelector } from "react-redux"; -import { Flex, Text, SearchAndAdd, NoSearchResults } from "@appsmith/ads"; +import { + Flex, + Text, + SearchAndAdd, + NoSearchResults, + EntityGroupsList, +} from "@appsmith/ads"; import styled from "styled-components"; import { selectJSSegmentEditorList } from "ee/selectors/appIDESelectors"; import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks"; -import { - getCurrentApplicationId, - getCurrentPageId, - getPagePermissions, -} from "selectors/editorSelectors"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; -import { getHasCreateActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers"; import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider"; import { useJSAdd } from "ee/pages/Editor/IDE/EditorPane/JS/hooks"; -import { JSListItem } from "ee/pages/Editor/IDE/EditorPane/JS/ListItem"; +import { JSListItem } from "ee/pages/Editor/IDE/EditorPane/JS/old/ListItem"; import { BlankState } from "./BlankState"; import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages"; import { filterEntityGroupsBySearchTerm } from "IDE/utils"; +import { useLocation } from "react-router"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; +import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo"; +import { useCreateActionsPermissions } from "ee/entities/IDE/hooks/useCreateActionsPermissions"; +import type { EntityItem } from "ee/entities/IDE/constants"; +import { JSEntity } from "ee/pages/Editor/IDE/EditorPane/JS/ListItem"; const JSContainer = styled(Flex)` & .t--entity-item { @@ -30,25 +36,23 @@ const JSContainer = styled(Flex)` const ListJSObjects = () => { const [searchTerm, setSearchTerm] = useState(""); - const pageId = useSelector(getCurrentPageId); const itemGroups = useSelector(selectJSSegmentEditorList); const activeActionBaseId = useActiveActionBaseId(); - const applicationId = useSelector(getCurrentApplicationId); - const pagePermissions = useSelector(getPagePermissions); + const location = useLocation(); + const ideType = getIDETypeByUrl(location.pathname); + const { editorId, parentEntityId } = useParentEntityInfo(ideType); + const canCreateActions = useCreateActionsPermissions(ideType); - const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const isNewADSTemplatesEnabled = useFeatureFlag( + FEATURE_FLAG.release_ads_entity_item_enabled, + ); const filteredItemGroups = filterEntityGroupsBySearchTerm( searchTerm, itemGroups, ); - const canCreateActions = getHasCreateActionPermission( - isFeatureEnabled, - pagePermissions, - ); - const { openAddJS } = useJSAdd(); return ( @@ -70,46 +74,60 @@ const ListJSObjects = () => { showAddButton={canCreateActions} /> ) : null} - - {filteredItemGroups.map(({ group, items }) => { - return ( - - {group !== "NA" ? ( - - - {group} - - - ) : null} - - {items.map((item) => { - return ( - - ); - })} - - - ); - })} + {isNewADSTemplatesEnabled ? ( + { + return { + groupTitle: group === "NA" ? "" : group, + items: items, + className: "", + renderList: (item: EntityItem) => { + return ; + }, + }; + })} + /> + ) : ( + filteredItemGroups.map(({ group, items }) => { + return ( + + {group !== "NA" ? ( + + + {group} + + + ) : null} + + {items.map((item) => { + return ( + + ); + })} + + + ); + }) + )} {filteredItemGroups.length === 0 && searchTerm !== "" ? ( + + + + + + + + > + ); +} diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/EntityItem/QueryEntityItem.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/EntityItem/QueryEntityItem.tsx new file mode 100644 index 000000000000..f74f53d02d28 --- /dev/null +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/EntityItem/QueryEntityItem.tsx @@ -0,0 +1,122 @@ +import React, { useCallback, useMemo } from "react"; +import { EntityItem } from "@appsmith/ads"; +import type { EntityItem as EntityItemProps } from "ee/entities/IDE/constants"; +import type { AppState } from "ee/reducers"; +import { + getActionByBaseId, + getDatasource, + getPlugins, +} from "ee/selectors/entitiesSelector"; +import { type Action, type StoredDatasource } from "entities/Action"; +import { useDispatch, useSelector } from "react-redux"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; +import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; +import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; +import AnalyticsUtil from "ee/utils/AnalyticsUtil"; +import type { Datasource } from "entities/Datasource"; +import history, { NavigationMethod } from "utils/history"; +import { keyBy } from "lodash"; +import { saveActionNameBasedOnIdeType } from "ee/actions/helpers"; +import { useNameEditorState } from "pages/Editor/IDE/EditorPane/hooks/useNameEditorState"; +import { useValidateEntityName } from "IDE"; +import { useLocation } from "react-router"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; +import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers"; +import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks"; +import { PluginType } from "entities/Plugin"; +import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo"; +import { getQueryContextMenuByIdeType } from "ee/pages/Editor/IDE/EditorPane/Query/utils/getQueryContextMenuByIdeType"; + +export const QueryEntityItem = ({ item }: { item: EntityItemProps }) => { + const action = useSelector((state: AppState) => + getActionByBaseId(state, item.key), + ) as Action; + const datasource = useSelector((state) => + getDatasource(state, (action?.datasource as StoredDatasource)?.id), + ) as Datasource; + const plugins = useSelector(getPlugins); + const pluginGroups = useMemo(() => keyBy(plugins, "id"), [plugins]); + const location = useLocation(); + const ideType = getIDETypeByUrl(location.pathname); + const activeActionBaseId = useActiveActionBaseId(); + const { parentEntityId } = useParentEntityInfo(ideType); + + const { editingEntity, enterEditMode, exitEditMode, updatingEntity } = + useNameEditorState(); + + const validateName = useValidateEntityName({ + entityName: item.title, + }); + const dispatch = useDispatch(); + const contextMenu = getQueryContextMenuByIdeType(ideType, action); + + const actionPermissions = action.userPermissions || []; + + const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + + const canManageAction = getHasManageActionPermission( + isFeatureEnabled, + actionPermissions, + ); + + const config = getActionConfig(action.pluginType); + const url = config?.getURL( + parentEntityId ?? "", + action.baseId, + action.pluginType, + pluginGroups[action.pluginId], + ); + + const switchToAction = useCallback(() => { + url && history.push(url, { invokedBy: NavigationMethod.EntityExplorer }); + AnalyticsUtil.logEvent("ENTITY_EXPLORER_CLICK", { + type: "QUERIES/APIs", + fromUrl: location.pathname, + toUrl: url, + name: action.name, + }); + AnalyticsUtil.logEvent("EDIT_ACTION_CLICK", { + actionId: action?.id, + datasourceId: datasource?.id, + pluginName: pluginGroups[action?.pluginId]?.name, + actionType: action?.pluginType === PluginType.DB ? "Query" : "API", + isMock: !!datasource?.isMock, + }); + }, [url, location.pathname, action, datasource, pluginGroups]); + + const nameEditorConfig = useMemo(() => { + return { + canEdit: canManageAction, + isEditing: editingEntity === action.id, + isLoading: updatingEntity === action.id, + onEditComplete: exitEditMode, + onNameSave: (newName: string) => + dispatch(saveActionNameBasedOnIdeType(action.id, newName, ideType)), + validateName: (newName: string) => validateName(newName, item.title), + }; + }, [ + canManageAction, + editingEntity, + exitEditMode, + ideType, + item.title, + action.id, + updatingEntity, + ]); + + return ( + enterEditMode(action.id)} + rightControl={contextMenu} + rightControlVisibility="hover" + startIcon={item.icon} + title={item.title} + /> + ); +}; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx index ef5dfccff8e1..a919ad44ad75 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx @@ -1,47 +1,55 @@ import React, { useState } from "react"; -import { Flex, Text, SearchAndAdd, NoSearchResults } from "@appsmith/ads"; +import { + Flex, + Text, + SearchAndAdd, + NoSearchResults, + EntityGroupsList, +} from "@appsmith/ads"; import { useSelector } from "react-redux"; -import { getHasCreateActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks"; -import { - getCurrentApplicationId, - getCurrentPageId, - getPagePermissions, -} from "selectors/editorSelectors"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; import { selectQuerySegmentEditorList } from "ee/selectors/appIDESelectors"; import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers"; import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider"; import { useQueryAdd } from "ee/pages/Editor/IDE/EditorPane/Query/hooks"; -import { QueryListItem } from "ee/pages/Editor/IDE/EditorPane/Query/ListItem"; +import { QueryListItem } from "ee/pages/Editor/IDE/EditorPane/Query/old/ListItem"; import { getShowWorkflowFeature } from "ee/selectors/workflowSelectors"; import { BlankState } from "./BlankState"; import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages"; import { filterEntityGroupsBySearchTerm } from "IDE/utils"; +import type { EntityItem } from "ee/entities/IDE/constants"; +import { ActionEntityItem } from "ee/pages/Editor/IDE/EditorPane/Query/ListItem"; +import { useLocation } from "react-router"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; +import { useParentEntityInfo } from "ee/IDE/hooks/useParentEntityInfo"; +import { useCreateActionsPermissions } from "ee/entities/IDE/hooks/useCreateActionsPermissions"; +import { objectKeys } from "@appsmith/utils"; const ListQuery = () => { const [searchTerm, setSearchTerm] = useState(""); - const pageId = useSelector(getCurrentPageId) as string; const itemGroups = useSelector(selectQuerySegmentEditorList); const activeActionBaseId = useActiveActionBaseId(); - const pagePermissions = useSelector(getPagePermissions); - const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + + const location = useLocation(); + const ideType = getIDETypeByUrl(location.pathname); + const { editorId, parentEntityId } = useParentEntityInfo(ideType); + const canCreateActions = useCreateActionsPermissions(ideType); + + const showWorkflows = useSelector(getShowWorkflowFeature); + + const isNewADSTemplatesEnabled = useFeatureFlag( + FEATURE_FLAG.release_ads_entity_item_enabled, + ); const filteredItemGroups = filterEntityGroupsBySearchTerm( searchTerm, itemGroups, ); - const canCreateActions = getHasCreateActionPermission( - isFeatureEnabled, - pagePermissions, - ); - const applicationId = useSelector(getCurrentApplicationId); - const { openAddQuery } = useQueryAdd(); - const showWorkflows = useSelector(getShowWorkflowFeature); return ( { px="spaces-3" py="spaces-3" > - {Object.keys(itemGroups).length === 0 && } + {objectKeys(itemGroups).length === 0 && } {itemGroups.length > 0 ? ( { showAddButton={canCreateActions} /> ) : null} - - {filteredItemGroups.map(({ group, items }) => { - return ( - - - + {isNewADSTemplatesEnabled ? ( + { + return { + groupTitle: group, + items: items, + className: "", + renderList: (item: EntityItem) => { + return ; + }, + }; + })} + /> + ) : ( + filteredItemGroups.map(({ group, items }) => { + return ( + + + + {group} + + + - {group} - + {items.map((file) => { + return ( + + ); + })} + - - {items.map((file) => { - return ( - - ); - })} - - - ); - })} + ); + }) + )} {filteredItemGroups.length === 0 && searchTerm !== "" ? ( { + const [isMenuOpen, toggleMenuOpen] = useToggle([false, true]); + + return ( + + + + + + + + + + {props.children} + + + ); +}; + +export default EntityContextMenu; diff --git a/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx b/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx index 4604f3c73f4a..e331334d58ca 100644 --- a/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx +++ b/app/client/src/pages/Editor/JSEditor/AppJSEditorContextMenu.tsx @@ -1,32 +1,4 @@ -import React, { useCallback, useMemo } from "react"; -import { useBoolean } from "usehooks-ts"; -import { useDispatch, useSelector } from "react-redux"; -import { - moveJSCollectionRequest, - copyJSCollectionRequest, - deleteJSCollection, -} from "actions/jsActionActions"; -import noop from "lodash/noop"; -import { - CONTEXT_COPY, - CONTEXT_DELETE, - CONFIRM_CONTEXT_DELETE, - CONTEXT_MOVE, - createMessage, - CONTEXT_RENAME, -} from "ee/constants/messages"; -import { getPageListAsOptions } from "ee/selectors/entitiesSelector"; -import { - autoIndentCode, - getAutoIndentShortcutKeyText, -} from "components/editorComponents/CodeEditor/utils/autoIndentUtils"; -import AnalyticsUtil from "ee/utils/AnalyticsUtil"; -import { updateJSCollectionBody } from "actions/jsPaneActions"; -import type { IconName } from "@blueprintjs/icons"; - -import type { ContextMenuOption } from "./JSEditorContextMenu"; -import JSEditorContextMenu from "./JSEditorContextMenu"; -import equal from "fast-deep-equal/es6"; +import React from "react"; import { getHasDeleteActionPermission, getHasManageActionPermission, @@ -34,26 +6,18 @@ 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"; -import type CodeMirror from "codemirror"; +import { Copy, Delete, Move, Prettify } from "./ContextMenuItems"; +import { RenameMenuItem } from "IDE"; +import { MenuSeparator } from "@appsmith/ads"; +import EntityContextMenu from "../IDE/EditorPane/components/EntityContextMenu"; interface AppJSEditorContextMenuProps { - pageId: string; jsCollection: JSCollection; } -const prettifyCodeKeyboardShortCut = getAutoIndentShortcutKeyText(); - export function AppJSEditorContextMenu({ jsCollection, - pageId, }: AppJSEditorContextMenuProps) { - const { - setFalse: cancelConfirmDelete, - setValue: setConfirmDelete, - value: confirmDelete, - } = useBoolean(false); - const dispatch = useDispatch(); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); const isDeletePermitted = getHasDeleteActionPermission( isFeatureEnabled, @@ -64,169 +28,18 @@ 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); - }, [dispatch, jsCollection.id]); - - const copyJSCollectionToPage = useCallback( - (actionId: string, actionName: string, pageId: string) => { - dispatch( - copyJSCollectionRequest({ - id: actionId, - destinationPageId: pageId, - name: actionName, - }), - ); - }, - [dispatch], - ); - - const moveJSCollectionToPage = useCallback( - (actionId: string, actionName: string, destinationPageId: string) => { - dispatch( - moveJSCollectionRequest({ - id: actionId, - destinationPageId, - name: actionName, - }), - ); - }, - [dispatch], - ); - const deleteJSCollectionFromPage = useCallback( - (actionId: string, actionName: string) => { - dispatch(deleteJSCollection({ id: actionId, name: actionName })); - setConfirmDelete(false); - }, - [dispatch, setConfirmDelete], - ); - - const menuPages = useSelector(getPageListAsOptions, equal); - - const options = useMemo(() => { - const confirmDeletion = (value: boolean, event?: Event) => { - event?.preventDefault?.(); - setConfirmDelete(value); - }; - - 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", - onSelect: noop, - label: createMessage(CONTEXT_COPY), - children: menuPages.map((page) => { - return { - ...page, - onSelect: () => - copyJSCollectionToPage(jsCollection.id, jsCollection.name, page.id), - }; - }), - }; - - const moveOption = { - icon: "swap-horizontal" as IconName, - value: "move", - onSelect: noop, - label: createMessage(CONTEXT_MOVE), - children: - menuPages.length > 1 - ? menuPages - .filter((page) => page.id !== pageId) // Remove current page from the list - .map((page) => { - return { - ...page, - onSelect: () => - moveJSCollectionToPage( - jsCollection.id, - jsCollection.name, - page.id, - ), - }; - }) - : [{ value: "No Pages", onSelect: noop, label: "No Pages" }], - }; - - const prettifyOptions = { - value: "prettify", - icon: "code" as IconName, - subText: prettifyCodeKeyboardShortCut, - onSelect: () => { - const editorElement = document.querySelector(".CodeMirror"); - - if ( - editorElement && - "CodeMirror" in editorElement && - editorElement.CodeMirror - ) { - const editor = editorElement.CodeMirror as CodeMirror.Editor; - - autoIndentCode(editor); - dispatch(updateJSCollectionBody(editor.getValue(), jsCollection.id)); - AnalyticsUtil.logEvent("PRETTIFY_CODE_MANUAL_TRIGGER"); - } - }, - label: "Prettify code", - }; - - const deleteOption = { - confirmDelete: confirmDelete, - icon: "delete-bin-line" as IconName, - 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", - }; - - 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 ( - + + + + + + + + ); } diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Copy.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Copy.tsx new file mode 100644 index 000000000000..3150111a9c0b --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Copy.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { MenuSub, MenuSubContent, MenuSubTrigger } from "@appsmith/ads"; +import { useDispatch, useSelector } from "react-redux"; +import { getPageList } from "selectors/editorSelectors"; +import { PageMenuItem } from "./PageMenuItem"; +import { useCallback } from "react"; +import { CONTEXT_COPY, createMessage } from "ee/constants/messages"; +import { copyJSCollectionRequest } from "actions/jsActionActions"; +import type { JSCollection } from "entities/JSCollection"; + +interface Props { + jsAction: JSCollection; + disabled?: boolean; +} + +export const Copy = ({ disabled, jsAction }: Props) => { + const menuPages = useSelector(getPageList); + const dispatch = useDispatch(); + + const copyJSActionToPage = useCallback( + (pageId: string) => + dispatch( + copyJSCollectionRequest({ + id: jsAction.id, + destinationPageId: pageId, + name: jsAction.name, + }), + ), + [jsAction.id, jsAction.name, dispatch], + ); + + return ( + + + {createMessage(CONTEXT_COPY)} + + + {menuPages.map((page) => { + return ( + + ); + })} + + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Delete.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Delete.tsx new file mode 100644 index 000000000000..e2d481884839 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Delete.tsx @@ -0,0 +1,56 @@ +import React, { useCallback, useState } from "react"; +import { + CONFIRM_CONTEXT_DELETE, + CONTEXT_DELETE, + createMessage, +} from "ee/constants/messages"; +import { MenuItem } from "@appsmith/ads"; +import { useDispatch } from "react-redux"; +import { deleteJSCollection } from "actions/jsActionActions"; +import type { JSCollection } from "entities/JSCollection"; + +interface Props { + jsAction: JSCollection; + disabled?: boolean; + deleteJSAction?: () => void; +} + +export const Delete = ({ deleteJSAction, disabled, jsAction }: Props) => { + const dispatch = useDispatch(); + const [confirmDelete, setConfirmDelete] = useState(false); + + const handleDeleteClick = useCallback(() => { + jsAction.isPublic && deleteJSAction + ? deleteJSAction() + : dispatch( + deleteJSCollection({ + id: jsAction?.id ?? "", + name: jsAction?.name ?? "", + }), + ); + }, [jsAction.id, jsAction.name, jsAction.isPublic, deleteJSAction, dispatch]); + + const handleSelect = useCallback( + (e?: Event) => { + e?.preventDefault(); + confirmDelete ? handleDeleteClick() : setConfirmDelete(true); + e?.stopPropagation(); + }, + [confirmDelete, handleDeleteClick], + ); + + const menuLabel = confirmDelete + ? createMessage(CONFIRM_CONTEXT_DELETE) + : createMessage(CONTEXT_DELETE); + + return ( + + {menuLabel} + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Move.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Move.tsx new file mode 100644 index 000000000000..2ef91ee55861 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Move.tsx @@ -0,0 +1,65 @@ +import { useDispatch, useSelector } from "react-redux"; +import { getCurrentPageId } from "selectors/editorSelectors"; +import { getPageList } from "ee/selectors/entitiesSelector"; +import React, { useCallback, useMemo } from "react"; +import { + MenuItem, + MenuSub, + MenuSubContent, + MenuSubTrigger, +} from "@appsmith/ads"; +import { CONTEXT_MOVE, createMessage } from "ee/constants/messages"; +import { PageMenuItem } from "./PageMenuItem"; +import { moveJSCollectionRequest } from "actions/jsActionActions"; +import type { JSCollection } from "entities/JSCollection"; + +interface Props { + jsAction: JSCollection; + disabled?: boolean; +} + +export const Move = ({ disabled, jsAction }: Props) => { + const dispatch = useDispatch(); + + const currentPageId = useSelector(getCurrentPageId); + const allPages = useSelector(getPageList); + const menuPages = useMemo(() => { + return allPages.filter((page) => page.pageId !== currentPageId); + }, [allPages, currentPageId]); + + const moveJSActionToPage = useCallback( + (destinationPageId: string) => + dispatch( + moveJSCollectionRequest({ + id: jsAction.id, + destinationPageId, + name: jsAction.name, + }), + ), + [dispatch, jsAction.id, jsAction.name], + ); + + return ( + + + {createMessage(CONTEXT_MOVE)} + + + {menuPages.length ? ( + menuPages.map((page) => { + return ( + + ); + }) + ) : ( + No pages + )} + + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/PageMenuItem.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/PageMenuItem.tsx new file mode 100644 index 000000000000..2aec776f4ba5 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/PageMenuItem.tsx @@ -0,0 +1,19 @@ +import type { Page } from "entities/Page"; +import React, { useCallback } from "react"; +import { MenuItem } from "@appsmith/ads"; + +export const PageMenuItem = (props: { + page: Page; + onSelect: (id: string) => void; + disabled?: boolean; +}) => { + const handleOnSelect = useCallback(() => { + props.onSelect(props.page.pageId); + }, [props]); + + return ( + + {props.page.pageName} + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Prettify.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Prettify.tsx new file mode 100644 index 000000000000..606425a53562 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Prettify.tsx @@ -0,0 +1,50 @@ +import React, { useCallback } from "react"; +import { MenuItem, Text } from "@appsmith/ads"; +import { useDispatch } from "react-redux"; +import type { JSCollection } from "entities/JSCollection"; +import { updateJSCollectionBody } from "actions/jsPaneActions"; +import { + autoIndentCode, + getAutoIndentShortcutKeyText, +} from "components/editorComponents/CodeEditor/utils/autoIndentUtils"; +import AnalyticsUtil from "ee/utils/AnalyticsUtil"; + +interface Props { + jsAction: JSCollection; + disabled?: boolean; +} + +const prettifyCodeKeyboardShortCut = getAutoIndentShortcutKeyText(); + +export const Prettify = ({ disabled, jsAction }: Props) => { + const dispatch = useDispatch(); + + const handleSelect = useCallback(() => { + const editorElement = document.querySelector(".CodeMirror"); + + if ( + editorElement && + "CodeMirror" in editorElement && + editorElement.CodeMirror + ) { + const editor = editorElement.CodeMirror as CodeMirror.Editor; + + autoIndentCode(editor); + dispatch(updateJSCollectionBody(editor.getValue(), jsAction.id)); + AnalyticsUtil.logEvent("PRETTIFY_CODE_MANUAL_TRIGGER"); + } + }, [jsAction.id, jsAction.name]); + + return ( + + Prettify code + + {prettifyCodeKeyboardShortCut} + + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Rename.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Rename.tsx new file mode 100644 index 000000000000..60e8131fe0d5 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/Rename.tsx @@ -0,0 +1,32 @@ +import React, { useCallback } from "react"; +import { MenuItem } from "@appsmith/ads"; +import { useDispatch } from "react-redux"; +import { initExplorerEntityNameEdit } from "actions/explorerActions"; +import { CONTEXT_RENAME, createMessage } from "ee/constants/messages"; +import type { JSCollection } from "entities/JSCollection"; + +interface Props { + jsAction: JSCollection; + disabled?: boolean; +} + +export const Rename = ({ disabled, jsAction }: Props) => { + const dispatch = useDispatch(); + + const setRename = useCallback(() => { + // We add a delay to avoid having the focus stuck in the menu trigger + setTimeout(() => { + dispatch(initExplorerEntityNameEdit(jsAction.id)); + }, 100); + }, [dispatch, jsAction.id]); + + return ( + + {createMessage(CONTEXT_RENAME)} + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/ShowBindings.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/ShowBindings.tsx new file mode 100644 index 000000000000..5a71e80848e8 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/ShowBindings.tsx @@ -0,0 +1,38 @@ +import React, { useCallback } from "react"; +import { CONTEXT_SHOW_BINDING, createMessage } from "ee/constants/messages"; +import { MenuItem } from "@appsmith/ads"; +import { useDispatch } from "react-redux"; +import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; +import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils"; +import type { JSCollection } from "entities/JSCollection"; + +interface Props { + jsAction: JSCollection; + disabled?: boolean; +} + +export const ShowBindings = ({ disabled, jsAction }: Props) => { + const dispatch = useDispatch(); + + const handleSelect = useCallback(() => { + dispatch({ + type: ReduxActionTypes.SET_ENTITY_INFO, + payload: { + entityId: jsAction.id, + entityName: jsAction.name, + entityType: ENTITY_TYPE.JSACTION, + show: true, + }, + }); + }, [jsAction.id, jsAction.name]); + + return ( + + {createMessage(CONTEXT_SHOW_BINDING)} + + ); +}; diff --git a/app/client/src/pages/Editor/JSEditor/ContextMenuItems/index.tsx b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/index.tsx new file mode 100644 index 000000000000..9d7cf3e78950 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/ContextMenuItems/index.tsx @@ -0,0 +1,6 @@ +export { Copy } from "./Copy"; +export { Move } from "./Move"; +export { Delete } from "./Delete"; +export { Rename } from "./Rename"; +export { ShowBindings } from "./ShowBindings"; +export { Prettify } from "./Prettify"; diff --git a/app/client/src/pages/Editor/JSEditor/JSEditorContextMenu.tsx b/app/client/src/pages/Editor/JSEditor/JSEditorContextMenu.tsx deleted file mode 100644 index 27fc6be61268..000000000000 --- a/app/client/src/pages/Editor/JSEditor/JSEditorContextMenu.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React from "react"; -import type { IconName } from "@blueprintjs/icons"; - -import { - Button, - Menu, - MenuContent, - MenuItem, - MenuSub, - MenuSubContent, - MenuSubTrigger, - MenuTrigger, - Text, -} from "@appsmith/ads"; - -export interface ContextMenuOption { - id?: string; - icon: IconName; - value: string; - onSelect?: (event?: Event) => void; - label: string; - subText?: string; - className?: string; - children?: Omit[]; -} - -interface EntityContextMenuProps { - className?: string; - options: ContextMenuOption[]; - onMenuClose: (() => void) | undefined; -} - -export function JSEditorContextMenu({ - className, - onMenuClose, - options, -}: EntityContextMenuProps) { - if (options.length === 0) { - return null; - } - - return ( - { - if (!open) { - onMenuClose?.(); - } - }} - > - - - - - {options.map((option, index) => { - if (option.children) { - return ( - - - {option.label} - - - {option.children.map((children) => ( - - {children.label} - - ))} - - - ); - } - - return ( - - - - {option.label} - - {option.subText && ( - - {option.subText} - - )} - - - ); - })} - - - ); -} - -export default JSEditorContextMenu; diff --git a/app/client/src/pages/Editor/JSEditor/index.tsx b/app/client/src/pages/Editor/JSEditor/index.tsx index 6fcd8956119b..220e58e21394 100644 --- a/app/client/src/pages/Editor/JSEditor/index.tsx +++ b/app/client/src/pages/Editor/JSEditor/index.tsx @@ -3,10 +3,7 @@ import type { RouteComponentProps } from "react-router"; import { useDispatch, useSelector } from "react-redux"; import JsEditorForm from "./Form"; import * as Sentry from "@sentry/react"; -import { - getCurrentPageId, - getJSCollectionDataByBaseId, -} from "selectors/editorSelectors"; +import { getJSCollectionDataByBaseId } from "selectors/editorSelectors"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import Spinner from "components/editorComponents/Spinner"; import styled from "styled-components"; @@ -27,7 +24,6 @@ type Props = RouteComponentProps<{ function JSEditor(props: Props) { const { baseCollectionId } = props.match.params; - const pageId = useSelector(getCurrentPageId); const dispatch = useDispatch(); const jsCollectionData = useSelector((state) => getJSCollectionDataByBaseId(state, baseCollectionId), @@ -40,10 +36,8 @@ function JSEditor(props: Props) { return null; } - return ( - - ); - }, [jsCollection, pageId]); + return ; + }, [jsCollection]); if (isCreating) { return ( diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 5f6b224f259b..274faa639cc6 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -1062,6 +1062,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction) { const isApi = pluginType === PluginType.API; const isQuery = pluginType === PluginType.DB; const isSaas = pluginType === PluginType.SAAS; + const isInternal = pluginType === PluginType.INTERNAL; const { parentEntityId } = resolveParentEntityMetadata(actionPayload.payload); if (!parentEntityId) return; @@ -1080,7 +1081,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction) { ); } - if (isQuery) { + if (isQuery || isInternal) { history.push( queryEditorIdURL({ baseParentEntityId,