name !== "createReactComponent")
- .filter((name) => !name.endsWith("Filled"))
.map((name) => {
content += `\n "${kebabCase(name).replace("icon-", "")}": "${name}",`;
});
diff --git a/app/client/packages/rts/package.json b/app/client/packages/rts/package.json
index bfd2be65e398..b2d5e490ce79 100644
--- a/app/client/packages/rts/package.json
+++ b/app/client/packages/rts/package.json
@@ -20,7 +20,7 @@
"astravel": "^0.6.1",
"axios": "^1.7.4",
"escodegen": "^2.0.0",
- "express": "^4.19.2",
+ "express": "^4.20.0",
"express-validator": "^6.14.2",
"http-status-codes": "^2.2.0",
"klona": "^2.0.5",
diff --git a/app/client/packages/storybook/.storybook/addons/theming/ThemingPopup.tsx b/app/client/packages/storybook/.storybook/addons/theming/ThemingPopup.tsx
index 390763d83558..f7ebfb17dde9 100644
--- a/app/client/packages/storybook/.storybook/addons/theming/ThemingPopup.tsx
+++ b/app/client/packages/storybook/.storybook/addons/theming/ThemingPopup.tsx
@@ -61,8 +61,6 @@ export const ThemingPopup: React.FC
= ({ leftShift, onClose }) => {
setBorderRadius={(value) => updateGlobal("borderRadius", value)}
seedColor={globals.seedColor}
setSeedColor={(value) => updateGlobal("seedColor", value)}
- fontFamily={globals.fontFamily}
- setFontFamily={(value) => updateGlobal("fontFamily", value)}
userDensity={globals.userDensity}
userSizing={globals.userSizing}
setUserDensity={(value) => updateGlobal("userDensity", value)}
diff --git a/app/client/packages/storybook/.storybook/decorators/theming.tsx b/app/client/packages/storybook/.storybook/decorators/theming.tsx
index 23b58e53a837..9efe5e3cd2ec 100644
--- a/app/client/packages/storybook/.storybook/decorators/theming.tsx
+++ b/app/client/packages/storybook/.storybook/decorators/theming.tsx
@@ -18,7 +18,6 @@ export const theming = (Story, args) => {
seedColor: args.globals.seedColor,
colorMode: args.parameters.colorMode || args.globals.colorMode,
borderRadius: args.globals.borderRadius,
- fontFamily: args.globals.fontFamily,
userDensity: args.globals.userDensity,
userSizing: args.globals.userSizing,
});
diff --git a/app/client/packages/storybook/src/components/ThemeSettings.tsx b/app/client/packages/storybook/src/components/ThemeSettings.tsx
index f9b227934de0..b3d77aa3b3db 100644
--- a/app/client/packages/storybook/src/components/ThemeSettings.tsx
+++ b/app/client/packages/storybook/src/components/ThemeSettings.tsx
@@ -2,7 +2,6 @@ import { Form } from "@storybook/components";
import React, { useCallback } from "react";
import { Flex, Text } from "@appsmith/wds";
import { ColorControl, BooleanControl, RangeControl } from "@storybook/blocks";
-import { FONT_METRICS } from "@appsmith/wds-theming";
import styled from "styled-components";
import { debounce } from "lodash";
import { AddonPanel } from "@storybook/components";
@@ -42,12 +41,10 @@ interface ThemeSettingsProps {
export const ThemeSettings = ({
borderRadius,
direction = "column",
- fontFamily,
isDarkMode,
seedColor,
setBorderRadius,
setDarkMode,
- setFontFamily,
setSeedColor,
setUserDensity,
setUserSizing,
@@ -106,36 +103,6 @@ export const ThemeSettings = ({
)}
- {setFontFamily && (
-
- Font Family
- setFontFamily(e.target.value)}
- size="100%"
- title="Font Family"
- >
-
- {Object.keys(FONT_METRICS)
- .filter((item) => {
- return (
- [
- "-apple-system",
- "BlinkMacSystemFont",
- "Segoe UI",
- ].includes(item) === false
- );
- })
- .map((font) => (
-
- ))}
-
-
- )}
-
{setUserDensity && (
Density
diff --git a/app/client/src/IDE/Structure/Toolbar.tsx b/app/client/src/IDE/Structure/Toolbar.tsx
new file mode 100644
index 000000000000..bd2f4f0ce7aa
--- /dev/null
+++ b/app/client/src/IDE/Structure/Toolbar.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+import { Flex } from "@appsmith/ads";
+
+interface ToolbarProps {
+ children?: React.ReactNode[] | React.ReactNode;
+}
+
+const Toolbar = (props: ToolbarProps) => {
+ return (
+
+ {props.children}
+
+ );
+};
+
+const Left = (props: ToolbarProps) => {
+ return (
+
+ {props.children}
+
+ );
+};
+
+const Right = (props: ToolbarProps) => {
+ return (
+
+ {props.children}
+
+ );
+};
+
+Toolbar.Left = Left;
+Toolbar.Right = Right;
+
+export default Toolbar;
diff --git a/app/client/src/IDE/hooks/index.ts b/app/client/src/IDE/hooks/index.ts
index 50897137a667..8fe85b2a9d84 100644
--- a/app/client/src/IDE/hooks/index.ts
+++ b/app/client/src/IDE/hooks/index.ts
@@ -1 +1,2 @@
export { useIsInSideBySideEditor } from "./useIsInSideBySideEditor";
+export { useIsEditorInitialised } from "ee/IDE/hooks/useIsEditorInitialised";
diff --git a/app/client/src/IDE/index.ts b/app/client/src/IDE/index.ts
index 8a12a855b8ec..c57c8ea1872f 100644
--- a/app/client/src/IDE/index.ts
+++ b/app/client/src/IDE/index.ts
@@ -12,6 +12,14 @@
export { IDE_HEADER_HEIGHT } from "./Structure/constants";
export { default as IDEHeader } from "./Structure/Header";
+/**
+ * The IDEToolbar gets exported with 2 layout subsections.
+ * IDEToolbar.Left and IDEToolbar.Right
+ * These are composable components that you can use to spread the content of the toolbar
+ * It is possible to use the Toolbar without using these subsections
+ */
+export { default as IDEToolbar } from "./Structure/Toolbar";
+
/* ====================================================
**** UI Components ****
Components that are smaller UI abstractions for easy use and standardisation within the IDE
diff --git a/app/client/src/PluginActionEditor/PluginActionContext.tsx b/app/client/src/PluginActionEditor/PluginActionContext.tsx
new file mode 100644
index 000000000000..21c76f6c8985
--- /dev/null
+++ b/app/client/src/PluginActionEditor/PluginActionContext.tsx
@@ -0,0 +1,62 @@
+import React, {
+ type ReactNode,
+ createContext,
+ useContext,
+ useMemo,
+} from "react";
+import type { Action } from "entities/Action";
+import type { Plugin } from "api/PluginApi";
+import type { Datasource } from "entities/Datasource";
+
+interface PluginActionContextType {
+ action: Action;
+ editorConfig: unknown[];
+ settingsConfig: unknown[];
+ plugin: Plugin;
+ datasource?: Datasource;
+}
+
+// No need to export this context to use it. Use the hook defined below instead
+const PluginActionContext = createContext(null);
+
+interface ChildrenProps {
+ children: ReactNode | ReactNode[];
+}
+
+export const PluginActionContextProvider = (
+ props: ChildrenProps & PluginActionContextType,
+) => {
+ const { action, children, datasource, editorConfig, plugin, settingsConfig } =
+ props;
+
+ // using useMemo to avoid unnecessary renders
+ const contextValue = useMemo(
+ () => ({
+ action,
+ datasource,
+ editorConfig,
+ plugin,
+ settingsConfig,
+ }),
+ [action, datasource, editorConfig, plugin, settingsConfig],
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+// By using this hook, you are guaranteed that the states are correctly
+// typed and set.
+// Without this, consumers of the context would need to keep doing a null check
+export const usePluginActionContext = () => {
+ const context = useContext(PluginActionContext);
+ if (!context) {
+ throw new Error(
+ "usePluginActionContext must be used within usePluginActionContextProvider",
+ );
+ }
+ return context;
+};
diff --git a/app/client/src/PluginActionEditor/PluginActionEditor.tsx b/app/client/src/PluginActionEditor/PluginActionEditor.tsx
new file mode 100644
index 000000000000..40470b7bd837
--- /dev/null
+++ b/app/client/src/PluginActionEditor/PluginActionEditor.tsx
@@ -0,0 +1,89 @@
+import React from "react";
+import { useLocation } from "react-router";
+import { identifyEntityFromPath } from "../navigation/FocusEntity";
+import { useSelector } from "react-redux";
+import {
+ getActionByBaseId,
+ getDatasource,
+ getEditorConfig,
+ getPlugin,
+ getPluginSettingConfigs,
+} from "ee/selectors/entitiesSelector";
+import { PluginActionContextProvider } from "./PluginActionContext";
+import { get } from "lodash";
+import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane";
+import Spinner from "components/editorComponents/Spinner";
+import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
+import { Text } from "@appsmith/ads";
+import { useIsEditorInitialised } from "IDE/hooks";
+
+interface ChildrenProps {
+ children: React.ReactNode | React.ReactNode[];
+}
+
+const PluginActionEditor = (props: ChildrenProps) => {
+ const { pathname } = useLocation();
+
+ const isEditorInitialized = useIsEditorInitialised();
+
+ const entity = identifyEntityFromPath(pathname);
+ const action = useSelector((state) => getActionByBaseId(state, entity.id));
+
+ const pluginId = get(action, "pluginId", "");
+ const plugin = useSelector((state) => getPlugin(state, pluginId));
+
+ const datasourceId = get(action, "datasource.id", "");
+ const datasource = useSelector((state) => getDatasource(state, datasourceId));
+
+ const settingsConfig = useSelector((state) =>
+ getPluginSettingConfigs(state, pluginId),
+ );
+
+ const editorConfig = useSelector((state) => getEditorConfig(state, pluginId));
+
+ if (!isEditorInitialized) {
+ return (
+
+
+
+ );
+ }
+
+ if (!action) {
+ return ;
+ }
+
+ if (!plugin) {
+ return (
+
+
+ Plugin not installed!
+
+
+ );
+ }
+
+ if (!settingsConfig || !editorConfig) {
+ return (
+
+
+ Editor config not found!
+
+
+ );
+ }
+
+ return (
+
+ {props.children}
+
+ );
+};
+
+export default PluginActionEditor;
diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx
new file mode 100644
index 000000000000..17db33e698a3
--- /dev/null
+++ b/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const PluginActionForm = () => {
+ return ;
+};
+
+export default PluginActionForm;
diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/index.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/index.ts
new file mode 100644
index 000000000000..bb106d466ee2
--- /dev/null
+++ b/app/client/src/PluginActionEditor/components/PluginActionForm/index.ts
@@ -0,0 +1 @@
+export { default } from "./PluginActionForm";
diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponsePane.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponsePane.tsx
new file mode 100644
index 000000000000..5a0be861970c
--- /dev/null
+++ b/app/client/src/PluginActionEditor/components/PluginActionResponsePane.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const PluginActionResponsePane = () => {
+ return ;
+};
+
+export default PluginActionResponsePane;
diff --git a/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx
new file mode 100644
index 000000000000..0d2e4f718d78
--- /dev/null
+++ b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx
@@ -0,0 +1,79 @@
+import React, { useCallback } from "react";
+import { IDEToolbar } from "IDE";
+import { Button, Menu, MenuContent, MenuTrigger, Tooltip } from "@appsmith/ads";
+import { modText } from "utils/helpers";
+import { usePluginActionContext } from "../PluginActionContext";
+import { useDispatch } from "react-redux";
+import AnalyticsUtil from "ee/utils/AnalyticsUtil";
+import { runAction } from "../../actions/pluginActionActions";
+
+interface PluginActionToolbarProps {
+ runOptions?: React.ReactNode;
+ children?: React.ReactNode[] | React.ReactNode;
+ menuContent?: React.ReactNode[] | React.ReactNode;
+}
+
+const PluginActionToolbar = (props: PluginActionToolbarProps) => {
+ const { action, datasource, plugin } = usePluginActionContext();
+ const dispatch = useDispatch();
+ const handleRunClick = useCallback(() => {
+ AnalyticsUtil.logEvent("RUN_QUERY_CLICK", {
+ actionName: action.name,
+ actionId: action.id,
+ pluginName: plugin.name,
+ datasourceId: datasource?.id,
+ isMock: datasource?.isMock,
+ });
+ dispatch(runAction(action.id));
+ }, [
+ action.id,
+ action.name,
+ datasource?.id,
+ datasource?.isMock,
+ dispatch,
+ plugin.name,
+ ]);
+ return (
+
+ {props.children}
+
+ {props.runOptions}
+
+
+
+
+
+
+
+ );
+};
+
+export default PluginActionToolbar;
diff --git a/app/client/src/PluginActionEditor/index.ts b/app/client/src/PluginActionEditor/index.ts
new file mode 100644
index 000000000000..0a58d00bdaae
--- /dev/null
+++ b/app/client/src/PluginActionEditor/index.ts
@@ -0,0 +1,8 @@
+export { default as PluginActionEditor } from "./PluginActionEditor";
+export {
+ PluginActionContextProvider,
+ usePluginActionContext,
+} from "./PluginActionContext";
+export { default as PluginActionToolbar } from "./components/PluginActionToolbar";
+export { default as PluginActionForm } from "./components/PluginActionForm";
+export { default as PluginActionResponsePane } from "./components/PluginActionResponsePane";
diff --git a/app/client/src/actions/widgetSelectionActions.ts b/app/client/src/actions/widgetSelectionActions.ts
index 092400d49bb8..940a48863746 100644
--- a/app/client/src/actions/widgetSelectionActions.ts
+++ b/app/client/src/actions/widgetSelectionActions.ts
@@ -8,6 +8,7 @@ export interface WidgetSelectionRequestPayload {
payload?: string[];
invokedBy?: NavigationMethod;
basePageId?: string;
+ parentId?: string;
}
export type WidgetSelectionRequest = (
@@ -15,6 +16,7 @@ export type WidgetSelectionRequest = (
payload?: string[],
invokedBy?: NavigationMethod,
basePageId?: string,
+ parentId?: string,
) => ReduxAction;
// Use to select a widget programmatically via platform action
@@ -23,9 +25,10 @@ export const selectWidgetInitAction: WidgetSelectionRequest = (
payload,
invokedBy?: NavigationMethod,
basePageId?: string,
+ parentId?: string,
) => ({
type: ReduxActionTypes.SELECT_WIDGET_INIT,
- payload: { selectionRequestType, payload, basePageId, invokedBy },
+ payload: { selectionRequestType, payload, basePageId, invokedBy, parentId },
});
export interface SetSelectedWidgetsPayload {
diff --git a/app/client/src/ce/IDE/hooks/useIsEditorInitialised.ts b/app/client/src/ce/IDE/hooks/useIsEditorInitialised.ts
new file mode 100644
index 000000000000..bff2ec9d4cb2
--- /dev/null
+++ b/app/client/src/ce/IDE/hooks/useIsEditorInitialised.ts
@@ -0,0 +1,8 @@
+import { useSelector } from "react-redux";
+import { getIsEditorInitialized } from "selectors/editorSelectors";
+
+function useIsEditorInitialised() {
+ return useSelector(getIsEditorInitialized);
+}
+
+export { useIsEditorInitialised };
diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts
index 7de903836a11..8407a185902a 100644
--- a/app/client/src/ce/constants/messages.ts
+++ b/app/client/src/ce/constants/messages.ts
@@ -364,8 +364,8 @@ export const DATASOURCE_UPDATE = (dsName: string) =>
`${dsName} datasource updated successfully`;
export const DATASOURCE_VALID = (dsName: string) =>
`${dsName} datasource is valid`;
-export const EDIT_DATASOURCE = () => "Edit datasource";
-export const SAVE_DATASOURCE = () => "Save as datasource";
+export const EDIT_DATASOURCE = () => "Edit";
+export const SAVE_DATASOURCE = () => "Save";
export const SAVE_DATASOURCE_MESSAGE = () =>
"Save the URL as a datasource to access authentication settings";
export const EDIT_DATASOURCE_MESSAGE = () =>
diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx
new file mode 100644
index 000000000000..e5f9366c05c5
--- /dev/null
+++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import {
+ PluginActionEditor,
+ PluginActionForm,
+ PluginActionResponsePane,
+} from "PluginActionEditor";
+import {
+ ConvertToModuleDisabler,
+ ConvertToModuleCallout,
+} from "./components/ConvertToModule";
+import AppPluginActionToolbar from "./components/AppPluginActionToolbar";
+
+const AppPluginActionEditor = () => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AppPluginActionEditor;
diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/AppPluginActionToolbar.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/AppPluginActionToolbar.tsx
new file mode 100644
index 000000000000..8606cee42fb3
--- /dev/null
+++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/AppPluginActionToolbar.tsx
@@ -0,0 +1,9 @@
+import React from "react";
+import { PluginActionToolbar } from "PluginActionEditor";
+import AppPluginActionMenu from "./PluginActionMoreActions";
+
+const AppPluginActionToolbar = () => {
+ return } />;
+};
+
+export default AppPluginActionToolbar;
diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCTA.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCTA.tsx
new file mode 100644
index 000000000000..83aac04d90e5
--- /dev/null
+++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCTA.tsx
@@ -0,0 +1,35 @@
+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";
+import { getPagePermissions } from "selectors/editorSelectors";
+import {
+ getHasCreateActionPermission,
+ getHasDeleteActionPermission,
+} from "ee/utils/BusinessFeatures/permissionPageHelpers";
+import { MODULE_TYPE } from "ee/constants/ModuleConstants";
+import ConvertToModuleInstanceCTA from "ee/pages/Editor/EntityEditor/ConvertToModuleInstanceCTA";
+
+const ConvertToModuleCTA = () => {
+ const { action } = usePluginActionContext();
+ const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
+ const pagePermissions = useSelector(getPagePermissions);
+ const isCreatePermitted = getHasCreateActionPermission(
+ isFeatureEnabled,
+ pagePermissions,
+ );
+ const isDeletePermitted = getHasDeleteActionPermission(
+ isFeatureEnabled,
+ action.userPermissions,
+ );
+ const convertToModuleProps = {
+ canCreateModuleInstance: isCreatePermitted,
+ canDeleteEntity: isDeletePermitted,
+ entityId: action.id,
+ moduleType: MODULE_TYPE.QUERY,
+ };
+ return ;
+};
+
+export default ConvertToModuleCTA;
diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCallout.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCallout.tsx
new file mode 100644
index 000000000000..a58ebc069dd3
--- /dev/null
+++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleCallout.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+import { Flex, Icon } from "@appsmith/ads";
+import { useSelector } from "react-redux";
+import { getIsActionConverting } from "ee/selectors/entitiesSelector";
+import { usePluginActionContext } from "PluginActionEditor";
+import {
+ ENTITY_ICON_SIZE,
+ EntityIcon,
+} from "pages/Editor/Explorer/ExplorerIcons";
+import ConvertEntityNotification from "ee/pages/common/ConvertEntityNotification";
+import { resolveIcon } from "pages/Editor/utils";
+
+const ConvertToModuleCallout = () => {
+ const { action, plugin } = usePluginActionContext();
+ const isConverting = useSelector((state) =>
+ getIsActionConverting(state, action.id),
+ );
+
+ const PluginIcon = resolveIcon({
+ iconLocation: plugin.iconLocation || "",
+ pluginType: plugin.type,
+ moduleType: action.actionConfiguration?.body?.moduleType,
+ }) || (
+
+
+
+ );
+
+ if (!isConverting) return null;
+ return (
+
+
+
+ );
+};
+
+export default ConvertToModuleCallout;
diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleDisabler.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleDisabler.tsx
new file mode 100644
index 000000000000..26cace30fc06
--- /dev/null
+++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/ConvertToModuleDisabler.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import Disabler from "pages/common/Disabler";
+import { usePluginActionContext } from "PluginActionEditor";
+import { useSelector } from "react-redux";
+import { getIsActionConverting } from "ee/selectors/entitiesSelector";
+
+interface Props {
+ children: React.ReactNode | React.ReactNode[];
+}
+
+const ConvertToModuleDisabler = (props: Props) => {
+ const { action } = usePluginActionContext();
+ const isConverting = useSelector((state) =>
+ getIsActionConverting(state, action.id),
+ );
+ return {props.children};
+};
+
+export default ConvertToModuleDisabler;
diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/index.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/index.tsx
new file mode 100644
index 000000000000..c390c4a5dd63
--- /dev/null
+++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/ConvertToModule/index.tsx
@@ -0,0 +1,3 @@
+export { default as ConvertToModuleCTA } from "./ConvertToModuleCTA";
+export { default as ConvertToModuleDisabler } from "./ConvertToModuleDisabler";
+export { default as ConvertToModuleCallout } from "./ConvertToModuleCallout";
diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/PluginActionMoreActions.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/PluginActionMoreActions.tsx
new file mode 100644
index 000000000000..d8d37c754dd6
--- /dev/null
+++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/components/PluginActionMoreActions.tsx
@@ -0,0 +1,183 @@
+import React, { useCallback, useMemo, useState } from "react";
+import {
+ getHasDeleteActionPermission,
+ getHasManageActionPermission,
+} from "ee/utils/BusinessFeatures/permissionPageHelpers";
+import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
+import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
+import { usePluginActionContext } from "PluginActionEditor";
+import {
+ MenuItem,
+ MenuSub,
+ MenuSubContent,
+ MenuSubTrigger,
+} from "@appsmith/ads";
+import {
+ CONFIRM_CONTEXT_DELETE,
+ CONTEXT_COPY,
+ CONTEXT_DELETE,
+ CONTEXT_MOVE,
+ createMessage,
+} from "ee/constants/messages";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ copyActionRequest,
+ deleteAction,
+ moveActionRequest,
+} from "actions/pluginActionActions";
+import { getCurrentPageId } from "selectors/editorSelectors";
+import type { Page } from "entities/Page";
+import { getPageList } from "ee/selectors/entitiesSelector";
+import { ConvertToModuleCTA } from "./ConvertToModule";
+
+const PageMenuItem = (props: {
+ page: Page;
+ onSelect: (id: string) => void;
+}) => {
+ const handleOnSelect = useCallback(() => {
+ props.onSelect(props.page.pageId);
+ }, [props]);
+ return ;
+};
+
+const Copy = () => {
+ const menuPages = useSelector(getPageList);
+ const { action } = usePluginActionContext();
+ const dispatch = useDispatch();
+
+ const copyActionToPage = useCallback(
+ (pageId: string) =>
+ dispatch(
+ copyActionRequest({
+ id: action.id,
+ destinationPageId: pageId,
+ name: action.name,
+ }),
+ ),
+ [action.id, action.name, dispatch],
+ );
+
+ return (
+
+
+ {createMessage(CONTEXT_COPY)}
+
+
+ {menuPages.map((page) => {
+ return (
+
+ );
+ })}
+
+
+ );
+};
+
+const Move = () => {
+ const dispatch = useDispatch();
+ const { action } = usePluginActionContext();
+
+ const currentPageId = useSelector(getCurrentPageId);
+ const allPages = useSelector(getPageList);
+ const menuPages = useMemo(() => {
+ return allPages.filter((page) => page.pageId !== currentPageId);
+ }, [allPages, currentPageId]);
+
+ const moveActionToPage = useCallback(
+ (destinationPageId: string) =>
+ dispatch(
+ moveActionRequest({
+ id: action.id,
+ destinationPageId,
+ originalPageId: currentPageId,
+ name: action.name,
+ }),
+ ),
+ [dispatch, action.id, action.name, currentPageId],
+ );
+
+ return (
+
+
+ {createMessage(CONTEXT_MOVE)}
+
+
+ {menuPages.length > 1 ? (
+ menuPages.map((page) => {
+ return (
+
+ );
+ })
+ ) : (
+
+ )}
+
+
+ );
+};
+
+const Delete = () => {
+ const dispatch = useDispatch();
+ const { action } = usePluginActionContext();
+
+ const [confirmDelete, setConfirmDelete] = useState(false);
+
+ const deleteActionFromPage = useCallback(() => {
+ dispatch(deleteAction({ id: action.id, name: action.name }));
+ }, [action.id, action.name, dispatch]);
+
+ const handleSelect = useCallback(() => {
+ confirmDelete ? deleteActionFromPage() : setConfirmDelete(true);
+ }, [confirmDelete, deleteActionFromPage]);
+
+ const menuLabel = confirmDelete
+ ? createMessage(CONFIRM_CONTEXT_DELETE)
+ : createMessage(CONTEXT_DELETE);
+
+ return (
+
+ );
+};
+
+const AppPluginActionMenu = () => {
+ const { action } = usePluginActionContext();
+
+ const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
+ const isChangePermitted = getHasManageActionPermission(
+ isFeatureEnabled,
+ action.userPermissions,
+ );
+ const isDeletePermitted = getHasDeleteActionPermission(
+ isFeatureEnabled,
+ action?.userPermissions,
+ );
+
+ return (
+ <>
+
+ {isChangePermitted && (
+ <>
+
+
+ >
+ )}
+ {isDeletePermitted && }
+ >
+ );
+};
+
+export default AppPluginActionMenu;
diff --git a/app/client/src/ce/pages/Editor/Explorer/helpers.tsx b/app/client/src/ce/pages/Editor/Explorer/helpers.tsx
index 587f89bf6365..0a1baac42ea8 100644
--- a/app/client/src/ce/pages/Editor/Explorer/helpers.tsx
+++ b/app/client/src/ce/pages/Editor/Explorer/helpers.tsx
@@ -7,7 +7,9 @@ import {
DATA_SOURCES_EDITOR_ID_PATH,
matchBuilderPath,
matchViewerPath,
- BUILDER_VIEWER_PATH_PREFIX,
+ VIEWER_PATH,
+ VIEWER_CUSTOM_PATH,
+ VIEWER_PATH_DEPRECATED,
} from "constants/routes";
import {
@@ -80,11 +82,7 @@ export const getActionIdFromURL = () => {
};
export function getAppViewerPageIdFromPath(path: string): string | null {
- const regexes = [
- `${BUILDER_VIEWER_PATH_PREFIX}:applicationSlug/:pageSlug(.*\\-):basePageId`, // VIEWER_PATH
- `${BUILDER_VIEWER_PATH_PREFIX}:customSlug(.*\\-):basePageId`, // VIEWER_CUSTOM_PATH
- `/applications/:baseApplicationId/pages/:basePageId`, // VIEWER_PATH_DEPRECATED
- ];
+ const regexes = [VIEWER_PATH, VIEWER_CUSTOM_PATH, VIEWER_PATH_DEPRECATED];
for (const regex of regexes) {
const match = matchPath<{ basePageId: string }>(path, { path: regex });
if (match?.params.basePageId) {
diff --git a/app/client/src/components/editorComponents/StoreAsDatasource.tsx b/app/client/src/components/editorComponents/StoreAsDatasource.tsx
index 84c2f5111afb..b605ca26eb38 100644
--- a/app/client/src/components/editorComponents/StoreAsDatasource.tsx
+++ b/app/client/src/components/editorComponents/StoreAsDatasource.tsx
@@ -60,11 +60,12 @@ function StoreAsDatasource(props: storeDataSourceProps) {
return (